@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
@@ -3,7 +3,7 @@ import { BUILD_RECORD, compositionPackageRoots, duplicatePluginCopies, ensureGlo
3
3
  import { ensureGlobalNativeBuild } from "../../builders/native/index.mjs";
4
4
  import { catalogInstallSpecifiers, catalogPackageRoots, ensureLayerPackages, packageCatalogHash, runtimeLayerPackageSpecifiers } from "../layerPackageInstaller/index.mjs";
5
5
  import { rootCompositions } from "../../builders/cli/sessionComposition.mjs";
6
- import { ensureMcpAppBuild } from "../../builders/server/mcpAppBuild.mjs";
6
+ import { ensureMcpAppBuild, mcpAppKey } from "../../builders/server/mcpAppBuild.mjs";
7
7
  import { mcpPluginCandidates } from "../../builders/server/mcpBundle.mjs";
8
8
  import { pruneAfterSync } from "../syncPrune/index.mjs";
9
9
  import { buildLocalPackages, localBuildStamp } from "./localBuilds.mjs";
@@ -11,9 +11,10 @@ import fs from "node:fs";
11
11
  import path from "node:path";
12
12
  import { writeFileAtomic } from "@agimon-ai/po-core/runtimeJson";
13
13
  import { spawnSync } from "node:child_process";
14
- import { globalDoomConfigDirectory } from "@agimon-ai/po-config/config";
14
+ import { globalPoConfigDirectory } from "@agimon-ai/po-config/config";
15
15
  import { isLocalPackageSpecifier, loadMajorModesConfigLenient } from "@agimon-ai/po-config/majorModes";
16
16
  import { piAgentDirectory } from "@agimon-ai/po-core/piSettings";
17
+ import { splitPackageSpecifier, testPackageRoot } from "@agimon-ai/po-core/moduleResolution";
17
18
  import crypto from "node:crypto";
18
19
  import { createWorkspaceRegistry, identifyWorkspace, readWorkspaceMarker, writeWorkspaceMarker } from "@agimon-ai/po-core/history";
19
20
  //#region src/composition/syncService/index.ts
@@ -170,7 +171,7 @@ function webPluginRootsOf(globalRoot, rootPackageRoots, environment) {
170
171
  * leaves it out of the build when it cannot compose it.
171
172
  */
172
173
  function globalWebPluginRoots(targetRoot, homeDirectory, environment, onNotice) {
173
- const globalRoot = globalDoomConfigDirectory(homeDirectory);
174
+ const globalRoot = globalPoConfigDirectory(homeDirectory);
174
175
  const admitted = workspaceRegistry(homeDirectory, environment, onNotice).list().map((workspace) => workspace.root).filter((root) => fs.existsSync(root));
175
176
  return webPluginRootsOf(globalRoot, packageRootsByRoot(composeRoots([.../* @__PURE__ */ new Set([
176
177
  globalRoot,
@@ -226,14 +227,14 @@ function resolveRoots(options, progress) {
226
227
  ...repository === void 0 ? [] : [repository]
227
228
  ])];
228
229
  }
229
- /** The packages a root's layers name by local path, each resolved against its layer's base directory. */
230
- function localPackages(config) {
230
+ /** Local-path selections and checkout test overrides, stamped and built by the same local build pipeline. */
231
+ function localPackages(config, environment) {
231
232
  const definitions = [...config.default ? [config.default] : [], ...Object.values(config.layers)];
232
233
  const packages = /* @__PURE__ */ new Map();
233
234
  for (const definition of definitions) for (const configured of definition.packages ?? []) {
234
235
  const selector = typeof configured === "string" ? configured : configured.name;
235
- if (!isLocalPackageSpecifier(selector)) continue;
236
- const directory = path.resolve(definition.baseDirectory, selector);
236
+ const directory = isLocalPackageSpecifier(selector) ? path.resolve(definition.baseDirectory, selector) : testPackageRoot(splitPackageSpecifier(selector).name, environment);
237
+ if (directory === void 0) continue;
237
238
  if (!packages.has(directory)) packages.set(directory, {
238
239
  selector,
239
240
  directory
@@ -274,7 +275,8 @@ function poPackageVersion() {
274
275
  * refuses that pointer's builds and syncs with its own install.
275
276
  */
276
277
  function syncHostIdentity(globalRoot, environment) {
277
- return `po@${poPackageVersion()}+po-web@${webHostVersion(globalRoot, environment)}`;
278
+ const checkout = environment.PO_TEST === "1" && environment.PO_TEST_PACKAGES ? `+checkout@${crypto.createHash("sha256").update(environment.PO_TEST_PACKAGES).digest("hex")}` : "";
279
+ return `po@${poPackageVersion()}+po-web@${webHostVersion(globalRoot, environment)}${checkout}`;
278
280
  }
279
281
  /** The fast-exit signature: catalogue hash, each root's selection and local build stamps, host identity. */
280
282
  function syncSignature(options, roots, catalogueHash) {
@@ -285,7 +287,7 @@ function syncSignature(options, roots, catalogueHash) {
285
287
  roots: roots.map((root) => ({
286
288
  root,
287
289
  selection: rootSelectionFingerprint(root, homeDirectory, environment),
288
- local: localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config).map(({ directory }) => [directory, localBuildStamp(directory)])
290
+ local: localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config, environment).map(({ directory }) => [directory, localBuildStamp(directory)])
289
291
  })),
290
292
  host: syncHostIdentity(options.globalRoot, environment)
291
293
  };
@@ -503,6 +505,24 @@ function realPath(file) {
503
505
  }
504
506
  }
505
507
  /**
508
+ * Whether the MCP app build the server will look for exists for every root. Its key hashes the
509
+ * widget packages' built files, which the signature does not cover, so a rebuilt `dist/` moves
510
+ * the key without moving the signature.
511
+ */
512
+ function mcpAppsBuilt(globalRoot, roots, homeDirectory, environment) {
513
+ for (const compositions of composeRoots(roots, homeDirectory, environment).values()) {
514
+ if (compositions instanceof Error) continue;
515
+ let key;
516
+ try {
517
+ key = mcpAppKey(mcpPluginCandidates(compositions));
518
+ } catch {
519
+ return false;
520
+ }
521
+ if (key !== void 0 && !fs.existsSync(path.join(globalRoot, BUILD_DIRECTORY, "mcp-app", key, MCP_APP_FILE))) return false;
522
+ }
523
+ return true;
524
+ }
525
+ /**
506
526
  * One sync pass under the lock. A failed step ends the pass with a `failed` report and leaves
507
527
  * the pointer alone; a lock that no longer names this holder (`lost`) ends it as busy.
508
528
  */
@@ -513,7 +533,7 @@ async function syncOnce(options, deps, emit, lost) {
513
533
  const roots = resolveRoots(options, progress);
514
534
  const catalogueHash = packageCatalogHash(environment);
515
535
  const previous = readSyncPointer(globalRoot);
516
- if (previous !== void 0 && (options.origin === "background" || previous.roots.every((root) => root.unsynced.length === 0)) && previous.signature === syncSignature(options, roots, catalogueHash) && pointerArtifacts(globalRoot, previous).every((file) => fs.existsSync(file))) {
536
+ if (previous !== void 0 && (options.origin === "background" || previous.roots.every((root) => root.unsynced.length === 0)) && previous.signature === syncSignature(options, roots, catalogueHash) && pointerArtifacts(globalRoot, previous).every((file) => fs.existsSync(file)) && mcpAppsBuilt(globalRoot, roots, homeDirectory, environment)) {
517
537
  progress.line(ROOTS_LABEL, `nothing changed since generation ${String(previous.generation)}`);
518
538
  return {
519
539
  ...emptyReport("noop", [], deps),
@@ -571,7 +591,7 @@ async function syncOnce(options, deps, emit, lost) {
571
591
  return report("failed", [`Installing packages failed: ${errorMessage(error)}`], null, []);
572
592
  }
573
593
  packagesDone(packageSummary(packages));
574
- const localByRoot = new Map(roots.map((root) => [root, localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config)]));
594
+ const localByRoot = new Map(roots.map((root) => [root, localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config, environment)]));
575
595
  const localDirectories = [...localByRoot.values()].flat().map(({ directory }) => directory);
576
596
  if (localDirectories.length > 0) {
577
597
  const localDone = progress.start(LOCAL_LABEL, "building local packages");
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/composition/syncService/index.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/po-config/config';\nimport { isLocalPackageSpecifier, loadMajorModesConfigLenient } from '@agimon-ai/po-config/majorModes';\nimport {\n createWorkspaceRegistry,\n identifyWorkspace,\n readWorkspaceMarker,\n type WorkspaceRegistry,\n writeWorkspaceMarker,\n} from '@agimon-ai/po-core/history';\nimport { piAgentDirectory } from '@agimon-ai/po-core/piSettings';\nimport { writeFileAtomic } from '@agimon-ai/po-core/runtimeJson';\n\nimport { poPackageRoot } from '../../builders/cli/entryResolution';\nimport { rootCompositions } from '../../builders/cli/sessionComposition';\nimport { ensureGlobalNativeBuild } from '../../builders/native';\nimport { ensureMcpAppBuild } from '../../builders/server/mcpAppBuild';\nimport { mcpPluginCandidates } from '../../builders/server/mcpBundle';\nimport {\n BUILD_RECORD,\n compositionPackageRoots,\n duplicatePluginCopies,\n ensureGlobalWebBuild,\n webHostVersion,\n} from '../../builders/web';\nimport {\n catalogInstallSpecifiers,\n catalogPackageRoots,\n ensureLayerPackages,\n type LayerPackageResult,\n packageCatalogHash,\n runtimeLayerPackageSpecifiers,\n} from '../layerPackageInstaller';\nimport { pruneAfterSync, type SyncPruneInput } from '../syncPrune';\nimport { buildLocalPackages, type LocalPackage, localBuildStamp } from './localBuilds';\n\nexport { localBuildStamp } from './localBuilds';\n\n/**\n * The sync service: installs what the configuration names, ensures the global builds,\n * prunes, and reports what happened as one `SyncReport`.\n *\n * The roots are the Po home, every registered workspace whose folder exists, and the\n * repository sync runs from, which it registers. One install covers the catalogue and every\n * root's published packages. When the signature over the catalogue, each root's selection,\n * each local package's build stamp and the host identity (the po package and po-web host\n * versions) matches `current.json` and every artifact it names exists, sync returns `noop`\n * without installing or building. The pointer records that identity, so a server from another\n * Po install can tell the build is not its own.\n *\n * Only one sync runs at a time per Po home, guarded by `<po home>/build/.sync.lock`. A CLI\n * run that finds the lock held reports `busy`. A background run instead leaves\n * `.sync.rerun`, and the holder runs once more, so the background request is never lost.\n */\n\nconst BUILD_DIRECTORY = 'build';\nconst LOCK_FILE = '.sync.lock';\nconst RERUN_FILE = '.sync.rerun';\nconst POINTER_FILE = 'current.json';\nconst LAST_ATTEMPT_FILE = 'last-attempt.json';\nconst MCP_APP_FILE = 'index.html';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst ROOTS_LABEL = 'roots';\nconst PACKAGES_LABEL = 'packages';\nconst WEB_LABEL = 'web';\nconst NATIVE_LABEL = 'native';\nconst MCP_LABEL = 'mcp app';\nconst PRUNE_LABEL = 'prune';\nconst HOOKS_LABEL = 'hooks';\nconst LOCAL_LABEL = 'local';\nconst BYTES_PER_MEGABYTE = 1024 * 1024;\n/** A lock file younger than this that cannot be parsed is still being written by its owner. */\nconst UNREADABLE_LOCK_GRACE_MS = 10_000;\nconst NO_SUCH_PROCESS = 'ESRCH';\n/**\n * Bumped when the pointer gains a field a reader relies on, so an older pointer never matches\n * the fast-exit signature and the next sync rewrites it. 2: per-root `fingerprints`. 3: `host`.\n */\nconst SIGNATURE_FORMAT = 3;\nconst PACKAGE_MANIFEST = 'package.json';\nconst ALREADY_EXISTS = 'EEXIST';\nconst NO_SUCH_FILE = 'ENOENT';\n\nexport const SYNC_EXIT_OK = 0;\nexport const SYNC_EXIT_FAILED = 1;\n/** EX_TEMPFAIL: another sync holds the lock. */\nexport const SYNC_EXIT_BUSY = 75;\n\nexport type SyncOrigin = 'cli' | 'background';\nexport type SyncStatus = 'ok' | 'noop' | 'partial' | 'failed' | 'busy';\n\nexport interface SyncResult {\n packages: Pick<LayerPackageResult, 'installed' | 'updated' | 'unchecked'>;\n /** The global web build key, or why there is none. */\n web: { status: 'built' | 'cached'; key: string } | { status: 'skipped'; reason: string };\n /** The global native build key, or why there is none. */\n native?: { status: 'built' | 'cached'; key: string } | { status: 'skipped'; reason: string };\n /** The MCP app build keys, comma separated, when any root's packages declare MCP UI widgets. */\n mcpApp?: string;\n}\n\nexport interface SyncRootReport {\n root: string;\n status: 'ok' | 'partial' | 'failed';\n unsynced: { selector: string; reason: string; stderr?: string[] }[];\n}\n\nexport interface SyncReport {\n status: SyncStatus;\n /** Assigned once sync writes a pointer; null until then. */\n generation: number | null;\n /** Assigned once sync computes the fast-exit signature; null until then. */\n catalogueHash: string | null;\n webKey: string | null;\n mcpKeys: string[];\n roots: SyncRootReport[];\n /** External commands sync ran, such as local package builds. */\n commands: string[];\n errors: string[];\n finishedAt: string;\n}\n\n/** One progress event; the CLI renders these as text or as JSON lines. */\nexport type SyncEvent =\n | { type: 'start'; label: string; message: string }\n | { type: 'line'; label: string; message: string }\n | { type: 'done'; label: string; summary: string }\n | { type: 'result'; result: SyncResult };\n\nexport interface SyncLockHolder {\n pid: number;\n /** Unique per acquisition, so a holder can tell its lock from a later one, even under its pid. */\n token?: string;\n startedAt: string;\n origin: SyncOrigin;\n}\n\nexport interface SyncOptions {\n homeDirectory: string;\n environment: NodeJS.ProcessEnv;\n /** The Po home every global build and the lock live under. */\n globalRoot: string;\n /**\n * The repository sync runs from: registered as a workspace, synced as a root, and its hooks\n * regenerated. Undefined with `--global`, which registers nothing.\n */\n repoRoot?: string;\n origin: SyncOrigin;\n onEvent?: (event: SyncEvent) => void;\n}\n\nexport interface SyncDeps {\n install: typeof ensureLayerPackages;\n buildWeb: typeof ensureGlobalWebBuild;\n buildNative: typeof ensureGlobalNativeBuild;\n bundleMcpApp: typeof ensureMcpAppBuild;\n prune: typeof pruneAfterSync;\n /** Whether a lock holder's process still runs. */\n isAlive: (pid: number) => boolean;\n pid: number;\n now: () => Date;\n}\n\n/** `process.kill(pid, 0)` probes without signalling; only ESRCH means the process is gone. */\nexport function processIsAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means it runs under another user, which still holds the lock.\n return (error as NodeJS.ErrnoException).code !== NO_SUCH_PROCESS;\n }\n}\n\nconst DEFAULT_DEPS: SyncDeps = {\n install: ensureLayerPackages,\n buildWeb: ensureGlobalWebBuild,\n buildNative: ensureGlobalNativeBuild,\n bundleMcpApp: ensureMcpAppBuild,\n prune: pruneAfterSync,\n isAlive: processIsAlive,\n pid: process.pid,\n now: () => new Date(),\n};\n\nexport function syncExitCode(status: SyncStatus): number {\n if (status === 'busy') return SYNC_EXIT_BUSY;\n return status === 'ok' || status === 'noop' ? SYNC_EXIT_OK : SYNC_EXIT_FAILED;\n}\n\nexport function syncLockPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, LOCK_FILE);\n}\n\nexport function syncRerunPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, RERUN_FILE);\n}\n\nfunction pluralize(count: number, noun: string): string {\n return `${String(count)} ${noun}${count === 1 ? '' : 's'}`;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction packageSummary(result: SyncResult['packages']): string {\n const parts: string[] = [];\n if (result.updated.length > 0) parts.push(`updated ${pluralize(result.updated.length, 'package')}`);\n if (result.installed.length > 0) parts.push(`installed ${pluralize(result.installed.length, 'missing package')}`);\n if (parts.length === 0) parts.push('already up to date');\n if (result.unchecked.length > 0) parts.push(`${pluralize(result.unchecked.length, 'package')} left unchecked`);\n return parts.join(', ');\n}\n\nexport function formatSyncResult(result: SyncResult): string {\n return [\n `packages: ${packageSummary(result.packages)}`,\n `web: ${result.web.status === 'skipped' ? result.web.reason : `${result.web.key} (${result.web.status})`}`,\n ...(result.native === undefined\n ? []\n : [\n `native: ${result.native.status === 'skipped' ? result.native.reason : `${result.native.key} (${result.native.status})`}`,\n ]),\n ...(result.mcpApp === undefined ? [] : [`mcp app: ${result.mcpApp}`]),\n '',\n ].join('\\n');\n}\n\nfunction workspaceRegistry(\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n onNotice: (message: string) => void,\n): WorkspaceRegistry {\n return createWorkspaceRegistry({\n directory: path.join(piAgentDirectory(environment, homeDirectory), 'server'),\n onNotice,\n });\n}\n\ntype RootCompositions = ReturnType<typeof rootCompositions>;\n\n/**\n * Each root's compositions, resolved the way the server resolves them: a selection that does\n * not resolve yet (an unbuilt local package, say) is left out with a diagnostic instead of\n * failing the whole root, so sync and the server derive the same web, MCP and fingerprint\n * inputs. A root that cannot be composed at all maps to why.\n */\nfunction composeRoots(\n roots: readonly string[],\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n): Map<string, RootCompositions | Error> {\n return new Map(\n roots.map((root): [string, RootCompositions | Error] => {\n try {\n return [root, rootCompositions({ root, homeDirectory, environment, tolerateUnsynced: true })];\n } catch (error) {\n return [root, error instanceof Error ? error : new Error(String(error))];\n }\n }),\n );\n}\n\n/** Each root's composition package roots; a root that cannot be composed is left out with a notice. */\nfunction packageRootsByRoot(\n composed: ReadonlyMap<string, RootCompositions | Error>,\n onNotice: (message: string) => void,\n): Map<string, string[]> {\n return new Map(\n [...composed].map(([root, compositions]) => {\n if (!(compositions instanceof Error)) return [root, compositionPackageRoots(compositions)];\n onNotice(`'${root}' is left out of the web build: ${compositions.message}`);\n return [root, []];\n }),\n );\n}\n\n/** The selectors a lenient composition left out, each once with the first diagnostic that explains it. */\nfunction skippedSelections(compositions: RootCompositions): SyncRootReport['unsynced'] {\n const skipped = new Map<string, string>();\n for (const composition of compositions)\n for (const selection of composition.selections)\n if (selection.outcome === 'missing-optional' && !selection.optional && !skipped.has(selection.selector))\n skipped.set(selection.selector, selection.diagnostic ?? 'it does not resolve');\n return [...skipped].map(([selector, reason]) => ({ selector, reason }));\n}\n\n/**\n * The plugin roots one global web build covers: every installed catalogue package, so each\n * first-party plugin has a slot whether or not a config names it yet, then each root's own\n * composition package roots. Sync builds with these and the server derives the same key.\n */\nfunction webPluginRootsOf(\n globalRoot: string,\n rootPackageRoots: ReadonlyMap<string, readonly string[]>,\n environment: NodeJS.ProcessEnv,\n): string[] {\n return [...catalogPackageRoots(globalRoot, environment), ...[...rootPackageRoots.values()].flat()];\n}\n\n/**\n * Every package root the global web build covers: the catalogue packages, the global root,\n * every workspace the server has admitted, and `targetRoot`, which the server admits when it\n * opens it. A root whose configuration cannot be read is left out with a notice, as sync\n * leaves it out of the build when it cannot compose it.\n */\nexport function globalWebPluginRoots(\n targetRoot: string,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n onNotice: (message: string) => void,\n): string[] {\n const globalRoot = globalDoomConfigDirectory(homeDirectory);\n const admitted = workspaceRegistry(homeDirectory, environment, onNotice)\n .list()\n .map((workspace) => workspace.root)\n .filter((root) => fs.existsSync(root));\n const roots = [...new Set([globalRoot, ...admitted, targetRoot])];\n return webPluginRootsOf(\n globalRoot,\n packageRootsByRoot(composeRoots(roots, homeDirectory, environment), onNotice),\n environment,\n );\n}\n\n/**\n * Registers the repository as a workspace the way the server admits one, so one repository\n * never gets two ids, and returns the workspace root it belongs to.\n */\nfunction registerRepository(\n registry: WorkspaceRegistry,\n repoRoot: string,\n onNotice: (message: string) => void,\n): string {\n const checkoutRoot = fs.realpathSync(repoRoot);\n const { id, root, moved } = identifyWorkspace({\n records: registry.list(),\n checkoutRoot,\n marker: readWorkspaceMarker(checkoutRoot),\n });\n const record = registry.list().find((workspace) => workspace.id === id);\n if (record === undefined || moved)\n registry.add({ id, root, ...(record?.name === undefined ? {} : { name: record.name }) });\n if (moved) onNotice(`workspace '${id}' moved to '${root}'`);\n try {\n if (readWorkspaceMarker(root) === undefined) writeWorkspaceMarker(root, id);\n } catch (error) {\n onNotice(`'${root}' id could not be recorded in its git data: ${errorMessage(error)}`);\n }\n return root;\n}\n\n/**\n * The roots this sync covers: the Po home, every registered workspace whose folder exists,\n * and the repository, registered first. Entries whose folder is gone are removed with a warning.\n */\nfunction resolveRoots(options: SyncOptions, progress: Steps): string[] {\n const notice = (message: string): void => progress.line(ROOTS_LABEL, message);\n const registry = workspaceRegistry(options.homeDirectory, options.environment, notice);\n const repository =\n options.repoRoot === undefined || path.resolve(options.repoRoot) === path.resolve(options.globalRoot)\n ? undefined\n : registerRepository(registry, options.repoRoot, notice);\n const live: string[] = [];\n for (const workspace of registry.list()) {\n if (fs.existsSync(workspace.root)) {\n live.push(workspace.root);\n continue;\n }\n notice(`warning: removed workspace '${workspace.id}': '${workspace.root}' no longer exists`);\n registry.remove(workspace.id);\n }\n return [...new Set([options.globalRoot, ...live, ...(repository === undefined ? [] : [repository])])];\n}\n\n/** The packages a root's layers name by local path, each resolved against its layer's base directory. */\nfunction localPackages(config: ReturnType<typeof loadMajorModesConfigLenient>['config']): LocalPackage[] {\n const definitions = [...(config.default ? [config.default] : []), ...Object.values(config.layers)];\n const packages = new Map<string, LocalPackage>();\n for (const definition of definitions)\n for (const configured of definition.packages ?? []) {\n const selector = typeof configured === 'string' ? configured : configured.name;\n if (!isLocalPackageSpecifier(selector)) continue;\n const directory = path.resolve(definition.baseDirectory, selector);\n if (!packages.has(directory)) packages.set(directory, { selector, directory });\n }\n return [...packages.values()];\n}\n\n/**\n * SHA-256 over every mode's composition fingerprint, or why the root cannot be composed. The\n * pointer records it per root, so a server can tell a root whose selection changed since the\n * last sync by computing it again with the environment sync ran under.\n */\nexport function rootSelectionFingerprint(root: string, homeDirectory: string, environment: NodeJS.ProcessEnv): string {\n try {\n const fingerprints = rootCompositions({ root, homeDirectory, environment, tolerateUnsynced: true }).map(\n (composition) => composition.fingerprint,\n );\n return crypto.createHash('sha256').update(fingerprints.join('\\n')).digest('hex');\n } catch (error) {\n return `unresolved:${errorMessage(error)}`;\n }\n}\n\n/** The po package version this process runs from, or why it cannot be read. */\nfunction poPackageVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(poPackageRoot(), PACKAGE_MANIFEST), 'utf8')) as {\n version?: unknown;\n };\n return typeof manifest.version === 'string' ? manifest.version : 'unversioned';\n } catch (error) {\n return `unreadable:${errorMessage(error)}`;\n }\n}\n\n/**\n * The Po install a sync or a server runs from: its po package version and the po-web host\n * version it builds with. Sync records it in the pointer; a server whose identity differs\n * refuses that pointer's builds and syncs with its own install.\n */\nexport function syncHostIdentity(globalRoot: string, environment: NodeJS.ProcessEnv): string {\n return `po@${poPackageVersion()}+po-web@${webHostVersion(globalRoot, environment)}`;\n}\n\n/** The fast-exit signature: catalogue hash, each root's selection and local build stamps, host identity. */\nfunction syncSignature(options: SyncOptions, roots: readonly string[], catalogueHash: string): string {\n const { homeDirectory, environment } = options;\n const value = {\n format: SIGNATURE_FORMAT,\n catalogueHash,\n roots: roots.map((root) => ({\n root,\n selection: rootSelectionFingerprint(root, homeDirectory, environment),\n local: localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config).map(\n ({ directory }) => [directory, localBuildStamp(directory)],\n ),\n })),\n host: syncHostIdentity(options.globalRoot, environment),\n };\n return crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');\n}\n\n/** `current.json`: the last successful report minus its transient fields, plus what fast exit needs. */\nexport interface SyncPointer {\n generation: number;\n signature: string;\n catalogueHash: string;\n webKey: string | null;\n nativeKey: string | null;\n mcpKeys: string[];\n roots: SyncRootReport[];\n commands: string[];\n /** Each root's `rootSelectionFingerprint` at the time of the sync; absent in older pointers. */\n fingerprints?: Record<string, string>;\n /** The `syncHostIdentity` of the install that wrote the pointer; absent in older pointers. */\n host?: string;\n}\n\nexport function syncPointerPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, POINTER_FILE);\n}\n\nexport function syncLastAttemptPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, LAST_ATTEMPT_FILE);\n}\n\n/** The current pointer, or undefined when sync has not written one yet or it is unreadable. */\nexport function readSyncPointer(globalRoot: string): SyncPointer | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(syncPointerPath(globalRoot), 'utf8')) as Partial<SyncPointer>;\n return typeof parsed.signature === 'string' && typeof parsed.generation === 'number'\n ? (parsed as SyncPointer)\n : undefined;\n } catch {\n // Missing or unreadable: there is nothing to fast-exit against.\n return undefined;\n }\n}\n\n/** The files whose presence proves each artifact a pointer names is complete. */\nfunction pointerArtifacts(\n globalRoot: string,\n pointer: Pick<SyncPointer, 'webKey' | 'nativeKey' | 'mcpKeys'>,\n): string[] {\n const build = path.join(globalRoot, BUILD_DIRECTORY);\n return [\n ...(pointer.webKey === null ? [] : [path.join(build, WEB_LABEL, pointer.webKey, BUILD_RECORD)]),\n ...(pointer.nativeKey === null ? [] : [path.join(build, NATIVE_LABEL, pointer.nativeKey, BUILD_RECORD)]),\n ...pointer.mcpKeys.map((key) => path.join(build, 'mcp-app', key, MCP_APP_FILE)),\n ];\n}\n\n/** The report of the last sync attempt, successful or not, or undefined when there is none. */\nexport function readSyncLastAttempt(globalRoot: string): SyncReport | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(syncLastAttemptPath(globalRoot), 'utf8')) as Partial<SyncReport>;\n return Array.isArray(parsed.roots) ? (parsed as SyncReport) : undefined;\n } catch {\n // Missing or unreadable: no attempt to report reasons from.\n return undefined;\n }\n}\n\nfunction writeJsonFile(file: string, value: unknown): void {\n writeFileAtomic(file, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\ntype LockAttempt = { acquired: true; token: string } | { acquired: false; holder: SyncLockHolder | undefined };\n\nfunction readHolder(lockPath: string): SyncLockHolder | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as Partial<SyncLockHolder>;\n return typeof parsed.pid === 'number' ? (parsed as SyncLockHolder) : undefined;\n } catch {\n // Missing or half-written: the caller decides from the file's age.\n return undefined;\n }\n}\n\nfunction lockIsStale(lockPath: string, deps: SyncDeps): boolean {\n const holder = readHolder(lockPath);\n if (holder !== undefined) return !deps.isAlive(holder.pid);\n try {\n return deps.now().getTime() - fs.statSync(lockPath).mtimeMs > UNREADABLE_LOCK_GRACE_MS;\n } catch {\n // Gone between the failed create and this check: free to retry.\n return true;\n }\n}\n\nfunction tryCreateLock(lockPath: string, holder: SyncLockHolder): boolean {\n try {\n const descriptor = fs.openSync(lockPath, 'wx');\n try {\n fs.writeSync(descriptor, JSON.stringify(holder));\n } finally {\n fs.closeSync(descriptor);\n }\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === ALREADY_EXISTS) return false;\n throw error;\n }\n}\n\n/**\n * Takes over a lock judged stale. Removing it by path could remove a lock another taker created\n * since the check, so it is first renamed to a name only this process knows and checked again\n * there: a lock that turns out live is linked back (link never replaces), a stale one is dropped\n * before the exclusive create, which at most one taker wins.\n */\nfunction takeOverStaleLock(lockPath: string, holder: SyncLockHolder, deps: SyncDeps): boolean {\n const moved = `${lockPath}.${String(deps.pid)}.${crypto.randomUUID()}.stale`;\n try {\n fs.renameSync(lockPath, moved);\n } catch (error) {\n // Already gone: released, or moved by another taker; the create decides.\n if ((error as NodeJS.ErrnoException).code === NO_SUCH_FILE) return tryCreateLock(lockPath, holder);\n throw error;\n }\n try {\n if (!lockIsStale(moved, deps)) {\n try {\n fs.linkSync(moved, lockPath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== ALREADY_EXISTS) throw error;\n }\n return false;\n }\n } finally {\n fs.rmSync(moved, { force: true });\n }\n return tryCreateLock(lockPath, holder);\n}\n\n/** Creates the lock, taking over one whose holder is dead. Never inspects other processes beyond `kill(pid, 0)`. */\nfunction acquireLock(lockPath: string, origin: SyncOrigin, deps: SyncDeps): LockAttempt {\n fs.mkdirSync(path.dirname(lockPath), { recursive: true });\n const token = crypto.randomUUID();\n const holder: SyncLockHolder = { pid: deps.pid, token, startedAt: deps.now().toISOString(), origin };\n if (tryCreateLock(lockPath, holder)) return { acquired: true, token };\n // A racing taker that wins the takeover makes this attempt report busy, as it should.\n if (lockIsStale(lockPath, deps) && takeOverStaleLock(lockPath, holder, deps)) return { acquired: true, token };\n return { acquired: false, holder: readHolder(lockPath) };\n}\n\n/**\n * The lock as the holder with `token` sees it: undefined while the lock still names it, else\n * the busy report to stop with. A takeover that moved a live lock aside can let a third taker\n * create a new one, so a holder checks before each step whose overlap would do harm.\n */\nfunction lostLock(lockPath: string, token: string, deps: SyncDeps): SyncReport | undefined {\n const holder = readHolder(lockPath);\n if (holder?.token === token) return undefined;\n const by = holder === undefined ? 'another process' : `pid ${String(holder.pid)}`;\n return emptyReport('busy', [`po sync lost its lock to ${by}`], deps);\n}\n\nfunction releaseLock(lockPath: string, token: string): void {\n if (readHolder(lockPath)?.token === token) fs.rmSync(lockPath, { force: true });\n}\n\n/** Consumes the rerun marker; true when a background run asked for another pass. */\nfunction takeRerunRequest(rerunPath: string): boolean {\n if (!fs.existsSync(rerunPath)) return false;\n fs.rmSync(rerunPath, { force: true });\n return true;\n}\n\nfunction emptyReport(status: SyncStatus, errors: string[], deps: SyncDeps): SyncReport {\n return {\n status,\n generation: null,\n catalogueHash: null,\n webKey: null,\n mcpKeys: [],\n roots: [],\n commands: [],\n errors,\n finishedAt: deps.now().toISOString(),\n };\n}\n\n/** A step reporter over the event stream, mirroring the presenter's start/done/line. */\nfunction steps(emit: (event: SyncEvent) => void) {\n return {\n start(label: string, message: string): (summary: string) => void {\n emit({ type: 'start', label, message });\n return (summary) => emit({ type: 'done', label, summary });\n },\n line(label: string, message: string): void {\n emit({ type: 'line', label, message });\n },\n };\n}\n\ntype Steps = ReturnType<typeof steps>;\n\n/**\n * Drops old builds and unreferenced store content once installs and builds are done.\n * Never fatal: a failed prune leaves everything for the next explicit sync.\n */\nasync function pruneStep(progress: Steps, prune: SyncDeps['prune'], input: SyncPruneInput): Promise<void> {\n const done = progress.start(PRUNE_LABEL, 'dropping old builds and unused packages');\n try {\n const result = await prune(input);\n const runtime =\n 'skipped' in result.runtime\n ? result.runtime.skipped\n : `${(result.runtime.bytes / BYTES_PER_MEGABYTE).toFixed(1)} MB of packages released`;\n done(`removed ${pluralize(result.builds.length, 'old build')}, ${runtime}`);\n } catch (error) {\n done(`failed: ${errorMessage(error)}`);\n }\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs, in a root that has the emitter. */\nfunction emitFrontendHooks(root: string, progress: Steps, commands: string[]): void {\n const emitter = path.join(root, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n commands.push(`${process.execPath} ${emitter} --write`);\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: root, encoding: 'utf8' });\n if (result.status === 0) {\n progress.line(HOOKS_LABEL, `regenerated for Claude Code and Codex in '${root}'`);\n return;\n }\n progress.line(\n HOOKS_LABEL,\n `emit-hooks failed in '${root}' (${result.stderr?.trim() || `exit ${String(result.status)}`})`,\n );\n}\n\n/** A path with its symlinks resolved, or as given when it does not exist. */\nfunction realPath(file: string): string {\n try {\n return fs.realpathSync(file);\n } catch {\n // Missing: compare it as written.\n return path.resolve(file);\n }\n}\n\n/**\n * One sync pass under the lock. A failed step ends the pass with a `failed` report and leaves\n * the pointer alone; a lock that no longer names this holder (`lost`) ends it as busy.\n */\nasync function syncOnce(\n options: SyncOptions,\n deps: SyncDeps,\n emit: (event: SyncEvent) => void,\n lost: () => SyncReport | undefined,\n): Promise<SyncReport> {\n const { homeDirectory, environment, globalRoot } = options;\n const progress = steps(emit);\n const commands: string[] = [];\n const roots = resolveRoots(options, progress);\n const catalogueHash = packageCatalogHash(environment);\n const previous = readSyncPointer(globalRoot);\n if (\n previous !== undefined &&\n // Unsynced selectors are retried only when a person runs sync: with an unchanged signature a\n // background retry repeats the same failure, while something outside the signature (a package\n // manager installed since) is what a manual `po sync` is for. The pointer keeps the reasons.\n (options.origin === 'background' || previous.roots.every((root) => root.unsynced.length === 0)) &&\n previous.signature === syncSignature(options, roots, catalogueHash) &&\n pointerArtifacts(globalRoot, previous).every((file) => fs.existsSync(file))\n ) {\n progress.line(ROOTS_LABEL, `nothing changed since generation ${String(previous.generation)}`);\n return {\n ...emptyReport('noop', [], deps),\n generation: previous.generation,\n catalogueHash,\n webKey: previous.webKey,\n mcpKeys: previous.mcpKeys,\n roots: previous.roots,\n };\n }\n const unsynced = new Map<string, SyncRootReport['unsynced']>(roots.map((root) => [root, []]));\n const report = (status: 'ok' | 'failed', errors: string[], webKey: string | null, mcpKeys: string[]) => {\n const rootReports = roots.map((root): SyncRootReport => {\n const entries = unsynced.get(root) ?? [];\n return { root, status: status === 'ok' && entries.length > 0 ? 'partial' : status, unsynced: entries };\n });\n const partial = status === 'ok' && rootReports.some((root) => root.status === 'partial');\n return {\n ...emptyReport(partial ? 'partial' : status, errors, deps),\n generation: previous?.generation ?? null,\n catalogueHash,\n webKey,\n mcpKeys,\n roots: rootReports,\n commands,\n };\n };\n\n const lostBeforeInstall = lost();\n if (lostBeforeInstall !== undefined) return lostBeforeInstall;\n const packagesDone = progress.start(PACKAGES_LABEL, 'checking configured packages for updates');\n let packages: SyncResult['packages'];\n try {\n const configOf = (root: string) => loadMajorModesConfigLenient(root, homeDirectory, environment).config;\n const globalConfig = configOf(globalRoot);\n // One install covers the catalogue and every package some root does not get from its own\n // node_modules, including those the runtime already provides; the runtime manifest declares\n // exactly those, so pruning can drop anything no root needs. A name a root resolves from its\n // own node_modules stays out, so a workspace's local copy is never shadowed by, or blocked\n // on, a registry install.\n const specifiers = new Set(catalogInstallSpecifiers(environment));\n for (const root of roots) {\n const config = configOf(root);\n for (const specifier of runtimeLayerPackageSpecifiers(config, Object.keys(config.layers), root))\n specifiers.add(specifier);\n }\n packages = await deps.install({\n repoRoot: globalRoot,\n config: globalConfig,\n // Every declared layer, so a later mode switch finds its packages installed.\n layers: Object.keys(globalConfig.layers),\n environment,\n // A background run never bumps versions; only a person running sync asks for updates.\n refresh: options.origin === 'cli',\n specifiers: [...specifiers],\n exactManifest: true,\n onProgress: (message) => progress.line(PACKAGES_LABEL, message),\n });\n } catch (error) {\n packagesDone('failed');\n return report('failed', [`Installing packages failed: ${errorMessage(error)}`], null, []);\n }\n packagesDone(packageSummary(packages));\n\n const localByRoot = new Map(\n roots.map((root) => [root, localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config)]),\n );\n const localDirectories = [...localByRoot.values()].flat().map(({ directory }) => directory);\n if (localDirectories.length > 0) {\n const localDone = progress.start(LOCAL_LABEL, 'building local packages');\n const outcomes = await buildLocalPackages({\n globalRoot,\n environment,\n directories: localDirectories,\n onCommand: (command) => commands.push(command),\n onNotice: (message) => progress.line(LOCAL_LABEL, message),\n });\n let failed = 0;\n for (const [root, packagesOfRoot] of localByRoot)\n for (const { selector, directory } of packagesOfRoot) {\n const outcome = outcomes.get(directory);\n if (outcome?.status !== 'failed') continue;\n unsynced.get(root)?.push({\n selector,\n reason: outcome.reason,\n ...(outcome.stderr === undefined ? {} : { stderr: outcome.stderr }),\n });\n }\n for (const outcome of outcomes.values()) if (outcome.status === 'failed') failed += 1;\n localDone(failed === 0 ? `${pluralize(outcomes.size, 'package')} ready` : `${String(failed)} failed`);\n }\n\n const composed = composeRoots(roots, homeDirectory, environment);\n // What a root's selection leaves out still syncs the rest of the root; each is reported once.\n for (const [root, compositions] of composed) {\n if (compositions instanceof Error) continue;\n const entries = unsynced.get(root) ?? [];\n for (const skipped of skippedSelections(compositions))\n if (!entries.some((entry) => entry.selector === skipped.selector)) entries.push(skipped);\n }\n\n const webDone = progress.start(WEB_LABEL, 'ensuring the global web build');\n let rootPackageRoots = packageRootsByRoot(composed, (message) => progress.line(WEB_LABEL, message));\n const buildWeb = (packageRoots: Map<string, string[]>) =>\n deps.buildWeb({\n poHome: globalRoot,\n pluginRoots: webPluginRootsOf(globalRoot, packageRoots, environment),\n environment,\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n let built = await buildWeb(rootPackageRoots);\n if (built.status === 'failed') {\n // A local copy is the likeliest cause; one retry without them keeps the rest serving.\n const local = new Set(localDirectories.map(realPath));\n const withoutLocal = new Map(\n [...rootPackageRoots].map(([root, packageRoots]) => [\n root,\n packageRoots.filter((packageRoot) => !local.has(realPath(packageRoot))),\n ]),\n );\n const leftOut = [...rootPackageRoots].some(\n ([root, packageRoots]) => withoutLocal.get(root)?.length !== packageRoots.length,\n );\n const retried = leftOut ? await buildWeb(withoutLocal) : built;\n if (retried.status === 'failed') {\n webDone('failed');\n const retryReason = leftOut ? `; without local copies: ${retried.reason}` : '';\n return report('failed', [`The global web build failed: ${built.reason}${retryReason}`], null, []);\n }\n for (const [root, packagesOfRoot] of localByRoot) {\n const included = new Set((rootPackageRoots.get(root) ?? []).map(realPath));\n const entries = unsynced.get(root) ?? [];\n for (const { selector, directory } of packagesOfRoot)\n if (included.has(realPath(directory)) && !entries.some((entry) => entry.selector === selector))\n entries.push({\n selector,\n reason: `left out of the web build, which failed with it: ${built.reason}`,\n });\n }\n progress.line(WEB_LABEL, `built without local copies after: ${built.reason}`);\n rootPackageRoots = withoutLocal;\n built = retried;\n }\n const pluginRoots = webPluginRootsOf(globalRoot, rootPackageRoots, environment);\n const web: SyncResult['web'] = built.status === 'skipped' ? built : { status: built.status, key: built.build.key };\n webDone(web.status === 'skipped' ? web.reason : `${web.key} (${web.status})`);\n const webKey = web.status === 'skipped' ? null : web.key;\n\n const nativeDone = progress.start(NATIVE_LABEL, 'ensuring the global native build');\n const nativeBuilt = await deps.buildNative({\n poHome: globalRoot,\n pluginRoots,\n onNotice: (message) => progress.line(NATIVE_LABEL, message),\n });\n if (nativeBuilt.status === 'failed') {\n nativeDone('failed');\n return report('failed', [`The global native build failed: ${nativeBuilt.reason}`], webKey, []);\n }\n const native: NonNullable<SyncResult['native']> =\n nativeBuilt.status === 'skipped' ? nativeBuilt : { status: nativeBuilt.status, key: nativeBuilt.build.key };\n nativeDone(native.status === 'skipped' ? native.reason : `${native.key} (${native.status})`);\n const nativeKey = native.status === 'skipped' ? null : native.key;\n\n // One MCP app build per root; its key is a content hash, so roots with one widget set share it.\n const mcpDone = progress.start(MCP_LABEL, 'ensuring the MCP app build of each root');\n const mcpKeys: string[] = [];\n for (const [root, compositions] of composed) {\n if (compositions instanceof Error) {\n progress.line(MCP_LABEL, `'${root}' is left out of the MCP app build: ${compositions.message}`);\n continue;\n }\n try {\n const app = await deps.bundleMcpApp({ poHome: globalRoot, candidates: mcpPluginCandidates(compositions) });\n if (app !== undefined && !mcpKeys.includes(app.key)) mcpKeys.push(app.key);\n } catch (error) {\n unsynced.get(root)?.push({ selector: MCP_LABEL, reason: `the MCP app build failed: ${errorMessage(error)}` });\n }\n }\n mcpDone(mcpKeys.length === 0 ? 'no MCP UI widgets' : mcpKeys.join(', '));\n const mcpApp = mcpKeys.length === 0 ? undefined : mcpKeys.join(', ');\n\n // The pointer names only artifacts that exist; a missing one keeps the previous pointer.\n const missing = pointerArtifacts(globalRoot, { webKey, nativeKey, mcpKeys }).filter((file) => !fs.existsSync(file));\n if (missing.length > 0)\n return report('failed', [`Build artifacts are missing: ${missing.join(', ')}`], webKey, mcpKeys);\n // A root that names two copies of one plugin serves only one of them under its plugin id.\n if (built.status !== 'skipped')\n for (const [root, packageRoots] of rootPackageRoots)\n for (const [pluginId, slots] of duplicatePluginCopies(built.build, packageRoots))\n unsynced.get(root)?.push({\n selector: pluginId,\n reason: `two copies of plugin '${pluginId}' are selected (${slots.join(', ')})`,\n });\n const succeeded = report('ok', [], webKey, mcpKeys);\n const generation = (previous?.generation ?? 0) + 1;\n const pointer: SyncPointer = {\n generation,\n signature: syncSignature(options, roots, catalogueHash),\n catalogueHash,\n webKey,\n nativeKey,\n mcpKeys,\n roots: succeeded.roots,\n commands,\n fingerprints: Object.fromEntries(\n roots.map((root) => [root, rootSelectionFingerprint(root, homeDirectory, environment)]),\n ),\n host: syncHostIdentity(globalRoot, environment),\n };\n const lostBeforePointer = lost();\n if (lostBeforePointer !== undefined) return lostBeforePointer;\n writeJsonFile(syncPointerPath(globalRoot), pointer);\n\n await pruneStep(progress, deps.prune, {\n homeDirectory,\n environment,\n buildsRoot: globalRoot,\n ...(webKey === null ? {} : { webKey }),\n ...(nativeKey === null ? {} : { nativeKey }),\n ...(mcpKeys.length === 0 ? {} : { mcpAppKeys: mcpKeys }),\n });\n // The checkout itself too: a linked worktree registers under its main workspace root.\n for (const root of new Set([...roots, ...(options.repoRoot === undefined ? [] : [realPath(options.repoRoot)])]))\n emitFrontendHooks(root, progress, commands);\n emit({ type: 'result', result: { packages, web, native, ...(mcpApp === undefined ? {} : { mcpApp }) } });\n return { ...succeeded, generation };\n}\n\n/**\n * One pass whose report lands in `last-attempt.json`, so a failed background run leaves its\n * reasons. A pass that lost the lock leaves the file to the holder that took it.\n */\nasync function attempt(\n options: SyncOptions,\n deps: SyncDeps,\n emit: (event: SyncEvent) => void,\n lost: () => SyncReport | undefined,\n): Promise<SyncReport> {\n let report: SyncReport;\n try {\n report = await syncOnce(options, deps, emit, lost);\n } catch (error) {\n report = emptyReport('failed', [`po sync failed: ${errorMessage(error)}`], deps);\n }\n if (report.status === 'busy') return report;\n try {\n writeJsonFile(syncLastAttemptPath(options.globalRoot), report);\n } catch (error) {\n report = {\n ...report,\n errors: [...report.errors, `last-attempt.json could not be written: ${errorMessage(error)}`],\n };\n }\n return report;\n}\n\n/**\n * Runs sync under the Po home lock and reports the outcome.\n *\n * Busy when another live sync holds the lock; a background caller leaves a rerun marker\n * first. The holder repeats its pass while a marker is present. A marker that lands after\n * its last check but before the release is caught after the release: the holder takes the\n * lock again and runs once more, or leaves the marker to whoever took the lock first.\n */\nexport async function runSync(options: SyncOptions, overrides: Partial<SyncDeps> = {}): Promise<SyncReport> {\n const deps: SyncDeps = { ...DEFAULT_DEPS, ...overrides };\n const emit = options.onEvent ?? ((): void => {});\n const lockPath = syncLockPath(options.globalRoot);\n const rerunPath = syncRerunPath(options.globalRoot);\n let lock = acquireLock(lockPath, options.origin, deps);\n if (!lock.acquired) {\n if (options.origin === 'background') fs.writeFileSync(rerunPath, deps.now().toISOString());\n const holder = lock.holder === undefined ? 'another process' : `pid ${String(lock.holder.pid)}`;\n return emptyReport('busy', [`po sync is already running (${holder})`], deps);\n }\n for (;;) {\n const { token } = lock;\n const lost = () => lostLock(lockPath, token, deps);\n let report: SyncReport;\n try {\n // A marker left before this pass started is satisfied by this pass.\n takeRerunRequest(rerunPath);\n report = await attempt(options, deps, emit, lost);\n // A pass that lost the lock leaves the rerun marker to the holder that took it.\n while (report.status !== 'busy' && takeRerunRequest(rerunPath)) report = await attempt(options, deps, emit, lost);\n } finally {\n releaseLock(lockPath, token);\n }\n if (report.status === 'busy' || !fs.existsSync(rerunPath)) return report;\n lock = acquireLock(lockPath, options.origin, deps);\n if (!lock.acquired) return report;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/composition/syncService/index.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { globalPoConfigDirectory } from '@agimon-ai/po-config/config';\nimport { isLocalPackageSpecifier, loadMajorModesConfigLenient } from '@agimon-ai/po-config/majorModes';\nimport {\n createWorkspaceRegistry,\n identifyWorkspace,\n readWorkspaceMarker,\n type WorkspaceRegistry,\n writeWorkspaceMarker,\n} from '@agimon-ai/po-core/history';\nimport { splitPackageSpecifier, testPackageRoot } from '@agimon-ai/po-core/moduleResolution';\nimport { piAgentDirectory } from '@agimon-ai/po-core/piSettings';\nimport { writeFileAtomic } from '@agimon-ai/po-core/runtimeJson';\n\nimport { poPackageRoot } from '../../builders/cli/entryResolution';\nimport { rootCompositions } from '../../builders/cli/sessionComposition';\nimport { ensureGlobalNativeBuild } from '../../builders/native';\nimport { ensureMcpAppBuild, mcpAppKey } from '../../builders/server/mcpAppBuild';\nimport { mcpPluginCandidates } from '../../builders/server/mcpBundle';\nimport {\n BUILD_RECORD,\n compositionPackageRoots,\n duplicatePluginCopies,\n ensureGlobalWebBuild,\n webHostVersion,\n} from '../../builders/web';\nimport {\n catalogInstallSpecifiers,\n catalogPackageRoots,\n ensureLayerPackages,\n type LayerPackageResult,\n packageCatalogHash,\n runtimeLayerPackageSpecifiers,\n} from '../layerPackageInstaller';\nimport { pruneAfterSync, type SyncPruneInput } from '../syncPrune';\nimport { buildLocalPackages, type LocalPackage, localBuildStamp } from './localBuilds';\n\nexport { localBuildStamp } from './localBuilds';\n\n/**\n * The sync service: installs what the configuration names, ensures the global builds,\n * prunes, and reports what happened as one `SyncReport`.\n *\n * The roots are the Po home, every registered workspace whose folder exists, and the\n * repository sync runs from, which it registers. One install covers the catalogue and every\n * root's published packages. When the signature over the catalogue, each root's selection,\n * each local package's build stamp and the host identity (the po package and po-web host\n * versions) matches `current.json` and every artifact it names exists, sync returns `noop`\n * without installing or building. The pointer records that identity, so a server from another\n * Po install can tell the build is not its own.\n *\n * Only one sync runs at a time per Po home, guarded by `<po home>/build/.sync.lock`. A CLI\n * run that finds the lock held reports `busy`. A background run instead leaves\n * `.sync.rerun`, and the holder runs once more, so the background request is never lost.\n */\n\nconst BUILD_DIRECTORY = 'build';\nconst LOCK_FILE = '.sync.lock';\nconst RERUN_FILE = '.sync.rerun';\nconst POINTER_FILE = 'current.json';\nconst LAST_ATTEMPT_FILE = 'last-attempt.json';\nconst MCP_APP_FILE = 'index.html';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst ROOTS_LABEL = 'roots';\nconst PACKAGES_LABEL = 'packages';\nconst WEB_LABEL = 'web';\nconst NATIVE_LABEL = 'native';\nconst MCP_LABEL = 'mcp app';\nconst PRUNE_LABEL = 'prune';\nconst HOOKS_LABEL = 'hooks';\nconst LOCAL_LABEL = 'local';\nconst BYTES_PER_MEGABYTE = 1024 * 1024;\n/** A lock file younger than this that cannot be parsed is still being written by its owner. */\nconst UNREADABLE_LOCK_GRACE_MS = 10_000;\nconst NO_SUCH_PROCESS = 'ESRCH';\n/**\n * Bumped when the pointer gains a field a reader relies on, so an older pointer never matches\n * the fast-exit signature and the next sync rewrites it. 2: per-root `fingerprints`. 3: `host`.\n */\nconst SIGNATURE_FORMAT = 3;\nconst PACKAGE_MANIFEST = 'package.json';\nconst ALREADY_EXISTS = 'EEXIST';\nconst NO_SUCH_FILE = 'ENOENT';\n\nexport const SYNC_EXIT_OK = 0;\nexport const SYNC_EXIT_FAILED = 1;\n/** EX_TEMPFAIL: another sync holds the lock. */\nexport const SYNC_EXIT_BUSY = 75;\n\nexport type SyncOrigin = 'cli' | 'background';\nexport type SyncStatus = 'ok' | 'noop' | 'partial' | 'failed' | 'busy';\n\nexport interface SyncResult {\n packages: Pick<LayerPackageResult, 'installed' | 'updated' | 'unchecked'>;\n /** The global web build key, or why there is none. */\n web: { status: 'built' | 'cached'; key: string } | { status: 'skipped'; reason: string };\n /** The global native build key, or why there is none. */\n native?: { status: 'built' | 'cached'; key: string } | { status: 'skipped'; reason: string };\n /** The MCP app build keys, comma separated, when any root's packages declare MCP UI widgets. */\n mcpApp?: string;\n}\n\nexport interface SyncRootReport {\n root: string;\n status: 'ok' | 'partial' | 'failed';\n unsynced: { selector: string; reason: string; stderr?: string[] }[];\n}\n\nexport interface SyncReport {\n status: SyncStatus;\n /** Assigned once sync writes a pointer; null until then. */\n generation: number | null;\n /** Assigned once sync computes the fast-exit signature; null until then. */\n catalogueHash: string | null;\n webKey: string | null;\n mcpKeys: string[];\n roots: SyncRootReport[];\n /** External commands sync ran, such as local package builds. */\n commands: string[];\n errors: string[];\n finishedAt: string;\n}\n\n/** One progress event; the CLI renders these as text or as JSON lines. */\nexport type SyncEvent =\n | { type: 'start'; label: string; message: string }\n | { type: 'line'; label: string; message: string }\n | { type: 'done'; label: string; summary: string }\n | { type: 'result'; result: SyncResult };\n\nexport interface SyncLockHolder {\n pid: number;\n /** Unique per acquisition, so a holder can tell its lock from a later one, even under its pid. */\n token?: string;\n startedAt: string;\n origin: SyncOrigin;\n}\n\nexport interface SyncOptions {\n homeDirectory: string;\n environment: NodeJS.ProcessEnv;\n /** The Po home every global build and the lock live under. */\n globalRoot: string;\n /**\n * The repository sync runs from: registered as a workspace, synced as a root, and its hooks\n * regenerated. Undefined with `--global`, which registers nothing.\n */\n repoRoot?: string;\n origin: SyncOrigin;\n onEvent?: (event: SyncEvent) => void;\n}\n\nexport interface SyncDeps {\n install: typeof ensureLayerPackages;\n buildWeb: typeof ensureGlobalWebBuild;\n buildNative: typeof ensureGlobalNativeBuild;\n bundleMcpApp: typeof ensureMcpAppBuild;\n prune: typeof pruneAfterSync;\n /** Whether a lock holder's process still runs. */\n isAlive: (pid: number) => boolean;\n pid: number;\n now: () => Date;\n}\n\n/** `process.kill(pid, 0)` probes without signalling; only ESRCH means the process is gone. */\nexport function processIsAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means it runs under another user, which still holds the lock.\n return (error as NodeJS.ErrnoException).code !== NO_SUCH_PROCESS;\n }\n}\n\nconst DEFAULT_DEPS: SyncDeps = {\n install: ensureLayerPackages,\n buildWeb: ensureGlobalWebBuild,\n buildNative: ensureGlobalNativeBuild,\n bundleMcpApp: ensureMcpAppBuild,\n prune: pruneAfterSync,\n isAlive: processIsAlive,\n pid: process.pid,\n now: () => new Date(),\n};\n\nexport function syncExitCode(status: SyncStatus): number {\n if (status === 'busy') return SYNC_EXIT_BUSY;\n return status === 'ok' || status === 'noop' ? SYNC_EXIT_OK : SYNC_EXIT_FAILED;\n}\n\nexport function syncLockPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, LOCK_FILE);\n}\n\nexport function syncRerunPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, RERUN_FILE);\n}\n\nfunction pluralize(count: number, noun: string): string {\n return `${String(count)} ${noun}${count === 1 ? '' : 's'}`;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction packageSummary(result: SyncResult['packages']): string {\n const parts: string[] = [];\n if (result.updated.length > 0) parts.push(`updated ${pluralize(result.updated.length, 'package')}`);\n if (result.installed.length > 0) parts.push(`installed ${pluralize(result.installed.length, 'missing package')}`);\n if (parts.length === 0) parts.push('already up to date');\n if (result.unchecked.length > 0) parts.push(`${pluralize(result.unchecked.length, 'package')} left unchecked`);\n return parts.join(', ');\n}\n\nexport function formatSyncResult(result: SyncResult): string {\n return [\n `packages: ${packageSummary(result.packages)}`,\n `web: ${result.web.status === 'skipped' ? result.web.reason : `${result.web.key} (${result.web.status})`}`,\n ...(result.native === undefined\n ? []\n : [\n `native: ${result.native.status === 'skipped' ? result.native.reason : `${result.native.key} (${result.native.status})`}`,\n ]),\n ...(result.mcpApp === undefined ? [] : [`mcp app: ${result.mcpApp}`]),\n '',\n ].join('\\n');\n}\n\nfunction workspaceRegistry(\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n onNotice: (message: string) => void,\n): WorkspaceRegistry {\n return createWorkspaceRegistry({\n directory: path.join(piAgentDirectory(environment, homeDirectory), 'server'),\n onNotice,\n });\n}\n\ntype RootCompositions = ReturnType<typeof rootCompositions>;\n\n/**\n * Each root's compositions, resolved the way the server resolves them: a selection that does\n * not resolve yet (an unbuilt local package, say) is left out with a diagnostic instead of\n * failing the whole root, so sync and the server derive the same web, MCP and fingerprint\n * inputs. A root that cannot be composed at all maps to why.\n */\nfunction composeRoots(\n roots: readonly string[],\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n): Map<string, RootCompositions | Error> {\n return new Map(\n roots.map((root): [string, RootCompositions | Error] => {\n try {\n return [root, rootCompositions({ root, homeDirectory, environment, tolerateUnsynced: true })];\n } catch (error) {\n return [root, error instanceof Error ? error : new Error(String(error))];\n }\n }),\n );\n}\n\n/** Each root's composition package roots; a root that cannot be composed is left out with a notice. */\nfunction packageRootsByRoot(\n composed: ReadonlyMap<string, RootCompositions | Error>,\n onNotice: (message: string) => void,\n): Map<string, string[]> {\n return new Map(\n [...composed].map(([root, compositions]) => {\n if (!(compositions instanceof Error)) return [root, compositionPackageRoots(compositions)];\n onNotice(`'${root}' is left out of the web build: ${compositions.message}`);\n return [root, []];\n }),\n );\n}\n\n/** The selectors a lenient composition left out, each once with the first diagnostic that explains it. */\nfunction skippedSelections(compositions: RootCompositions): SyncRootReport['unsynced'] {\n const skipped = new Map<string, string>();\n for (const composition of compositions)\n for (const selection of composition.selections)\n if (selection.outcome === 'missing-optional' && !selection.optional && !skipped.has(selection.selector))\n skipped.set(selection.selector, selection.diagnostic ?? 'it does not resolve');\n return [...skipped].map(([selector, reason]) => ({ selector, reason }));\n}\n\n/**\n * The plugin roots one global web build covers: every installed catalogue package, so each\n * first-party plugin has a slot whether or not a config names it yet, then each root's own\n * composition package roots. Sync builds with these and the server derives the same key.\n */\nfunction webPluginRootsOf(\n globalRoot: string,\n rootPackageRoots: ReadonlyMap<string, readonly string[]>,\n environment: NodeJS.ProcessEnv,\n): string[] {\n return [...catalogPackageRoots(globalRoot, environment), ...[...rootPackageRoots.values()].flat()];\n}\n\n/**\n * Every package root the global web build covers: the catalogue packages, the global root,\n * every workspace the server has admitted, and `targetRoot`, which the server admits when it\n * opens it. A root whose configuration cannot be read is left out with a notice, as sync\n * leaves it out of the build when it cannot compose it.\n */\nexport function globalWebPluginRoots(\n targetRoot: string,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n onNotice: (message: string) => void,\n): string[] {\n const globalRoot = globalPoConfigDirectory(homeDirectory);\n const admitted = workspaceRegistry(homeDirectory, environment, onNotice)\n .list()\n .map((workspace) => workspace.root)\n .filter((root) => fs.existsSync(root));\n const roots = [...new Set([globalRoot, ...admitted, targetRoot])];\n return webPluginRootsOf(\n globalRoot,\n packageRootsByRoot(composeRoots(roots, homeDirectory, environment), onNotice),\n environment,\n );\n}\n\n/**\n * Registers the repository as a workspace the way the server admits one, so one repository\n * never gets two ids, and returns the workspace root it belongs to.\n */\nfunction registerRepository(\n registry: WorkspaceRegistry,\n repoRoot: string,\n onNotice: (message: string) => void,\n): string {\n const checkoutRoot = fs.realpathSync(repoRoot);\n const { id, root, moved } = identifyWorkspace({\n records: registry.list(),\n checkoutRoot,\n marker: readWorkspaceMarker(checkoutRoot),\n });\n const record = registry.list().find((workspace) => workspace.id === id);\n if (record === undefined || moved)\n registry.add({ id, root, ...(record?.name === undefined ? {} : { name: record.name }) });\n if (moved) onNotice(`workspace '${id}' moved to '${root}'`);\n try {\n if (readWorkspaceMarker(root) === undefined) writeWorkspaceMarker(root, id);\n } catch (error) {\n onNotice(`'${root}' id could not be recorded in its git data: ${errorMessage(error)}`);\n }\n return root;\n}\n\n/**\n * The roots this sync covers: the Po home, every registered workspace whose folder exists,\n * and the repository, registered first. Entries whose folder is gone are removed with a warning.\n */\nfunction resolveRoots(options: SyncOptions, progress: Steps): string[] {\n const notice = (message: string): void => progress.line(ROOTS_LABEL, message);\n const registry = workspaceRegistry(options.homeDirectory, options.environment, notice);\n const repository =\n options.repoRoot === undefined || path.resolve(options.repoRoot) === path.resolve(options.globalRoot)\n ? undefined\n : registerRepository(registry, options.repoRoot, notice);\n const live: string[] = [];\n for (const workspace of registry.list()) {\n if (fs.existsSync(workspace.root)) {\n live.push(workspace.root);\n continue;\n }\n notice(`warning: removed workspace '${workspace.id}': '${workspace.root}' no longer exists`);\n registry.remove(workspace.id);\n }\n return [...new Set([options.globalRoot, ...live, ...(repository === undefined ? [] : [repository])])];\n}\n\n/** Local-path selections and checkout test overrides, stamped and built by the same local build pipeline. */\nfunction localPackages(\n config: ReturnType<typeof loadMajorModesConfigLenient>['config'],\n environment: NodeJS.ProcessEnv,\n): LocalPackage[] {\n const definitions = [...(config.default ? [config.default] : []), ...Object.values(config.layers)];\n const packages = new Map<string, LocalPackage>();\n for (const definition of definitions)\n for (const configured of definition.packages ?? []) {\n const selector = typeof configured === 'string' ? configured : configured.name;\n const directory = isLocalPackageSpecifier(selector)\n ? path.resolve(definition.baseDirectory, selector)\n : testPackageRoot(splitPackageSpecifier(selector).name, environment);\n if (directory === undefined) continue;\n if (!packages.has(directory)) packages.set(directory, { selector, directory });\n }\n return [...packages.values()];\n}\n\n/**\n * SHA-256 over every mode's composition fingerprint, or why the root cannot be composed. The\n * pointer records it per root, so a server can tell a root whose selection changed since the\n * last sync by computing it again with the environment sync ran under.\n */\nexport function rootSelectionFingerprint(root: string, homeDirectory: string, environment: NodeJS.ProcessEnv): string {\n try {\n const fingerprints = rootCompositions({ root, homeDirectory, environment, tolerateUnsynced: true }).map(\n (composition) => composition.fingerprint,\n );\n return crypto.createHash('sha256').update(fingerprints.join('\\n')).digest('hex');\n } catch (error) {\n return `unresolved:${errorMessage(error)}`;\n }\n}\n\n/** The po package version this process runs from, or why it cannot be read. */\nfunction poPackageVersion(): string {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(poPackageRoot(), PACKAGE_MANIFEST), 'utf8')) as {\n version?: unknown;\n };\n return typeof manifest.version === 'string' ? manifest.version : 'unversioned';\n } catch (error) {\n return `unreadable:${errorMessage(error)}`;\n }\n}\n\n/**\n * The Po install a sync or a server runs from: its po package version and the po-web host\n * version it builds with. Sync records it in the pointer; a server whose identity differs\n * refuses that pointer's builds and syncs with its own install.\n */\nexport function syncHostIdentity(globalRoot: string, environment: NodeJS.ProcessEnv): string {\n const checkout =\n environment.PO_TEST === '1' && environment.PO_TEST_PACKAGES\n ? `+checkout@${crypto.createHash('sha256').update(environment.PO_TEST_PACKAGES).digest('hex')}`\n : '';\n return `po@${poPackageVersion()}+po-web@${webHostVersion(globalRoot, environment)}${checkout}`;\n}\n\n/** The fast-exit signature: catalogue hash, each root's selection and local build stamps, host identity. */\nfunction syncSignature(options: SyncOptions, roots: readonly string[], catalogueHash: string): string {\n const { homeDirectory, environment } = options;\n const value = {\n format: SIGNATURE_FORMAT,\n catalogueHash,\n roots: roots.map((root) => ({\n root,\n selection: rootSelectionFingerprint(root, homeDirectory, environment),\n local: localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config, environment).map(\n ({ directory }) => [directory, localBuildStamp(directory)],\n ),\n })),\n host: syncHostIdentity(options.globalRoot, environment),\n };\n return crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');\n}\n\n/** `current.json`: the last successful report minus its transient fields, plus what fast exit needs. */\nexport interface SyncPointer {\n generation: number;\n signature: string;\n catalogueHash: string;\n webKey: string | null;\n nativeKey: string | null;\n mcpKeys: string[];\n roots: SyncRootReport[];\n commands: string[];\n /** Each root's `rootSelectionFingerprint` at the time of the sync; absent in older pointers. */\n fingerprints?: Record<string, string>;\n /** The `syncHostIdentity` of the install that wrote the pointer; absent in older pointers. */\n host?: string;\n}\n\nexport function syncPointerPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, POINTER_FILE);\n}\n\nexport function syncLastAttemptPath(globalRoot: string): string {\n return path.join(globalRoot, BUILD_DIRECTORY, LAST_ATTEMPT_FILE);\n}\n\n/** The current pointer, or undefined when sync has not written one yet or it is unreadable. */\nexport function readSyncPointer(globalRoot: string): SyncPointer | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(syncPointerPath(globalRoot), 'utf8')) as Partial<SyncPointer>;\n return typeof parsed.signature === 'string' && typeof parsed.generation === 'number'\n ? (parsed as SyncPointer)\n : undefined;\n } catch {\n // Missing or unreadable: there is nothing to fast-exit against.\n return undefined;\n }\n}\n\n/** The files whose presence proves each artifact a pointer names is complete. */\nfunction pointerArtifacts(\n globalRoot: string,\n pointer: Pick<SyncPointer, 'webKey' | 'nativeKey' | 'mcpKeys'>,\n): string[] {\n const build = path.join(globalRoot, BUILD_DIRECTORY);\n return [\n ...(pointer.webKey === null ? [] : [path.join(build, WEB_LABEL, pointer.webKey, BUILD_RECORD)]),\n ...(pointer.nativeKey === null ? [] : [path.join(build, NATIVE_LABEL, pointer.nativeKey, BUILD_RECORD)]),\n ...pointer.mcpKeys.map((key) => path.join(build, 'mcp-app', key, MCP_APP_FILE)),\n ];\n}\n\n/** The report of the last sync attempt, successful or not, or undefined when there is none. */\nexport function readSyncLastAttempt(globalRoot: string): SyncReport | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(syncLastAttemptPath(globalRoot), 'utf8')) as Partial<SyncReport>;\n return Array.isArray(parsed.roots) ? (parsed as SyncReport) : undefined;\n } catch {\n // Missing or unreadable: no attempt to report reasons from.\n return undefined;\n }\n}\n\nfunction writeJsonFile(file: string, value: unknown): void {\n writeFileAtomic(file, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\ntype LockAttempt = { acquired: true; token: string } | { acquired: false; holder: SyncLockHolder | undefined };\n\nfunction readHolder(lockPath: string): SyncLockHolder | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as Partial<SyncLockHolder>;\n return typeof parsed.pid === 'number' ? (parsed as SyncLockHolder) : undefined;\n } catch {\n // Missing or half-written: the caller decides from the file's age.\n return undefined;\n }\n}\n\nfunction lockIsStale(lockPath: string, deps: SyncDeps): boolean {\n const holder = readHolder(lockPath);\n if (holder !== undefined) return !deps.isAlive(holder.pid);\n try {\n return deps.now().getTime() - fs.statSync(lockPath).mtimeMs > UNREADABLE_LOCK_GRACE_MS;\n } catch {\n // Gone between the failed create and this check: free to retry.\n return true;\n }\n}\n\nfunction tryCreateLock(lockPath: string, holder: SyncLockHolder): boolean {\n try {\n const descriptor = fs.openSync(lockPath, 'wx');\n try {\n fs.writeSync(descriptor, JSON.stringify(holder));\n } finally {\n fs.closeSync(descriptor);\n }\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === ALREADY_EXISTS) return false;\n throw error;\n }\n}\n\n/**\n * Takes over a lock judged stale. Removing it by path could remove a lock another taker created\n * since the check, so it is first renamed to a name only this process knows and checked again\n * there: a lock that turns out live is linked back (link never replaces), a stale one is dropped\n * before the exclusive create, which at most one taker wins.\n */\nfunction takeOverStaleLock(lockPath: string, holder: SyncLockHolder, deps: SyncDeps): boolean {\n const moved = `${lockPath}.${String(deps.pid)}.${crypto.randomUUID()}.stale`;\n try {\n fs.renameSync(lockPath, moved);\n } catch (error) {\n // Already gone: released, or moved by another taker; the create decides.\n if ((error as NodeJS.ErrnoException).code === NO_SUCH_FILE) return tryCreateLock(lockPath, holder);\n throw error;\n }\n try {\n if (!lockIsStale(moved, deps)) {\n try {\n fs.linkSync(moved, lockPath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== ALREADY_EXISTS) throw error;\n }\n return false;\n }\n } finally {\n fs.rmSync(moved, { force: true });\n }\n return tryCreateLock(lockPath, holder);\n}\n\n/** Creates the lock, taking over one whose holder is dead. Never inspects other processes beyond `kill(pid, 0)`. */\nfunction acquireLock(lockPath: string, origin: SyncOrigin, deps: SyncDeps): LockAttempt {\n fs.mkdirSync(path.dirname(lockPath), { recursive: true });\n const token = crypto.randomUUID();\n const holder: SyncLockHolder = { pid: deps.pid, token, startedAt: deps.now().toISOString(), origin };\n if (tryCreateLock(lockPath, holder)) return { acquired: true, token };\n // A racing taker that wins the takeover makes this attempt report busy, as it should.\n if (lockIsStale(lockPath, deps) && takeOverStaleLock(lockPath, holder, deps)) return { acquired: true, token };\n return { acquired: false, holder: readHolder(lockPath) };\n}\n\n/**\n * The lock as the holder with `token` sees it: undefined while the lock still names it, else\n * the busy report to stop with. A takeover that moved a live lock aside can let a third taker\n * create a new one, so a holder checks before each step whose overlap would do harm.\n */\nfunction lostLock(lockPath: string, token: string, deps: SyncDeps): SyncReport | undefined {\n const holder = readHolder(lockPath);\n if (holder?.token === token) return undefined;\n const by = holder === undefined ? 'another process' : `pid ${String(holder.pid)}`;\n return emptyReport('busy', [`po sync lost its lock to ${by}`], deps);\n}\n\nfunction releaseLock(lockPath: string, token: string): void {\n if (readHolder(lockPath)?.token === token) fs.rmSync(lockPath, { force: true });\n}\n\n/** Consumes the rerun marker; true when a background run asked for another pass. */\nfunction takeRerunRequest(rerunPath: string): boolean {\n if (!fs.existsSync(rerunPath)) return false;\n fs.rmSync(rerunPath, { force: true });\n return true;\n}\n\nfunction emptyReport(status: SyncStatus, errors: string[], deps: SyncDeps): SyncReport {\n return {\n status,\n generation: null,\n catalogueHash: null,\n webKey: null,\n mcpKeys: [],\n roots: [],\n commands: [],\n errors,\n finishedAt: deps.now().toISOString(),\n };\n}\n\n/** A step reporter over the event stream, mirroring the presenter's start/done/line. */\nfunction steps(emit: (event: SyncEvent) => void) {\n return {\n start(label: string, message: string): (summary: string) => void {\n emit({ type: 'start', label, message });\n return (summary) => emit({ type: 'done', label, summary });\n },\n line(label: string, message: string): void {\n emit({ type: 'line', label, message });\n },\n };\n}\n\ntype Steps = ReturnType<typeof steps>;\n\n/**\n * Drops old builds and unreferenced store content once installs and builds are done.\n * Never fatal: a failed prune leaves everything for the next explicit sync.\n */\nasync function pruneStep(progress: Steps, prune: SyncDeps['prune'], input: SyncPruneInput): Promise<void> {\n const done = progress.start(PRUNE_LABEL, 'dropping old builds and unused packages');\n try {\n const result = await prune(input);\n const runtime =\n 'skipped' in result.runtime\n ? result.runtime.skipped\n : `${(result.runtime.bytes / BYTES_PER_MEGABYTE).toFixed(1)} MB of packages released`;\n done(`removed ${pluralize(result.builds.length, 'old build')}, ${runtime}`);\n } catch (error) {\n done(`failed: ${errorMessage(error)}`);\n }\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs, in a root that has the emitter. */\nfunction emitFrontendHooks(root: string, progress: Steps, commands: string[]): void {\n const emitter = path.join(root, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n commands.push(`${process.execPath} ${emitter} --write`);\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: root, encoding: 'utf8' });\n if (result.status === 0) {\n progress.line(HOOKS_LABEL, `regenerated for Claude Code and Codex in '${root}'`);\n return;\n }\n progress.line(\n HOOKS_LABEL,\n `emit-hooks failed in '${root}' (${result.stderr?.trim() || `exit ${String(result.status)}`})`,\n );\n}\n\n/** A path with its symlinks resolved, or as given when it does not exist. */\nfunction realPath(file: string): string {\n try {\n return fs.realpathSync(file);\n } catch {\n // Missing: compare it as written.\n return path.resolve(file);\n }\n}\n\n/**\n * Whether the MCP app build the server will look for exists for every root. Its key hashes the\n * widget packages' built files, which the signature does not cover, so a rebuilt `dist/` moves\n * the key without moving the signature.\n */\nfunction mcpAppsBuilt(\n globalRoot: string,\n roots: readonly string[],\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n): boolean {\n for (const compositions of composeRoots(roots, homeDirectory, environment).values()) {\n if (compositions instanceof Error) continue;\n let key: string | undefined;\n try {\n key = mcpAppKey(mcpPluginCandidates(compositions));\n } catch {\n // A widget package that cannot be read: not built; the full pass reports why.\n return false;\n }\n if (key !== undefined && !fs.existsSync(path.join(globalRoot, BUILD_DIRECTORY, 'mcp-app', key, MCP_APP_FILE)))\n return false;\n }\n return true;\n}\n/**\n * One sync pass under the lock. A failed step ends the pass with a `failed` report and leaves\n * the pointer alone; a lock that no longer names this holder (`lost`) ends it as busy.\n */\nasync function syncOnce(\n options: SyncOptions,\n deps: SyncDeps,\n emit: (event: SyncEvent) => void,\n lost: () => SyncReport | undefined,\n): Promise<SyncReport> {\n const { homeDirectory, environment, globalRoot } = options;\n const progress = steps(emit);\n const commands: string[] = [];\n const roots = resolveRoots(options, progress);\n const catalogueHash = packageCatalogHash(environment);\n const previous = readSyncPointer(globalRoot);\n if (\n previous !== undefined &&\n // Unsynced selectors are retried only when a person runs sync: with an unchanged signature a\n // background retry repeats the same failure, while something outside the signature (a package\n // manager installed since) is what a manual `po sync` is for. The pointer keeps the reasons.\n (options.origin === 'background' || previous.roots.every((root) => root.unsynced.length === 0)) &&\n previous.signature === syncSignature(options, roots, catalogueHash) &&\n pointerArtifacts(globalRoot, previous).every((file) => fs.existsSync(file)) &&\n mcpAppsBuilt(globalRoot, roots, homeDirectory, environment)\n ) {\n progress.line(ROOTS_LABEL, `nothing changed since generation ${String(previous.generation)}`);\n return {\n ...emptyReport('noop', [], deps),\n generation: previous.generation,\n catalogueHash,\n webKey: previous.webKey,\n mcpKeys: previous.mcpKeys,\n roots: previous.roots,\n };\n }\n const unsynced = new Map<string, SyncRootReport['unsynced']>(roots.map((root) => [root, []]));\n const report = (status: 'ok' | 'failed', errors: string[], webKey: string | null, mcpKeys: string[]) => {\n const rootReports = roots.map((root): SyncRootReport => {\n const entries = unsynced.get(root) ?? [];\n return { root, status: status === 'ok' && entries.length > 0 ? 'partial' : status, unsynced: entries };\n });\n const partial = status === 'ok' && rootReports.some((root) => root.status === 'partial');\n return {\n ...emptyReport(partial ? 'partial' : status, errors, deps),\n generation: previous?.generation ?? null,\n catalogueHash,\n webKey,\n mcpKeys,\n roots: rootReports,\n commands,\n };\n };\n\n const lostBeforeInstall = lost();\n if (lostBeforeInstall !== undefined) return lostBeforeInstall;\n const packagesDone = progress.start(PACKAGES_LABEL, 'checking configured packages for updates');\n let packages: SyncResult['packages'];\n try {\n const configOf = (root: string) => loadMajorModesConfigLenient(root, homeDirectory, environment).config;\n const globalConfig = configOf(globalRoot);\n // One install covers the catalogue and every package some root does not get from its own\n // node_modules, including those the runtime already provides; the runtime manifest declares\n // exactly those, so pruning can drop anything no root needs. A name a root resolves from its\n // own node_modules stays out, so a workspace's local copy is never shadowed by, or blocked\n // on, a registry install.\n const specifiers = new Set(catalogInstallSpecifiers(environment));\n for (const root of roots) {\n const config = configOf(root);\n for (const specifier of runtimeLayerPackageSpecifiers(config, Object.keys(config.layers), root))\n specifiers.add(specifier);\n }\n packages = await deps.install({\n repoRoot: globalRoot,\n config: globalConfig,\n // Every declared layer, so a later mode switch finds its packages installed.\n layers: Object.keys(globalConfig.layers),\n environment,\n // A background run never bumps versions; only a person running sync asks for updates.\n refresh: options.origin === 'cli',\n specifiers: [...specifiers],\n exactManifest: true,\n onProgress: (message) => progress.line(PACKAGES_LABEL, message),\n });\n } catch (error) {\n packagesDone('failed');\n return report('failed', [`Installing packages failed: ${errorMessage(error)}`], null, []);\n }\n packagesDone(packageSummary(packages));\n\n const localByRoot = new Map(\n roots.map((root) => [\n root,\n localPackages(loadMajorModesConfigLenient(root, homeDirectory, environment).config, environment),\n ]),\n );\n const localDirectories = [...localByRoot.values()].flat().map(({ directory }) => directory);\n if (localDirectories.length > 0) {\n const localDone = progress.start(LOCAL_LABEL, 'building local packages');\n const outcomes = await buildLocalPackages({\n globalRoot,\n environment,\n directories: localDirectories,\n onCommand: (command) => commands.push(command),\n onNotice: (message) => progress.line(LOCAL_LABEL, message),\n });\n let failed = 0;\n for (const [root, packagesOfRoot] of localByRoot)\n for (const { selector, directory } of packagesOfRoot) {\n const outcome = outcomes.get(directory);\n if (outcome?.status !== 'failed') continue;\n unsynced.get(root)?.push({\n selector,\n reason: outcome.reason,\n ...(outcome.stderr === undefined ? {} : { stderr: outcome.stderr }),\n });\n }\n for (const outcome of outcomes.values()) if (outcome.status === 'failed') failed += 1;\n localDone(failed === 0 ? `${pluralize(outcomes.size, 'package')} ready` : `${String(failed)} failed`);\n }\n\n const composed = composeRoots(roots, homeDirectory, environment);\n // What a root's selection leaves out still syncs the rest of the root; each is reported once.\n for (const [root, compositions] of composed) {\n if (compositions instanceof Error) continue;\n const entries = unsynced.get(root) ?? [];\n for (const skipped of skippedSelections(compositions))\n if (!entries.some((entry) => entry.selector === skipped.selector)) entries.push(skipped);\n }\n\n const webDone = progress.start(WEB_LABEL, 'ensuring the global web build');\n let rootPackageRoots = packageRootsByRoot(composed, (message) => progress.line(WEB_LABEL, message));\n const buildWeb = (packageRoots: Map<string, string[]>) =>\n deps.buildWeb({\n poHome: globalRoot,\n pluginRoots: webPluginRootsOf(globalRoot, packageRoots, environment),\n environment,\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n let built = await buildWeb(rootPackageRoots);\n if (built.status === 'failed') {\n // A local copy is the likeliest cause; one retry without them keeps the rest serving.\n const local = new Set(localDirectories.map(realPath));\n const withoutLocal = new Map(\n [...rootPackageRoots].map(([root, packageRoots]) => [\n root,\n packageRoots.filter((packageRoot) => !local.has(realPath(packageRoot))),\n ]),\n );\n const leftOut = [...rootPackageRoots].some(\n ([root, packageRoots]) => withoutLocal.get(root)?.length !== packageRoots.length,\n );\n const retried = leftOut ? await buildWeb(withoutLocal) : built;\n if (retried.status === 'failed') {\n webDone('failed');\n const retryReason = leftOut ? `; without local copies: ${retried.reason}` : '';\n return report('failed', [`The global web build failed: ${built.reason}${retryReason}`], null, []);\n }\n for (const [root, packagesOfRoot] of localByRoot) {\n const included = new Set((rootPackageRoots.get(root) ?? []).map(realPath));\n const entries = unsynced.get(root) ?? [];\n for (const { selector, directory } of packagesOfRoot)\n if (included.has(realPath(directory)) && !entries.some((entry) => entry.selector === selector))\n entries.push({\n selector,\n reason: `left out of the web build, which failed with it: ${built.reason}`,\n });\n }\n progress.line(WEB_LABEL, `built without local copies after: ${built.reason}`);\n rootPackageRoots = withoutLocal;\n built = retried;\n }\n const pluginRoots = webPluginRootsOf(globalRoot, rootPackageRoots, environment);\n const web: SyncResult['web'] = built.status === 'skipped' ? built : { status: built.status, key: built.build.key };\n webDone(web.status === 'skipped' ? web.reason : `${web.key} (${web.status})`);\n const webKey = web.status === 'skipped' ? null : web.key;\n\n const nativeDone = progress.start(NATIVE_LABEL, 'ensuring the global native build');\n const nativeBuilt = await deps.buildNative({\n poHome: globalRoot,\n pluginRoots,\n onNotice: (message) => progress.line(NATIVE_LABEL, message),\n });\n if (nativeBuilt.status === 'failed') {\n nativeDone('failed');\n return report('failed', [`The global native build failed: ${nativeBuilt.reason}`], webKey, []);\n }\n const native: NonNullable<SyncResult['native']> =\n nativeBuilt.status === 'skipped' ? nativeBuilt : { status: nativeBuilt.status, key: nativeBuilt.build.key };\n nativeDone(native.status === 'skipped' ? native.reason : `${native.key} (${native.status})`);\n const nativeKey = native.status === 'skipped' ? null : native.key;\n\n // One MCP app build per root; its key is a content hash, so roots with one widget set share it.\n const mcpDone = progress.start(MCP_LABEL, 'ensuring the MCP app build of each root');\n const mcpKeys: string[] = [];\n for (const [root, compositions] of composed) {\n if (compositions instanceof Error) {\n progress.line(MCP_LABEL, `'${root}' is left out of the MCP app build: ${compositions.message}`);\n continue;\n }\n try {\n const app = await deps.bundleMcpApp({ poHome: globalRoot, candidates: mcpPluginCandidates(compositions) });\n if (app !== undefined && !mcpKeys.includes(app.key)) mcpKeys.push(app.key);\n } catch (error) {\n unsynced.get(root)?.push({ selector: MCP_LABEL, reason: `the MCP app build failed: ${errorMessage(error)}` });\n }\n }\n mcpDone(mcpKeys.length === 0 ? 'no MCP UI widgets' : mcpKeys.join(', '));\n const mcpApp = mcpKeys.length === 0 ? undefined : mcpKeys.join(', ');\n\n // The pointer names only artifacts that exist; a missing one keeps the previous pointer.\n const missing = pointerArtifacts(globalRoot, { webKey, nativeKey, mcpKeys }).filter((file) => !fs.existsSync(file));\n if (missing.length > 0)\n return report('failed', [`Build artifacts are missing: ${missing.join(', ')}`], webKey, mcpKeys);\n // A root that names two copies of one plugin serves only one of them under its plugin id.\n if (built.status !== 'skipped')\n for (const [root, packageRoots] of rootPackageRoots)\n for (const [pluginId, slots] of duplicatePluginCopies(built.build, packageRoots))\n unsynced.get(root)?.push({\n selector: pluginId,\n reason: `two copies of plugin '${pluginId}' are selected (${slots.join(', ')})`,\n });\n const succeeded = report('ok', [], webKey, mcpKeys);\n const generation = (previous?.generation ?? 0) + 1;\n const pointer: SyncPointer = {\n generation,\n signature: syncSignature(options, roots, catalogueHash),\n catalogueHash,\n webKey,\n nativeKey,\n mcpKeys,\n roots: succeeded.roots,\n commands,\n fingerprints: Object.fromEntries(\n roots.map((root) => [root, rootSelectionFingerprint(root, homeDirectory, environment)]),\n ),\n host: syncHostIdentity(globalRoot, environment),\n };\n const lostBeforePointer = lost();\n if (lostBeforePointer !== undefined) return lostBeforePointer;\n writeJsonFile(syncPointerPath(globalRoot), pointer);\n\n await pruneStep(progress, deps.prune, {\n homeDirectory,\n environment,\n buildsRoot: globalRoot,\n ...(webKey === null ? {} : { webKey }),\n ...(nativeKey === null ? {} : { nativeKey }),\n ...(mcpKeys.length === 0 ? {} : { mcpAppKeys: mcpKeys }),\n });\n // The checkout itself too: a linked worktree registers under its main workspace root.\n for (const root of new Set([...roots, ...(options.repoRoot === undefined ? [] : [realPath(options.repoRoot)])]))\n emitFrontendHooks(root, progress, commands);\n emit({ type: 'result', result: { packages, web, native, ...(mcpApp === undefined ? {} : { mcpApp }) } });\n return { ...succeeded, generation };\n}\n\n/**\n * One pass whose report lands in `last-attempt.json`, so a failed background run leaves its\n * reasons. A pass that lost the lock leaves the file to the holder that took it.\n */\nasync function attempt(\n options: SyncOptions,\n deps: SyncDeps,\n emit: (event: SyncEvent) => void,\n lost: () => SyncReport | undefined,\n): Promise<SyncReport> {\n let report: SyncReport;\n try {\n report = await syncOnce(options, deps, emit, lost);\n } catch (error) {\n report = emptyReport('failed', [`po sync failed: ${errorMessage(error)}`], deps);\n }\n if (report.status === 'busy') return report;\n try {\n writeJsonFile(syncLastAttemptPath(options.globalRoot), report);\n } catch (error) {\n report = {\n ...report,\n errors: [...report.errors, `last-attempt.json could not be written: ${errorMessage(error)}`],\n };\n }\n return report;\n}\n\n/**\n * Runs sync under the Po home lock and reports the outcome.\n *\n * Busy when another live sync holds the lock; a background caller leaves a rerun marker\n * first. The holder repeats its pass while a marker is present. A marker that lands after\n * its last check but before the release is caught after the release: the holder takes the\n * lock again and runs once more, or leaves the marker to whoever took the lock first.\n */\nexport async function runSync(options: SyncOptions, overrides: Partial<SyncDeps> = {}): Promise<SyncReport> {\n const deps: SyncDeps = { ...DEFAULT_DEPS, ...overrides };\n const emit = options.onEvent ?? ((): void => {});\n const lockPath = syncLockPath(options.globalRoot);\n const rerunPath = syncRerunPath(options.globalRoot);\n let lock = acquireLock(lockPath, options.origin, deps);\n if (!lock.acquired) {\n if (options.origin === 'background') fs.writeFileSync(rerunPath, deps.now().toISOString());\n const holder = lock.holder === undefined ? 'another process' : `pid ${String(lock.holder.pid)}`;\n return emptyReport('busy', [`po sync is already running (${holder})`], deps);\n }\n for (;;) {\n const { token } = lock;\n const lost = () => lostLock(lockPath, token, deps);\n let report: SyncReport;\n try {\n // A marker left before this pass started is satisfied by this pass.\n takeRerunRequest(rerunPath);\n report = await attempt(options, deps, emit, lost);\n // A pass that lost the lock leaves the rerun marker to the holder that took it.\n while (report.status !== 'busy' && takeRerunRequest(rerunPath)) report = await attempt(options, deps, emit, lost);\n } finally {\n releaseLock(lockPath, token);\n }\n if (report.status === 'busy' || !fs.existsSync(rerunPath)) return report;\n lock = acquireLock(lockPath, options.origin, deps);\n if (!lock.acquired) return report;\n }\n}\n"],"mappings":"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"}
@@ -7,6 +7,7 @@ let _agimon_ai_po_core_runtimeJson = require("@agimon-ai/po-core/runtimeJson");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_crypto = require("node:crypto");
9
9
  node_crypto = require_runtime.__toESM(node_crypto, 1);
10
+ let _agimon_ai_po_core_testMode = require("@agimon-ai/po-core/testMode");
10
11
  //#region src/composition/syncService/localBuilds.ts
11
12
  /**
12
13
  * Builds the packages sync roots name by local path, so the web, native and MCP builds read
@@ -138,9 +139,13 @@ function loginShellPath(environment) {
138
139
  });
139
140
  return result.status === 0 && result.stdout.trim() !== "" ? result.stdout.trim() : void 0;
140
141
  }
141
- /** The build environment: the caller's minus every `PO_*` and harness variable. */
142
+ /** Strip harness state, then restore only validated test isolation for nested Po commands. */
142
143
  function buildEnvironment(environment) {
143
- return Object.fromEntries(Object.entries(environment).filter(([name]) => !HARNESS_VARIABLES.has(name) && !HARNESS_PREFIXES.some((prefix) => name.startsWith(prefix))));
144
+ const testContext = (0, _agimon_ai_po_core_testMode.getValidatedTestContext)(environment);
145
+ return {
146
+ ...Object.fromEntries(Object.entries(environment).filter(([name]) => !HARNESS_VARIABLES.has(name) && !HARNESS_PREFIXES.some((prefix) => name.startsWith(prefix)))),
147
+ ...testContext?.env
148
+ };
144
149
  }
145
150
  function entryPaths(value) {
146
151
  if (typeof value === "string") return [value];
@@ -1 +1 @@
1
- {"version":3,"file":"localBuilds.cjs","names":["path","fs","crypto","spawnSync","spawn"],"sources":["../../../../src/composition/syncService/localBuilds.ts"],"sourcesContent":["import { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { writeFileAtomic } from '@agimon-ai/po-core/runtimeJson';\n\n/**\n * Builds the packages sync roots name by local path, so the web, native and MCP builds read\n * current output. Each package builds at most once per sync, in dependency order within the\n * local set, with the package manager its nearest lockfile names. Install never runs.\n */\n\nconst LOCAL_SOURCE_DIRECTORY = 'src';\nconst PACKAGE_MANIFEST = 'package.json';\nconst NODE_MODULES = 'node_modules';\nconst LOCAL_BUILDS_FILE = path.join('build', 'local-builds.json');\nconst BUILD_SCRIPT = 'build';\nconst STDERR_TAIL_LINES = 20;\n/** Bytes of stderr kept per build while it runs; older output is dropped as it arrives. */\nconst STDERR_TAIL_BYTES = 64 * 1024;\n/**\n * A local build that runs longer than this is stopped and reported as failed. Sync holds its\n * lock while a build runs, so a hung build must not hold it forever.\n */\nexport const LOCAL_BUILD_TIMEOUT_MS = 10 * 60 * 1000;\n/** How long a build's stderr may stay open after it exits, held by a process it left running. */\nconst STDERR_CLOSE_GRACE_MS = 1000;\nconst MILLISECONDS_PER_SECOND = 1000;\nconst LOGIN_SHELL_TIMEOUT_MS = 10_000;\nconst DEFAULT_PACKAGE_MANAGER = 'npm';\nconst NO_SUCH_PROCESS = 'ESRCH';\n/** Checked nearest-first in each folder; the first lockfile found names the package manager. */\nconst LOCKFILES: readonly (readonly [string, string])[] = [\n ['pnpm-lock.yaml', 'pnpm'],\n ['bun.lock', 'bun'],\n ['bun.lockb', 'bun'],\n ['yarn.lock', 'yarn'],\n ['package-lock.json', 'npm'],\n];\n/** Variables a Po session or another harness exports; a package build must not see them. */\nconst HARNESS_PREFIXES = ['PO_', 'PI_', 'CLAUDE_', 'CODEX_'];\nconst HARNESS_VARIABLES = new Set(['CLAUDECODE', 'MCP_UI_VIEWER', 'npm_config_user_agent']);\nconst DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];\n\nexport interface LocalPackage {\n /** The selector as a root's configuration names it. */\n selector: string;\n directory: string;\n}\n\nexport type LocalBuildOutcome =\n | { status: 'built' | 'cached' }\n | { status: 'failed'; reason: string; stderr?: string[] };\n\nexport interface LocalBuildInput {\n globalRoot: string;\n environment: NodeJS.ProcessEnv;\n directories: readonly string[];\n /** Receives each command line as it starts. */\n onCommand: (command: string) => void;\n onNotice: (message: string) => void;\n /** Overrides `LOCAL_BUILD_TIMEOUT_MS`. */\n timeoutMs?: number;\n}\n\ninterface Manifest {\n name?: string;\n main?: unknown;\n module?: unknown;\n exports?: unknown;\n scripts?: Record<string, unknown>;\n [field: string]: unknown;\n}\n\nfunction sourceFiles(directory: string): string[] {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(directory, { withFileTypes: true });\n } catch {\n // A package without a source folder has only its manifest to stamp.\n return [];\n }\n return entries.flatMap((entry) => {\n const full = path.join(directory, entry.name);\n if (entry.isDirectory()) return entry.name === NODE_MODULES ? [] : sourceFiles(full);\n return entry.isFile() ? [full] : [];\n });\n}\n\n/**\n * The build stamp of a local package: an mtime and size signature over its `package.json`\n * and every file under `src/`.\n * ponytail: it misses edits to dependencies outside the package; a content hash is the upgrade.\n */\nexport function localBuildStamp(packageDirectory: string): string {\n const files = [\n path.join(packageDirectory, PACKAGE_MANIFEST),\n ...sourceFiles(path.join(packageDirectory, LOCAL_SOURCE_DIRECTORY)),\n ];\n const hash = crypto.createHash('sha256');\n for (const file of files.sort()) {\n const relative = path.relative(packageDirectory, file);\n try {\n const stat = fs.statSync(file);\n hash.update(`${relative}\\0${String(stat.size)}\\0${String(stat.mtimeMs)}\\n`);\n } catch {\n // Recorded as missing, so the stamp moves when the file appears.\n hash.update(`${relative}\\0missing\\n`);\n }\n }\n return hash.digest('hex');\n}\n\nexport function localBuildsPath(globalRoot: string): string {\n return path.join(globalRoot, LOCAL_BUILDS_FILE);\n}\n\nfunction readStamps(globalRoot: string): Record<string, string> {\n try {\n const parsed = JSON.parse(fs.readFileSync(localBuildsPath(globalRoot), 'utf8')) as unknown;\n return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, string>) : {};\n } catch {\n // Missing or unreadable: every package builds once.\n return {};\n }\n}\n\nfunction readManifest(directory: string): Manifest | undefined {\n try {\n return JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as Manifest;\n } catch {\n return undefined;\n }\n}\n\n/** The package manager the nearest lockfile names, walking up from the package. */\nexport function packageManagerFor(directory: string): string {\n let current = path.resolve(directory);\n for (;;) {\n for (const [lockfile, manager] of LOCKFILES) if (fs.existsSync(path.join(current, lockfile))) return manager;\n const parent = path.dirname(current);\n if (parent === current) return DEFAULT_PACKAGE_MANAGER;\n current = parent;\n }\n}\n\nfunction findOnPath(binary: string, searchPath: string | undefined): string | undefined {\n for (const directory of (searchPath ?? '').split(path.delimiter).filter(Boolean)) {\n const candidate = path.join(directory, binary);\n try {\n fs.accessSync(candidate, fs.constants.X_OK);\n if (fs.statSync(candidate).isFile()) return candidate;\n } catch {\n // Not in this folder.\n }\n }\n return undefined;\n}\n\n/**\n * The login shell's PATH. Desktop apps launched from Finder get a minimal PATH (inferred,\n * not checked here), so a package manager installed through a shell profile is missing.\n */\nfunction loginShellPath(environment: NodeJS.ProcessEnv): string | undefined {\n const shell = environment.SHELL ?? '/bin/zsh';\n const result = spawnSync(shell, ['-ilc', 'printf %s \"$PATH\"'], {\n encoding: 'utf8',\n timeout: LOGIN_SHELL_TIMEOUT_MS,\n });\n return result.status === 0 && result.stdout.trim() !== '' ? result.stdout.trim() : undefined;\n}\n\n/** The build environment: the caller's minus every `PO_*` and harness variable. */\nexport function buildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n return Object.fromEntries(\n Object.entries(environment).filter(\n ([name]) => !HARNESS_VARIABLES.has(name) && !HARNESS_PREFIXES.some((prefix) => name.startsWith(prefix)),\n ),\n );\n}\n\nfunction entryPaths(value: unknown): string[] {\n if (typeof value === 'string') return [value];\n if (Array.isArray(value)) return value.flatMap(entryPaths);\n if (typeof value === 'object' && value !== null) return Object.values(value).flatMap(entryPaths);\n return [];\n}\n\n/** Whether every file `main`, `module` and `exports` name exists; false when they name none. */\nfunction builtEntriesPresent(directory: string, manifest: Manifest): boolean {\n const entries = [manifest.main, manifest.module, manifest.exports]\n .flatMap(entryPaths)\n .filter((entry) => !entry.includes('*'));\n return entries.length > 0 && entries.every((entry) => fs.existsSync(path.resolve(directory, entry)));\n}\n\n/** The directories ordered so each local package builds after the local packages it depends on. */\nfunction dependencyOrder(directories: readonly string[], manifests: Map<string, Manifest | undefined>): string[] {\n const byName = new Map<string, string>();\n for (const directory of directories) {\n const name = manifests.get(directory)?.name;\n if (name !== undefined) byName.set(name, directory);\n }\n const ordered: string[] = [];\n const visiting = new Set<string>();\n const visit = (directory: string): void => {\n if (ordered.includes(directory) || visiting.has(directory)) return;\n visiting.add(directory);\n const manifest = manifests.get(directory);\n for (const field of DEPENDENCY_FIELDS) {\n const dependencies = manifest?.[field];\n if (typeof dependencies !== 'object' || dependencies === null) continue;\n for (const name of Object.keys(dependencies)) {\n const dependency = byName.get(name);\n if (dependency !== undefined) visit(dependency);\n }\n }\n visiting.delete(directory);\n ordered.push(directory);\n };\n for (const directory of directories) visit(directory);\n return ordered;\n}\n\nfunction stderrTail(stderr: string): string[] {\n return stderr.trimEnd().split('\\n').filter(Boolean).slice(-STDERR_TAIL_LINES);\n}\n\ninterface BuildRun {\n status: number | null;\n signal: NodeJS.Signals | null;\n error?: Error;\n timedOut: boolean;\n /** It exited while a process it started still held stderr open; that process was stopped. */\n leftRunning: boolean;\n /** The last `STDERR_TAIL_BYTES` of stderr. */\n stderr: string;\n}\n\n/** Signals that end this process; a build in its own process group would not receive them. */\nconst FORWARDED_SIGNALS: readonly NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGHUP'];\nconst SEPARATE_PROCESS_GROUPS = process.platform !== 'win32';\n\n/**\n * Kills the build and everything it started. The build leads its own process group, so the\n * package manager's tsc or vite children die with it.\n * ponytail: Windows has no process groups here, so only the package manager is killed there;\n * `taskkill /T /F` is the upgrade if Windows builds must stop their children too.\n */\nfunction killBuild(child: ChildProcess): void {\n if (!SEPARATE_PROCESS_GROUPS || child.pid === undefined) {\n child.kill('SIGKILL');\n return;\n }\n try {\n process.kill(-child.pid, 'SIGKILL');\n } catch (error) {\n // ESRCH: every process of the group has exited already, which is the goal. Anything else\n // (EPERM) falls back to the package manager alone rather than crash inside a timer.\n if ((error as NodeJS.ErrnoException).code !== NO_SUCH_PROCESS) child.kill('SIGKILL');\n }\n}\n\n/**\n * Runs one build script without buffering its output: stdout is discarded and only the stderr\n * tail is kept. A build still running after `timeoutMs` is killed with every process it started\n * and reported as timed out. A build that exits while something it started still holds stderr\n * open has that process group killed when the grace ends, so nothing writes after it returns.\n */\nfunction runBuildScript(\n binary: string,\n directory: string,\n environment: NodeJS.ProcessEnv,\n timeoutMs: number,\n): Promise<BuildRun> {\n return new Promise((resolve) => {\n let tail = Buffer.alloc(0);\n let timedOut = false;\n let leftRunning = false;\n let settled = false;\n let grace: NodeJS.Timeout | undefined;\n // Its own process group, so a kill reaches its children. Still awaited: never unref'd.\n const child = spawn(binary, ['run', BUILD_SCRIPT], {\n cwd: directory,\n env: environment,\n stdio: ['ignore', 'ignore', 'pipe'],\n detached: SEPARATE_PROCESS_GROUPS,\n });\n // A detached build no longer gets the terminal's Ctrl+C, so this process passes it on.\n const forward = (signal: NodeJS.Signals): void => {\n killBuild(child);\n if (process.listenerCount(signal) > 1) return;\n // Only this handler was listening: end as the signal would have without it.\n process.removeListener(signal, forward);\n process.kill(process.pid, signal);\n };\n if (SEPARATE_PROCESS_GROUPS) for (const signal of FORWARDED_SIGNALS) process.on(signal, forward);\n const finish = (result: Pick<BuildRun, 'status' | 'signal' | 'error'>): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearTimeout(grace);\n for (const signal of FORWARDED_SIGNALS) process.removeListener(signal, forward);\n child.stderr?.destroy();\n resolve({ ...result, timedOut, leftRunning, stderr: tail.toString('utf8') });\n };\n const timer = setTimeout(() => {\n timedOut = true;\n killBuild(child);\n }, timeoutMs);\n child.stderr?.on('data', (chunk: Buffer) => {\n tail = Buffer.concat([tail, chunk]);\n if (tail.length > STDERR_TAIL_BYTES) tail = tail.subarray(tail.length - STDERR_TAIL_BYTES);\n });\n child.on('error', (error) => finish({ status: null, signal: null, error }));\n child.on('close', (status, signal) => finish({ status, signal }));\n // A process the build left running can hold stderr open, so 'close' may never come.\n child.on('exit', (status, signal) => {\n grace = setTimeout(\n () => {\n if (!timedOut) leftRunning = true;\n killBuild(child);\n finish({ status, signal });\n },\n timedOut ? 0 : STDERR_CLOSE_GRACE_MS,\n );\n });\n });\n}\n\n/**\n * Builds every local package that changed since its last successful build, and returns each\n * directory's outcome. A failure never stops the others.\n */\nexport async function buildLocalPackages(input: LocalBuildInput): Promise<Map<string, LocalBuildOutcome>> {\n const { globalRoot, environment, onCommand, onNotice } = input;\n const timeoutMs = input.timeoutMs ?? LOCAL_BUILD_TIMEOUT_MS;\n const directories = [...new Set(input.directories)];\n const manifests = new Map(directories.map((directory) => [directory, readManifest(directory)]));\n const stamps = readStamps(globalRoot);\n const outcomes = new Map<string, LocalBuildOutcome>();\n const childEnvironment = buildEnvironment(environment);\n let searchPath = environment.PATH;\n let loginPathTried = false;\n const resolveBinary = (manager: string): string | undefined => {\n const found = findOnPath(manager, searchPath);\n if (found !== undefined || loginPathTried || process.platform !== 'darwin') return found;\n loginPathTried = true;\n const loginPath = loginShellPath(environment);\n if (loginPath === undefined) return undefined;\n searchPath = loginPath;\n childEnvironment.PATH = loginPath;\n return findOnPath(manager, searchPath);\n };\n\n for (const directory of dependencyOrder(directories, manifests)) {\n const manifest = manifests.get(directory);\n if (manifest === undefined) {\n outcomes.set(directory, { status: 'failed', reason: `'${directory}' has no readable package.json` });\n continue;\n }\n const stamp = localBuildStamp(directory);\n // ponytail: an unchanged stamp skips the build even when a dependency outside the\n // package changed; a content hash over the dependency closure is the upgrade.\n if (stamps[directory] === stamp) {\n outcomes.set(directory, { status: 'cached' });\n continue;\n }\n if (typeof manifest.scripts?.[BUILD_SCRIPT] !== 'string') {\n outcomes.set(\n directory,\n builtEntriesPresent(directory, manifest)\n ? { status: 'cached' }\n : { status: 'failed', reason: 'it has no build script and no built entries' },\n );\n continue;\n }\n const manager = packageManagerFor(directory);\n const binary = resolveBinary(manager);\n if (binary === undefined) {\n outcomes.set(directory, { status: 'failed', reason: `${manager} is not on PATH` });\n continue;\n }\n const command = `${binary} run ${BUILD_SCRIPT} (in ${directory})`;\n onCommand(command);\n onNotice(`building ${manifest.name ?? directory}`);\n const result = await runBuildScript(binary, directory, childEnvironment, timeoutMs);\n if (result.status === 0 && !result.timedOut && !result.leftRunning) {\n // The stamp after the build, as the sync pointer records it: a build that writes into\n // src/ or package.json must not look changed to the next sync.\n stamps[directory] = localBuildStamp(directory);\n writeFileAtomic(localBuildsPath(globalRoot), `${JSON.stringify(stamps, null, 2)}\\n`);\n outcomes.set(directory, { status: 'built' });\n continue;\n }\n const exit = result.timedOut\n ? `timed out after ${String(timeoutMs / MILLISECONDS_PER_SECOND)} s`\n : result.status === 0 && result.leftRunning\n ? 'it exited but left processes running, which were stopped'\n : (result.error?.message ?? `exit ${String(result.status ?? result.signal)}`);\n outcomes.set(directory, {\n status: 'failed',\n reason: `${manager} run ${BUILD_SCRIPT} failed (${exit})`,\n stderr: stderrTail(result.stderr),\n });\n }\n return outcomes;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"localBuilds.cjs","names":["path","fs","crypto","spawnSync","getValidatedTestContext","spawn"],"sources":["../../../../src/composition/syncService/localBuilds.ts"],"sourcesContent":["import { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { writeFileAtomic } from '@agimon-ai/po-core/runtimeJson';\nimport { getValidatedTestContext } from '@agimon-ai/po-core/testMode';\n\n/**\n * Builds the packages sync roots name by local path, so the web, native and MCP builds read\n * current output. Each package builds at most once per sync, in dependency order within the\n * local set, with the package manager its nearest lockfile names. Install never runs.\n */\n\nconst LOCAL_SOURCE_DIRECTORY = 'src';\nconst PACKAGE_MANIFEST = 'package.json';\nconst NODE_MODULES = 'node_modules';\nconst LOCAL_BUILDS_FILE = path.join('build', 'local-builds.json');\nconst BUILD_SCRIPT = 'build';\nconst STDERR_TAIL_LINES = 20;\n/** Bytes of stderr kept per build while it runs; older output is dropped as it arrives. */\nconst STDERR_TAIL_BYTES = 64 * 1024;\n/**\n * A local build that runs longer than this is stopped and reported as failed. Sync holds its\n * lock while a build runs, so a hung build must not hold it forever.\n */\nexport const LOCAL_BUILD_TIMEOUT_MS = 10 * 60 * 1000;\n/** How long a build's stderr may stay open after it exits, held by a process it left running. */\nconst STDERR_CLOSE_GRACE_MS = 1000;\nconst MILLISECONDS_PER_SECOND = 1000;\nconst LOGIN_SHELL_TIMEOUT_MS = 10_000;\nconst DEFAULT_PACKAGE_MANAGER = 'npm';\nconst NO_SUCH_PROCESS = 'ESRCH';\n/** Checked nearest-first in each folder; the first lockfile found names the package manager. */\nconst LOCKFILES: readonly (readonly [string, string])[] = [\n ['pnpm-lock.yaml', 'pnpm'],\n ['bun.lock', 'bun'],\n ['bun.lockb', 'bun'],\n ['yarn.lock', 'yarn'],\n ['package-lock.json', 'npm'],\n];\n/** Variables a Po session or another harness exports; a package build must not see them. */\nconst HARNESS_PREFIXES = ['PO_', 'PI_', 'CLAUDE_', 'CODEX_'];\nconst HARNESS_VARIABLES = new Set(['CLAUDECODE', 'MCP_UI_VIEWER', 'npm_config_user_agent']);\nconst DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];\n\nexport interface LocalPackage {\n /** The selector as a root's configuration names it. */\n selector: string;\n directory: string;\n}\n\nexport type LocalBuildOutcome =\n | { status: 'built' | 'cached' }\n | { status: 'failed'; reason: string; stderr?: string[] };\n\nexport interface LocalBuildInput {\n globalRoot: string;\n environment: NodeJS.ProcessEnv;\n directories: readonly string[];\n /** Receives each command line as it starts. */\n onCommand: (command: string) => void;\n onNotice: (message: string) => void;\n /** Overrides `LOCAL_BUILD_TIMEOUT_MS`. */\n timeoutMs?: number;\n}\n\ninterface Manifest {\n name?: string;\n main?: unknown;\n module?: unknown;\n exports?: unknown;\n scripts?: Record<string, unknown>;\n [field: string]: unknown;\n}\n\nfunction sourceFiles(directory: string): string[] {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(directory, { withFileTypes: true });\n } catch {\n // A package without a source folder has only its manifest to stamp.\n return [];\n }\n return entries.flatMap((entry) => {\n const full = path.join(directory, entry.name);\n if (entry.isDirectory()) return entry.name === NODE_MODULES ? [] : sourceFiles(full);\n return entry.isFile() ? [full] : [];\n });\n}\n\n/**\n * The build stamp of a local package: an mtime and size signature over its `package.json`\n * and every file under `src/`.\n * ponytail: it misses edits to dependencies outside the package; a content hash is the upgrade.\n */\nexport function localBuildStamp(packageDirectory: string): string {\n const files = [\n path.join(packageDirectory, PACKAGE_MANIFEST),\n ...sourceFiles(path.join(packageDirectory, LOCAL_SOURCE_DIRECTORY)),\n ];\n const hash = crypto.createHash('sha256');\n for (const file of files.sort()) {\n const relative = path.relative(packageDirectory, file);\n try {\n const stat = fs.statSync(file);\n hash.update(`${relative}\\0${String(stat.size)}\\0${String(stat.mtimeMs)}\\n`);\n } catch {\n // Recorded as missing, so the stamp moves when the file appears.\n hash.update(`${relative}\\0missing\\n`);\n }\n }\n return hash.digest('hex');\n}\n\nexport function localBuildsPath(globalRoot: string): string {\n return path.join(globalRoot, LOCAL_BUILDS_FILE);\n}\n\nfunction readStamps(globalRoot: string): Record<string, string> {\n try {\n const parsed = JSON.parse(fs.readFileSync(localBuildsPath(globalRoot), 'utf8')) as unknown;\n return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, string>) : {};\n } catch {\n // Missing or unreadable: every package builds once.\n return {};\n }\n}\n\nfunction readManifest(directory: string): Manifest | undefined {\n try {\n return JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as Manifest;\n } catch {\n return undefined;\n }\n}\n\n/** The package manager the nearest lockfile names, walking up from the package. */\nexport function packageManagerFor(directory: string): string {\n let current = path.resolve(directory);\n for (;;) {\n for (const [lockfile, manager] of LOCKFILES) if (fs.existsSync(path.join(current, lockfile))) return manager;\n const parent = path.dirname(current);\n if (parent === current) return DEFAULT_PACKAGE_MANAGER;\n current = parent;\n }\n}\n\nfunction findOnPath(binary: string, searchPath: string | undefined): string | undefined {\n for (const directory of (searchPath ?? '').split(path.delimiter).filter(Boolean)) {\n const candidate = path.join(directory, binary);\n try {\n fs.accessSync(candidate, fs.constants.X_OK);\n if (fs.statSync(candidate).isFile()) return candidate;\n } catch {\n // Not in this folder.\n }\n }\n return undefined;\n}\n\n/**\n * The login shell's PATH. Desktop apps launched from Finder get a minimal PATH (inferred,\n * not checked here), so a package manager installed through a shell profile is missing.\n */\nfunction loginShellPath(environment: NodeJS.ProcessEnv): string | undefined {\n const shell = environment.SHELL ?? '/bin/zsh';\n const result = spawnSync(shell, ['-ilc', 'printf %s \"$PATH\"'], {\n encoding: 'utf8',\n timeout: LOGIN_SHELL_TIMEOUT_MS,\n });\n return result.status === 0 && result.stdout.trim() !== '' ? result.stdout.trim() : undefined;\n}\n\n/** Strip harness state, then restore only validated test isolation for nested Po commands. */\nexport function buildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const testContext = getValidatedTestContext(environment);\n return {\n ...Object.fromEntries(\n Object.entries(environment).filter(\n ([name]) => !HARNESS_VARIABLES.has(name) && !HARNESS_PREFIXES.some((prefix) => name.startsWith(prefix)),\n ),\n ),\n ...testContext?.env,\n };\n}\n\nfunction entryPaths(value: unknown): string[] {\n if (typeof value === 'string') return [value];\n if (Array.isArray(value)) return value.flatMap(entryPaths);\n if (typeof value === 'object' && value !== null) return Object.values(value).flatMap(entryPaths);\n return [];\n}\n\n/** Whether every file `main`, `module` and `exports` name exists; false when they name none. */\nfunction builtEntriesPresent(directory: string, manifest: Manifest): boolean {\n const entries = [manifest.main, manifest.module, manifest.exports]\n .flatMap(entryPaths)\n .filter((entry) => !entry.includes('*'));\n return entries.length > 0 && entries.every((entry) => fs.existsSync(path.resolve(directory, entry)));\n}\n\n/** The directories ordered so each local package builds after the local packages it depends on. */\nfunction dependencyOrder(directories: readonly string[], manifests: Map<string, Manifest | undefined>): string[] {\n const byName = new Map<string, string>();\n for (const directory of directories) {\n const name = manifests.get(directory)?.name;\n if (name !== undefined) byName.set(name, directory);\n }\n const ordered: string[] = [];\n const visiting = new Set<string>();\n const visit = (directory: string): void => {\n if (ordered.includes(directory) || visiting.has(directory)) return;\n visiting.add(directory);\n const manifest = manifests.get(directory);\n for (const field of DEPENDENCY_FIELDS) {\n const dependencies = manifest?.[field];\n if (typeof dependencies !== 'object' || dependencies === null) continue;\n for (const name of Object.keys(dependencies)) {\n const dependency = byName.get(name);\n if (dependency !== undefined) visit(dependency);\n }\n }\n visiting.delete(directory);\n ordered.push(directory);\n };\n for (const directory of directories) visit(directory);\n return ordered;\n}\n\nfunction stderrTail(stderr: string): string[] {\n return stderr.trimEnd().split('\\n').filter(Boolean).slice(-STDERR_TAIL_LINES);\n}\n\ninterface BuildRun {\n status: number | null;\n signal: NodeJS.Signals | null;\n error?: Error;\n timedOut: boolean;\n /** It exited while a process it started still held stderr open; that process was stopped. */\n leftRunning: boolean;\n /** The last `STDERR_TAIL_BYTES` of stderr. */\n stderr: string;\n}\n\n/** Signals that end this process; a build in its own process group would not receive them. */\nconst FORWARDED_SIGNALS: readonly NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGHUP'];\nconst SEPARATE_PROCESS_GROUPS = process.platform !== 'win32';\n\n/**\n * Kills the build and everything it started. The build leads its own process group, so the\n * package manager's tsc or vite children die with it.\n * ponytail: Windows has no process groups here, so only the package manager is killed there;\n * `taskkill /T /F` is the upgrade if Windows builds must stop their children too.\n */\nfunction killBuild(child: ChildProcess): void {\n if (!SEPARATE_PROCESS_GROUPS || child.pid === undefined) {\n child.kill('SIGKILL');\n return;\n }\n try {\n process.kill(-child.pid, 'SIGKILL');\n } catch (error) {\n // ESRCH: every process of the group has exited already, which is the goal. Anything else\n // (EPERM) falls back to the package manager alone rather than crash inside a timer.\n if ((error as NodeJS.ErrnoException).code !== NO_SUCH_PROCESS) child.kill('SIGKILL');\n }\n}\n\n/**\n * Runs one build script without buffering its output: stdout is discarded and only the stderr\n * tail is kept. A build still running after `timeoutMs` is killed with every process it started\n * and reported as timed out. A build that exits while something it started still holds stderr\n * open has that process group killed when the grace ends, so nothing writes after it returns.\n */\nfunction runBuildScript(\n binary: string,\n directory: string,\n environment: NodeJS.ProcessEnv,\n timeoutMs: number,\n): Promise<BuildRun> {\n return new Promise((resolve) => {\n let tail = Buffer.alloc(0);\n let timedOut = false;\n let leftRunning = false;\n let settled = false;\n let grace: NodeJS.Timeout | undefined;\n // Its own process group, so a kill reaches its children. Still awaited: never unref'd.\n const child = spawn(binary, ['run', BUILD_SCRIPT], {\n cwd: directory,\n env: environment,\n stdio: ['ignore', 'ignore', 'pipe'],\n detached: SEPARATE_PROCESS_GROUPS,\n });\n // A detached build no longer gets the terminal's Ctrl+C, so this process passes it on.\n const forward = (signal: NodeJS.Signals): void => {\n killBuild(child);\n if (process.listenerCount(signal) > 1) return;\n // Only this handler was listening: end as the signal would have without it.\n process.removeListener(signal, forward);\n process.kill(process.pid, signal);\n };\n if (SEPARATE_PROCESS_GROUPS) for (const signal of FORWARDED_SIGNALS) process.on(signal, forward);\n const finish = (result: Pick<BuildRun, 'status' | 'signal' | 'error'>): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearTimeout(grace);\n for (const signal of FORWARDED_SIGNALS) process.removeListener(signal, forward);\n child.stderr?.destroy();\n resolve({ ...result, timedOut, leftRunning, stderr: tail.toString('utf8') });\n };\n const timer = setTimeout(() => {\n timedOut = true;\n killBuild(child);\n }, timeoutMs);\n child.stderr?.on('data', (chunk: Buffer) => {\n tail = Buffer.concat([tail, chunk]);\n if (tail.length > STDERR_TAIL_BYTES) tail = tail.subarray(tail.length - STDERR_TAIL_BYTES);\n });\n child.on('error', (error) => finish({ status: null, signal: null, error }));\n child.on('close', (status, signal) => finish({ status, signal }));\n // A process the build left running can hold stderr open, so 'close' may never come.\n child.on('exit', (status, signal) => {\n grace = setTimeout(\n () => {\n if (!timedOut) leftRunning = true;\n killBuild(child);\n finish({ status, signal });\n },\n timedOut ? 0 : STDERR_CLOSE_GRACE_MS,\n );\n });\n });\n}\n\n/**\n * Builds every local package that changed since its last successful build, and returns each\n * directory's outcome. A failure never stops the others.\n */\nexport async function buildLocalPackages(input: LocalBuildInput): Promise<Map<string, LocalBuildOutcome>> {\n const { globalRoot, environment, onCommand, onNotice } = input;\n const timeoutMs = input.timeoutMs ?? LOCAL_BUILD_TIMEOUT_MS;\n const directories = [...new Set(input.directories)];\n const manifests = new Map(directories.map((directory) => [directory, readManifest(directory)]));\n const stamps = readStamps(globalRoot);\n const outcomes = new Map<string, LocalBuildOutcome>();\n const childEnvironment = buildEnvironment(environment);\n let searchPath = environment.PATH;\n let loginPathTried = false;\n const resolveBinary = (manager: string): string | undefined => {\n const found = findOnPath(manager, searchPath);\n if (found !== undefined || loginPathTried || process.platform !== 'darwin') return found;\n loginPathTried = true;\n const loginPath = loginShellPath(environment);\n if (loginPath === undefined) return undefined;\n searchPath = loginPath;\n childEnvironment.PATH = loginPath;\n return findOnPath(manager, searchPath);\n };\n\n for (const directory of dependencyOrder(directories, manifests)) {\n const manifest = manifests.get(directory);\n if (manifest === undefined) {\n outcomes.set(directory, { status: 'failed', reason: `'${directory}' has no readable package.json` });\n continue;\n }\n const stamp = localBuildStamp(directory);\n // ponytail: an unchanged stamp skips the build even when a dependency outside the\n // package changed; a content hash over the dependency closure is the upgrade.\n if (stamps[directory] === stamp) {\n outcomes.set(directory, { status: 'cached' });\n continue;\n }\n if (typeof manifest.scripts?.[BUILD_SCRIPT] !== 'string') {\n outcomes.set(\n directory,\n builtEntriesPresent(directory, manifest)\n ? { status: 'cached' }\n : { status: 'failed', reason: 'it has no build script and no built entries' },\n );\n continue;\n }\n const manager = packageManagerFor(directory);\n const binary = resolveBinary(manager);\n if (binary === undefined) {\n outcomes.set(directory, { status: 'failed', reason: `${manager} is not on PATH` });\n continue;\n }\n const command = `${binary} run ${BUILD_SCRIPT} (in ${directory})`;\n onCommand(command);\n onNotice(`building ${manifest.name ?? directory}`);\n const result = await runBuildScript(binary, directory, childEnvironment, timeoutMs);\n if (result.status === 0 && !result.timedOut && !result.leftRunning) {\n // The stamp after the build, as the sync pointer records it: a build that writes into\n // src/ or package.json must not look changed to the next sync.\n stamps[directory] = localBuildStamp(directory);\n writeFileAtomic(localBuildsPath(globalRoot), `${JSON.stringify(stamps, null, 2)}\\n`);\n outcomes.set(directory, { status: 'built' });\n continue;\n }\n const exit = result.timedOut\n ? `timed out after ${String(timeoutMs / MILLISECONDS_PER_SECOND)} s`\n : result.status === 0 && result.leftRunning\n ? 'it exited but left processes running, which were stopped'\n : (result.error?.message ?? `exit ${String(result.status ?? result.signal)}`);\n outcomes.set(directory, {\n status: 'failed',\n reason: `${manager} run ${BUILD_SCRIPT} failed (${exit})`,\n stderr: stderrTail(result.stderr),\n });\n }\n return outcomes;\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { writeFileAtomic } from "@agimon-ai/po-core/runtimeJson";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
5
  import crypto from "node:crypto";
6
+ import { getValidatedTestContext } from "@agimon-ai/po-core/testMode";
6
7
  //#region src/composition/syncService/localBuilds.ts
7
8
  /**
8
9
  * Builds the packages sync roots name by local path, so the web, native and MCP builds read
@@ -134,9 +135,13 @@ function loginShellPath(environment) {
134
135
  });
135
136
  return result.status === 0 && result.stdout.trim() !== "" ? result.stdout.trim() : void 0;
136
137
  }
137
- /** The build environment: the caller's minus every `PO_*` and harness variable. */
138
+ /** Strip harness state, then restore only validated test isolation for nested Po commands. */
138
139
  function buildEnvironment(environment) {
139
- return Object.fromEntries(Object.entries(environment).filter(([name]) => !HARNESS_VARIABLES.has(name) && !HARNESS_PREFIXES.some((prefix) => name.startsWith(prefix))));
140
+ const testContext = getValidatedTestContext(environment);
141
+ return {
142
+ ...Object.fromEntries(Object.entries(environment).filter(([name]) => !HARNESS_VARIABLES.has(name) && !HARNESS_PREFIXES.some((prefix) => name.startsWith(prefix)))),
143
+ ...testContext?.env
144
+ };
140
145
  }
141
146
  function entryPaths(value) {
142
147
  if (typeof value === "string") return [value];