@agimon-ai/po 0.0.0-alpha.2 → 0.0.0-alpha.21

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 (255) hide show
  1. package/README.md +14 -11
  2. package/dist/antigravity.mjs +1 -2
  3. package/dist/baseCommand.mjs +1 -2
  4. package/dist/bin/cli.cjs +37 -14
  5. package/dist/bin/cli.cjs.map +1 -1
  6. package/dist/bin/cli.mjs +34 -14
  7. package/dist/bin/cli.mjs.map +1 -1
  8. package/dist/bin/{doomRunner.cjs → poRunner.cjs} +5 -5
  9. package/dist/bin/poRunner.cjs.map +1 -0
  10. package/dist/bin/{doomRunner.d.mts → poRunner.d.mts} +2 -2
  11. package/dist/bin/poRunner.d.mts.map +1 -0
  12. package/dist/bin/{doomRunner.mjs → poRunner.mjs} +5 -5
  13. package/dist/bin/poRunner.mjs.map +1 -0
  14. package/dist/builders/cli/extensionAssembler/index.cjs +14 -12
  15. package/dist/builders/cli/extensionAssembler/index.cjs.map +1 -1
  16. package/dist/builders/cli/extensionAssembler/index.d.mts +2 -1
  17. package/dist/builders/cli/extensionAssembler/index.d.mts.map +1 -1
  18. package/dist/builders/cli/extensionAssembler/index.mjs +14 -12
  19. package/dist/builders/cli/extensionAssembler/index.mjs.map +1 -1
  20. package/dist/catalog/index.json +68 -51
  21. package/dist/cliApp.mjs +1 -2
  22. package/dist/codex.mjs +1 -2
  23. package/dist/compatibilityCommand.mjs +1 -2
  24. package/dist/compatibilityContext.mjs +1 -2
  25. package/dist/compatibilityOptions.mjs +1 -2
  26. package/dist/config.cjs +57 -12
  27. package/dist/config.d.mts +3 -3
  28. package/dist/config.mjs +3 -3
  29. package/dist/doctorCommand.mjs +1 -2
  30. package/dist/emitMcpCommand.mjs +1 -2
  31. package/dist/explainCommand.mjs +1 -2
  32. package/dist/extensions/server.cjs +11 -7
  33. package/dist/extensions/server.cjs.map +1 -1
  34. package/dist/extensions/server.d.mts +2 -2
  35. package/dist/extensions/server.d.mts.map +1 -1
  36. package/dist/extensions/server.mjs +12 -8
  37. package/dist/extensions/server.mjs.map +1 -1
  38. package/dist/harnessContext.mjs +1 -2
  39. package/dist/harnessState.cjs +66 -11
  40. package/dist/harnessState.d.mts +2 -2
  41. package/dist/harnessState.mjs +1 -2
  42. package/dist/index.cjs +60 -15
  43. package/dist/index.d.mts +3 -3
  44. package/dist/index.mjs +3 -3
  45. package/dist/initCommand.mjs +1 -2
  46. package/dist/matrixOptions.mjs +1 -2
  47. package/dist/options.mjs +1 -2
  48. package/dist/process.mjs +1 -2
  49. package/dist/projectTrust.cjs +2 -2
  50. package/dist/projectTrust.d.mts +2 -2
  51. package/dist/projectTrust.mjs +1 -2
  52. package/dist/repository.cjs +1 -1
  53. package/dist/repository.d.mts +2 -2
  54. package/dist/repository.mjs +1 -2
  55. package/dist/router.mjs +1 -2
  56. package/dist/shared.mjs +1 -2
  57. package/dist/src/bin/agentDirectory.cjs +18 -12
  58. package/dist/src/bin/agentDirectory.cjs.map +1 -1
  59. package/dist/src/bin/agentDirectory.mjs +15 -12
  60. package/dist/src/bin/agentDirectory.mjs.map +1 -1
  61. package/dist/src/builders/apiContractExport/index.cjs +6 -6
  62. package/dist/src/builders/apiContractExport/index.cjs.map +1 -1
  63. package/dist/src/builders/apiContractExport/index.mjs +9 -9
  64. package/dist/src/builders/apiContractExport/index.mjs.map +1 -1
  65. package/dist/src/builders/cli/entryResolution.cjs +3 -3
  66. package/dist/src/builders/cli/entryResolution.cjs.map +1 -1
  67. package/dist/src/builders/cli/entryResolution.mjs +3 -3
  68. package/dist/src/builders/cli/entryResolution.mjs.map +1 -1
  69. package/dist/src/builders/cli/harnessContext.cjs +5 -5
  70. package/dist/src/builders/cli/harnessContext.cjs.map +1 -1
  71. package/dist/src/builders/cli/harnessContext.mjs +7 -7
  72. package/dist/src/builders/cli/harnessContext.mjs.map +1 -1
  73. package/dist/src/builders/hooks/index.cjs +1 -1
  74. package/dist/src/builders/hooks/index.cjs.map +1 -1
  75. package/dist/src/builders/hooks/index.mjs +2 -2
  76. package/dist/src/builders/hooks/index.mjs.map +1 -1
  77. package/dist/src/builders/server/backgroundSync.cjs +13 -1
  78. package/dist/src/builders/server/backgroundSync.cjs.map +1 -1
  79. package/dist/src/builders/server/backgroundSync.mjs +13 -1
  80. package/dist/src/builders/server/backgroundSync.mjs.map +1 -1
  81. package/dist/src/builders/server/bundlerService.cjs +151 -0
  82. package/dist/src/builders/server/bundlerService.cjs.map +1 -0
  83. package/dist/src/builders/server/bundlerService.mjs +151 -0
  84. package/dist/src/builders/server/bundlerService.mjs.map +1 -0
  85. package/dist/src/builders/server/computerUseBinding.cjs +2 -2
  86. package/dist/src/builders/server/computerUseBinding.cjs.map +1 -1
  87. package/dist/src/builders/server/computerUseBinding.mjs +3 -3
  88. package/dist/src/builders/server/computerUseBinding.mjs.map +1 -1
  89. package/dist/src/builders/server/defaultWorkspace.cjs +1 -1
  90. package/dist/src/builders/server/defaultWorkspace.cjs.map +1 -1
  91. package/dist/src/builders/server/defaultWorkspace.mjs +2 -2
  92. package/dist/src/builders/server/defaultWorkspace.mjs.map +1 -1
  93. package/dist/src/builders/server/idleRestart.cjs +3 -3
  94. package/dist/src/builders/server/idleRestart.cjs.map +1 -1
  95. package/dist/src/builders/server/idleRestart.mjs +3 -3
  96. package/dist/src/builders/server/idleRestart.mjs.map +1 -1
  97. package/dist/src/builders/server/index.cjs +6 -6
  98. package/dist/src/builders/server/index.cjs.map +1 -1
  99. package/dist/src/builders/server/index.mjs +6 -6
  100. package/dist/src/builders/server/index.mjs.map +1 -1
  101. package/dist/src/builders/server/logSink.cjs +3 -3
  102. package/dist/src/builders/server/logSink.cjs.map +1 -1
  103. package/dist/src/builders/server/logSink.mjs +2 -2
  104. package/dist/src/builders/server/logSink.mjs.map +1 -1
  105. package/dist/src/builders/server/mcpBundle.cjs.map +1 -1
  106. package/dist/src/builders/server/mcpBundle.d.mts +2 -2
  107. package/dist/src/builders/server/mcpBundle.mjs.map +1 -1
  108. package/dist/src/builders/server/packageUrl.cjs +25 -18
  109. package/dist/src/builders/server/packageUrl.cjs.map +1 -1
  110. package/dist/src/builders/server/packageUrl.mjs +25 -18
  111. package/dist/src/builders/server/packageUrl.mjs.map +1 -1
  112. package/dist/src/builders/server/runtime.cjs +157 -168
  113. package/dist/src/builders/server/runtime.cjs.map +1 -1
  114. package/dist/src/builders/server/runtime.mjs +161 -171
  115. package/dist/src/builders/server/runtime.mjs.map +1 -1
  116. package/dist/src/builders/server/selectionStatus.cjs +3 -3
  117. package/dist/src/builders/server/selectionStatus.cjs.map +1 -1
  118. package/dist/src/builders/server/selectionStatus.mjs +3 -3
  119. package/dist/src/builders/server/selectionStatus.mjs.map +1 -1
  120. package/dist/src/builders/server/sessionArguments.cjs.map +1 -1
  121. package/dist/src/builders/server/sessionArguments.mjs.map +1 -1
  122. package/dist/src/builders/server/watcherService.cjs +65 -0
  123. package/dist/src/builders/server/watcherService.cjs.map +1 -0
  124. package/dist/src/builders/server/watcherService.mjs +61 -0
  125. package/dist/src/builders/server/watcherService.mjs.map +1 -0
  126. package/dist/src/builders/server/webTemplateDecision.cjs +1 -1
  127. package/dist/src/builders/server/webTemplateDecision.cjs.map +1 -1
  128. package/dist/src/builders/server/webTemplateDecision.mjs +2 -2
  129. package/dist/src/builders/server/webTemplateDecision.mjs.map +1 -1
  130. package/dist/src/builders/web/bundle.cjs.map +1 -1
  131. package/dist/src/builders/web/bundle.d.mts +4 -3
  132. package/dist/src/builders/web/bundle.mjs.map +1 -1
  133. package/dist/src/builders/web/bundleAssetPolicy.d.mts +1 -0
  134. package/dist/src/builders/web/index.cjs +1 -1
  135. package/dist/src/builders/web/index.cjs.map +1 -1
  136. package/dist/src/builders/web/index.d.mts +1 -0
  137. package/dist/src/builders/web/index.mjs +1 -1
  138. package/dist/src/builders/web/index.mjs.map +1 -1
  139. package/dist/src/builders/web/webPluginGenerate.cjs +4 -15
  140. package/dist/src/builders/web/webPluginGenerate.cjs.map +1 -1
  141. package/dist/src/builders/web/webPluginGenerate.d.mts +5 -9
  142. package/dist/src/builders/web/webPluginGenerate.d.mts.map +1 -1
  143. package/dist/src/builders/web/webPluginGenerate.mjs +4 -15
  144. package/dist/src/builders/web/webPluginGenerate.mjs.map +1 -1
  145. package/dist/src/builders/web/webPluginVite.d.mts +1 -0
  146. package/dist/src/cli/cliApp.cjs +1 -1
  147. package/dist/src/cli/cliApp.cjs.map +1 -1
  148. package/dist/src/cli/cliApp.mjs +1 -1
  149. package/dist/src/cli/cliApp.mjs.map +1 -1
  150. package/dist/src/cli/commands/compat/index.cjs +1 -1
  151. package/dist/src/cli/commands/compat/index.cjs.map +1 -1
  152. package/dist/src/cli/commands/compat/index.mjs +1 -1
  153. package/dist/src/cli/commands/compat/index.mjs.map +1 -1
  154. package/dist/src/cli/commands/doctor/index.cjs +2 -2
  155. package/dist/src/cli/commands/doctor/index.cjs.map +1 -1
  156. package/dist/src/cli/commands/doctor/index.d.mts +1 -0
  157. package/dist/src/cli/commands/doctor/index.d.mts.map +1 -1
  158. package/dist/src/cli/commands/doctor/index.mjs +4 -4
  159. package/dist/src/cli/commands/doctor/index.mjs.map +1 -1
  160. package/dist/src/cli/commands/explain/index.cjs +3 -1
  161. package/dist/src/cli/commands/explain/index.cjs.map +1 -1
  162. package/dist/src/cli/commands/explain/index.d.mts.map +1 -1
  163. package/dist/src/cli/commands/explain/index.mjs +4 -2
  164. package/dist/src/cli/commands/explain/index.mjs.map +1 -1
  165. package/dist/src/cli/commands/init/index.cjs +2 -2
  166. package/dist/src/cli/commands/init/index.cjs.map +1 -1
  167. package/dist/src/cli/commands/init/index.mjs +3 -3
  168. package/dist/src/cli/commands/init/index.mjs.map +1 -1
  169. package/dist/src/cli/commands/init/presenter.cjs +1 -1
  170. package/dist/src/cli/commands/init/presenter.cjs.map +1 -1
  171. package/dist/src/cli/commands/init/presenter.mjs +2 -2
  172. package/dist/src/cli/commands/init/presenter.mjs.map +1 -1
  173. package/dist/src/cli/commands/sync/index.cjs +4 -4
  174. package/dist/src/cli/commands/sync/index.cjs.map +1 -1
  175. package/dist/src/cli/commands/sync/index.d.mts +1 -0
  176. package/dist/src/cli/commands/sync/index.mjs +7 -7
  177. package/dist/src/cli/commands/sync/index.mjs.map +1 -1
  178. package/dist/src/cli/commands/sync/presenter.cjs +1 -1
  179. package/dist/src/cli/commands/sync/presenter.cjs.map +1 -1
  180. package/dist/src/cli/commands/sync/presenter.d.mts +1 -1
  181. package/dist/src/cli/commands/sync/presenter.mjs +1 -1
  182. package/dist/src/cli/commands/sync/presenter.mjs.map +1 -1
  183. package/dist/src/cli/commands/web/index.cjs +2 -1
  184. package/dist/src/cli/commands/web/index.cjs.map +1 -1
  185. package/dist/src/cli/commands/web/index.mjs +2 -1
  186. package/dist/src/cli/commands/web/index.mjs.map +1 -1
  187. package/dist/src/cli/harnessOptions.cjs +1 -1
  188. package/dist/src/cli/harnessOptions.cjs.map +1 -1
  189. package/dist/src/cli/harnessOptions.mjs +2 -2
  190. package/dist/src/cli/harnessOptions.mjs.map +1 -1
  191. package/dist/src/cli/help.cjs +2 -0
  192. package/dist/src/cli/help.cjs.map +1 -1
  193. package/dist/src/cli/help.mjs +2 -0
  194. package/dist/src/cli/help.mjs.map +1 -1
  195. package/dist/src/cli/matrixOptions.cjs +1 -1
  196. package/dist/src/cli/matrixOptions.cjs.map +1 -1
  197. package/dist/src/cli/matrixOptions.d.mts +1 -1
  198. package/dist/src/cli/matrixOptions.mjs +1 -1
  199. package/dist/src/cli/matrixOptions.mjs.map +1 -1
  200. package/dist/src/cli/server/run.cjs +1 -1
  201. package/dist/src/cli/server/run.cjs.map +1 -1
  202. package/dist/src/cli/server/run.mjs +2 -2
  203. package/dist/src/cli/server/run.mjs.map +1 -1
  204. package/dist/src/composition/harnessState/index.cjs +66 -11
  205. package/dist/src/composition/harnessState/index.cjs.map +1 -1
  206. package/dist/src/composition/harnessState/index.d.mts +3 -3
  207. package/dist/src/composition/harnessState/index.mjs +2 -2
  208. package/dist/src/composition/harnessState/index.mjs.map +1 -1
  209. package/dist/src/composition/layerPackageInstaller/index.cjs +5 -4
  210. package/dist/src/composition/layerPackageInstaller/index.cjs.map +1 -1
  211. package/dist/src/composition/layerPackageInstaller/index.mjs +6 -5
  212. package/dist/src/composition/layerPackageInstaller/index.mjs.map +1 -1
  213. package/dist/src/composition/poHomeMigration/index.cjs +1 -1
  214. package/dist/src/composition/poHomeMigration/index.cjs.map +1 -1
  215. package/dist/src/composition/poHomeMigration/index.mjs +1 -1
  216. package/dist/src/composition/poHomeMigration/index.mjs.map +1 -1
  217. package/dist/src/composition/projectTrust/index.cjs +6 -6
  218. package/dist/src/composition/projectTrust/index.cjs.map +1 -1
  219. package/dist/src/composition/projectTrust/index.d.mts +7 -7
  220. package/dist/src/composition/projectTrust/index.d.mts.map +1 -1
  221. package/dist/src/composition/projectTrust/index.mjs +6 -6
  222. package/dist/src/composition/projectTrust/index.mjs.map +1 -1
  223. package/dist/src/composition/repository/index.cjs +8 -8
  224. package/dist/src/composition/repository/index.cjs.map +1 -1
  225. package/dist/src/composition/repository/index.d.mts +5 -5
  226. package/dist/src/composition/repository/index.d.mts.map +1 -1
  227. package/dist/src/composition/repository/index.mjs +10 -10
  228. package/dist/src/composition/repository/index.mjs.map +1 -1
  229. package/dist/src/composition/syncService/index.cjs +29 -9
  230. package/dist/src/composition/syncService/index.cjs.map +1 -1
  231. package/dist/src/composition/syncService/index.d.mts.map +1 -1
  232. package/dist/src/composition/syncService/index.mjs +31 -11
  233. package/dist/src/composition/syncService/index.mjs.map +1 -1
  234. package/dist/src/composition/syncService/localBuilds.cjs +7 -2
  235. package/dist/src/composition/syncService/localBuilds.cjs.map +1 -1
  236. package/dist/src/composition/syncService/localBuilds.mjs +7 -2
  237. package/dist/src/composition/syncService/localBuilds.mjs.map +1 -1
  238. package/dist/src/extensions/setupDiagnostics.cjs +3 -3
  239. package/dist/src/extensions/setupDiagnostics.cjs.map +1 -1
  240. package/dist/src/extensions/setupDiagnostics.mjs +3 -3
  241. package/dist/src/extensions/setupDiagnostics.mjs.map +1 -1
  242. package/dist/utils.mjs +1 -2
  243. package/docs/server/api-export.md +3 -3
  244. package/docs/server/api.md +23 -22
  245. package/docs/server/getting-started.md +7 -7
  246. package/docs/server/index.md +4 -4
  247. package/docs/server/ipc.md +7 -11
  248. package/docs/server/lifecycle.md +17 -17
  249. package/docs/server/security.md +8 -8
  250. package/package.json +42 -45
  251. package/src/prompts/po-author-extension/SKILL.md +1 -1
  252. package/src/prompts/po-author-extension/references/extension-contract.md +4 -4
  253. package/dist/bin/doomRunner.cjs.map +0 -1
  254. package/dist/bin/doomRunner.d.mts.map +0 -1
  255. package/dist/bin/doomRunner.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"computerUseBinding.mjs","names":[],"sources":["../../../../src/builders/server/computerUseBinding.ts"],"sourcesContent":["import { randomUUID, timingSafeEqual } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport type { DoomComputerUseHostBinding } from '@agimon-ai/po-core/hubChannel';\nimport { doomApiCallerFrom } from '@agimon-ai/po-core/packageApi';\n\nimport { ComputerUseRecordingStore } from './computerUseRecordings';\n\nconst VERSION = 1;\nconst MAX_BYTES = 8 * 1024 * 1024;\nconst DESKTOP_HEADER = 'x-po-desktop';\ntype Transport = Pick<NodeJS.Process, 'send' | 'connected' | 'on' | 'off'>;\n\n/** The capability is discovered over the inherited Desktop IPC channel, never from environment flags. */\nexport async function createComputerUseBinding(\n transport: Transport = process,\n remotePolicy: {\n isDeviceAuthorized?: (deviceId: string) => boolean;\n stepUpRequired?: () => boolean;\n } = {},\n): Promise<DoomComputerUseHostBinding | undefined> {\n if (!transport.send || !transport.connected) return undefined;\n const recordings = new ComputerUseRecordingStore(await fs.mkdtemp(path.join(os.tmpdir(), 'po-recordings-')));\n let available = false;\n let enabled = false;\n let closed = false;\n let token = '';\n let generation: string | undefined;\n const ownedSessions = new Set<string>();\n const revokedSessions = new Set<string>();\n const listeners = new Map<() => void, string | undefined>();\n const delegations = new Map<\n string,\n { deviceId: string; approved: boolean; abort: AbortController; grantId?: string; expiresAt?: number }\n >();\n const nativeAuthorized = (headers: Headers): boolean => {\n const proof = headers.get(DESKTOP_HEADER) ?? '';\n return available && /^[a-f0-9]{64}$/u.test(proof) && timingSafeEqual(Buffer.from(proof), Buffer.from(token));\n };\n const remoteCaller = (headers: Headers) => {\n const caller = doomApiCallerFrom(headers);\n return caller?.locality === 'remote' && remotePolicy.isDeviceAuthorized?.(caller.deviceId) === true\n ? caller\n : undefined;\n };\n const notify = (sessionId?: string): void => {\n for (const [listener, session] of listeners) if (sessionId === undefined || session === sessionId) listener();\n };\n const revoke = (sessionId: string, retainRecording = false): void => {\n const delegated = delegations.get(sessionId);\n if (delegated === undefined) return;\n if (!retainRecording) delegations.delete(sessionId);\n delegated.expiresAt = undefined;\n delegated.abort.abort();\n revokedSessions.add(sessionId);\n if (delegated.grantId !== undefined && available) {\n void exchange(\n { type: 'po:computer-use:request', sessionId, operation: 'stop', payload: { grantId: delegated.grantId } },\n 30_000,\n )\n .then((result) => recordings.importStop(sessionId, delegated.grantId!, result.result))\n .catch(() => close());\n }\n notify(sessionId);\n };\n const pending = new Map<string, { finish: (value?: Record<string, unknown>, error?: Error) => void }>();\n const send = (value: object): void => {\n if (closed || !transport.connected) throw new Error('Desktop computer use is disconnected.');\n transport.send!(value, (error: Error | null) => {\n if (error) close();\n });\n };\n const close = (): void => {\n if (closed) return;\n closed = true;\n available = false;\n token = '';\n transport.off('message', onMessage);\n transport.off('disconnect', close);\n transport.off('exit', close);\n for (const request of pending.values())\n request.finish(undefined, new Error('Desktop computer use is disconnected.'));\n pending.clear();\n for (const sessionId of delegations.keys()) revoke(sessionId);\n notify();\n listeners.clear();\n void recordings.close().catch(() => process.emitWarning('Computer-use recording cleanup failed.'));\n // Keep ownership pinned while the server is alive, including after Desktop disconnects.\n };\n const onMessage = (value: unknown): void => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const message = value as Record<string, unknown>;\n if (message.version !== VERSION) return;\n if (message.type === 'po:computer-use:availability' && typeof message.enabled === 'boolean') {\n enabled = message.enabled;\n // Native stop/expiry also notifies availability without changing the global setting.\n for (const sessionId of delegations.keys()) revoke(sessionId, true);\n notify();\n return;\n }\n if (typeof message.requestId !== 'string') return;\n if (message.type !== 'po:computer-use:ready' && message.type !== 'po:computer-use:response') return;\n if (Buffer.byteLength(JSON.stringify(message)) > MAX_BYTES) {\n close();\n return;\n }\n pending.get(message.requestId)?.finish(message);\n };\n transport.on('message', onMessage);\n transport.on('disconnect', close);\n transport.on('exit', close);\n\n const exchange = (\n value: Record<string, unknown>,\n timeoutMs: number,\n signal?: AbortSignal,\n ): Promise<Record<string, unknown>> =>\n new Promise((resolve, reject) => {\n signal?.throwIfAborted();\n if (closed || pending.size >= 128) throw new Error('Desktop computer-use transport is unavailable or busy.');\n const requestId = randomUUID();\n const cancel = (): void => {\n try {\n send({ type: 'po:computer-use:cancel', version: VERSION, requestId });\n } catch {\n /* Already disconnected. */\n }\n };\n const finish = (result?: Record<string, unknown>, error?: Error): void => {\n if (!pending.delete(requestId)) return;\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n if (error) reject(error);\n else resolve(result!);\n };\n const onAbort = (): void => {\n cancel();\n finish(undefined, new Error('Desktop computer-use request was cancelled.'));\n };\n const timer = setTimeout(() => {\n cancel();\n finish(undefined, new Error('Desktop computer-use request timed out.'));\n }, timeoutMs);\n pending.set(requestId, { finish });\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n const message = { ...value, version: VERSION, requestId };\n if (Buffer.byteLength(JSON.stringify(message)) > MAX_BYTES)\n throw new Error('Desktop computer-use request is too large.');\n send(message);\n } catch (error) {\n finish(undefined, error instanceof Error ? error : new Error(String(error)));\n }\n });\n\n try {\n const ready = await exchange({ type: 'po:computer-use:hello' }, 1_500);\n if (\n ready.type !== 'po:computer-use:ready' ||\n typeof ready.token !== 'string' ||\n !/^[a-f0-9]{64}$/u.test(ready.token)\n ) {\n throw new Error('Invalid Desktop capability handshake.');\n }\n token = ready.token;\n available = true;\n enabled = ready.enabled === true;\n } catch {\n close();\n return undefined;\n }\n\n return {\n get available() {\n return available;\n },\n get enabled() {\n return available && enabled;\n },\n authorize: nativeAuthorized,\n readRecording: (scope, artifactId, range) => recordings.read(scope.sessionId, artifactId, range),\n authorizeActivation(headers) {\n if (!available || !enabled) return false;\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n return (\n caller !== undefined &&\n (remotePolicy.stepUpRequired?.() === true ? caller.stepUp === 'verified' : caller.stepUp === 'not-required')\n );\n },\n authorizePending(sessionId, headers) {\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n const delegated = delegations.get(sessionId);\n return (\n available &&\n enabled &&\n caller !== undefined &&\n delegated !== undefined &&\n delegated.deviceId === caller.deviceId &&\n delegated.expiresAt === undefined &&\n !delegated.abort.signal.aborted\n );\n },\n authorizeRecording(sessionId, headers) {\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n const delegated = delegations.get(sessionId);\n return (\n available && caller !== undefined && delegated?.approved === true && delegated.deviceId === caller.deviceId\n );\n },\n authorizeSession(sessionId, headers) {\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n const delegated = delegations.get(sessionId);\n if (available && enabled && caller !== undefined && !ownedSessions.has(sessionId)) return true;\n return (\n available &&\n enabled &&\n caller !== undefined &&\n delegated?.approved === true &&\n delegated.expiresAt !== undefined &&\n delegated.expiresAt > Date.now() &&\n delegated.deviceId === caller.deviceId &&\n !delegated.abort.signal.aborted\n );\n },\n claimSession(sessionId, headers) {\n if (!available || !enabled) throw new Error('Desktop computer use is unavailable.');\n const caller = headers === undefined ? undefined : remoteCaller(headers);\n if (caller !== undefined) {\n const previous = delegations.get(sessionId);\n if (previous !== undefined && previous.deviceId !== caller.deviceId)\n throw new Error('This session belongs to another paired device.');\n if (previous?.abort.signal.aborted) {\n previous.abort = new AbortController();\n previous.grantId = undefined;\n }\n if (previous === undefined)\n delegations.set(sessionId, {\n deviceId: caller.deviceId,\n approved: false,\n abort: new AbortController(),\n });\n } else if (headers !== undefined && !nativeAuthorized(headers)) {\n throw new Error('Native or paired authorization is required.');\n }\n revokedSessions.delete(sessionId);\n ownedSessions.add(sessionId);\n },\n revokeDevice(deviceId) {\n for (const [sessionId, delegated] of delegations) if (delegated.deviceId === deviceId) revoke(sessionId);\n },\n ownsSession: (sessionId) => ownedSessions.has(sessionId),\n forgetSession: (sessionId) => {\n revoke(sessionId);\n void recordings\n .forgetSession(sessionId)\n .catch(() => process.emitWarning('Computer-use recording cleanup failed.'));\n // Keep private history pinned to Desktop, but never replay device approval after restart.\n },\n subscribe(listener, sessionId) {\n listeners.set(listener, sessionId);\n return () => {\n listeners.delete(listener);\n };\n },\n async request(scope, request) {\n if (!available) throw new Error('Desktop computer use is unavailable.');\n if (revokedSessions.has(scope.sessionId) && request.operation !== 'stop' && request.operation !== 'status')\n throw new Error('Computer-use delegation was revoked.');\n if (request.operation !== 'status' && request.operation !== 'targets' && !ownedSessions.has(scope.sessionId)) {\n throw new Error('This session has not been authorized by Desktop.');\n }\n const delegated = delegations.get(scope.sessionId);\n if (delegated !== undefined && remotePolicy.isDeviceAuthorized?.(delegated.deviceId) !== true) {\n revoke(scope.sessionId);\n if (request.operation !== 'stop') throw new Error('The paired device was revoked.');\n }\n if (request.operation === 'stop' && delegated !== undefined) {\n delegated.expiresAt = undefined;\n delegated.abort.abort();\n }\n const signal =\n delegated === undefined || request.operation === 'stop'\n ? request.signal\n : request.signal === undefined\n ? delegated.abort.signal\n : AbortSignal.any([request.signal, delegated.abort.signal]);\n const result = await exchange(\n {\n type: 'po:computer-use:request',\n sessionId: scope.sessionId,\n operation: request.operation,\n ...(request.payload === undefined ? {} : { payload: request.payload }),\n },\n request.operation === 'activate' ? 120_000 : 30_000,\n signal,\n );\n if (\n typeof result.hostGeneration !== 'string' ||\n (generation !== undefined && generation !== result.hostGeneration)\n ) {\n close();\n throw new Error('Desktop generation changed. Authorize a new session.');\n }\n generation = result.hostGeneration;\n if (result.ok !== true)\n throw new Error(typeof result.error === 'string' ? result.error : 'Desktop request failed.');\n if (request.operation === 'activate' && delegated !== undefined) {\n if (\n delegations.get(scope.sessionId) !== delegated ||\n delegated.abort.signal.aborted ||\n remotePolicy.isDeviceAuthorized?.(delegated.deviceId) !== true\n )\n throw new Error('The paired activation was revoked.');\n delegated.approved = true;\n const grant = result.result as { grantId?: unknown; expiresAt?: unknown } | undefined;\n if (typeof grant?.expiresAt === 'number') delegated.expiresAt = grant.expiresAt;\n if (typeof grant?.grantId === 'string') delegated.grantId = grant.grantId;\n }\n if (request.operation === 'stop') {\n if (delegated !== undefined) delegated.expiresAt = undefined;\n const grantId = (request.payload as { grantId?: unknown } | undefined)?.grantId;\n if (typeof grantId !== 'string') throw new Error('Recording finalization requires its grant.');\n return recordings.importStop(scope.sessionId, grantId, result.result);\n }\n return result.result;\n },\n close() {\n if (closed) return;\n try {\n send({ type: 'po:computer-use:close', version: VERSION });\n } finally {\n close();\n }\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"computerUseBinding.mjs","names":[],"sources":["../../../../src/builders/server/computerUseBinding.ts"],"sourcesContent":["import { randomUUID, timingSafeEqual } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport type { PoComputerUseHostBinding } from '@agimon-ai/po-core/hubChannel';\nimport { poApiCallerFrom } from '@agimon-ai/po-core/packageApi';\n\nimport { ComputerUseRecordingStore } from './computerUseRecordings';\n\nconst VERSION = 1;\nconst MAX_BYTES = 8 * 1024 * 1024;\nconst DESKTOP_HEADER = 'x-po-desktop';\ntype Transport = Pick<NodeJS.Process, 'send' | 'connected' | 'on' | 'off'>;\n\n/** The capability is discovered over the inherited Desktop IPC channel, never from environment flags. */\nexport async function createComputerUseBinding(\n transport: Transport = process,\n remotePolicy: {\n isDeviceAuthorized?: (deviceId: string) => boolean;\n stepUpRequired?: () => boolean;\n } = {},\n): Promise<PoComputerUseHostBinding | undefined> {\n if (!transport.send || !transport.connected) return undefined;\n const recordings = new ComputerUseRecordingStore(await fs.mkdtemp(path.join(os.tmpdir(), 'po-recordings-')));\n let available = false;\n let enabled = false;\n let closed = false;\n let token = '';\n let generation: string | undefined;\n const ownedSessions = new Set<string>();\n const revokedSessions = new Set<string>();\n const listeners = new Map<() => void, string | undefined>();\n const delegations = new Map<\n string,\n { deviceId: string; approved: boolean; abort: AbortController; grantId?: string; expiresAt?: number }\n >();\n const nativeAuthorized = (headers: Headers): boolean => {\n const proof = headers.get(DESKTOP_HEADER) ?? '';\n return available && /^[a-f0-9]{64}$/u.test(proof) && timingSafeEqual(Buffer.from(proof), Buffer.from(token));\n };\n const remoteCaller = (headers: Headers) => {\n const caller = poApiCallerFrom(headers);\n return caller?.locality === 'remote' && remotePolicy.isDeviceAuthorized?.(caller.deviceId) === true\n ? caller\n : undefined;\n };\n const notify = (sessionId?: string): void => {\n for (const [listener, session] of listeners)\n if (sessionId === undefined || session === undefined || session === sessionId) listener();\n };\n const revoke = (sessionId: string, retainRecording = false): void => {\n const delegated = delegations.get(sessionId);\n if (delegated === undefined) return;\n if (!retainRecording) delegations.delete(sessionId);\n delegated.expiresAt = undefined;\n delegated.abort.abort();\n revokedSessions.add(sessionId);\n if (delegated.grantId !== undefined && available) {\n void exchange(\n { type: 'po:computer-use:request', sessionId, operation: 'stop', payload: { grantId: delegated.grantId } },\n 30_000,\n )\n .then((result) => recordings.importStop(sessionId, delegated.grantId!, result.result))\n .catch(() => close());\n }\n notify(sessionId);\n };\n const pending = new Map<string, { finish: (value?: Record<string, unknown>, error?: Error) => void }>();\n const send = (value: object): void => {\n if (closed || !transport.connected) throw new Error('Desktop computer use is disconnected.');\n transport.send!(value, (error: Error | null) => {\n if (error) close();\n });\n };\n const close = (): void => {\n if (closed) return;\n closed = true;\n available = false;\n token = '';\n transport.off('message', onMessage);\n transport.off('disconnect', close);\n transport.off('exit', close);\n for (const request of pending.values())\n request.finish(undefined, new Error('Desktop computer use is disconnected.'));\n pending.clear();\n for (const sessionId of delegations.keys()) revoke(sessionId);\n notify();\n listeners.clear();\n void recordings.close().catch(() => process.emitWarning('Computer-use recording cleanup failed.'));\n // Keep ownership pinned while the server is alive, including after Desktop disconnects.\n };\n const onMessage = (value: unknown): void => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return;\n const message = value as Record<string, unknown>;\n if (message.version !== VERSION) return;\n if (message.type === 'po:computer-use:availability' && typeof message.enabled === 'boolean') {\n enabled = message.enabled;\n // Native stop/expiry also notifies availability without changing the global setting.\n for (const sessionId of delegations.keys()) revoke(sessionId, true);\n notify();\n return;\n }\n if (typeof message.requestId !== 'string') return;\n if (message.type !== 'po:computer-use:ready' && message.type !== 'po:computer-use:response') return;\n if (Buffer.byteLength(JSON.stringify(message)) > MAX_BYTES) {\n close();\n return;\n }\n pending.get(message.requestId)?.finish(message);\n };\n transport.on('message', onMessage);\n transport.on('disconnect', close);\n transport.on('exit', close);\n\n const exchange = (\n value: Record<string, unknown>,\n timeoutMs: number,\n signal?: AbortSignal,\n ): Promise<Record<string, unknown>> =>\n new Promise((resolve, reject) => {\n signal?.throwIfAborted();\n if (closed || pending.size >= 128) throw new Error('Desktop computer-use transport is unavailable or busy.');\n const requestId = randomUUID();\n const cancel = (): void => {\n try {\n send({ type: 'po:computer-use:cancel', version: VERSION, requestId });\n } catch {\n /* Already disconnected. */\n }\n };\n const finish = (result?: Record<string, unknown>, error?: Error): void => {\n if (!pending.delete(requestId)) return;\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n if (error) reject(error);\n else resolve(result!);\n };\n const onAbort = (): void => {\n cancel();\n finish(undefined, new Error('Desktop computer-use request was cancelled.'));\n };\n const timer = setTimeout(() => {\n cancel();\n finish(undefined, new Error('Desktop computer-use request timed out.'));\n }, timeoutMs);\n pending.set(requestId, { finish });\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n const message = { ...value, version: VERSION, requestId };\n if (Buffer.byteLength(JSON.stringify(message)) > MAX_BYTES)\n throw new Error('Desktop computer-use request is too large.');\n send(message);\n } catch (error) {\n finish(undefined, error instanceof Error ? error : new Error(String(error)));\n }\n });\n\n try {\n const ready = await exchange({ type: 'po:computer-use:hello' }, 1_500);\n if (\n ready.type !== 'po:computer-use:ready' ||\n typeof ready.token !== 'string' ||\n !/^[a-f0-9]{64}$/u.test(ready.token)\n ) {\n throw new Error('Invalid Desktop capability handshake.');\n }\n token = ready.token;\n available = true;\n enabled = ready.enabled === true;\n } catch {\n close();\n return undefined;\n }\n\n return {\n get available() {\n return available;\n },\n get enabled() {\n return available && enabled;\n },\n authorize: nativeAuthorized,\n readRecording: (scope, artifactId, range) => recordings.read(scope.sessionId, artifactId, range),\n authorizeActivation(headers) {\n if (!available || !enabled) return false;\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n return (\n caller !== undefined &&\n (remotePolicy.stepUpRequired?.() === true ? caller.stepUp === 'verified' : caller.stepUp === 'not-required')\n );\n },\n authorizePending(sessionId, headers) {\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n const delegated = delegations.get(sessionId);\n return (\n available &&\n enabled &&\n caller !== undefined &&\n delegated !== undefined &&\n delegated.deviceId === caller.deviceId &&\n delegated.expiresAt === undefined &&\n !delegated.abort.signal.aborted\n );\n },\n authorizeRecording(sessionId, headers) {\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n const delegated = delegations.get(sessionId);\n return (\n available && caller !== undefined && delegated?.approved === true && delegated.deviceId === caller.deviceId\n );\n },\n authorizeSession(sessionId, headers) {\n if (nativeAuthorized(headers)) return true;\n const caller = remoteCaller(headers);\n const delegated = delegations.get(sessionId);\n if (available && enabled && caller !== undefined && !ownedSessions.has(sessionId)) return true;\n return (\n available &&\n enabled &&\n caller !== undefined &&\n delegated?.approved === true &&\n delegated.expiresAt !== undefined &&\n delegated.expiresAt > Date.now() &&\n delegated.deviceId === caller.deviceId &&\n !delegated.abort.signal.aborted\n );\n },\n claimSession(sessionId, headers) {\n if (!available || !enabled) throw new Error('Desktop computer use is unavailable.');\n const caller = headers === undefined ? undefined : remoteCaller(headers);\n if (caller !== undefined) {\n const previous = delegations.get(sessionId);\n if (previous !== undefined && previous.deviceId !== caller.deviceId)\n throw new Error('This session belongs to another paired device.');\n if (previous?.abort.signal.aborted) {\n previous.abort = new AbortController();\n previous.grantId = undefined;\n }\n if (previous === undefined)\n delegations.set(sessionId, {\n deviceId: caller.deviceId,\n approved: false,\n abort: new AbortController(),\n });\n } else if (headers !== undefined && !nativeAuthorized(headers)) {\n throw new Error('Native or paired authorization is required.');\n }\n revokedSessions.delete(sessionId);\n ownedSessions.add(sessionId);\n },\n revokeDevice(deviceId) {\n for (const [sessionId, delegated] of delegations) if (delegated.deviceId === deviceId) revoke(sessionId);\n },\n ownsSession: (sessionId) => ownedSessions.has(sessionId),\n forgetSession: (sessionId) => {\n revoke(sessionId);\n void recordings\n .forgetSession(sessionId)\n .catch(() => process.emitWarning('Computer-use recording cleanup failed.'));\n // Keep private history pinned to Desktop, but never replay device approval after restart.\n },\n subscribe(listener, sessionId) {\n listeners.set(listener, sessionId);\n return () => {\n listeners.delete(listener);\n };\n },\n async request(scope, request) {\n if (!available) throw new Error('Desktop computer use is unavailable.');\n if (revokedSessions.has(scope.sessionId) && request.operation !== 'stop' && request.operation !== 'status')\n throw new Error('Computer-use delegation was revoked.');\n if (request.operation !== 'status' && request.operation !== 'targets' && !ownedSessions.has(scope.sessionId)) {\n throw new Error('This session has not been authorized by Desktop.');\n }\n const delegated = delegations.get(scope.sessionId);\n if (delegated !== undefined && remotePolicy.isDeviceAuthorized?.(delegated.deviceId) !== true) {\n revoke(scope.sessionId);\n if (request.operation !== 'stop') throw new Error('The paired device was revoked.');\n }\n if (request.operation === 'stop' && delegated !== undefined) {\n delegated.expiresAt = undefined;\n delegated.abort.abort();\n }\n const signal =\n delegated === undefined || request.operation === 'stop'\n ? request.signal\n : request.signal === undefined\n ? delegated.abort.signal\n : AbortSignal.any([request.signal, delegated.abort.signal]);\n const result = await exchange(\n {\n type: 'po:computer-use:request',\n sessionId: scope.sessionId,\n operation: request.operation,\n ...(request.payload === undefined ? {} : { payload: request.payload }),\n },\n request.operation === 'activate' ? 120_000 : 30_000,\n signal,\n );\n if (\n typeof result.hostGeneration !== 'string' ||\n (generation !== undefined && generation !== result.hostGeneration)\n ) {\n close();\n throw new Error('Desktop generation changed. Authorize a new session.');\n }\n generation = result.hostGeneration;\n if (result.ok !== true)\n throw new Error(typeof result.error === 'string' ? result.error : 'Desktop request failed.');\n if (request.operation === 'activate' && delegated !== undefined) {\n if (\n delegations.get(scope.sessionId) !== delegated ||\n delegated.abort.signal.aborted ||\n remotePolicy.isDeviceAuthorized?.(delegated.deviceId) !== true\n )\n throw new Error('The paired activation was revoked.');\n delegated.approved = true;\n const grant = result.result as { grantId?: unknown; expiresAt?: unknown } | undefined;\n if (typeof grant?.expiresAt === 'number') delegated.expiresAt = grant.expiresAt;\n if (typeof grant?.grantId === 'string') delegated.grantId = grant.grantId;\n }\n if (request.operation === 'stop') {\n if (delegated !== undefined) delegated.expiresAt = undefined;\n const grantId = (request.payload as { grantId?: unknown } | undefined)?.grantId;\n if (typeof grantId !== 'string') throw new Error('Recording finalization requires its grant.');\n return recordings.importStop(scope.sessionId, grantId, result.result);\n }\n return result.result;\n },\n close() {\n if (closed) return;\n try {\n send({ type: 'po:computer-use:close', version: VERSION });\n } finally {\n close();\n }\n },\n };\n}\n"],"mappings":";;;;;;;AAUA,MAAM,UAAU;AAChB,MAAM,YAAY;AAClB,MAAM,iBAAiB;;AAIvB,eAAsB,yBACpB,YAAuB,SACvB,eAGI,CAAC,GAC0C;CAC/C,IAAI,CAAC,UAAU,QAAQ,CAAC,UAAU,WAAW,OAAO,KAAA;CACpD,MAAM,aAAa,IAAI,0BAA0B,MAAM,GAAG,QAAQ,KAAK,KAAK,GAAG,OAAO,GAAG,gBAAgB,CAAC,CAAC;CAC3G,IAAI,YAAY;CAChB,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAI,QAAQ;CACZ,IAAI;CACJ,MAAM,gCAAgB,IAAI,IAAY;CACtC,MAAM,kCAAkB,IAAI,IAAY;CACxC,MAAM,4BAAY,IAAI,IAAoC;CAC1D,MAAM,8BAAc,IAAI,IAGtB;CACF,MAAM,oBAAoB,YAA8B;EACtD,MAAM,QAAQ,QAAQ,IAAI,cAAc,KAAK;EAC7C,OAAO,aAAa,kBAAkB,KAAK,KAAK,KAAK,gBAAgB,OAAO,KAAK,KAAK,GAAG,OAAO,KAAK,KAAK,CAAC;CAC7G;CACA,MAAM,gBAAgB,YAAqB;EACzC,MAAM,SAAS,gBAAgB,OAAO;EACtC,OAAO,QAAQ,aAAa,YAAY,aAAa,qBAAqB,OAAO,QAAQ,MAAM,OAC3F,SACA,KAAA;CACN;CACA,MAAM,UAAU,cAA6B;EAC3C,KAAK,MAAM,CAAC,UAAU,YAAY,WAChC,IAAI,cAAc,KAAA,KAAa,YAAY,KAAA,KAAa,YAAY,WAAW,SAAS;CAC5F;CACA,MAAM,UAAU,WAAmB,kBAAkB,UAAgB;EACnE,MAAM,YAAY,YAAY,IAAI,SAAS;EAC3C,IAAI,cAAc,KAAA,GAAW;EAC7B,IAAI,CAAC,iBAAiB,YAAY,OAAO,SAAS;EAClD,UAAU,YAAY,KAAA;EACtB,UAAU,MAAM,MAAM;EACtB,gBAAgB,IAAI,SAAS;EAC7B,IAAI,UAAU,YAAY,KAAA,KAAa,WACrC,SACE;GAAE,MAAM;GAA2B;GAAW,WAAW;GAAQ,SAAS,EAAE,SAAS,UAAU,QAAQ;EAAE,GACzG,GACF,CAAC,CACE,MAAM,WAAW,WAAW,WAAW,WAAW,UAAU,SAAU,OAAO,MAAM,CAAC,CAAC,CACrF,YAAY,MAAM,CAAC;EAExB,OAAO,SAAS;CAClB;CACA,MAAM,0BAAU,IAAI,IAAkF;CACtG,MAAM,QAAQ,UAAwB;EACpC,IAAI,UAAU,CAAC,UAAU,WAAW,MAAM,IAAI,MAAM,uCAAuC;EAC3F,UAAU,KAAM,QAAQ,UAAwB;GAC9C,IAAI,OAAO,MAAM;EACnB,CAAC;CACH;CACA,MAAM,cAAoB;EACxB,IAAI,QAAQ;EACZ,SAAS;EACT,YAAY;EACZ,QAAQ;EACR,UAAU,IAAI,WAAW,SAAS;EAClC,UAAU,IAAI,cAAc,KAAK;EACjC,UAAU,IAAI,QAAQ,KAAK;EAC3B,KAAK,MAAM,WAAW,QAAQ,OAAO,GACnC,QAAQ,OAAO,KAAA,mBAAW,IAAI,MAAM,uCAAuC,CAAC;EAC9E,QAAQ,MAAM;EACd,KAAK,MAAM,aAAa,YAAY,KAAK,GAAG,OAAO,SAAS;EAC5D,OAAO;EACP,UAAU,MAAM;EAChB,WAAgB,MAAM,CAAC,CAAC,YAAY,QAAQ,YAAY,wCAAwC,CAAC;CAEnG;CACA,MAAM,aAAa,UAAyB;EAC1C,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;EACjE,MAAM,UAAU;EAChB,IAAI,QAAQ,YAAY,SAAS;EACjC,IAAI,QAAQ,SAAS,kCAAkC,OAAO,QAAQ,YAAY,WAAW;GAC3F,UAAU,QAAQ;GAElB,KAAK,MAAM,aAAa,YAAY,KAAK,GAAG,OAAO,WAAW,IAAI;GAClE,OAAO;GACP;EACF;EACA,IAAI,OAAO,QAAQ,cAAc,UAAU;EAC3C,IAAI,QAAQ,SAAS,2BAA2B,QAAQ,SAAS,4BAA4B;EAC7F,IAAI,OAAO,WAAW,KAAK,UAAU,OAAO,CAAC,IAAI,WAAW;GAC1D,MAAM;GACN;EACF;EACA,QAAQ,IAAI,QAAQ,SAAS,CAAC,EAAE,OAAO,OAAO;CAChD;CACA,UAAU,GAAG,WAAW,SAAS;CACjC,UAAU,GAAG,cAAc,KAAK;CAChC,UAAU,GAAG,QAAQ,KAAK;CAE1B,MAAM,YACJ,OACA,WACA,WAEA,IAAI,SAAS,SAAS,WAAW;EAC/B,QAAQ,eAAe;EACvB,IAAI,UAAU,QAAQ,QAAQ,KAAK,MAAM,IAAI,MAAM,wDAAwD;EAC3G,MAAM,YAAY,WAAW;EAC7B,MAAM,eAAqB;GACzB,IAAI;IACF,KAAK;KAAE,MAAM;KAA0B,SAAS;KAAS;IAAU,CAAC;GACtE,QAAQ,CAER;EACF;EACA,MAAM,UAAU,QAAkC,UAAwB;GACxE,IAAI,CAAC,QAAQ,OAAO,SAAS,GAAG;GAChC,aAAa,KAAK;GAClB,QAAQ,oBAAoB,SAAS,OAAO;GAC5C,IAAI,OAAO,OAAO,KAAK;QAClB,QAAQ,MAAO;EACtB;EACA,MAAM,gBAAsB;GAC1B,OAAO;GACP,OAAO,KAAA,mBAAW,IAAI,MAAM,6CAA6C,CAAC;EAC5E;EACA,MAAM,QAAQ,iBAAiB;GAC7B,OAAO;GACP,OAAO,KAAA,mBAAW,IAAI,MAAM,yCAAyC,CAAC;EACxE,GAAG,SAAS;EACZ,QAAQ,IAAI,WAAW,EAAE,OAAO,CAAC;EACjC,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EACzD,IAAI;GACF,MAAM,UAAU;IAAE,GAAG;IAAO,SAAS;IAAS;GAAU;GACxD,IAAI,OAAO,WAAW,KAAK,UAAU,OAAO,CAAC,IAAI,WAC/C,MAAM,IAAI,MAAM,4CAA4C;GAC9D,KAAK,OAAO;EACd,SAAS,OAAO;GACd,OAAO,KAAA,GAAW,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;EAC7E;CACF,CAAC;CAEH,IAAI;EACF,MAAM,QAAQ,MAAM,SAAS,EAAE,MAAM,wBAAwB,GAAG,IAAK;EACrE,IACE,MAAM,SAAS,2BACf,OAAO,MAAM,UAAU,YACvB,CAAC,kBAAkB,KAAK,MAAM,KAAK,GAEnC,MAAM,IAAI,MAAM,uCAAuC;EAEzD,QAAQ,MAAM;EACd,YAAY;EACZ,UAAU,MAAM,YAAY;CAC9B,QAAQ;EACN,MAAM;EACN;CACF;CAEA,OAAO;EACL,IAAI,YAAY;GACd,OAAO;EACT;EACA,IAAI,UAAU;GACZ,OAAO,aAAa;EACtB;EACA,WAAW;EACX,gBAAgB,OAAO,YAAY,UAAU,WAAW,KAAK,MAAM,WAAW,YAAY,KAAK;EAC/F,oBAAoB,SAAS;GAC3B,IAAI,CAAC,aAAa,CAAC,SAAS,OAAO;GACnC,IAAI,iBAAiB,OAAO,GAAG,OAAO;GACtC,MAAM,SAAS,aAAa,OAAO;GACnC,OACE,WAAW,KAAA,MACV,aAAa,iBAAiB,MAAM,OAAO,OAAO,WAAW,aAAa,OAAO,WAAW;EAEjG;EACA,iBAAiB,WAAW,SAAS;GACnC,IAAI,iBAAiB,OAAO,GAAG,OAAO;GACtC,MAAM,SAAS,aAAa,OAAO;GACnC,MAAM,YAAY,YAAY,IAAI,SAAS;GAC3C,OACE,aACA,WACA,WAAW,KAAA,KACX,cAAc,KAAA,KACd,UAAU,aAAa,OAAO,YAC9B,UAAU,cAAc,KAAA,KACxB,CAAC,UAAU,MAAM,OAAO;EAE5B;EACA,mBAAmB,WAAW,SAAS;GACrC,IAAI,iBAAiB,OAAO,GAAG,OAAO;GACtC,MAAM,SAAS,aAAa,OAAO;GACnC,MAAM,YAAY,YAAY,IAAI,SAAS;GAC3C,OACE,aAAa,WAAW,KAAA,KAAa,WAAW,aAAa,QAAQ,UAAU,aAAa,OAAO;EAEvG;EACA,iBAAiB,WAAW,SAAS;GACnC,IAAI,iBAAiB,OAAO,GAAG,OAAO;GACtC,MAAM,SAAS,aAAa,OAAO;GACnC,MAAM,YAAY,YAAY,IAAI,SAAS;GAC3C,IAAI,aAAa,WAAW,WAAW,KAAA,KAAa,CAAC,cAAc,IAAI,SAAS,GAAG,OAAO;GAC1F,OACE,aACA,WACA,WAAW,KAAA,KACX,WAAW,aAAa,QACxB,UAAU,cAAc,KAAA,KACxB,UAAU,YAAY,KAAK,IAAI,KAC/B,UAAU,aAAa,OAAO,YAC9B,CAAC,UAAU,MAAM,OAAO;EAE5B;EACA,aAAa,WAAW,SAAS;GAC/B,IAAI,CAAC,aAAa,CAAC,SAAS,MAAM,IAAI,MAAM,sCAAsC;GAClF,MAAM,SAAS,YAAY,KAAA,IAAY,KAAA,IAAY,aAAa,OAAO;GACvE,IAAI,WAAW,KAAA,GAAW;IACxB,MAAM,WAAW,YAAY,IAAI,SAAS;IAC1C,IAAI,aAAa,KAAA,KAAa,SAAS,aAAa,OAAO,UACzD,MAAM,IAAI,MAAM,gDAAgD;IAClE,IAAI,UAAU,MAAM,OAAO,SAAS;KAClC,SAAS,QAAQ,IAAI,gBAAgB;KACrC,SAAS,UAAU,KAAA;IACrB;IACA,IAAI,aAAa,KAAA,GACf,YAAY,IAAI,WAAW;KACzB,UAAU,OAAO;KACjB,UAAU;KACV,OAAO,IAAI,gBAAgB;IAC7B,CAAC;GACL,OAAO,IAAI,YAAY,KAAA,KAAa,CAAC,iBAAiB,OAAO,GAC3D,MAAM,IAAI,MAAM,6CAA6C;GAE/D,gBAAgB,OAAO,SAAS;GAChC,cAAc,IAAI,SAAS;EAC7B;EACA,aAAa,UAAU;GACrB,KAAK,MAAM,CAAC,WAAW,cAAc,aAAa,IAAI,UAAU,aAAa,UAAU,OAAO,SAAS;EACzG;EACA,cAAc,cAAc,cAAc,IAAI,SAAS;EACvD,gBAAgB,cAAc;GAC5B,OAAO,SAAS;GAChB,WACG,cAAc,SAAS,CAAC,CACxB,YAAY,QAAQ,YAAY,wCAAwC,CAAC;EAE9E;EACA,UAAU,UAAU,WAAW;GAC7B,UAAU,IAAI,UAAU,SAAS;GACjC,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;EACA,MAAM,QAAQ,OAAO,SAAS;GAC5B,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,sCAAsC;GACtE,IAAI,gBAAgB,IAAI,MAAM,SAAS,KAAK,QAAQ,cAAc,UAAU,QAAQ,cAAc,UAChG,MAAM,IAAI,MAAM,sCAAsC;GACxD,IAAI,QAAQ,cAAc,YAAY,QAAQ,cAAc,aAAa,CAAC,cAAc,IAAI,MAAM,SAAS,GACzG,MAAM,IAAI,MAAM,kDAAkD;GAEpE,MAAM,YAAY,YAAY,IAAI,MAAM,SAAS;GACjD,IAAI,cAAc,KAAA,KAAa,aAAa,qBAAqB,UAAU,QAAQ,MAAM,MAAM;IAC7F,OAAO,MAAM,SAAS;IACtB,IAAI,QAAQ,cAAc,QAAQ,MAAM,IAAI,MAAM,gCAAgC;GACpF;GACA,IAAI,QAAQ,cAAc,UAAU,cAAc,KAAA,GAAW;IAC3D,UAAU,YAAY,KAAA;IACtB,UAAU,MAAM,MAAM;GACxB;GACA,MAAM,SACJ,cAAc,KAAA,KAAa,QAAQ,cAAc,SAC7C,QAAQ,SACR,QAAQ,WAAW,KAAA,IACjB,UAAU,MAAM,SAChB,YAAY,IAAI,CAAC,QAAQ,QAAQ,UAAU,MAAM,MAAM,CAAC;GAChE,MAAM,SAAS,MAAM,SACnB;IACE,MAAM;IACN,WAAW,MAAM;IACjB,WAAW,QAAQ;IACnB,GAAI,QAAQ,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,QAAQ,QAAQ;GACtE,GACA,QAAQ,cAAc,aAAa,OAAU,KAC7C,MACF;GACA,IACE,OAAO,OAAO,mBAAmB,YAChC,eAAe,KAAA,KAAa,eAAe,OAAO,gBACnD;IACA,MAAM;IACN,MAAM,IAAI,MAAM,sDAAsD;GACxE;GACA,aAAa,OAAO;GACpB,IAAI,OAAO,OAAO,MAChB,MAAM,IAAI,MAAM,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,yBAAyB;GAC7F,IAAI,QAAQ,cAAc,cAAc,cAAc,KAAA,GAAW;IAC/D,IACE,YAAY,IAAI,MAAM,SAAS,MAAM,aACrC,UAAU,MAAM,OAAO,WACvB,aAAa,qBAAqB,UAAU,QAAQ,MAAM,MAE1D,MAAM,IAAI,MAAM,oCAAoC;IACtD,UAAU,WAAW;IACrB,MAAM,QAAQ,OAAO;IACrB,IAAI,OAAO,OAAO,cAAc,UAAU,UAAU,YAAY,MAAM;IACtE,IAAI,OAAO,OAAO,YAAY,UAAU,UAAU,UAAU,MAAM;GACpE;GACA,IAAI,QAAQ,cAAc,QAAQ;IAChC,IAAI,cAAc,KAAA,GAAW,UAAU,YAAY,KAAA;IACnD,MAAM,UAAW,QAAQ,SAA+C;IACxE,IAAI,OAAO,YAAY,UAAU,MAAM,IAAI,MAAM,4CAA4C;IAC7F,OAAO,WAAW,WAAW,MAAM,WAAW,SAAS,OAAO,MAAM;GACtE;GACA,OAAO,OAAO;EAChB;EACA,QAAQ;GACN,IAAI,QAAQ;GACZ,IAAI;IACF,KAAK;KAAE,MAAM;KAAyB,SAAS;IAAQ,CAAC;GAC1D,UAAU;IACR,MAAM;GACR;EACF;CACF;AACF"}
@@ -20,7 +20,7 @@ const run = (0, node_util.promisify)(node_child_process.execFile);
20
20
  */
21
21
  async function createDefaultWorkspaceFolder(name, homeDirectory) {
22
22
  if (name === "" || node_path.default.basename(name) !== name || name === "." || name === "..") throw new Error(`"${name}" cannot be used as a workspace folder name.`);
23
- const folder = node_path.default.join((0, _agimon_ai_po_config_config.globalDoomConfigDirectory)(homeDirectory), WORKSPACES_FOLDER, name);
23
+ const folder = node_path.default.join((0, _agimon_ai_po_config_config.globalPoConfigDirectory)(homeDirectory), WORKSPACES_FOLDER, name);
24
24
  await node_fs.default.promises.mkdir(folder, {
25
25
  recursive: true,
26
26
  mode: DIRECTORY_MODE
@@ -1 +1 @@
1
- {"version":3,"file":"defaultWorkspace.cjs","names":["promisify","execFile","path","globalDoomConfigDirectory","fs"],"sources":["../../../../src/builders/server/defaultWorkspace.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/po-config/config';\n\nconst WORKSPACES_FOLDER = 'workspace';\nconst DIRECTORY_MODE = 0o700;\nconst GIT_TIMEOUT_MS = 15_000;\n\nconst run = promisify(execFile);\n\n/**\n * Creates `~/.po/workspace/<name>` for a workspace added by name alone.\n *\n * The folder is made a git repository because workspace admission resolves a\n * path to its nearest repository root; an empty folder would otherwise resolve\n * to an ancestor such as home, which a `.po` directory marks as a root.\n */\nexport async function createDefaultWorkspaceFolder(name: string, homeDirectory: string): Promise<string> {\n if (name === '' || path.basename(name) !== name || name === '.' || name === '..')\n throw new Error(`\"${name}\" cannot be used as a workspace folder name.`);\n const folder = path.join(globalDoomConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);\n await fs.promises.mkdir(folder, { recursive: true, mode: DIRECTORY_MODE });\n if (!fs.existsSync(path.join(folder, '.git'))) {\n await run('git', ['init', '--quiet'], { cwd: folder, timeout: GIT_TIMEOUT_MS, windowsHide: true });\n }\n return folder;\n}\n"],"mappings":";;;;;;;;;AAOA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,OAAA,GAAMA,UAAAA,UAAAA,CAAUC,mBAAAA,QAAQ;;;;;;;;AAS9B,eAAsB,6BAA6B,MAAc,eAAwC;CACvG,IAAI,SAAS,MAAMC,UAAAA,QAAK,SAAS,IAAI,MAAM,QAAQ,SAAS,OAAO,SAAS,MAC1E,MAAM,IAAI,MAAM,IAAI,KAAK,6CAA6C;CACxE,MAAM,SAASA,UAAAA,QAAK,MAAA,GAAKC,4BAAAA,0BAAAA,CAA0B,aAAa,GAAG,mBAAmB,IAAI;CAC1F,MAAMC,QAAAA,QAAG,SAAS,MAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAe,CAAC;CACzE,IAAI,CAACA,QAAAA,QAAG,WAAWF,UAAAA,QAAK,KAAK,QAAQ,MAAM,CAAC,GAC1C,MAAM,IAAI,OAAO,CAAC,QAAQ,SAAS,GAAG;EAAE,KAAK;EAAQ,SAAS;EAAgB,aAAa;CAAK,CAAC;CAEnG,OAAO;AACT"}
1
+ {"version":3,"file":"defaultWorkspace.cjs","names":["promisify","execFile","path","globalPoConfigDirectory","fs"],"sources":["../../../../src/builders/server/defaultWorkspace.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { globalPoConfigDirectory } from '@agimon-ai/po-config/config';\n\nconst WORKSPACES_FOLDER = 'workspace';\nconst DIRECTORY_MODE = 0o700;\nconst GIT_TIMEOUT_MS = 15_000;\n\nconst run = promisify(execFile);\n\n/**\n * Creates `~/.po/workspace/<name>` for a workspace added by name alone.\n *\n * The folder is made a git repository because workspace admission resolves a\n * path to its nearest repository root; an empty folder would otherwise resolve\n * to an ancestor such as home, which a `.po` directory marks as a root.\n */\nexport async function createDefaultWorkspaceFolder(name: string, homeDirectory: string): Promise<string> {\n if (name === '' || path.basename(name) !== name || name === '.' || name === '..')\n throw new Error(`\"${name}\" cannot be used as a workspace folder name.`);\n const folder = path.join(globalPoConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);\n await fs.promises.mkdir(folder, { recursive: true, mode: DIRECTORY_MODE });\n if (!fs.existsSync(path.join(folder, '.git'))) {\n await run('git', ['init', '--quiet'], { cwd: folder, timeout: GIT_TIMEOUT_MS, windowsHide: true });\n }\n return folder;\n}\n"],"mappings":";;;;;;;;;AAOA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,OAAA,GAAMA,UAAAA,UAAAA,CAAUC,mBAAAA,QAAQ;;;;;;;;AAS9B,eAAsB,6BAA6B,MAAc,eAAwC;CACvG,IAAI,SAAS,MAAMC,UAAAA,QAAK,SAAS,IAAI,MAAM,QAAQ,SAAS,OAAO,SAAS,MAC1E,MAAM,IAAI,MAAM,IAAI,KAAK,6CAA6C;CACxE,MAAM,SAASA,UAAAA,QAAK,MAAA,GAAKC,4BAAAA,wBAAAA,CAAwB,aAAa,GAAG,mBAAmB,IAAI;CACxF,MAAMC,QAAAA,QAAG,SAAS,MAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAe,CAAC;CACzE,IAAI,CAACA,QAAAA,QAAG,WAAWF,UAAAA,QAAK,KAAK,QAAQ,MAAM,CAAC,GAC1C,MAAM,IAAI,OAAO,CAAC,QAAQ,SAAS,GAAG;EAAE,KAAK;EAAQ,SAAS;EAAgB,aAAa;CAAK,CAAC;CAEnG,OAAO;AACT"}
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { execFile } from "node:child_process";
4
- import { globalDoomConfigDirectory } from "@agimon-ai/po-config/config";
4
+ import { globalPoConfigDirectory } from "@agimon-ai/po-config/config";
5
5
  import { promisify } from "node:util";
6
6
  //#region src/builders/server/defaultWorkspace.ts
7
7
  const WORKSPACES_FOLDER = "workspace";
@@ -17,7 +17,7 @@ const run = promisify(execFile);
17
17
  */
18
18
  async function createDefaultWorkspaceFolder(name, homeDirectory) {
19
19
  if (name === "" || path.basename(name) !== name || name === "." || name === "..") throw new Error(`"${name}" cannot be used as a workspace folder name.`);
20
- const folder = path.join(globalDoomConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);
20
+ const folder = path.join(globalPoConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);
21
21
  await fs.promises.mkdir(folder, {
22
22
  recursive: true,
23
23
  mode: DIRECTORY_MODE
@@ -1 +1 @@
1
- {"version":3,"file":"defaultWorkspace.mjs","names":[],"sources":["../../../../src/builders/server/defaultWorkspace.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/po-config/config';\n\nconst WORKSPACES_FOLDER = 'workspace';\nconst DIRECTORY_MODE = 0o700;\nconst GIT_TIMEOUT_MS = 15_000;\n\nconst run = promisify(execFile);\n\n/**\n * Creates `~/.po/workspace/<name>` for a workspace added by name alone.\n *\n * The folder is made a git repository because workspace admission resolves a\n * path to its nearest repository root; an empty folder would otherwise resolve\n * to an ancestor such as home, which a `.po` directory marks as a root.\n */\nexport async function createDefaultWorkspaceFolder(name: string, homeDirectory: string): Promise<string> {\n if (name === '' || path.basename(name) !== name || name === '.' || name === '..')\n throw new Error(`\"${name}\" cannot be used as a workspace folder name.`);\n const folder = path.join(globalDoomConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);\n await fs.promises.mkdir(folder, { recursive: true, mode: DIRECTORY_MODE });\n if (!fs.existsSync(path.join(folder, '.git'))) {\n await run('git', ['init', '--quiet'], { cwd: folder, timeout: GIT_TIMEOUT_MS, windowsHide: true });\n }\n return folder;\n}\n"],"mappings":";;;;;;AAOA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,MAAM,UAAU,QAAQ;;;;;;;;AAS9B,eAAsB,6BAA6B,MAAc,eAAwC;CACvG,IAAI,SAAS,MAAM,KAAK,SAAS,IAAI,MAAM,QAAQ,SAAS,OAAO,SAAS,MAC1E,MAAM,IAAI,MAAM,IAAI,KAAK,6CAA6C;CACxE,MAAM,SAAS,KAAK,KAAK,0BAA0B,aAAa,GAAG,mBAAmB,IAAI;CAC1F,MAAM,GAAG,SAAS,MAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAe,CAAC;CACzE,IAAI,CAAC,GAAG,WAAW,KAAK,KAAK,QAAQ,MAAM,CAAC,GAC1C,MAAM,IAAI,OAAO,CAAC,QAAQ,SAAS,GAAG;EAAE,KAAK;EAAQ,SAAS;EAAgB,aAAa;CAAK,CAAC;CAEnG,OAAO;AACT"}
1
+ {"version":3,"file":"defaultWorkspace.mjs","names":[],"sources":["../../../../src/builders/server/defaultWorkspace.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { globalPoConfigDirectory } from '@agimon-ai/po-config/config';\n\nconst WORKSPACES_FOLDER = 'workspace';\nconst DIRECTORY_MODE = 0o700;\nconst GIT_TIMEOUT_MS = 15_000;\n\nconst run = promisify(execFile);\n\n/**\n * Creates `~/.po/workspace/<name>` for a workspace added by name alone.\n *\n * The folder is made a git repository because workspace admission resolves a\n * path to its nearest repository root; an empty folder would otherwise resolve\n * to an ancestor such as home, which a `.po` directory marks as a root.\n */\nexport async function createDefaultWorkspaceFolder(name: string, homeDirectory: string): Promise<string> {\n if (name === '' || path.basename(name) !== name || name === '.' || name === '..')\n throw new Error(`\"${name}\" cannot be used as a workspace folder name.`);\n const folder = path.join(globalPoConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);\n await fs.promises.mkdir(folder, { recursive: true, mode: DIRECTORY_MODE });\n if (!fs.existsSync(path.join(folder, '.git'))) {\n await run('git', ['init', '--quiet'], { cwd: folder, timeout: GIT_TIMEOUT_MS, windowsHide: true });\n }\n return folder;\n}\n"],"mappings":";;;;;;AAOA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,MAAM,UAAU,QAAQ;;;;;;;;AAS9B,eAAsB,6BAA6B,MAAc,eAAwC;CACvG,IAAI,SAAS,MAAM,KAAK,SAAS,IAAI,MAAM,QAAQ,SAAS,OAAO,SAAS,MAC1E,MAAM,IAAI,MAAM,IAAI,KAAK,6CAA6C;CACxE,MAAM,SAAS,KAAK,KAAK,wBAAwB,aAAa,GAAG,mBAAmB,IAAI;CACxF,MAAM,GAAG,SAAS,MAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAe,CAAC;CACzE,IAAI,CAAC,GAAG,WAAW,KAAK,KAAK,QAAQ,MAAM,CAAC,GAC1C,MAAM,IAAI,OAAO,CAAC,QAAQ,SAAS,GAAG;EAAE,KAAK;EAAQ,SAAS;EAAgB,aAAa;CAAK,CAAC;CAEnG,OAAO;AACT"}
@@ -13,11 +13,11 @@ const RECYCLE_FILE = "recycle.json";
13
13
  * as they are there. Subagents count because a restart ends them mid-run just like a turn.
14
14
  */
15
15
  const RESTART_BLOCKING_PROVIDERS = [
16
- "doom-runner",
16
+ "po-runner",
17
17
  "team-direct-runs",
18
- "doom-task"
18
+ "po-task"
19
19
  ];
20
- const RUNNER_PROVIDER = "doom-runner";
20
+ const RUNNER_PROVIDER = "po-runner";
21
21
  const RUNNER_WORKING_STATUS = "running";
22
22
  /** True when a session's background-work snapshot holds work a restart would kill; fails closed. */
23
23
  function holdsRestart(snapshot) {
@@ -1 +1 @@
1
- {"version":3,"file":"idleRestart.cjs","names":["path","fs"],"sources":["../../../../src/builders/server/idleRestart.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { DoomBackgroundWorkSnapshot } from '@agimon-ai/po-core/backgroundWork';\n\n/** Superseded package versions held in memory before a restart is pending. */\nexport const SUPERSEDED_LIMIT = 8;\n/** Quiet time before an idle restart, re-checked once it elapses. */\nexport const RESTART_DEBOUNCE_MS = 30_000;\n/** Written only by a deliberate idle restart; a crash leaves none. */\nexport const RECYCLE_FILE = 'recycle.json';\n/** Temporary failure: a supervisor restarts the server on the same port and token. */\nexport const RESTART_EXIT_CODE = 75;\n\n/**\n * Background work a restart would kill, matching po-autostop's gate: runners (open\n * terminals and background commands) and running subagents. Workflow runs are left out,\n * as they are there. Subagents count because a restart ends them mid-run just like a turn.\n */\nconst RESTART_BLOCKING_PROVIDERS: readonly string[] = ['doom-runner', 'team-direct-runs', 'doom-task'];\nconst RUNNER_PROVIDER = 'doom-runner';\nconst RUNNER_WORKING_STATUS = 'running';\n\n/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */\nexport function holdsRestart(snapshot: DoomBackgroundWorkSnapshot): boolean {\n if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;\n return snapshot.items.some((item) => {\n if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;\n // A runner item without a status is not known to be done.\n if (item.provider !== RUNNER_PROVIDER || item.status === undefined) return true;\n return item.status === RUNNER_WORKING_STATUS;\n });\n}\n\n/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */\nexport const TEST_IDLE_RESTART_LIMIT_ENV = 'PO_TEST_IDLE_RESTART_LIMIT';\n/** Test-only: shortens the idle-restart debounce for e2e tests. */\nexport const TEST_IDLE_RESTART_DEBOUNCE_ENV = 'PO_TEST_IDLE_RESTART_DEBOUNCE_MS';\n\nfunction parseTestOverride(environment: NodeJS.ProcessEnv, name: string, minimum: number): number | undefined {\n const raw = environment[name];\n if (raw === undefined || raw === '') return undefined;\n const value = Number(raw);\n if (!Number.isSafeInteger(value) || value < minimum)\n throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);\n return value;\n}\n\n/**\n * Test-only overrides for the idle restart limit and debounce, read from the environment.\n * Production never sets these; an invalid value fails startup rather than being ignored.\n */\nexport function testIdleRestartOverrides(\n environment: NodeJS.ProcessEnv,\n): Pick<IdleRestartOptions, 'limit' | 'debounceMs'> {\n const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);\n const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);\n return { ...(limit === undefined ? {} : { limit }), ...(debounceMs === undefined ? {} : { debounceMs }) };\n}\n\nexport interface IdleRestartOptions {\n /** True while any session runs a turn or a compaction, or a terminal is open. */\n readonly busy: () => Promise<boolean>;\n /** Starts the restart. Called at most once. */\n readonly restart: () => void;\n readonly limit?: number;\n readonly debounceMs?: number;\n readonly setTimer?: (callback: () => void, ms: number) => () => void;\n readonly onError?: (error: unknown) => void;\n}\n\nexport interface IdleRestart {\n /** Records package URLs replaced by newer ones; each URL counts once. */\n supersede(urls: readonly string[]): void;\n /** Re-arms the debounce when a restart is pending; call when activity may have stopped. */\n poke(): void;\n readonly pending: () => boolean;\n dispose(): void;\n}\n\nconst defaultTimer = (callback: () => void, ms: number): (() => void) => {\n const timer = setTimeout(callback, ms);\n timer.unref();\n return () => clearTimeout(timer);\n};\n\n/**\n * Restarts the server at idle once old package versions pile up in memory.\n *\n * Native `import()` never unloads a module, so every superseded URL stays resident. At\n * `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the\n * busy check runs again when the debounce elapses, so a turn that started meanwhile\n * defers it to the next poke.\n */\nexport function createIdleRestart(options: IdleRestartOptions): IdleRestart {\n const limit = options.limit ?? SUPERSEDED_LIMIT;\n const debounceMs = options.debounceMs ?? RESTART_DEBOUNCE_MS;\n const setTimer = options.setTimer ?? defaultTimer;\n const superseded = new Set<string>();\n let cancel: (() => void) | undefined;\n let fired = false;\n const pending = () => superseded.size >= limit;\n const poke = (): void => {\n if (!pending() || fired) return;\n cancel?.();\n cancel = setTimer(() => {\n cancel = undefined;\n void options\n .busy()\n .then((busy) => {\n if (busy || fired) return;\n fired = true;\n options.restart();\n })\n .catch((error: unknown) => options.onError?.(error));\n }, debounceMs);\n };\n return {\n supersede(urls) {\n for (const url of urls) superseded.add(url);\n poke();\n },\n poke,\n pending,\n dispose() {\n cancel?.();\n cancel = undefined;\n fired = true;\n },\n };\n}\n\nexport function writeRecycleFile(directory: string, sessionIds: readonly string[]): void {\n fs.mkdirSync(directory, { recursive: true });\n const target = path.join(directory, RECYCLE_FILE);\n const staging = `${target}.${String(process.pid)}.tmp`;\n fs.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\\n`);\n fs.renameSync(staging, target);\n}\n\n/** The session ids a deliberate restart left open; empty when there is no valid file. */\nexport function readRecycleFile(directory: string): string[] {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, RECYCLE_FILE), 'utf8')) as unknown;\n const ids = (parsed as { sessionIds?: unknown } | null)?.sessionIds;\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : [];\n } catch {\n // Missing (the usual case, including after a crash) or unreadable: nothing to reopen.\n return [];\n }\n}\n\nexport function removeRecycleFile(directory: string): void {\n fs.rmSync(path.join(directory, RECYCLE_FILE), { force: true });\n}\n\n/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */\nexport async function reopenRecycledSessions(\n directory: string,\n reopen: (sessionId: string) => Promise<void>,\n onError: (sessionId: string, error: unknown) => void,\n): Promise<string[]> {\n const ids = readRecycleFile(directory);\n const reopened: string[] = [];\n try {\n for (const id of ids) {\n try {\n await reopen(id);\n reopened.push(id);\n } catch (error) {\n onError(id, error);\n }\n }\n } finally {\n removeRecycleFile(directory);\n }\n return reopened;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"idleRestart.cjs","names":["path","fs"],"sources":["../../../../src/builders/server/idleRestart.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { PoBackgroundWorkSnapshot } from '@agimon-ai/po-core/backgroundWork';\n\n/** Superseded package versions held in memory before a restart is pending. */\nexport const SUPERSEDED_LIMIT = 8;\n/** Quiet time before an idle restart, re-checked once it elapses. */\nexport const RESTART_DEBOUNCE_MS = 30_000;\n/** Written only by a deliberate idle restart; a crash leaves none. */\nexport const RECYCLE_FILE = 'recycle.json';\n/** Temporary failure: a supervisor restarts the server on the same port and token. */\nexport const RESTART_EXIT_CODE = 75;\n\n/**\n * Background work a restart would kill, matching po-autostop's gate: runners (open\n * terminals and background commands) and running subagents. Workflow runs are left out,\n * as they are there. Subagents count because a restart ends them mid-run just like a turn.\n */\nconst RESTART_BLOCKING_PROVIDERS: readonly string[] = ['po-runner', 'team-direct-runs', 'po-task'];\nconst RUNNER_PROVIDER = 'po-runner';\nconst RUNNER_WORKING_STATUS = 'running';\n\n/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */\nexport function holdsRestart(snapshot: PoBackgroundWorkSnapshot): boolean {\n if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;\n return snapshot.items.some((item) => {\n if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;\n // A runner item without a status is not known to be done.\n if (item.provider !== RUNNER_PROVIDER || item.status === undefined) return true;\n return item.status === RUNNER_WORKING_STATUS;\n });\n}\n\n/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */\nexport const TEST_IDLE_RESTART_LIMIT_ENV = 'PO_TEST_IDLE_RESTART_LIMIT';\n/** Test-only: shortens the idle-restart debounce for e2e tests. */\nexport const TEST_IDLE_RESTART_DEBOUNCE_ENV = 'PO_TEST_IDLE_RESTART_DEBOUNCE_MS';\n\nfunction parseTestOverride(environment: NodeJS.ProcessEnv, name: string, minimum: number): number | undefined {\n const raw = environment[name];\n if (raw === undefined || raw === '') return undefined;\n const value = Number(raw);\n if (!Number.isSafeInteger(value) || value < minimum)\n throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);\n return value;\n}\n\n/**\n * Test-only overrides for the idle restart limit and debounce, read from the environment.\n * Production never sets these; an invalid value fails startup rather than being ignored.\n */\nexport function testIdleRestartOverrides(\n environment: NodeJS.ProcessEnv,\n): Pick<IdleRestartOptions, 'limit' | 'debounceMs'> {\n const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);\n const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);\n return { ...(limit === undefined ? {} : { limit }), ...(debounceMs === undefined ? {} : { debounceMs }) };\n}\n\nexport interface IdleRestartOptions {\n /** True while any session runs a turn or a compaction, or a terminal is open. */\n readonly busy: () => Promise<boolean>;\n /** Starts the restart. Called at most once. */\n readonly restart: () => void;\n readonly limit?: number;\n readonly debounceMs?: number;\n readonly setTimer?: (callback: () => void, ms: number) => () => void;\n readonly onError?: (error: unknown) => void;\n}\n\nexport interface IdleRestart {\n /** Records package URLs replaced by newer ones; each URL counts once. */\n supersede(urls: readonly string[]): void;\n /** Re-arms the debounce when a restart is pending; call when activity may have stopped. */\n poke(): void;\n readonly pending: () => boolean;\n dispose(): void;\n}\n\nconst defaultTimer = (callback: () => void, ms: number): (() => void) => {\n const timer = setTimeout(callback, ms);\n timer.unref();\n return () => clearTimeout(timer);\n};\n\n/**\n * Restarts the server at idle once old package versions pile up in memory.\n *\n * Native `import()` never unloads a module, so every superseded URL stays resident. At\n * `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the\n * busy check runs again when the debounce elapses, so a turn that started meanwhile\n * defers it to the next poke.\n */\nexport function createIdleRestart(options: IdleRestartOptions): IdleRestart {\n const limit = options.limit ?? SUPERSEDED_LIMIT;\n const debounceMs = options.debounceMs ?? RESTART_DEBOUNCE_MS;\n const setTimer = options.setTimer ?? defaultTimer;\n const superseded = new Set<string>();\n let cancel: (() => void) | undefined;\n let fired = false;\n const pending = () => superseded.size >= limit;\n const poke = (): void => {\n if (!pending() || fired) return;\n cancel?.();\n cancel = setTimer(() => {\n cancel = undefined;\n void options\n .busy()\n .then((busy) => {\n if (busy || fired) return;\n fired = true;\n options.restart();\n })\n .catch((error: unknown) => options.onError?.(error));\n }, debounceMs);\n };\n return {\n supersede(urls) {\n for (const url of urls) superseded.add(url);\n poke();\n },\n poke,\n pending,\n dispose() {\n cancel?.();\n cancel = undefined;\n fired = true;\n },\n };\n}\n\nexport function writeRecycleFile(directory: string, sessionIds: readonly string[]): void {\n fs.mkdirSync(directory, { recursive: true });\n const target = path.join(directory, RECYCLE_FILE);\n const staging = `${target}.${String(process.pid)}.tmp`;\n fs.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\\n`);\n fs.renameSync(staging, target);\n}\n\n/** The session ids a deliberate restart left open; empty when there is no valid file. */\nexport function readRecycleFile(directory: string): string[] {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, RECYCLE_FILE), 'utf8')) as unknown;\n const ids = (parsed as { sessionIds?: unknown } | null)?.sessionIds;\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : [];\n } catch {\n // Missing (the usual case, including after a crash) or unreadable: nothing to reopen.\n return [];\n }\n}\n\nexport function removeRecycleFile(directory: string): void {\n fs.rmSync(path.join(directory, RECYCLE_FILE), { force: true });\n}\n\n/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */\nexport async function reopenRecycledSessions(\n directory: string,\n reopen: (sessionId: string) => Promise<void>,\n onError: (sessionId: string, error: unknown) => void,\n): Promise<string[]> {\n const ids = readRecycleFile(directory);\n const reopened: string[] = [];\n try {\n for (const id of ids) {\n try {\n await reopen(id);\n reopened.push(id);\n } catch (error) {\n onError(id, error);\n }\n }\n } finally {\n removeRecycleFile(directory);\n }\n return reopened;\n}\n"],"mappings":"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"}
@@ -10,11 +10,11 @@ const RECYCLE_FILE = "recycle.json";
10
10
  * as they are there. Subagents count because a restart ends them mid-run just like a turn.
11
11
  */
12
12
  const RESTART_BLOCKING_PROVIDERS = [
13
- "doom-runner",
13
+ "po-runner",
14
14
  "team-direct-runs",
15
- "doom-task"
15
+ "po-task"
16
16
  ];
17
- const RUNNER_PROVIDER = "doom-runner";
17
+ const RUNNER_PROVIDER = "po-runner";
18
18
  const RUNNER_WORKING_STATUS = "running";
19
19
  /** True when a session's background-work snapshot holds work a restart would kill; fails closed. */
20
20
  function holdsRestart(snapshot) {
@@ -1 +1 @@
1
- {"version":3,"file":"idleRestart.mjs","names":[],"sources":["../../../../src/builders/server/idleRestart.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { DoomBackgroundWorkSnapshot } from '@agimon-ai/po-core/backgroundWork';\n\n/** Superseded package versions held in memory before a restart is pending. */\nexport const SUPERSEDED_LIMIT = 8;\n/** Quiet time before an idle restart, re-checked once it elapses. */\nexport const RESTART_DEBOUNCE_MS = 30_000;\n/** Written only by a deliberate idle restart; a crash leaves none. */\nexport const RECYCLE_FILE = 'recycle.json';\n/** Temporary failure: a supervisor restarts the server on the same port and token. */\nexport const RESTART_EXIT_CODE = 75;\n\n/**\n * Background work a restart would kill, matching po-autostop's gate: runners (open\n * terminals and background commands) and running subagents. Workflow runs are left out,\n * as they are there. Subagents count because a restart ends them mid-run just like a turn.\n */\nconst RESTART_BLOCKING_PROVIDERS: readonly string[] = ['doom-runner', 'team-direct-runs', 'doom-task'];\nconst RUNNER_PROVIDER = 'doom-runner';\nconst RUNNER_WORKING_STATUS = 'running';\n\n/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */\nexport function holdsRestart(snapshot: DoomBackgroundWorkSnapshot): boolean {\n if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;\n return snapshot.items.some((item) => {\n if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;\n // A runner item without a status is not known to be done.\n if (item.provider !== RUNNER_PROVIDER || item.status === undefined) return true;\n return item.status === RUNNER_WORKING_STATUS;\n });\n}\n\n/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */\nexport const TEST_IDLE_RESTART_LIMIT_ENV = 'PO_TEST_IDLE_RESTART_LIMIT';\n/** Test-only: shortens the idle-restart debounce for e2e tests. */\nexport const TEST_IDLE_RESTART_DEBOUNCE_ENV = 'PO_TEST_IDLE_RESTART_DEBOUNCE_MS';\n\nfunction parseTestOverride(environment: NodeJS.ProcessEnv, name: string, minimum: number): number | undefined {\n const raw = environment[name];\n if (raw === undefined || raw === '') return undefined;\n const value = Number(raw);\n if (!Number.isSafeInteger(value) || value < minimum)\n throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);\n return value;\n}\n\n/**\n * Test-only overrides for the idle restart limit and debounce, read from the environment.\n * Production never sets these; an invalid value fails startup rather than being ignored.\n */\nexport function testIdleRestartOverrides(\n environment: NodeJS.ProcessEnv,\n): Pick<IdleRestartOptions, 'limit' | 'debounceMs'> {\n const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);\n const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);\n return { ...(limit === undefined ? {} : { limit }), ...(debounceMs === undefined ? {} : { debounceMs }) };\n}\n\nexport interface IdleRestartOptions {\n /** True while any session runs a turn or a compaction, or a terminal is open. */\n readonly busy: () => Promise<boolean>;\n /** Starts the restart. Called at most once. */\n readonly restart: () => void;\n readonly limit?: number;\n readonly debounceMs?: number;\n readonly setTimer?: (callback: () => void, ms: number) => () => void;\n readonly onError?: (error: unknown) => void;\n}\n\nexport interface IdleRestart {\n /** Records package URLs replaced by newer ones; each URL counts once. */\n supersede(urls: readonly string[]): void;\n /** Re-arms the debounce when a restart is pending; call when activity may have stopped. */\n poke(): void;\n readonly pending: () => boolean;\n dispose(): void;\n}\n\nconst defaultTimer = (callback: () => void, ms: number): (() => void) => {\n const timer = setTimeout(callback, ms);\n timer.unref();\n return () => clearTimeout(timer);\n};\n\n/**\n * Restarts the server at idle once old package versions pile up in memory.\n *\n * Native `import()` never unloads a module, so every superseded URL stays resident. At\n * `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the\n * busy check runs again when the debounce elapses, so a turn that started meanwhile\n * defers it to the next poke.\n */\nexport function createIdleRestart(options: IdleRestartOptions): IdleRestart {\n const limit = options.limit ?? SUPERSEDED_LIMIT;\n const debounceMs = options.debounceMs ?? RESTART_DEBOUNCE_MS;\n const setTimer = options.setTimer ?? defaultTimer;\n const superseded = new Set<string>();\n let cancel: (() => void) | undefined;\n let fired = false;\n const pending = () => superseded.size >= limit;\n const poke = (): void => {\n if (!pending() || fired) return;\n cancel?.();\n cancel = setTimer(() => {\n cancel = undefined;\n void options\n .busy()\n .then((busy) => {\n if (busy || fired) return;\n fired = true;\n options.restart();\n })\n .catch((error: unknown) => options.onError?.(error));\n }, debounceMs);\n };\n return {\n supersede(urls) {\n for (const url of urls) superseded.add(url);\n poke();\n },\n poke,\n pending,\n dispose() {\n cancel?.();\n cancel = undefined;\n fired = true;\n },\n };\n}\n\nexport function writeRecycleFile(directory: string, sessionIds: readonly string[]): void {\n fs.mkdirSync(directory, { recursive: true });\n const target = path.join(directory, RECYCLE_FILE);\n const staging = `${target}.${String(process.pid)}.tmp`;\n fs.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\\n`);\n fs.renameSync(staging, target);\n}\n\n/** The session ids a deliberate restart left open; empty when there is no valid file. */\nexport function readRecycleFile(directory: string): string[] {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, RECYCLE_FILE), 'utf8')) as unknown;\n const ids = (parsed as { sessionIds?: unknown } | null)?.sessionIds;\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : [];\n } catch {\n // Missing (the usual case, including after a crash) or unreadable: nothing to reopen.\n return [];\n }\n}\n\nexport function removeRecycleFile(directory: string): void {\n fs.rmSync(path.join(directory, RECYCLE_FILE), { force: true });\n}\n\n/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */\nexport async function reopenRecycledSessions(\n directory: string,\n reopen: (sessionId: string) => Promise<void>,\n onError: (sessionId: string, error: unknown) => void,\n): Promise<string[]> {\n const ids = readRecycleFile(directory);\n const reopened: string[] = [];\n try {\n for (const id of ids) {\n try {\n await reopen(id);\n reopened.push(id);\n } catch (error) {\n onError(id, error);\n }\n }\n } finally {\n removeRecycleFile(directory);\n }\n return reopened;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"idleRestart.mjs","names":[],"sources":["../../../../src/builders/server/idleRestart.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { PoBackgroundWorkSnapshot } from '@agimon-ai/po-core/backgroundWork';\n\n/** Superseded package versions held in memory before a restart is pending. */\nexport const SUPERSEDED_LIMIT = 8;\n/** Quiet time before an idle restart, re-checked once it elapses. */\nexport const RESTART_DEBOUNCE_MS = 30_000;\n/** Written only by a deliberate idle restart; a crash leaves none. */\nexport const RECYCLE_FILE = 'recycle.json';\n/** Temporary failure: a supervisor restarts the server on the same port and token. */\nexport const RESTART_EXIT_CODE = 75;\n\n/**\n * Background work a restart would kill, matching po-autostop's gate: runners (open\n * terminals and background commands) and running subagents. Workflow runs are left out,\n * as they are there. Subagents count because a restart ends them mid-run just like a turn.\n */\nconst RESTART_BLOCKING_PROVIDERS: readonly string[] = ['po-runner', 'team-direct-runs', 'po-task'];\nconst RUNNER_PROVIDER = 'po-runner';\nconst RUNNER_WORKING_STATUS = 'running';\n\n/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */\nexport function holdsRestart(snapshot: PoBackgroundWorkSnapshot): boolean {\n if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;\n return snapshot.items.some((item) => {\n if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;\n // A runner item without a status is not known to be done.\n if (item.provider !== RUNNER_PROVIDER || item.status === undefined) return true;\n return item.status === RUNNER_WORKING_STATUS;\n });\n}\n\n/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */\nexport const TEST_IDLE_RESTART_LIMIT_ENV = 'PO_TEST_IDLE_RESTART_LIMIT';\n/** Test-only: shortens the idle-restart debounce for e2e tests. */\nexport const TEST_IDLE_RESTART_DEBOUNCE_ENV = 'PO_TEST_IDLE_RESTART_DEBOUNCE_MS';\n\nfunction parseTestOverride(environment: NodeJS.ProcessEnv, name: string, minimum: number): number | undefined {\n const raw = environment[name];\n if (raw === undefined || raw === '') return undefined;\n const value = Number(raw);\n if (!Number.isSafeInteger(value) || value < minimum)\n throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);\n return value;\n}\n\n/**\n * Test-only overrides for the idle restart limit and debounce, read from the environment.\n * Production never sets these; an invalid value fails startup rather than being ignored.\n */\nexport function testIdleRestartOverrides(\n environment: NodeJS.ProcessEnv,\n): Pick<IdleRestartOptions, 'limit' | 'debounceMs'> {\n const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);\n const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);\n return { ...(limit === undefined ? {} : { limit }), ...(debounceMs === undefined ? {} : { debounceMs }) };\n}\n\nexport interface IdleRestartOptions {\n /** True while any session runs a turn or a compaction, or a terminal is open. */\n readonly busy: () => Promise<boolean>;\n /** Starts the restart. Called at most once. */\n readonly restart: () => void;\n readonly limit?: number;\n readonly debounceMs?: number;\n readonly setTimer?: (callback: () => void, ms: number) => () => void;\n readonly onError?: (error: unknown) => void;\n}\n\nexport interface IdleRestart {\n /** Records package URLs replaced by newer ones; each URL counts once. */\n supersede(urls: readonly string[]): void;\n /** Re-arms the debounce when a restart is pending; call when activity may have stopped. */\n poke(): void;\n readonly pending: () => boolean;\n dispose(): void;\n}\n\nconst defaultTimer = (callback: () => void, ms: number): (() => void) => {\n const timer = setTimeout(callback, ms);\n timer.unref();\n return () => clearTimeout(timer);\n};\n\n/**\n * Restarts the server at idle once old package versions pile up in memory.\n *\n * Native `import()` never unloads a module, so every superseded URL stays resident. At\n * `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the\n * busy check runs again when the debounce elapses, so a turn that started meanwhile\n * defers it to the next poke.\n */\nexport function createIdleRestart(options: IdleRestartOptions): IdleRestart {\n const limit = options.limit ?? SUPERSEDED_LIMIT;\n const debounceMs = options.debounceMs ?? RESTART_DEBOUNCE_MS;\n const setTimer = options.setTimer ?? defaultTimer;\n const superseded = new Set<string>();\n let cancel: (() => void) | undefined;\n let fired = false;\n const pending = () => superseded.size >= limit;\n const poke = (): void => {\n if (!pending() || fired) return;\n cancel?.();\n cancel = setTimer(() => {\n cancel = undefined;\n void options\n .busy()\n .then((busy) => {\n if (busy || fired) return;\n fired = true;\n options.restart();\n })\n .catch((error: unknown) => options.onError?.(error));\n }, debounceMs);\n };\n return {\n supersede(urls) {\n for (const url of urls) superseded.add(url);\n poke();\n },\n poke,\n pending,\n dispose() {\n cancel?.();\n cancel = undefined;\n fired = true;\n },\n };\n}\n\nexport function writeRecycleFile(directory: string, sessionIds: readonly string[]): void {\n fs.mkdirSync(directory, { recursive: true });\n const target = path.join(directory, RECYCLE_FILE);\n const staging = `${target}.${String(process.pid)}.tmp`;\n fs.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\\n`);\n fs.renameSync(staging, target);\n}\n\n/** The session ids a deliberate restart left open; empty when there is no valid file. */\nexport function readRecycleFile(directory: string): string[] {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, RECYCLE_FILE), 'utf8')) as unknown;\n const ids = (parsed as { sessionIds?: unknown } | null)?.sessionIds;\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : [];\n } catch {\n // Missing (the usual case, including after a crash) or unreadable: nothing to reopen.\n return [];\n }\n}\n\nexport function removeRecycleFile(directory: string): void {\n fs.rmSync(path.join(directory, RECYCLE_FILE), { force: true });\n}\n\n/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */\nexport async function reopenRecycledSessions(\n directory: string,\n reopen: (sessionId: string) => Promise<void>,\n onError: (sessionId: string, error: unknown) => void,\n): Promise<string[]> {\n const ids = readRecycleFile(directory);\n const reopened: string[] = [];\n try {\n for (const id of ids) {\n try {\n await reopen(id);\n reopened.push(id);\n } catch (error) {\n onError(id, error);\n }\n }\n } finally {\n removeRecycleFile(directory);\n }\n return reopened;\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ let node_fs = require("node:fs");
3
3
  node_fs = require_runtime.__toESM(node_fs, 1);
4
4
  let node_path = require("node:path");
5
5
  node_path = require_runtime.__toESM(node_path, 1);
6
- let _agimon_ai_po_core_serverFacet = require("@agimon-ai/po-core/serverFacet");
6
+ let _agimon_ai_po_core_serverPlugin = require("@agimon-ai/po-core/serverPlugin");
7
7
  //#region src/builders/server/index.ts
8
8
  function packageRootOf(entry) {
9
9
  let directory = node_path.default.dirname(node_fs.default.realpathSync(entry));
@@ -15,14 +15,14 @@ function packageRootOf(entry) {
15
15
  }
16
16
  }
17
17
  /**
18
- * The server facets the given compositions select, in host installation order.
18
+ * The server packages the given compositions select, in host installation order.
19
19
  *
20
20
  * Each composition contributes the packages owning its selected entries, and
21
21
  * each package records which major mode and layer admitted it. A package that
22
22
  * two different directories claim is ambiguous, and a built entry that resolves
23
23
  * outside its package is refused.
24
24
  */
25
- function serverFacetCandidates(compositions) {
25
+ function serverPackageCandidates(compositions) {
26
26
  const packages = /* @__PURE__ */ new Map();
27
27
  for (const composition of compositions) {
28
28
  const majorMode = composition.majorMode?.name;
@@ -30,7 +30,7 @@ function serverFacetCandidates(compositions) {
30
30
  const roots = new Set(composition.packageEntries.map(packageRootOf));
31
31
  for (const root of roots) {
32
32
  const manifest = JSON.parse(node_fs.default.readFileSync(node_path.default.join(root, "package.json"), "utf8"));
33
- for (const declaration of (0, _agimon_ai_po_core_serverFacet.declaredServerFacetsOf)(root, manifest)) {
33
+ for (const declaration of (0, _agimon_ai_po_core_serverPlugin.declaredServerPackagesOf)(root, manifest)) {
34
34
  const previous = packages.get(declaration.packageName);
35
35
  if (previous && previous.declaration.packageDir !== root) throw new Error(`Ambiguous server bundle package: ${declaration.packageName}`);
36
36
  const candidate = previous ?? {
@@ -46,7 +46,7 @@ function serverFacetCandidates(compositions) {
46
46
  }
47
47
  }
48
48
  }
49
- return (0, _agimon_ai_po_core_serverFacet.orderServerFacets)([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {
49
+ return (0, _agimon_ai_po_core_serverPlugin.orderServerPackages)([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {
50
50
  const module = node_fs.default.realpathSync(node_path.default.resolve(declaration.packageDir, declaration.dist));
51
51
  const relative = node_path.default.relative(declaration.packageDir, module);
52
52
  if (relative === ".." || relative.startsWith(`..${node_path.default.sep}`) || node_path.default.isAbsolute(relative)) throw new Error(`Server entry escapes package: ${declaration.packageName}`);
@@ -58,6 +58,6 @@ function serverFacetCandidates(compositions) {
58
58
  });
59
59
  }
60
60
  //#endregion
61
- exports.serverFacetCandidates = serverFacetCandidates;
61
+ exports.serverPackageCandidates = serverPackageCandidates;
62
62
 
63
63
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["path","fs","declaredServerFacetsOf","orderServerFacets"],"sources":["../../../../src/builders/server/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n type DeclaredServerFacet,\n type DoomServerBundleOwner,\n declaredServerFacetsOf,\n orderServerFacets,\n} from '@agimon-ai/po-core/serverFacet';\n\nimport type { ExtensionComposition } from '../cli/extensionAssembler';\n\nfunction packageRootOf(entry: string): string {\n let directory = path.dirname(fs.realpathSync(entry));\n for (;;) {\n if (fs.existsSync(path.join(directory, 'package.json'))) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`No package manifest owns ${entry}`);\n directory = parent;\n }\n}\n\n/** One server facet a composition set selects, with the built entry a host imports. */\nexport interface ServerFacetCandidate {\n readonly declaration: DeclaredServerFacet;\n /** Absolute, symlink-resolved path of the package's built `dist` entry. */\n readonly module: string;\n /** Every major mode and layer that admitted the package. */\n readonly owners: readonly DoomServerBundleOwner[];\n}\n\n/**\n * The server facets the given compositions select, in host installation order.\n *\n * Each composition contributes the packages owning its selected entries, and\n * each package records which major mode and layer admitted it. A package that\n * two different directories claim is ambiguous, and a built entry that resolves\n * outside its package is refused.\n */\nexport function serverFacetCandidates(compositions: readonly ExtensionComposition[]): ServerFacetCandidate[] {\n const packages = new Map<string, { declaration: DeclaredServerFacet; owners: DoomServerBundleOwner[] }>();\n for (const composition of compositions) {\n const majorMode = composition.majorMode?.name;\n if (!majorMode) throw new Error('A server bundle composition needs its major mode');\n const roots = new Set(composition.packageEntries.map(packageRootOf));\n for (const root of roots) {\n const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) as Record<string, unknown>;\n for (const declaration of declaredServerFacetsOf(root, manifest)) {\n const previous = packages.get(declaration.packageName);\n if (previous && previous.declaration.packageDir !== root) {\n throw new Error(`Ambiguous server bundle package: ${declaration.packageName}`);\n }\n const candidate = previous ?? { declaration, owners: [] };\n const layers = composition.selections\n .filter(\n (selection) => selection.outcome === 'resolved' && selection.path && packageRootOf(selection.path) === root,\n )\n .map((selection) => selection.layer);\n // Fixed host packages have no layer selection; default is always explicitly admitted.\n for (const layer of new Set(layers.length > 0 ? layers : ['default'])) {\n if (!candidate.owners.some((owner) => owner.majorMode === majorMode && owner.layer === layer)) {\n candidate.owners.push({ majorMode, layer });\n }\n }\n packages.set(declaration.packageName, candidate);\n }\n }\n }\n return orderServerFacets([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {\n const module = fs.realpathSync(path.resolve(declaration.packageDir, declaration.dist));\n const relative = path.relative(declaration.packageDir, module);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n throw new Error(`Server entry escapes package: ${declaration.packageName}`);\n }\n return { declaration, module, owners: packages.get(declaration.packageName)!.owners };\n });\n}\n"],"mappings":";;;;;;;AAYA,SAAS,cAAc,OAAuB;CAC5C,IAAI,YAAYA,UAAAA,QAAK,QAAQC,QAAAA,QAAG,aAAa,KAAK,CAAC;CACnD,SAAS;EACP,IAAIA,QAAAA,QAAG,WAAWD,UAAAA,QAAK,KAAK,WAAW,cAAc,CAAC,GAAG,OAAO;EAChE,MAAM,SAASA,UAAAA,QAAK,QAAQ,SAAS;EACrC,IAAI,WAAW,WAAW,MAAM,IAAI,MAAM,4BAA4B,OAAO;EAC7E,YAAY;CACd;AACF;;;;;;;;;AAmBA,SAAgB,sBAAsB,cAAuE;CAC3G,MAAM,2BAAW,IAAI,IAAmF;CACxG,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,YAAY,YAAY,WAAW;EACzC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,kDAAkD;EAClF,MAAM,QAAQ,IAAI,IAAI,YAAY,eAAe,IAAI,aAAa,CAAC;EACnE,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,WAAW,KAAK,MAAMC,QAAAA,QAAG,aAAaD,UAAAA,QAAK,KAAK,MAAM,cAAc,GAAG,MAAM,CAAC;GACpF,KAAK,MAAM,gBAAA,GAAeE,+BAAAA,uBAAAA,CAAuB,MAAM,QAAQ,GAAG;IAChE,MAAM,WAAW,SAAS,IAAI,YAAY,WAAW;IACrD,IAAI,YAAY,SAAS,YAAY,eAAe,MAClD,MAAM,IAAI,MAAM,oCAAoC,YAAY,aAAa;IAE/E,MAAM,YAAY,YAAY;KAAE;KAAa,QAAQ,CAAC;IAAE;IACxD,MAAM,SAAS,YAAY,WACxB,QACE,cAAc,UAAU,YAAY,cAAc,UAAU,QAAQ,cAAc,UAAU,IAAI,MAAM,IACzG,CAAC,CACA,KAAK,cAAc,UAAU,KAAK;IAErC,KAAK,MAAM,SAAS,IAAI,IAAI,OAAO,SAAS,IAAI,SAAS,CAAC,SAAS,CAAC,GAClE,IAAI,CAAC,UAAU,OAAO,MAAM,UAAU,MAAM,cAAc,aAAa,MAAM,UAAU,KAAK,GAC1F,UAAU,OAAO,KAAK;KAAE;KAAW;IAAM,CAAC;IAG9C,SAAS,IAAI,YAAY,aAAa,SAAS;GACjD;EACF;CACF;CACA,QAAA,GAAOC,+BAAAA,kBAAAA,CAAkB,CAAC,GAAG,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,kBAAkB,WAAW,CAAC,CAAC,CAAC,KAAK,gBAAgB;EAC1G,MAAM,SAASF,QAAAA,QAAG,aAAaD,UAAAA,QAAK,QAAQ,YAAY,YAAY,YAAY,IAAI,CAAC;EACrF,MAAM,WAAWA,UAAAA,QAAK,SAAS,YAAY,YAAY,MAAM;EAC7D,IAAI,aAAa,QAAQ,SAAS,WAAW,KAAKA,UAAAA,QAAK,KAAK,KAAKA,UAAAA,QAAK,WAAW,QAAQ,GACvF,MAAM,IAAI,MAAM,iCAAiC,YAAY,aAAa;EAE5E,OAAO;GAAE;GAAa;GAAQ,QAAQ,SAAS,IAAI,YAAY,WAAW,CAAC,CAAE;EAAO;CACtF,CAAC;AACH"}
1
+ {"version":3,"file":"index.cjs","names":["path","fs","declaredServerPackagesOf","orderServerPackages"],"sources":["../../../../src/builders/server/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n type DeclaredServerPackage,\n type PoServerBundleOwner,\n declaredServerPackagesOf,\n orderServerPackages,\n} from '@agimon-ai/po-core/serverPlugin';\n\nimport type { ExtensionComposition } from '../cli/extensionAssembler';\n\nfunction packageRootOf(entry: string): string {\n let directory = path.dirname(fs.realpathSync(entry));\n for (;;) {\n if (fs.existsSync(path.join(directory, 'package.json'))) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`No package manifest owns ${entry}`);\n directory = parent;\n }\n}\n\n/** One server package a composition set selects, with the built entry a host imports. */\nexport interface ServerPackageCandidate {\n readonly declaration: DeclaredServerPackage;\n /** Absolute, symlink-resolved path of the package's built `dist` entry. */\n readonly module: string;\n /** Every major mode and layer that admitted the package. */\n readonly owners: readonly PoServerBundleOwner[];\n}\n\n/**\n * The server packages the given compositions select, in host installation order.\n *\n * Each composition contributes the packages owning its selected entries, and\n * each package records which major mode and layer admitted it. A package that\n * two different directories claim is ambiguous, and a built entry that resolves\n * outside its package is refused.\n */\nexport function serverPackageCandidates(compositions: readonly ExtensionComposition[]): ServerPackageCandidate[] {\n const packages = new Map<string, { declaration: DeclaredServerPackage; owners: PoServerBundleOwner[] }>();\n for (const composition of compositions) {\n const majorMode = composition.majorMode?.name;\n if (!majorMode) throw new Error('A server bundle composition needs its major mode');\n const roots = new Set(composition.packageEntries.map(packageRootOf));\n for (const root of roots) {\n const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) as Record<string, unknown>;\n for (const declaration of declaredServerPackagesOf(root, manifest)) {\n const previous = packages.get(declaration.packageName);\n if (previous && previous.declaration.packageDir !== root) {\n throw new Error(`Ambiguous server bundle package: ${declaration.packageName}`);\n }\n const candidate = previous ?? { declaration, owners: [] };\n const layers = composition.selections\n .filter(\n (selection) => selection.outcome === 'resolved' && selection.path && packageRootOf(selection.path) === root,\n )\n .map((selection) => selection.layer);\n // Fixed host packages have no layer selection; default is always explicitly admitted.\n for (const layer of new Set(layers.length > 0 ? layers : ['default'])) {\n if (!candidate.owners.some((owner) => owner.majorMode === majorMode && owner.layer === layer)) {\n candidate.owners.push({ majorMode, layer });\n }\n }\n packages.set(declaration.packageName, candidate);\n }\n }\n }\n return orderServerPackages([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {\n const module = fs.realpathSync(path.resolve(declaration.packageDir, declaration.dist));\n const relative = path.relative(declaration.packageDir, module);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n throw new Error(`Server entry escapes package: ${declaration.packageName}`);\n }\n return { declaration, module, owners: packages.get(declaration.packageName)!.owners };\n });\n}\n"],"mappings":";;;;;;;AAYA,SAAS,cAAc,OAAuB;CAC5C,IAAI,YAAYA,UAAAA,QAAK,QAAQC,QAAAA,QAAG,aAAa,KAAK,CAAC;CACnD,SAAS;EACP,IAAIA,QAAAA,QAAG,WAAWD,UAAAA,QAAK,KAAK,WAAW,cAAc,CAAC,GAAG,OAAO;EAChE,MAAM,SAASA,UAAAA,QAAK,QAAQ,SAAS;EACrC,IAAI,WAAW,WAAW,MAAM,IAAI,MAAM,4BAA4B,OAAO;EAC7E,YAAY;CACd;AACF;;;;;;;;;AAmBA,SAAgB,wBAAwB,cAAyE;CAC/G,MAAM,2BAAW,IAAI,IAAmF;CACxG,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,YAAY,YAAY,WAAW;EACzC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,kDAAkD;EAClF,MAAM,QAAQ,IAAI,IAAI,YAAY,eAAe,IAAI,aAAa,CAAC;EACnE,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,WAAW,KAAK,MAAMC,QAAAA,QAAG,aAAaD,UAAAA,QAAK,KAAK,MAAM,cAAc,GAAG,MAAM,CAAC;GACpF,KAAK,MAAM,gBAAA,GAAeE,gCAAAA,yBAAAA,CAAyB,MAAM,QAAQ,GAAG;IAClE,MAAM,WAAW,SAAS,IAAI,YAAY,WAAW;IACrD,IAAI,YAAY,SAAS,YAAY,eAAe,MAClD,MAAM,IAAI,MAAM,oCAAoC,YAAY,aAAa;IAE/E,MAAM,YAAY,YAAY;KAAE;KAAa,QAAQ,CAAC;IAAE;IACxD,MAAM,SAAS,YAAY,WACxB,QACE,cAAc,UAAU,YAAY,cAAc,UAAU,QAAQ,cAAc,UAAU,IAAI,MAAM,IACzG,CAAC,CACA,KAAK,cAAc,UAAU,KAAK;IAErC,KAAK,MAAM,SAAS,IAAI,IAAI,OAAO,SAAS,IAAI,SAAS,CAAC,SAAS,CAAC,GAClE,IAAI,CAAC,UAAU,OAAO,MAAM,UAAU,MAAM,cAAc,aAAa,MAAM,UAAU,KAAK,GAC1F,UAAU,OAAO,KAAK;KAAE;KAAW;IAAM,CAAC;IAG9C,SAAS,IAAI,YAAY,aAAa,SAAS;GACjD;EACF;CACF;CACA,QAAA,GAAOC,gCAAAA,oBAAAA,CAAoB,CAAC,GAAG,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,kBAAkB,WAAW,CAAC,CAAC,CAAC,KAAK,gBAAgB;EAC5G,MAAM,SAASF,QAAAA,QAAG,aAAaD,UAAAA,QAAK,QAAQ,YAAY,YAAY,YAAY,IAAI,CAAC;EACrF,MAAM,WAAWA,UAAAA,QAAK,SAAS,YAAY,YAAY,MAAM;EAC7D,IAAI,aAAa,QAAQ,SAAS,WAAW,KAAKA,UAAAA,QAAK,KAAK,KAAKA,UAAAA,QAAK,WAAW,QAAQ,GACvF,MAAM,IAAI,MAAM,iCAAiC,YAAY,aAAa;EAE5E,OAAO;GAAE;GAAa;GAAQ,QAAQ,SAAS,IAAI,YAAY,WAAW,CAAC,CAAE;EAAO;CACtF,CAAC;AACH"}
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { declaredServerFacetsOf, orderServerFacets } from "@agimon-ai/po-core/serverFacet";
3
+ import { declaredServerPackagesOf, orderServerPackages } from "@agimon-ai/po-core/serverPlugin";
4
4
  //#region src/builders/server/index.ts
5
5
  function packageRootOf(entry) {
6
6
  let directory = path.dirname(fs.realpathSync(entry));
@@ -12,14 +12,14 @@ function packageRootOf(entry) {
12
12
  }
13
13
  }
14
14
  /**
15
- * The server facets the given compositions select, in host installation order.
15
+ * The server packages the given compositions select, in host installation order.
16
16
  *
17
17
  * Each composition contributes the packages owning its selected entries, and
18
18
  * each package records which major mode and layer admitted it. A package that
19
19
  * two different directories claim is ambiguous, and a built entry that resolves
20
20
  * outside its package is refused.
21
21
  */
22
- function serverFacetCandidates(compositions) {
22
+ function serverPackageCandidates(compositions) {
23
23
  const packages = /* @__PURE__ */ new Map();
24
24
  for (const composition of compositions) {
25
25
  const majorMode = composition.majorMode?.name;
@@ -27,7 +27,7 @@ function serverFacetCandidates(compositions) {
27
27
  const roots = new Set(composition.packageEntries.map(packageRootOf));
28
28
  for (const root of roots) {
29
29
  const manifest = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
30
- for (const declaration of declaredServerFacetsOf(root, manifest)) {
30
+ for (const declaration of declaredServerPackagesOf(root, manifest)) {
31
31
  const previous = packages.get(declaration.packageName);
32
32
  if (previous && previous.declaration.packageDir !== root) throw new Error(`Ambiguous server bundle package: ${declaration.packageName}`);
33
33
  const candidate = previous ?? {
@@ -43,7 +43,7 @@ function serverFacetCandidates(compositions) {
43
43
  }
44
44
  }
45
45
  }
46
- return orderServerFacets([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {
46
+ return orderServerPackages([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {
47
47
  const module = fs.realpathSync(path.resolve(declaration.packageDir, declaration.dist));
48
48
  const relative = path.relative(declaration.packageDir, module);
49
49
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`Server entry escapes package: ${declaration.packageName}`);
@@ -55,6 +55,6 @@ function serverFacetCandidates(compositions) {
55
55
  });
56
56
  }
57
57
  //#endregion
58
- export { serverFacetCandidates };
58
+ export { serverPackageCandidates };
59
59
 
60
60
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/builders/server/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n type DeclaredServerFacet,\n type DoomServerBundleOwner,\n declaredServerFacetsOf,\n orderServerFacets,\n} from '@agimon-ai/po-core/serverFacet';\n\nimport type { ExtensionComposition } from '../cli/extensionAssembler';\n\nfunction packageRootOf(entry: string): string {\n let directory = path.dirname(fs.realpathSync(entry));\n for (;;) {\n if (fs.existsSync(path.join(directory, 'package.json'))) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`No package manifest owns ${entry}`);\n directory = parent;\n }\n}\n\n/** One server facet a composition set selects, with the built entry a host imports. */\nexport interface ServerFacetCandidate {\n readonly declaration: DeclaredServerFacet;\n /** Absolute, symlink-resolved path of the package's built `dist` entry. */\n readonly module: string;\n /** Every major mode and layer that admitted the package. */\n readonly owners: readonly DoomServerBundleOwner[];\n}\n\n/**\n * The server facets the given compositions select, in host installation order.\n *\n * Each composition contributes the packages owning its selected entries, and\n * each package records which major mode and layer admitted it. A package that\n * two different directories claim is ambiguous, and a built entry that resolves\n * outside its package is refused.\n */\nexport function serverFacetCandidates(compositions: readonly ExtensionComposition[]): ServerFacetCandidate[] {\n const packages = new Map<string, { declaration: DeclaredServerFacet; owners: DoomServerBundleOwner[] }>();\n for (const composition of compositions) {\n const majorMode = composition.majorMode?.name;\n if (!majorMode) throw new Error('A server bundle composition needs its major mode');\n const roots = new Set(composition.packageEntries.map(packageRootOf));\n for (const root of roots) {\n const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) as Record<string, unknown>;\n for (const declaration of declaredServerFacetsOf(root, manifest)) {\n const previous = packages.get(declaration.packageName);\n if (previous && previous.declaration.packageDir !== root) {\n throw new Error(`Ambiguous server bundle package: ${declaration.packageName}`);\n }\n const candidate = previous ?? { declaration, owners: [] };\n const layers = composition.selections\n .filter(\n (selection) => selection.outcome === 'resolved' && selection.path && packageRootOf(selection.path) === root,\n )\n .map((selection) => selection.layer);\n // Fixed host packages have no layer selection; default is always explicitly admitted.\n for (const layer of new Set(layers.length > 0 ? layers : ['default'])) {\n if (!candidate.owners.some((owner) => owner.majorMode === majorMode && owner.layer === layer)) {\n candidate.owners.push({ majorMode, layer });\n }\n }\n packages.set(declaration.packageName, candidate);\n }\n }\n }\n return orderServerFacets([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {\n const module = fs.realpathSync(path.resolve(declaration.packageDir, declaration.dist));\n const relative = path.relative(declaration.packageDir, module);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n throw new Error(`Server entry escapes package: ${declaration.packageName}`);\n }\n return { declaration, module, owners: packages.get(declaration.packageName)!.owners };\n });\n}\n"],"mappings":";;;;AAYA,SAAS,cAAc,OAAuB;CAC5C,IAAI,YAAY,KAAK,QAAQ,GAAG,aAAa,KAAK,CAAC;CACnD,SAAS;EACP,IAAI,GAAG,WAAW,KAAK,KAAK,WAAW,cAAc,CAAC,GAAG,OAAO;EAChE,MAAM,SAAS,KAAK,QAAQ,SAAS;EACrC,IAAI,WAAW,WAAW,MAAM,IAAI,MAAM,4BAA4B,OAAO;EAC7E,YAAY;CACd;AACF;;;;;;;;;AAmBA,SAAgB,sBAAsB,cAAuE;CAC3G,MAAM,2BAAW,IAAI,IAAmF;CACxG,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,YAAY,YAAY,WAAW;EACzC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,kDAAkD;EAClF,MAAM,QAAQ,IAAI,IAAI,YAAY,eAAe,IAAI,aAAa,CAAC;EACnE,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,WAAW,KAAK,MAAM,GAAG,aAAa,KAAK,KAAK,MAAM,cAAc,GAAG,MAAM,CAAC;GACpF,KAAK,MAAM,eAAe,uBAAuB,MAAM,QAAQ,GAAG;IAChE,MAAM,WAAW,SAAS,IAAI,YAAY,WAAW;IACrD,IAAI,YAAY,SAAS,YAAY,eAAe,MAClD,MAAM,IAAI,MAAM,oCAAoC,YAAY,aAAa;IAE/E,MAAM,YAAY,YAAY;KAAE;KAAa,QAAQ,CAAC;IAAE;IACxD,MAAM,SAAS,YAAY,WACxB,QACE,cAAc,UAAU,YAAY,cAAc,UAAU,QAAQ,cAAc,UAAU,IAAI,MAAM,IACzG,CAAC,CACA,KAAK,cAAc,UAAU,KAAK;IAErC,KAAK,MAAM,SAAS,IAAI,IAAI,OAAO,SAAS,IAAI,SAAS,CAAC,SAAS,CAAC,GAClE,IAAI,CAAC,UAAU,OAAO,MAAM,UAAU,MAAM,cAAc,aAAa,MAAM,UAAU,KAAK,GAC1F,UAAU,OAAO,KAAK;KAAE;KAAW;IAAM,CAAC;IAG9C,SAAS,IAAI,YAAY,aAAa,SAAS;GACjD;EACF;CACF;CACA,OAAO,kBAAkB,CAAC,GAAG,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,kBAAkB,WAAW,CAAC,CAAC,CAAC,KAAK,gBAAgB;EAC1G,MAAM,SAAS,GAAG,aAAa,KAAK,QAAQ,YAAY,YAAY,YAAY,IAAI,CAAC;EACrF,MAAM,WAAW,KAAK,SAAS,YAAY,YAAY,MAAM;EAC7D,IAAI,aAAa,QAAQ,SAAS,WAAW,KAAK,KAAK,KAAK,KAAK,KAAK,WAAW,QAAQ,GACvF,MAAM,IAAI,MAAM,iCAAiC,YAAY,aAAa;EAE5E,OAAO;GAAE;GAAa;GAAQ,QAAQ,SAAS,IAAI,YAAY,WAAW,CAAC,CAAE;EAAO;CACtF,CAAC;AACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/builders/server/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n type DeclaredServerPackage,\n type PoServerBundleOwner,\n declaredServerPackagesOf,\n orderServerPackages,\n} from '@agimon-ai/po-core/serverPlugin';\n\nimport type { ExtensionComposition } from '../cli/extensionAssembler';\n\nfunction packageRootOf(entry: string): string {\n let directory = path.dirname(fs.realpathSync(entry));\n for (;;) {\n if (fs.existsSync(path.join(directory, 'package.json'))) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`No package manifest owns ${entry}`);\n directory = parent;\n }\n}\n\n/** One server package a composition set selects, with the built entry a host imports. */\nexport interface ServerPackageCandidate {\n readonly declaration: DeclaredServerPackage;\n /** Absolute, symlink-resolved path of the package's built `dist` entry. */\n readonly module: string;\n /** Every major mode and layer that admitted the package. */\n readonly owners: readonly PoServerBundleOwner[];\n}\n\n/**\n * The server packages the given compositions select, in host installation order.\n *\n * Each composition contributes the packages owning its selected entries, and\n * each package records which major mode and layer admitted it. A package that\n * two different directories claim is ambiguous, and a built entry that resolves\n * outside its package is refused.\n */\nexport function serverPackageCandidates(compositions: readonly ExtensionComposition[]): ServerPackageCandidate[] {\n const packages = new Map<string, { declaration: DeclaredServerPackage; owners: PoServerBundleOwner[] }>();\n for (const composition of compositions) {\n const majorMode = composition.majorMode?.name;\n if (!majorMode) throw new Error('A server bundle composition needs its major mode');\n const roots = new Set(composition.packageEntries.map(packageRootOf));\n for (const root of roots) {\n const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) as Record<string, unknown>;\n for (const declaration of declaredServerPackagesOf(root, manifest)) {\n const previous = packages.get(declaration.packageName);\n if (previous && previous.declaration.packageDir !== root) {\n throw new Error(`Ambiguous server bundle package: ${declaration.packageName}`);\n }\n const candidate = previous ?? { declaration, owners: [] };\n const layers = composition.selections\n .filter(\n (selection) => selection.outcome === 'resolved' && selection.path && packageRootOf(selection.path) === root,\n )\n .map((selection) => selection.layer);\n // Fixed host packages have no layer selection; default is always explicitly admitted.\n for (const layer of new Set(layers.length > 0 ? layers : ['default'])) {\n if (!candidate.owners.some((owner) => owner.majorMode === majorMode && owner.layer === layer)) {\n candidate.owners.push({ majorMode, layer });\n }\n }\n packages.set(declaration.packageName, candidate);\n }\n }\n }\n return orderServerPackages([...packages.values()].map(({ declaration }) => declaration)).map((declaration) => {\n const module = fs.realpathSync(path.resolve(declaration.packageDir, declaration.dist));\n const relative = path.relative(declaration.packageDir, module);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n throw new Error(`Server entry escapes package: ${declaration.packageName}`);\n }\n return { declaration, module, owners: packages.get(declaration.packageName)!.owners };\n });\n}\n"],"mappings":";;;;AAYA,SAAS,cAAc,OAAuB;CAC5C,IAAI,YAAY,KAAK,QAAQ,GAAG,aAAa,KAAK,CAAC;CACnD,SAAS;EACP,IAAI,GAAG,WAAW,KAAK,KAAK,WAAW,cAAc,CAAC,GAAG,OAAO;EAChE,MAAM,SAAS,KAAK,QAAQ,SAAS;EACrC,IAAI,WAAW,WAAW,MAAM,IAAI,MAAM,4BAA4B,OAAO;EAC7E,YAAY;CACd;AACF;;;;;;;;;AAmBA,SAAgB,wBAAwB,cAAyE;CAC/G,MAAM,2BAAW,IAAI,IAAmF;CACxG,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,YAAY,YAAY,WAAW;EACzC,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,kDAAkD;EAClF,MAAM,QAAQ,IAAI,IAAI,YAAY,eAAe,IAAI,aAAa,CAAC;EACnE,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,WAAW,KAAK,MAAM,GAAG,aAAa,KAAK,KAAK,MAAM,cAAc,GAAG,MAAM,CAAC;GACpF,KAAK,MAAM,eAAe,yBAAyB,MAAM,QAAQ,GAAG;IAClE,MAAM,WAAW,SAAS,IAAI,YAAY,WAAW;IACrD,IAAI,YAAY,SAAS,YAAY,eAAe,MAClD,MAAM,IAAI,MAAM,oCAAoC,YAAY,aAAa;IAE/E,MAAM,YAAY,YAAY;KAAE;KAAa,QAAQ,CAAC;IAAE;IACxD,MAAM,SAAS,YAAY,WACxB,QACE,cAAc,UAAU,YAAY,cAAc,UAAU,QAAQ,cAAc,UAAU,IAAI,MAAM,IACzG,CAAC,CACA,KAAK,cAAc,UAAU,KAAK;IAErC,KAAK,MAAM,SAAS,IAAI,IAAI,OAAO,SAAS,IAAI,SAAS,CAAC,SAAS,CAAC,GAClE,IAAI,CAAC,UAAU,OAAO,MAAM,UAAU,MAAM,cAAc,aAAa,MAAM,UAAU,KAAK,GAC1F,UAAU,OAAO,KAAK;KAAE;KAAW;IAAM,CAAC;IAG9C,SAAS,IAAI,YAAY,aAAa,SAAS;GACjD;EACF;CACF;CACA,OAAO,oBAAoB,CAAC,GAAG,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,kBAAkB,WAAW,CAAC,CAAC,CAAC,KAAK,gBAAgB;EAC5G,MAAM,SAAS,GAAG,aAAa,KAAK,QAAQ,YAAY,YAAY,YAAY,IAAI,CAAC;EACrF,MAAM,WAAW,KAAK,SAAS,YAAY,YAAY,MAAM;EAC7D,IAAI,aAAa,QAAQ,SAAS,WAAW,KAAK,KAAK,KAAK,KAAK,KAAK,WAAW,QAAQ,GACvF,MAAM,IAAI,MAAM,iCAAiC,YAAY,aAAa;EAE5E,OAAO;GAAE;GAAa;GAAQ,QAAQ,SAAS,IAAI,YAAY,WAAW,CAAC,CAAE;EAAO;CACtF,CAAC;AACH"}
@@ -2,7 +2,7 @@ let node_child_process = require("node:child_process");
2
2
  let node_timers_promises = require("node:timers/promises");
3
3
  let node_url = require("node:url");
4
4
  let node_util = require("node:util");
5
- let _agimon_ai_log_sink_mcp = require("@agimon-ai/log-sink-mcp");
5
+ let _agimon_ai_po_logsink = require("@agimon-ai/po-logsink");
6
6
  //#region src/builders/server/logSink.ts
7
7
  const START_TIMEOUT_MS = 5e3;
8
8
  const DISCOVERY_TIMEOUT_MS = 4e3;
@@ -12,7 +12,7 @@ const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
12
12
  async function healthy(env) {
13
13
  let timer;
14
14
  try {
15
- return await Promise.race([(0, _agimon_ai_log_sink_mcp.resolveLogSinkPort)({
15
+ return await Promise.race([(0, _agimon_ai_po_logsink.resolveLogSinkPort)({
16
16
  env,
17
17
  healthCheck: true
18
18
  }), new Promise((resolve) => {
@@ -28,7 +28,7 @@ async function ensureGlobalLogSink(options) {
28
28
  let failure;
29
29
  try {
30
30
  if (await healthy(env)) return;
31
- const cli = (0, node_url.fileURLToPath)({}.resolve("@agimon-ai/log-sink-mcp/cli"));
31
+ const cli = (0, node_url.fileURLToPath)({}.resolve("@agimon-ai/po-logsink/cli"));
32
32
  await execFileAsync(process.execPath, [
33
33
  cli,
34
34
  "start",
@@ -1 +1 @@
1
- {"version":3,"file":"logSink.cjs","names":["promisify","execFile","resolveLogSinkPort","fileURLToPath","sleep"],"sources":["../../../../src/builders/server/logSink.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\nimport { promisify } from 'node:util';\n\nimport { resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\n\nconst START_TIMEOUT_MS = 5_000;\nconst DISCOVERY_TIMEOUT_MS = 4_000;\nconst HEALTH_TIMEOUT_MS = 750;\nconst DISCOVERY_INTERVAL_MS = 1_100;\n\nconst execFileAsync = promisify(execFile);\n\nasync function healthy(env: NodeJS.ProcessEnv): Promise<boolean> {\n // Registry discovery may wait on a broken daemon's health endpoint. The\n // server must still start, without allowing that endpoint to select a local\n // fallback or hold the startup path indefinitely.\n let timer: NodeJS.Timeout | undefined;\n try {\n const result = await Promise.race([\n resolveLogSinkPort({ env, healthCheck: true }),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), HEALTH_TIMEOUT_MS);\n }),\n ]);\n return result !== undefined;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */\nexport async function ensureGlobalLogSink(options: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n notice: (message: string) => void;\n}): Promise<void> {\n const { cwd, env, notice } = options;\n let failure: string | undefined;\n try {\n if (await healthy(env)) return;\n const cli = fileURLToPath(import.meta.resolve('@agimon-ai/log-sink-mcp/cli'));\n await execFileAsync(process.execPath, [cli, 'start', '--global', '--http-only'], {\n cwd,\n env,\n timeout: START_TIMEOUT_MS,\n maxBuffer: 64 * 1024,\n });\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n\n const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;\n do {\n if (await healthy(env)) return;\n if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;\n await sleep(DISCOVERY_INTERVAL_MS);\n } while (Date.now() < deadline);\n\n notice(`Global log sink unavailable: ${failure ?? 'healthy discovery timed out'}. Telemetry remains nonfatal.`);\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAE9B,MAAM,iBAAA,GAAgBA,UAAAA,UAAAA,CAAUC,mBAAAA,QAAQ;AAExC,eAAe,QAAQ,KAA0C;CAI/D,IAAI;CACJ,IAAI;EAOF,OAAO,MANc,QAAQ,KAAK,EAAA,GAChCC,wBAAAA,mBAAAA,CAAmB;GAAE;GAAK,aAAa;EAAK,CAAC,GAC7C,IAAI,SAAoB,YAAY;GAClC,QAAQ,iBAAiB,QAAQ,KAAA,CAAS,GAAG,iBAAiB;EAChE,CAAC,CACH,CAAC,MACiB,KAAA;CACpB,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;;AAGA,eAAsB,oBAAoB,SAIxB;CAChB,MAAM,EAAE,KAAK,KAAK,WAAW;CAC7B,IAAI;CACJ,IAAI;EACF,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,MAAM,OAAA,GAAMC,SAAAA,cAAAA,CAAAA,CAAAA,EAA0B,QAAQ,6BAA6B,CAAC;EAC5E,MAAM,cAAc,QAAQ,UAAU;GAAC;GAAK;GAAS;GAAY;EAAa,GAAG;GAC/E;GACA;GACA,SAAS;GACT,WAAW;EACb,CAAC;CACH,SAAS,OAAO;EACd,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACjE;CAEA,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,GAAG;EACD,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,IAAI,KAAK,IAAI,IAAI,wBAAwB,UAAU;EACnD,OAAA,GAAMC,qBAAAA,WAAAA,CAAM,qBAAqB;CACnC,SAAS,KAAK,IAAI,IAAI;CAEtB,OAAO,gCAAgC,WAAW,8BAA8B,8BAA8B;AAChH"}
1
+ {"version":3,"file":"logSink.cjs","names":["promisify","execFile","resolveLogSinkPort","fileURLToPath","sleep"],"sources":["../../../../src/builders/server/logSink.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\nimport { promisify } from 'node:util';\n\nimport { resolveLogSinkPort } from '@agimon-ai/po-logsink';\n\nconst START_TIMEOUT_MS = 5_000;\nconst DISCOVERY_TIMEOUT_MS = 4_000;\nconst HEALTH_TIMEOUT_MS = 750;\nconst DISCOVERY_INTERVAL_MS = 1_100;\n\nconst execFileAsync = promisify(execFile);\n\nasync function healthy(env: NodeJS.ProcessEnv): Promise<boolean> {\n // Registry discovery may wait on a broken daemon's health endpoint. The\n // server must still start, without allowing that endpoint to select a local\n // fallback or hold the startup path indefinitely.\n let timer: NodeJS.Timeout | undefined;\n try {\n const result = await Promise.race([\n resolveLogSinkPort({ env, healthCheck: true }),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), HEALTH_TIMEOUT_MS);\n }),\n ]);\n return result !== undefined;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */\nexport async function ensureGlobalLogSink(options: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n notice: (message: string) => void;\n}): Promise<void> {\n const { cwd, env, notice } = options;\n let failure: string | undefined;\n try {\n if (await healthy(env)) return;\n const cli = fileURLToPath(import.meta.resolve('@agimon-ai/po-logsink/cli'));\n await execFileAsync(process.execPath, [cli, 'start', '--global', '--http-only'], {\n cwd,\n env,\n timeout: START_TIMEOUT_MS,\n maxBuffer: 64 * 1024,\n });\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n\n const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;\n do {\n if (await healthy(env)) return;\n if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;\n await sleep(DISCOVERY_INTERVAL_MS);\n } while (Date.now() < deadline);\n\n notice(`Global log sink unavailable: ${failure ?? 'healthy discovery timed out'}. Telemetry remains nonfatal.`);\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAE9B,MAAM,iBAAA,GAAgBA,UAAAA,UAAAA,CAAUC,mBAAAA,QAAQ;AAExC,eAAe,QAAQ,KAA0C;CAI/D,IAAI;CACJ,IAAI;EAOF,OAAO,MANc,QAAQ,KAAK,EAAA,GAChCC,sBAAAA,mBAAAA,CAAmB;GAAE;GAAK,aAAa;EAAK,CAAC,GAC7C,IAAI,SAAoB,YAAY;GAClC,QAAQ,iBAAiB,QAAQ,KAAA,CAAS,GAAG,iBAAiB;EAChE,CAAC,CACH,CAAC,MACiB,KAAA;CACpB,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;;AAGA,eAAsB,oBAAoB,SAIxB;CAChB,MAAM,EAAE,KAAK,KAAK,WAAW;CAC7B,IAAI;CACJ,IAAI;EACF,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,MAAM,OAAA,GAAMC,SAAAA,cAAAA,CAAAA,CAAAA,EAA0B,QAAQ,2BAA2B,CAAC;EAC1E,MAAM,cAAc,QAAQ,UAAU;GAAC;GAAK;GAAS;GAAY;EAAa,GAAG;GAC/E;GACA;GACA,SAAS;GACT,WAAW;EACb,CAAC;CACH,SAAS,OAAO;EACd,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACjE;CAEA,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,GAAG;EACD,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,IAAI,KAAK,IAAI,IAAI,wBAAwB,UAAU;EACnD,OAAA,GAAMC,qBAAAA,WAAAA,CAAM,qBAAqB;CACnC,SAAS,KAAK,IAAI,IAAI;CAEtB,OAAO,gCAAgC,WAAW,8BAA8B,8BAA8B;AAChH"}
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { promisify } from "node:util";
5
- import { resolveLogSinkPort } from "@agimon-ai/log-sink-mcp";
5
+ import { resolveLogSinkPort } from "@agimon-ai/po-logsink";
6
6
  //#region src/builders/server/logSink.ts
7
7
  const START_TIMEOUT_MS = 5e3;
8
8
  const DISCOVERY_TIMEOUT_MS = 4e3;
@@ -28,7 +28,7 @@ async function ensureGlobalLogSink(options) {
28
28
  let failure;
29
29
  try {
30
30
  if (await healthy(env)) return;
31
- const cli = fileURLToPath(import.meta.resolve("@agimon-ai/log-sink-mcp/cli"));
31
+ const cli = fileURLToPath(import.meta.resolve("@agimon-ai/po-logsink/cli"));
32
32
  await execFileAsync(process.execPath, [
33
33
  cli,
34
34
  "start",
@@ -1 +1 @@
1
- {"version":3,"file":"logSink.mjs","names":["sleep"],"sources":["../../../../src/builders/server/logSink.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\nimport { promisify } from 'node:util';\n\nimport { resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\n\nconst START_TIMEOUT_MS = 5_000;\nconst DISCOVERY_TIMEOUT_MS = 4_000;\nconst HEALTH_TIMEOUT_MS = 750;\nconst DISCOVERY_INTERVAL_MS = 1_100;\n\nconst execFileAsync = promisify(execFile);\n\nasync function healthy(env: NodeJS.ProcessEnv): Promise<boolean> {\n // Registry discovery may wait on a broken daemon's health endpoint. The\n // server must still start, without allowing that endpoint to select a local\n // fallback or hold the startup path indefinitely.\n let timer: NodeJS.Timeout | undefined;\n try {\n const result = await Promise.race([\n resolveLogSinkPort({ env, healthCheck: true }),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), HEALTH_TIMEOUT_MS);\n }),\n ]);\n return result !== undefined;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */\nexport async function ensureGlobalLogSink(options: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n notice: (message: string) => void;\n}): Promise<void> {\n const { cwd, env, notice } = options;\n let failure: string | undefined;\n try {\n if (await healthy(env)) return;\n const cli = fileURLToPath(import.meta.resolve('@agimon-ai/log-sink-mcp/cli'));\n await execFileAsync(process.execPath, [cli, 'start', '--global', '--http-only'], {\n cwd,\n env,\n timeout: START_TIMEOUT_MS,\n maxBuffer: 64 * 1024,\n });\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n\n const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;\n do {\n if (await healthy(env)) return;\n if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;\n await sleep(DISCOVERY_INTERVAL_MS);\n } while (Date.now() < deadline);\n\n notice(`Global log sink unavailable: ${failure ?? 'healthy discovery timed out'}. Telemetry remains nonfatal.`);\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAE9B,MAAM,gBAAgB,UAAU,QAAQ;AAExC,eAAe,QAAQ,KAA0C;CAI/D,IAAI;CACJ,IAAI;EAOF,OAAO,MANc,QAAQ,KAAK,CAChC,mBAAmB;GAAE;GAAK,aAAa;EAAK,CAAC,GAC7C,IAAI,SAAoB,YAAY;GAClC,QAAQ,iBAAiB,QAAQ,KAAA,CAAS,GAAG,iBAAiB;EAChE,CAAC,CACH,CAAC,MACiB,KAAA;CACpB,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;;AAGA,eAAsB,oBAAoB,SAIxB;CAChB,MAAM,EAAE,KAAK,KAAK,WAAW;CAC7B,IAAI;CACJ,IAAI;EACF,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,MAAM,MAAM,cAAc,YAAY,QAAQ,6BAA6B,CAAC;EAC5E,MAAM,cAAc,QAAQ,UAAU;GAAC;GAAK;GAAS;GAAY;EAAa,GAAG;GAC/E;GACA;GACA,SAAS;GACT,WAAW;EACb,CAAC;CACH,SAAS,OAAO;EACd,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACjE;CAEA,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,GAAG;EACD,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,IAAI,KAAK,IAAI,IAAI,wBAAwB,UAAU;EACnD,MAAMA,aAAM,qBAAqB;CACnC,SAAS,KAAK,IAAI,IAAI;CAEtB,OAAO,gCAAgC,WAAW,8BAA8B,8BAA8B;AAChH"}
1
+ {"version":3,"file":"logSink.mjs","names":["sleep"],"sources":["../../../../src/builders/server/logSink.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\nimport { promisify } from 'node:util';\n\nimport { resolveLogSinkPort } from '@agimon-ai/po-logsink';\n\nconst START_TIMEOUT_MS = 5_000;\nconst DISCOVERY_TIMEOUT_MS = 4_000;\nconst HEALTH_TIMEOUT_MS = 750;\nconst DISCOVERY_INTERVAL_MS = 1_100;\n\nconst execFileAsync = promisify(execFile);\n\nasync function healthy(env: NodeJS.ProcessEnv): Promise<boolean> {\n // Registry discovery may wait on a broken daemon's health endpoint. The\n // server must still start, without allowing that endpoint to select a local\n // fallback or hold the startup path indefinitely.\n let timer: NodeJS.Timeout | undefined;\n try {\n const result = await Promise.race([\n resolveLogSinkPort({ env, healthCheck: true }),\n new Promise<undefined>((resolve) => {\n timer = setTimeout(() => resolve(undefined), HEALTH_TIMEOUT_MS);\n }),\n ]);\n return result !== undefined;\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** Attach to the shared daemon or start it through its published CLI. Never own its shutdown. */\nexport async function ensureGlobalLogSink(options: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n notice: (message: string) => void;\n}): Promise<void> {\n const { cwd, env, notice } = options;\n let failure: string | undefined;\n try {\n if (await healthy(env)) return;\n const cli = fileURLToPath(import.meta.resolve('@agimon-ai/po-logsink/cli'));\n await execFileAsync(process.execPath, [cli, 'start', '--global', '--http-only'], {\n cwd,\n env,\n timeout: START_TIMEOUT_MS,\n maxBuffer: 64 * 1024,\n });\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n\n const deadline = Date.now() + DISCOVERY_TIMEOUT_MS;\n do {\n if (await healthy(env)) return;\n if (Date.now() + DISCOVERY_INTERVAL_MS > deadline) break;\n await sleep(DISCOVERY_INTERVAL_MS);\n } while (Date.now() < deadline);\n\n notice(`Global log sink unavailable: ${failure ?? 'healthy discovery timed out'}. Telemetry remains nonfatal.`);\n}\n"],"mappings":";;;;;;AAOA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;AAC1B,MAAM,wBAAwB;AAE9B,MAAM,gBAAgB,UAAU,QAAQ;AAExC,eAAe,QAAQ,KAA0C;CAI/D,IAAI;CACJ,IAAI;EAOF,OAAO,MANc,QAAQ,KAAK,CAChC,mBAAmB;GAAE;GAAK,aAAa;EAAK,CAAC,GAC7C,IAAI,SAAoB,YAAY;GAClC,QAAQ,iBAAiB,QAAQ,KAAA,CAAS,GAAG,iBAAiB;EAChE,CAAC,CACH,CAAC,MACiB,KAAA;CACpB,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;;AAGA,eAAsB,oBAAoB,SAIxB;CAChB,MAAM,EAAE,KAAK,KAAK,WAAW;CAC7B,IAAI;CACJ,IAAI;EACF,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,MAAM,MAAM,cAAc,YAAY,QAAQ,2BAA2B,CAAC;EAC1E,MAAM,cAAc,QAAQ,UAAU;GAAC;GAAK;GAAS;GAAY;EAAa,GAAG;GAC/E;GACA;GACA,SAAS;GACT,WAAW;EACb,CAAC;CACH,SAAS,OAAO;EACd,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACjE;CAEA,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,GAAG;EACD,IAAI,MAAM,QAAQ,GAAG,GAAG;EACxB,IAAI,KAAK,IAAI,IAAI,wBAAwB,UAAU;EACnD,MAAMA,aAAM,qBAAqB;CACnC,SAAS,KAAK,IAAI,IAAI;CAEtB,OAAO,gCAAgC,WAAW,8BAA8B,8BAA8B;AAChH"}
@@ -1 +1 @@
1
- {"version":3,"file":"mcpBundle.cjs","names":["path","fs"],"sources":["../../../../src/builders/server/mcpBundle.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { DoomServerBundleOwner } from '@agimon-ai/po-core/serverFacet';\n\nimport type { ExtensionComposition } from '../cli/extensionAssembler';\n\nexport interface DeclaredMcpPlugin {\n readonly packageName: string;\n readonly packageDir: string;\n readonly entry: string;\n readonly dist: string;\n readonly ui?: { readonly dist: string; readonly widgets: readonly string[] };\n}\n\nfunction packageRootOf(entry: string): string {\n let directory = path.dirname(fs.realpathSync(entry));\n for (;;) {\n if (fs.existsSync(path.join(directory, 'package.json'))) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`No package manifest owns ${entry}`);\n directory = parent;\n }\n}\n\nfunction packagePath(packageDir: string, field: string, value: unknown): string {\n if (typeof value !== 'string' || !value.startsWith('./') || value.includes('..'))\n throw new Error(`poMcp ${field} in ${packageDir} must be a package-relative ./path with no '..'.`);\n return value;\n}\n\nfunction declaredMcpPlugin(packageDir: string, manifest: Record<string, unknown>): DeclaredMcpPlugin | undefined {\n const value = manifest.poMcp;\n if (value === undefined) return undefined;\n if (typeof value !== 'object' || value === null || Array.isArray(value))\n throw new Error(`poMcp manifest in ${packageDir} must be an object.`);\n const declaration = value as Record<string, unknown>;\n if (!Array.isArray(declaration.scopes) || declaration.scopes.length !== 1 || declaration.scopes[0] !== 'session')\n throw new Error(`poMcp scopes in ${packageDir} must be exactly ['session'].`);\n const packageName = manifest.name;\n if (typeof packageName !== 'string' || packageName === '') throw new Error(`Package in ${packageDir} has no name.`);\n let ui: DeclaredMcpPlugin['ui'];\n if (declaration.ui !== undefined) {\n const value = declaration.ui as { dist?: unknown; widgets?: unknown } | null;\n if (\n typeof value !== 'object' ||\n value === null ||\n !Array.isArray(value.widgets) ||\n value.widgets.some((key: unknown) => typeof key !== 'string' || !key.startsWith(`${packageName}/`)) ||\n new Set(value.widgets).size !== value.widgets.length\n )\n throw new Error(`Invalid MCP widget manifest in ${packageDir}`);\n ui = { dist: packagePath(packageDir, 'ui.dist', value.dist), widgets: value.widgets as string[] };\n }\n return {\n packageName,\n packageDir,\n ...(ui === undefined ? {} : { ui }),\n entry: packagePath(packageDir, 'entry', declaration.entry),\n dist: packagePath(packageDir, 'dist', declaration.dist),\n };\n}\n\n/** One MCP plugin a composition set selects, with the built entry a host imports. */\nexport interface McpPluginCandidate {\n readonly declaration: DeclaredMcpPlugin;\n /** Absolute, symlink-resolved path of the package's built `dist` entry. */\n readonly module: string;\n /** Every major mode and layer that admitted the package. */\n readonly owners: readonly DoomServerBundleOwner[];\n}\n\n/**\n * The MCP plugins the given compositions select, in first-selection order.\n *\n * A package that two different directories claim is ambiguous, and a built\n * entry that resolves outside its package is refused.\n */\nexport function mcpPluginCandidates(compositions: readonly ExtensionComposition[]): McpPluginCandidate[] {\n const packages = new Map<string, { declaration: DeclaredMcpPlugin; owners: DoomServerBundleOwner[] }>();\n for (const composition of compositions) {\n const majorMode = composition.majorMode?.name;\n if (!majorMode) throw new Error('An MCP bundle composition needs its major mode');\n for (const root of new Set(composition.packageEntries.map(packageRootOf))) {\n const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) as Record<string, unknown>;\n const declaration = declaredMcpPlugin(root, manifest);\n if (declaration === undefined) continue;\n const previous = packages.get(declaration.packageName);\n if (previous && previous.declaration.packageDir !== root)\n throw new Error(`Ambiguous MCP bundle package: ${declaration.packageName}`);\n const candidate = previous ?? { declaration, owners: [] };\n const layers = composition.selections\n .filter(\n (selection) => selection.outcome === 'resolved' && selection.path && packageRootOf(selection.path) === root,\n )\n .map((selection) => selection.layer);\n for (const layer of new Set(layers.length > 0 ? layers : ['default'])) {\n if (!candidate.owners.some((owner) => owner.majorMode === majorMode && owner.layer === layer))\n candidate.owners.push({ majorMode, layer });\n }\n packages.set(declaration.packageName, candidate);\n }\n }\n return [...packages.values()].map(({ declaration, owners }) => {\n const module = fs.realpathSync(path.resolve(declaration.packageDir, declaration.dist));\n const relative = path.relative(declaration.packageDir, module);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`MCP entry escapes package: ${declaration.packageName}`);\n return { declaration, module, owners };\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcpBundle.cjs","names":["path","fs"],"sources":["../../../../src/builders/server/mcpBundle.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { PoServerBundleOwner } from '@agimon-ai/po-core/serverPlugin';\n\nimport type { ExtensionComposition } from '../cli/extensionAssembler';\n\nexport interface DeclaredMcpPlugin {\n readonly packageName: string;\n readonly packageDir: string;\n readonly entry: string;\n readonly dist: string;\n readonly ui?: { readonly dist: string; readonly widgets: readonly string[] };\n}\n\nfunction packageRootOf(entry: string): string {\n let directory = path.dirname(fs.realpathSync(entry));\n for (;;) {\n if (fs.existsSync(path.join(directory, 'package.json'))) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`No package manifest owns ${entry}`);\n directory = parent;\n }\n}\n\nfunction packagePath(packageDir: string, field: string, value: unknown): string {\n if (typeof value !== 'string' || !value.startsWith('./') || value.includes('..'))\n throw new Error(`poMcp ${field} in ${packageDir} must be a package-relative ./path with no '..'.`);\n return value;\n}\n\nfunction declaredMcpPlugin(packageDir: string, manifest: Record<string, unknown>): DeclaredMcpPlugin | undefined {\n const value = manifest.poMcp;\n if (value === undefined) return undefined;\n if (typeof value !== 'object' || value === null || Array.isArray(value))\n throw new Error(`poMcp manifest in ${packageDir} must be an object.`);\n const declaration = value as Record<string, unknown>;\n if (!Array.isArray(declaration.scopes) || declaration.scopes.length !== 1 || declaration.scopes[0] !== 'session')\n throw new Error(`poMcp scopes in ${packageDir} must be exactly ['session'].`);\n const packageName = manifest.name;\n if (typeof packageName !== 'string' || packageName === '') throw new Error(`Package in ${packageDir} has no name.`);\n let ui: DeclaredMcpPlugin['ui'];\n if (declaration.ui !== undefined) {\n const value = declaration.ui as { dist?: unknown; widgets?: unknown } | null;\n if (\n typeof value !== 'object' ||\n value === null ||\n !Array.isArray(value.widgets) ||\n value.widgets.some((key: unknown) => typeof key !== 'string' || !key.startsWith(`${packageName}/`)) ||\n new Set(value.widgets).size !== value.widgets.length\n )\n throw new Error(`Invalid MCP widget manifest in ${packageDir}`);\n ui = { dist: packagePath(packageDir, 'ui.dist', value.dist), widgets: value.widgets as string[] };\n }\n return {\n packageName,\n packageDir,\n ...(ui === undefined ? {} : { ui }),\n entry: packagePath(packageDir, 'entry', declaration.entry),\n dist: packagePath(packageDir, 'dist', declaration.dist),\n };\n}\n\n/** One MCP plugin a composition set selects, with the built entry a host imports. */\nexport interface McpPluginCandidate {\n readonly declaration: DeclaredMcpPlugin;\n /** Absolute, symlink-resolved path of the package's built `dist` entry. */\n readonly module: string;\n /** Every major mode and layer that admitted the package. */\n readonly owners: readonly PoServerBundleOwner[];\n}\n\n/**\n * The MCP plugins the given compositions select, in first-selection order.\n *\n * A package that two different directories claim is ambiguous, and a built\n * entry that resolves outside its package is refused.\n */\nexport function mcpPluginCandidates(compositions: readonly ExtensionComposition[]): McpPluginCandidate[] {\n const packages = new Map<string, { declaration: DeclaredMcpPlugin; owners: PoServerBundleOwner[] }>();\n for (const composition of compositions) {\n const majorMode = composition.majorMode?.name;\n if (!majorMode) throw new Error('An MCP bundle composition needs its major mode');\n for (const root of new Set(composition.packageEntries.map(packageRootOf))) {\n const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')) as Record<string, unknown>;\n const declaration = declaredMcpPlugin(root, manifest);\n if (declaration === undefined) continue;\n const previous = packages.get(declaration.packageName);\n if (previous && previous.declaration.packageDir !== root)\n throw new Error(`Ambiguous MCP bundle package: ${declaration.packageName}`);\n const candidate = previous ?? { declaration, owners: [] };\n const layers = composition.selections\n .filter(\n (selection) => selection.outcome === 'resolved' && selection.path && packageRootOf(selection.path) === root,\n )\n .map((selection) => selection.layer);\n for (const layer of new Set(layers.length > 0 ? layers : ['default'])) {\n if (!candidate.owners.some((owner) => owner.majorMode === majorMode && owner.layer === layer))\n candidate.owners.push({ majorMode, layer });\n }\n packages.set(declaration.packageName, candidate);\n }\n }\n return [...packages.values()].map(({ declaration, owners }) => {\n const module = fs.realpathSync(path.resolve(declaration.packageDir, declaration.dist));\n const relative = path.relative(declaration.packageDir, module);\n if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))\n throw new Error(`MCP entry escapes package: ${declaration.packageName}`);\n return { declaration, module, owners };\n });\n}\n"],"mappings":"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"}