dsh-compound-engineering 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/README.zh.md +161 -0
  4. package/cordis.patch.yml +6 -0
  5. package/lib/frontmatter.js +167 -0
  6. package/lib/index.js +46 -0
  7. package/lib/provider.js +201 -0
  8. package/package.json +62 -4
  9. package/skills/CODING_STANDARDS.md +68 -0
  10. package/skills/ce-babysit-pr/SKILL.md +59 -0
  11. package/skills/ce-babysit-pr/references/branch-currency.md +22 -0
  12. package/skills/ce-babysit-pr/references/envelope.md +44 -0
  13. package/skills/ce-babysit-pr/references/pipeline.md +15 -0
  14. package/skills/ce-babysit-pr/references/report.md +20 -0
  15. package/skills/ce-babysit-pr/references/settle.md +37 -0
  16. package/skills/ce-babysit-pr/references/setup.md +33 -0
  17. package/skills/ce-babysit-pr/references/stack-commands.md +39 -0
  18. package/skills/ce-babysit-pr/references/stack.md +43 -0
  19. package/skills/ce-babysit-pr/references/tick.md +102 -0
  20. package/skills/ce-babysit-pr/references/watch-loop.md +222 -0
  21. package/skills/ce-babysit-pr/scripts/pr-snapshot +3656 -0
  22. package/skills/ce-bakeoff/SKILL.md +48 -0
  23. package/skills/ce-bakeoff/references/candidates.md +37 -0
  24. package/skills/ce-bakeoff/references/judging.md +19 -0
  25. package/skills/ce-bakeoff/references/output.md +11 -0
  26. package/skills/ce-bakeoff/references/verification.md +15 -0
  27. package/skills/ce-brainstorm/SKILL.md +69 -0
  28. package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
  29. package/skills/ce-brainstorm/references/approaches.md +57 -0
  30. package/skills/ce-brainstorm/references/bakeoff.md +9 -0
  31. package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
  32. package/skills/ce-brainstorm/references/brainstorm-sections.md +458 -0
  33. package/skills/ce-brainstorm/references/dialogue.md +89 -0
  34. package/skills/ce-brainstorm/references/handoff.md +187 -0
  35. package/skills/ce-brainstorm/references/html-rendering.md +638 -0
  36. package/skills/ce-brainstorm/references/interaction-rules.md +33 -0
  37. package/skills/ce-brainstorm/references/markdown-rendering.md +242 -0
  38. package/skills/ce-brainstorm/references/model-tiers.md +13 -0
  39. package/skills/ce-brainstorm/references/output-mode.md +26 -0
  40. package/skills/ce-brainstorm/references/phase-0.md +104 -0
  41. package/skills/ce-brainstorm/references/plan-write.md +32 -0
  42. package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
  43. package/skills/ce-brainstorm/references/reasoning-elevation.md +124 -0
  44. package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
  45. package/skills/ce-brainstorm/references/synthesis-summary.md +149 -0
  46. package/skills/ce-brainstorm/references/universal-brainstorming.md +72 -0
  47. package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
  48. package/skills/ce-brainstorm/references/visual-probes.md +164 -0
  49. package/skills/ce-brainstorm/scripts/elevation-dispatch.sh +288 -0
  50. package/skills/ce-brainstorm/scripts/light-webserver.js +1275 -0
  51. package/skills/ce-brainstorm/scripts/packs-resolve.py +737 -0
  52. package/skills/ce-brainstorm/scripts/peer-job-runner.py +2594 -0
  53. package/skills/ce-code-review/SKILL.md +40 -0
  54. package/skills/ce-code-review/references/action-class-rubric.md +67 -0
  55. package/skills/ce-code-review/references/cross-model-recovery.md +38 -0
  56. package/skills/ce-code-review/references/cross-model-review.md +191 -0
  57. package/skills/ce-code-review/references/depth-paths.md +32 -0
  58. package/skills/ce-code-review/references/diff-scope.md +59 -0
  59. package/skills/ce-code-review/references/dispatch-reviewers.md +135 -0
  60. package/skills/ce-code-review/references/findings-schema.json +125 -0
  61. package/skills/ce-code-review/references/finish-input.md +118 -0
  62. package/skills/ce-code-review/references/finish-review.md +202 -0
  63. package/skills/ce-code-review/references/intent-and-plan.md +73 -0
  64. package/skills/ce-code-review/references/modes-and-output.md +156 -0
  65. package/skills/ce-code-review/references/persona-catalog.md +66 -0
  66. package/skills/ce-code-review/references/personas/adversarial-reviewer.md +111 -0
  67. package/skills/ce-code-review/references/personas/agent-native-reviewer.md +173 -0
  68. package/skills/ce-code-review/references/personas/api-contract-reviewer.md +44 -0
  69. package/skills/ce-code-review/references/personas/correctness-reviewer.md +48 -0
  70. package/skills/ce-code-review/references/personas/data-migration-reviewer.md +111 -0
  71. package/skills/ce-code-review/references/personas/deployment-verification-agent.md +157 -0
  72. package/skills/ce-code-review/references/personas/julik-frontend-races-reviewer.md +45 -0
  73. package/skills/ce-code-review/references/personas/learnings-researcher.md +260 -0
  74. package/skills/ce-code-review/references/personas/maintainability-reviewer.md +85 -0
  75. package/skills/ce-code-review/references/personas/performance-reviewer.md +45 -0
  76. package/skills/ce-code-review/references/personas/previous-comments-reviewer.md +59 -0
  77. package/skills/ce-code-review/references/personas/project-standards-reviewer.md +77 -0
  78. package/skills/ce-code-review/references/personas/reliability-reviewer.md +49 -0
  79. package/skills/ce-code-review/references/personas/security-reviewer.md +50 -0
  80. package/skills/ce-code-review/references/personas/swift-ios-reviewer.md +99 -0
  81. package/skills/ce-code-review/references/personas/testing-reviewer.md +51 -0
  82. package/skills/ce-code-review/references/review-output-template.md +172 -0
  83. package/skills/ce-code-review/references/scope.md +176 -0
  84. package/skills/ce-code-review/references/select-and-route.md +93 -0
  85. package/skills/ce-code-review/references/subagent-template.md +206 -0
  86. package/skills/ce-code-review/references/validator-batch-template.md +67 -0
  87. package/skills/ce-code-review/scripts/cross-model-adversarial-review.sh +1275 -0
  88. package/skills/ce-code-review/scripts/findings-mechanics.py +274 -0
  89. package/skills/ce-code-review/scripts/packs-resolve.py +737 -0
  90. package/skills/ce-code-review/scripts/peer-job-runner.py +2594 -0
  91. package/skills/ce-code-review/scripts/review-scope.py +419 -0
  92. package/skills/ce-code-review/scripts/run-log.py +280 -0
  93. package/skills/ce-commit/SKILL.md +60 -0
  94. package/skills/ce-commit-push-pr/SKILL.md +69 -0
  95. package/skills/ce-commit-push-pr/references/apply-and-handoff.md +79 -0
  96. package/skills/ce-commit-push-pr/references/branch-creation.md +47 -0
  97. package/skills/ce-commit-push-pr/references/commit-and-push.md +26 -0
  98. package/skills/ce-commit-push-pr/references/compose.md +29 -0
  99. package/skills/ce-commit-push-pr/references/context.md +42 -0
  100. package/skills/ce-commit-push-pr/references/gh-stack-cli.md +83 -0
  101. package/skills/ce-commit-push-pr/references/pr-description-writing.md +208 -0
  102. package/skills/ce-commit-push-pr/references/stack-submit.md +81 -0
  103. package/skills/ce-compound/SKILL.md +79 -0
  104. package/skills/ce-compound/assets/resolution-template.md +96 -0
  105. package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
  106. package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
  107. package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
  108. package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
  109. package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
  110. package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
  111. package/skills/ce-compound/references/agents/session-historian.md +82 -0
  112. package/skills/ce-compound/references/assembly.md +127 -0
  113. package/skills/ce-compound/references/concepts-vocabulary.md +98 -0
  114. package/skills/ce-compound/references/enhancement.md +42 -0
  115. package/skills/ce-compound/references/grounding-validation.md +89 -0
  116. package/skills/ce-compound/references/lightweight.md +56 -0
  117. package/skills/ce-compound/references/modes.md +13 -0
  118. package/skills/ce-compound/references/refresh-and-discoverability.md +96 -0
  119. package/skills/ce-compound/references/report.md +115 -0
  120. package/skills/ce-compound/references/research.md +144 -0
  121. package/skills/ce-compound/references/schema.yaml +236 -0
  122. package/skills/ce-compound/references/session-history.md +73 -0
  123. package/skills/ce-compound/references/yaml-schema.md +130 -0
  124. package/skills/ce-compound/scripts/packs-resolve.py +737 -0
  125. package/skills/ce-compound/scripts/session-history/__pycache__/dsh_session.cpython-314.pyc +0 -0
  126. package/skills/ce-compound/scripts/session-history/discover-sessions.sh +290 -0
  127. package/skills/ce-compound/scripts/session-history/discover_dsh.py +123 -0
  128. package/skills/ce-compound/scripts/session-history/dsh_session.py +225 -0
  129. package/skills/ce-compound/scripts/session-history/extract-errors.py +178 -0
  130. package/skills/ce-compound/scripts/session-history/extract-metadata.py +301 -0
  131. package/skills/ce-compound/scripts/session-history/extract-skeleton.py +346 -0
  132. package/skills/ce-compound/scripts/validate-doc-claims.py +497 -0
  133. package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
  134. package/skills/ce-compound-refresh/SKILL.md +88 -0
  135. package/skills/ce-compound-refresh/assets/resolution-template.md +96 -0
  136. package/skills/ce-compound-refresh/references/classify.md +42 -0
  137. package/skills/ce-compound-refresh/references/commit.md +7 -0
  138. package/skills/ce-compound-refresh/references/concepts-vocabulary.md +116 -0
  139. package/skills/ce-compound-refresh/references/discoverability.md +15 -0
  140. package/skills/ce-compound-refresh/references/investigate.md +23 -0
  141. package/skills/ce-compound-refresh/references/modes.md +27 -0
  142. package/skills/ce-compound-refresh/references/per-action-flows.md +142 -0
  143. package/skills/ce-compound-refresh/references/report.md +23 -0
  144. package/skills/ce-compound-refresh/references/schema.yaml +236 -0
  145. package/skills/ce-compound-refresh/references/scope.md +18 -0
  146. package/skills/ce-compound-refresh/references/worth-audit.md +35 -0
  147. package/skills/ce-compound-refresh/references/yaml-schema.md +130 -0
  148. package/skills/ce-compound-refresh/scripts/validate-doc-claims.py +497 -0
  149. package/skills/ce-compound-refresh/scripts/validate-frontmatter.py +137 -0
  150. package/skills/ce-debug/SKILL.md +122 -0
  151. package/skills/ce-debug/references/anti-patterns.md +91 -0
  152. package/skills/ce-debug/references/defense-in-depth.md +39 -0
  153. package/skills/ce-debug/references/fix.md +21 -0
  154. package/skills/ce-debug/references/investigate.md +114 -0
  155. package/skills/ce-debug/references/investigation-techniques.md +384 -0
  156. package/skills/ce-debug/references/pipeline-mode.md +78 -0
  157. package/skills/ce-debug/references/post-fix-handoff.md +60 -0
  158. package/skills/ce-debug/references/return-to-caller.md +57 -0
  159. package/skills/ce-doc-review/SKILL.md +75 -0
  160. package/skills/ce-doc-review/references/bulk-preview.md +146 -0
  161. package/skills/ce-doc-review/references/cross-model-review.md +207 -0
  162. package/skills/ce-doc-review/references/decision-primer.md +47 -0
  163. package/skills/ce-doc-review/references/dispatch.md +56 -0
  164. package/skills/ce-doc-review/references/document-intake.md +44 -0
  165. package/skills/ce-doc-review/references/findings-schema.json +85 -0
  166. package/skills/ce-doc-review/references/modes.md +20 -0
  167. package/skills/ce-doc-review/references/open-questions-defer.md +158 -0
  168. package/skills/ce-doc-review/references/persona-selection.md +41 -0
  169. package/skills/ce-doc-review/references/personas/adversarial-document-reviewer.md +109 -0
  170. package/skills/ce-doc-review/references/personas/coherence-reviewer.md +69 -0
  171. package/skills/ce-doc-review/references/personas/design-lens-reviewer.md +48 -0
  172. package/skills/ce-doc-review/references/personas/feasibility-reviewer.md +50 -0
  173. package/skills/ce-doc-review/references/personas/product-lens-reviewer.md +85 -0
  174. package/skills/ce-doc-review/references/personas/scope-guardian-reviewer.md +83 -0
  175. package/skills/ce-doc-review/references/personas/security-lens-reviewer.md +41 -0
  176. package/skills/ce-doc-review/references/personas/whole-doc-reviewer.md +25 -0
  177. package/skills/ce-doc-review/references/rendering-floor.md +175 -0
  178. package/skills/ce-doc-review/references/review-output-template.md +123 -0
  179. package/skills/ce-doc-review/references/subagent-template.md +188 -0
  180. package/skills/ce-doc-review/references/synthesis-and-presentation.md +350 -0
  181. package/skills/ce-doc-review/references/walkthrough.md +347 -0
  182. package/skills/ce-doc-review/scripts/cross-model-doc-review.sh +1270 -0
  183. package/skills/ce-doc-review/scripts/packs-resolve.py +737 -0
  184. package/skills/ce-doc-review/scripts/peer-job-runner.py +2594 -0
  185. package/skills/ce-dogfood/SKILL.md +66 -0
  186. package/skills/ce-dogfood/agents/openai.yaml +2 -0
  187. package/skills/ce-dogfood/references/dogfood-report-template.md +97 -0
  188. package/skills/ce-dogfood/references/phases.md +167 -0
  189. package/skills/ce-dogfood/references/test-matrix-taxonomy.md +62 -0
  190. package/skills/ce-dogfood/scripts/packs-resolve.py +737 -0
  191. package/skills/ce-explain/SKILL.md +81 -0
  192. package/skills/ce-explain/references/agents/behavior-trace-scout.md +19 -0
  193. package/skills/ce-explain/references/agents/work-recap-scout.md +23 -0
  194. package/skills/ce-explain/references/check-in.md +18 -0
  195. package/skills/ce-explain/references/destinations.md +35 -0
  196. package/skills/ce-explain/references/explainer-html.md +29 -0
  197. package/skills/ce-explain/references/explainer-markdown.md +24 -0
  198. package/skills/ce-explain/references/intake.md +50 -0
  199. package/skills/ce-explain/references/orchestration.md +46 -0
  200. package/skills/ce-handoff/SKILL.md +54 -0
  201. package/skills/ce-handoff/references/create.md +88 -0
  202. package/skills/ce-handoff/references/resume.md +46 -0
  203. package/skills/ce-ideate/SKILL.md +75 -0
  204. package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +166 -0
  205. package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
  206. package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
  207. package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
  208. package/skills/ce-ideate/references/decomposition.md +39 -0
  209. package/skills/ce-ideate/references/divergent-ideation.md +112 -0
  210. package/skills/ce-ideate/references/grounding.md +138 -0
  211. package/skills/ce-ideate/references/html-rendering.md +638 -0
  212. package/skills/ce-ideate/references/ideation-sections.md +191 -0
  213. package/skills/ce-ideate/references/issue-intelligence.md +44 -0
  214. package/skills/ce-ideate/references/markdown-rendering.md +242 -0
  215. package/skills/ce-ideate/references/output-mode.md +34 -0
  216. package/skills/ce-ideate/references/post-ideation-workflow.md +169 -0
  217. package/skills/ce-ideate/references/scope-gates.md +148 -0
  218. package/skills/ce-ideate/references/universal-ideation.md +119 -0
  219. package/skills/ce-ideate/references/user-research-artifacts.md +28 -0
  220. package/skills/ce-ideate/references/web-research-cache.md +56 -0
  221. package/skills/ce-noslop/SKILL.md +45 -0
  222. package/skills/ce-noslop/references/patterns.md +61 -0
  223. package/skills/ce-noslop/references/terminology.md +3 -0
  224. package/skills/ce-optimize/SKILL.md +64 -0
  225. package/skills/ce-optimize/references/agents/learnings-researcher.md +247 -0
  226. package/skills/ce-optimize/references/agents/repo-research-analyst.md +258 -0
  227. package/skills/ce-optimize/references/example-expensive-benchmark-spec.yaml +88 -0
  228. package/skills/ce-optimize/references/example-hard-spec.yaml +64 -0
  229. package/skills/ce-optimize/references/example-judge-spec.yaml +83 -0
  230. package/skills/ce-optimize/references/example-text-target-spec.yaml +80 -0
  231. package/skills/ce-optimize/references/experiment-log-schema.yaml +563 -0
  232. package/skills/ce-optimize/references/experiment-prompt-template.md +143 -0
  233. package/skills/ce-optimize/references/judge-prompt-template.md +117 -0
  234. package/skills/ce-optimize/references/loop.md +286 -0
  235. package/skills/ce-optimize/references/measurement.md +168 -0
  236. package/skills/ce-optimize/references/optimize-spec-schema.yaml +570 -0
  237. package/skills/ce-optimize/references/persistence.md +93 -0
  238. package/skills/ce-optimize/references/spec.md +107 -0
  239. package/skills/ce-optimize/references/text-targets.md +51 -0
  240. package/skills/ce-optimize/references/usage-guide.md +147 -0
  241. package/skills/ce-optimize/references/wrap-up.md +54 -0
  242. package/skills/ce-optimize/scripts/decide.mjs +676 -0
  243. package/skills/ce-optimize/scripts/experiment-worktree.sh +330 -0
  244. package/skills/ce-optimize/scripts/measure.sh +140 -0
  245. package/skills/ce-optimize/scripts/parallel-probe.sh +134 -0
  246. package/skills/ce-plan/SKILL.md +60 -0
  247. package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +66 -0
  248. package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
  249. package/skills/ce-plan/references/agents/best-practices-researcher.md +118 -0
  250. package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
  251. package/skills/ce-plan/references/agents/framework-docs-researcher.md +97 -0
  252. package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
  253. package/skills/ce-plan/references/agents/learnings-researcher.md +268 -0
  254. package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
  255. package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
  256. package/skills/ce-plan/references/agents/repo-research-analyst.md +262 -0
  257. package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
  258. package/skills/ce-plan/references/agents/slack-researcher.md +131 -0
  259. package/skills/ce-plan/references/agents/spec-flow-analyzer.md +84 -0
  260. package/skills/ce-plan/references/agents/web-researcher.md +125 -0
  261. package/skills/ce-plan/references/approach-altitude.md +55 -0
  262. package/skills/ce-plan/references/bakeoff.md +9 -0
  263. package/skills/ce-plan/references/deepening-workflow.md +263 -0
  264. package/skills/ce-plan/references/final-review.md +134 -0
  265. package/skills/ce-plan/references/html-rendering.md +638 -0
  266. package/skills/ce-plan/references/intake.md +158 -0
  267. package/skills/ce-plan/references/markdown-rendering.md +242 -0
  268. package/skills/ce-plan/references/output-contracts.md +29 -0
  269. package/skills/ce-plan/references/output-mode.md +45 -0
  270. package/skills/ce-plan/references/plan-handoff.md +116 -0
  271. package/skills/ce-plan/references/plan-sections.md +489 -0
  272. package/skills/ce-plan/references/reasoning-elevation.md +124 -0
  273. package/skills/ce-plan/references/research.md +211 -0
  274. package/skills/ce-plan/references/resume.md +61 -0
  275. package/skills/ce-plan/references/settled-decisions.md +45 -0
  276. package/skills/ce-plan/references/structure.md +207 -0
  277. package/skills/ce-plan/references/synthesis-summary.md +260 -0
  278. package/skills/ce-plan/references/universal-planning.md +170 -0
  279. package/skills/ce-plan/scripts/elevation-dispatch.sh +288 -0
  280. package/skills/ce-plan/scripts/packs-resolve.py +737 -0
  281. package/skills/ce-plan/scripts/peer-job-runner.py +2594 -0
  282. package/skills/ce-polish/SKILL.md +22 -0
  283. package/skills/ce-polish/agents/openai.yaml +2 -0
  284. package/skills/ce-polish/references/dev-server-astro.md +58 -0
  285. package/skills/ce-polish/references/dev-server-detection.md +40 -0
  286. package/skills/ce-polish/references/dev-server-next.md +61 -0
  287. package/skills/ce-polish/references/dev-server-nuxt.md +58 -0
  288. package/skills/ce-polish/references/dev-server-procfile.md +59 -0
  289. package/skills/ce-polish/references/dev-server-rails.md +50 -0
  290. package/skills/ce-polish/references/dev-server-remix.md +58 -0
  291. package/skills/ce-polish/references/dev-server-sveltekit.md +58 -0
  292. package/skills/ce-polish/references/dev-server-vite.md +48 -0
  293. package/skills/ce-polish/references/install-riffrec.md +35 -0
  294. package/skills/ce-polish/references/launch-json-schema.md +177 -0
  295. package/skills/ce-polish/references/live-loop.md +98 -0
  296. package/skills/ce-polish/references/live-remote.md +35 -0
  297. package/skills/ce-polish/references/live-start.md +93 -0
  298. package/skills/ce-polish/references/live-stream-contract.md +253 -0
  299. package/skills/ce-polish/references/run.md +67 -0
  300. package/skills/ce-polish/scripts/detect-project-type.sh +277 -0
  301. package/skills/ce-polish/scripts/detect-riffrec.sh +225 -0
  302. package/skills/ce-polish/scripts/live-endpoint.js +2841 -0
  303. package/skills/ce-polish/scripts/read-launch-json.sh +117 -0
  304. package/skills/ce-polish/scripts/resolve-package-manager.sh +95 -0
  305. package/skills/ce-polish/scripts/resolve-port.sh +307 -0
  306. package/skills/ce-pov/SKILL.md +64 -0
  307. package/skills/ce-pov/references/agents/external-evidence-researcher.md +26 -0
  308. package/skills/ce-pov/references/agents/pov-peer.md +43 -0
  309. package/skills/ce-pov/references/agents/precedent-activity-scout.md +21 -0
  310. package/skills/ce-pov/references/agents/project-grounding-scout.md +32 -0
  311. package/skills/ce-pov/references/boundaries.md +36 -0
  312. package/skills/ce-pov/references/cross-model-panel.md +596 -0
  313. package/skills/ce-pov/references/followup.md +9 -0
  314. package/skills/ce-pov/references/grounding.md +47 -0
  315. package/skills/ce-pov/references/intake.md +61 -0
  316. package/skills/ce-pov/references/invocation.md +47 -0
  317. package/skills/ce-pov/references/method.md +68 -0
  318. package/skills/ce-pov/references/pov-schema.json +56 -0
  319. package/skills/ce-pov/references/report.md +7 -0
  320. package/skills/ce-pov/scripts/cross-model-pov.sh +1110 -0
  321. package/skills/ce-pov/scripts/peer-job-runner.py +2594 -0
  322. package/skills/ce-product-pulse/SKILL.md +81 -0
  323. package/skills/ce-product-pulse/agents/openai.yaml +2 -0
  324. package/skills/ce-product-pulse/references/config.md +20 -0
  325. package/skills/ce-product-pulse/references/interview.md +260 -0
  326. package/skills/ce-product-pulse/references/report-template.md +89 -0
  327. package/skills/ce-product-pulse/references/run.md +46 -0
  328. package/skills/ce-product-pulse/references/setup.md +43 -0
  329. package/skills/ce-promote/SKILL.md +90 -0
  330. package/skills/ce-promote/agents/openai.yaml +2 -0
  331. package/skills/ce-promote/references/spiral-cli.md +149 -0
  332. package/skills/ce-proof/SKILL.md +65 -0
  333. package/skills/ce-proof/references/api.md +176 -0
  334. package/skills/ce-proof/references/workflows.md +124 -0
  335. package/skills/ce-prototype/SKILL.md +59 -0
  336. package/skills/ce-prototype/assets/annotate.css +206 -0
  337. package/skills/ce-prototype/assets/annotate.js +837 -0
  338. package/skills/ce-prototype/references/annotation-loop.md +32 -0
  339. package/skills/ce-prototype/references/build.md +39 -0
  340. package/skills/ce-prototype/references/craft-floor.md +43 -0
  341. package/skills/ce-prototype/references/preview.md +118 -0
  342. package/skills/ce-prototype/references/scoping.md +38 -0
  343. package/skills/ce-prototype/references/write-back.md +37 -0
  344. package/skills/ce-prototype/scripts/light-webserver.js +1275 -0
  345. package/skills/ce-resolve-pr-feedback/SKILL.md +60 -0
  346. package/skills/ce-resolve-pr-feedback/references/agents/pr-comment-resolver.md +59 -0
  347. package/skills/ce-resolve-pr-feedback/references/evaluation-rubric.md +141 -0
  348. package/skills/ce-resolve-pr-feedback/references/full-mode.md +336 -0
  349. package/skills/ce-resolve-pr-feedback/references/pipeline-mode.md +23 -0
  350. package/skills/ce-resolve-pr-feedback/references/resume.md +60 -0
  351. package/skills/ce-resolve-pr-feedback/references/return-to-caller.md +111 -0
  352. package/skills/ce-resolve-pr-feedback/references/targeted-mode.md +48 -0
  353. package/skills/ce-resolve-pr-feedback/scripts/get-pr-comments +176 -0
  354. package/skills/ce-resolve-pr-feedback/scripts/get-thread-for-comment +84 -0
  355. package/skills/ce-resolve-pr-feedback/scripts/pending-feedback.py +287 -0
  356. package/skills/ce-resolve-pr-feedback/scripts/reply-to-pr-thread +57 -0
  357. package/skills/ce-resolve-pr-feedback/scripts/resolve-pr-thread +23 -0
  358. package/skills/ce-retune/SKILL.md +42 -0
  359. package/skills/ce-retune/agents/openai.yaml +2 -0
  360. package/skills/ce-retune/references/baseline-mining.md +122 -0
  361. package/skills/ce-retune/references/corpus-audit.md +128 -0
  362. package/skills/ce-retune/references/cut-passes.md +129 -0
  363. package/skills/ce-retune/references/halt-taxonomy.md +123 -0
  364. package/skills/ce-retune/references/noise-floor.md +131 -0
  365. package/skills/ce-retune/references/workflow-shapes.md +73 -0
  366. package/skills/ce-riffrec-feedback-analysis/SKILL.md +33 -0
  367. package/skills/ce-riffrec-feedback-analysis/references/analyzer.md +18 -0
  368. package/skills/ce-riffrec-feedback-analysis/references/compound-engineering-feedback-format.md +116 -0
  369. package/skills/ce-riffrec-feedback-analysis/references/extensive-analysis.md +98 -0
  370. package/skills/ce-riffrec-feedback-analysis/references/install-riffrec.md +27 -0
  371. package/skills/ce-riffrec-feedback-analysis/references/quick-bug-report.md +38 -0
  372. package/skills/ce-riffrec-feedback-analysis/scripts/analyze_riffrec_zip.py +1306 -0
  373. package/skills/ce-setup/SKILL.md +103 -0
  374. package/skills/ce-setup/agents/openai.yaml +2 -0
  375. package/skills/ce-setup/assets/compounding-directive.md +11 -0
  376. package/skills/ce-setup/assets/noslop-directive.md +7 -0
  377. package/skills/ce-setup/assets/pack-rule-template.md +11 -0
  378. package/skills/ce-setup/references/acpx-cache-warm.md +14 -0
  379. package/skills/ce-setup/references/config-template.yaml +210 -0
  380. package/skills/ce-setup/references/legacy-codex-tool-map.md +15 -0
  381. package/skills/ce-setup/references/pack-scaffold.md +45 -0
  382. package/skills/ce-setup/references/repo-fixes.md +121 -0
  383. package/skills/ce-setup/scripts/check-health +929 -0
  384. package/skills/ce-setup/scripts/packs-resolve.py +737 -0
  385. package/skills/ce-simplify-code/SKILL.md +76 -0
  386. package/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
  387. package/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
  388. package/skills/ce-simplify-code/references/personas/comment-reviewer.md +24 -0
  389. package/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
  390. package/skills/ce-simplify-code/references/personas/test-value-reviewer.md +23 -0
  391. package/skills/ce-simplify-code/references/reviewer-dispatch.md +11 -0
  392. package/skills/ce-simplify-code/references/standalone-report.md +25 -0
  393. package/skills/ce-strategy/SKILL.md +61 -0
  394. package/skills/ce-strategy/references/grounding.md +22 -0
  395. package/skills/ce-strategy/references/interview.md +179 -0
  396. package/skills/ce-strategy/references/strategy-template.md +94 -0
  397. package/skills/ce-strategy/references/update-run.md +33 -0
  398. package/skills/ce-sweep/SKILL.md +93 -0
  399. package/skills/ce-sweep/agents/openai.yaml +2 -0
  400. package/skills/ce-sweep/references/agents/media-analyzer.md +54 -0
  401. package/skills/ce-sweep/references/interview.md +208 -0
  402. package/skills/ce-sweep/references/model-tiers.md +13 -0
  403. package/skills/ce-sweep/references/plan-template.md +62 -0
  404. package/skills/ce-sweep/references/run.md +126 -0
  405. package/skills/ce-sweep/references/sources/email.md +57 -0
  406. package/skills/ce-sweep/references/sources/github-issues.md +59 -0
  407. package/skills/ce-sweep/references/sources/slack.md +61 -0
  408. package/skills/ce-sweep/references/state-schema.md +157 -0
  409. package/skills/ce-sweep/references/subagent-template.md +60 -0
  410. package/skills/ce-sweep/scripts/analyze_riffrec_zip.py +1306 -0
  411. package/skills/ce-sweep/scripts/sweep-state.py +793 -0
  412. package/skills/ce-test-browser/SKILL.md +50 -0
  413. package/skills/ce-test-browser/references/agent-browser-driver.md +47 -0
  414. package/skills/ce-test-browser/references/pipeline-orchestration.md +45 -0
  415. package/skills/ce-test-browser/references/route-and-report.md +129 -0
  416. package/skills/ce-test-browser/scripts/resolve-port.sh +41 -0
  417. package/skills/ce-test-xcode/SKILL.md +26 -0
  418. package/skills/ce-test-xcode/agents/openai.yaml +2 -0
  419. package/skills/ce-test-xcode/references/setup-and-build.md +32 -0
  420. package/skills/ce-test-xcode/references/test-and-report.md +64 -0
  421. package/skills/ce-work/SKILL.md +62 -0
  422. package/skills/ce-work/references/agents/figma-design-sync.md +27 -0
  423. package/skills/ce-work/references/agents/implementation-worker.md +17 -0
  424. package/skills/ce-work/references/cross-model-execution.md +191 -0
  425. package/skills/ce-work/references/execution-engines.md +64 -0
  426. package/skills/ce-work/references/execution-strategy.md +73 -0
  427. package/skills/ce-work/references/implementation-loop.md +107 -0
  428. package/skills/ce-work/references/implementation-result-schema.json +52 -0
  429. package/skills/ce-work/references/input-triage.md +50 -0
  430. package/skills/ce-work/references/non-code-execution.md +23 -0
  431. package/skills/ce-work/references/return-to-caller.md +33 -0
  432. package/skills/ce-work/references/review-findings-followup.md +82 -0
  433. package/skills/ce-work/references/shipping-workflow.md +89 -0
  434. package/skills/ce-work/references/tracker-defer.md +150 -0
  435. package/skills/ce-work/references/work-intake.md +51 -0
  436. package/skills/ce-work/references/workspace-setup.md +27 -0
  437. package/skills/ce-work/scripts/__pycache__/unit_workspace_jobs.cpython-314.pyc +0 -0
  438. package/skills/ce-work/scripts/__pycache__/unit_workspace_state.cpython-314.pyc +0 -0
  439. package/skills/ce-work/scripts/cross-model-work.sh +1385 -0
  440. package/skills/ce-work/scripts/peer-job-runner.py +2594 -0
  441. package/skills/ce-work/scripts/unit-workspace.py +215 -0
  442. package/skills/ce-work/scripts/unit_workspace_ignored.py +93 -0
  443. package/skills/ce-work/scripts/unit_workspace_integration.py +677 -0
  444. package/skills/ce-work/scripts/unit_workspace_jobs.py +1216 -0
  445. package/skills/ce-work/scripts/unit_workspace_lifecycle.py +981 -0
  446. package/skills/ce-work/scripts/unit_workspace_state.py +1379 -0
  447. package/skills/ce-work/scripts/unit_workspace_transaction.py +612 -0
  448. package/skills/ce-worktree/SKILL.md +54 -0
  449. package/skills/lfg/SKILL.md +59 -0
  450. package/skills/lfg/references/debug-return.md +25 -0
  451. package/skills/lfg/references/intake.md +30 -0
  452. package/skills/lfg/references/next-work-handoff.md +96 -0
  453. package/skills/lfg/references/plan-brief.md +36 -0
  454. package/skills/lfg/references/review-followup.md +78 -0
  455. package/skills/lfg/references/shipping.md +65 -0
  456. package/skills/lfg/references/stage-routing.md +49 -0
  457. package/skills/lfg/references/task-visibility.md +11 -0
  458. package/skills/lfg/references/tracker-defer.md +150 -0
  459. package/skills/lfg/references/work-return.md +27 -0
  460. package/skills/wtf/SKILL.md +33 -0
  461. package/skills/wtf/agents/openai.yaml +2 -0
@@ -0,0 +1,1385 @@
1
+ #!/usr/bin/env bash
2
+ # Run one pre-sanctioned, write-capable implementation route in a controller-
3
+ # supplied detached workspace. The adapter never creates worktrees, changes
4
+ # recipients, integrates output, or retries through another route.
5
+ #
6
+ # Every route reaches its agent through acpx (the Agent Client Protocol
7
+ # client), which prints the JSON-RPC stream; one parser reads every route.
8
+ #
9
+ # Usage:
10
+ # cross-model-work.sh <authorization-json> <workspace> <unit-packet> <expected-packet-sha256> <result-dir>
11
+ #
12
+ # Routes: codex | claude | grok-cli | cursor | composer | grok-cursor | opencode | antigravity
13
+ # Output: <result-dir>/implementation-result.json and redacted adapter.log
14
+ # Exit: 0 host-resolvable terminal result, 1 failed/schema-invalid, 2 unavailable
15
+ #
16
+ # Introspection (no model call):
17
+ # cross-model-work.sh --emit-adapter <route>
18
+ # cross-model-work.sh --check-model antigravity <model>
19
+ # opens an Antigravity session for <model> without sending a prompt; exit 0
20
+ # when the account offers it, exit 2 with the reason on stderr otherwise
21
+
22
+ set -uo pipefail
23
+ umask 077
24
+
25
+ # Prefer the runner-exported interpreter (sys.executable via CE_PEER_PYTHON),
26
+ # else probe execution — Windows Store's python3 stub satisfies `command -v`
27
+ # then exits nonzero (see resolve-python convention / #1247).
28
+ PY="${CE_PEER_PYTHON:-}"
29
+ if [ -z "$PY" ]; then
30
+ PY="$(for c in python3 python py; do command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { echo "$c"; break; }; done)"
31
+ fi
32
+ [ -n "$PY" ] || { echo "no working Python 3 interpreter on PATH" >&2; exit 1; }
33
+
34
+ # Cursor's ACP server offers one preset per model and rejects effort variants,
35
+ # so grok-cursor runs at high/fast and composer at its fast tier.
36
+ M_GROK_CURSOR="grok-4.7[context=256k,reasoning_effort=high,fast=true]"
37
+ M_COMPOSER="composer-2.5[fast=true]"
38
+ M_ANTIGRAVITY="gemini-4-argon"
39
+
40
+ log() { printf '[cross-model-work] %s\n' "$*" >&2; }
41
+
42
+ route_target() {
43
+ case "$1" in
44
+ codex|claude|cursor|composer|antigravity) printf '%s' "$1" ;;
45
+ grok-cli|grok-cursor) printf 'grok' ;;
46
+ opencode) printf 'opencode' ;;
47
+ *) return 1 ;;
48
+ esac
49
+ }
50
+
51
+ route_harness() {
52
+ case "$1" in
53
+ codex) printf 'codex' ;;
54
+ claude) printf 'claude' ;;
55
+ grok-cli) printf 'grok' ;;
56
+ cursor|composer|grok-cursor) printf 'cursor-agent' ;;
57
+ opencode) printf 'opencode' ;;
58
+ antigravity) printf 'antigravity' ;;
59
+ *) return 1 ;;
60
+ esac
61
+ }
62
+
63
+ route_model() {
64
+ local route="$1" target override="${CE_WORK_MODEL_OVERRIDE:-}"
65
+ if [ -n "${MODEL_REQUESTED:-}" ]; then
66
+ printf '%s' "$MODEL_REQUESTED"
67
+ return
68
+ fi
69
+ target="$(route_target "$route")" || return 1
70
+ if [ -n "$override" ] && [ "${CE_WORK_MODEL_OVERRIDE_TARGET:-}" = "$target" ]; then
71
+ printf '%s' "$override"
72
+ return
73
+ fi
74
+ case "$route" in
75
+ codex|claude|grok-cli|cursor) printf 'auto' ;;
76
+ grok-cursor) printf '%s' "$M_GROK_CURSOR" ;;
77
+ composer) printf '%s' "$M_COMPOSER" ;;
78
+ opencode) printf 'auto' ;;
79
+ antigravity) printf '%s' "$M_ANTIGRAVITY" ;;
80
+ esac
81
+ }
82
+
83
+ route_available() {
84
+ case "$1" in
85
+ codex) command -v codex >/dev/null 2>&1 ;;
86
+ claude) command -v claude >/dev/null 2>&1 ;;
87
+ grok-cli) command -v grok >/dev/null 2>&1 ;;
88
+ cursor|composer|grok-cursor) command -v cursor-agent >/dev/null 2>&1 ;;
89
+ opencode) command -v opencode >/dev/null 2>&1 ;;
90
+ antigravity) command -v agy_acp_server.par >/dev/null 2>&1 ;;
91
+ *) return 1 ;;
92
+ esac
93
+ }
94
+
95
+ # --- acpx transport (keep byte-identical across migrated peer workers) -----
96
+ # Every route reaches its agent through acpx, which speaks the Agent Client
97
+ # Protocol and prints the JSON-RPC stream, so one parser covers every CLI.
98
+ # The calling worker supplies route_available, route_model, and log.
99
+ ACPX_VERSION="0.21.0"
100
+ ACPX_UNAVAILABLE="" # pre-egress reason; ACPX_SCOPE says whether every route shares it
101
+ ACPX_SCOPE=""
102
+ ACPX_NPM_REFRESH=false
103
+
104
+ acpx_agent() { # <route> -> acpx built-in agent name
105
+ case "$1" in
106
+ codex) printf 'codex' ;;
107
+ claude) printf 'claude' ;;
108
+ grok-cli) printf 'grok-build' ;;
109
+ grok-cursor|cursor|composer) printf 'cursor' ;;
110
+ opencode) printf 'opencode' ;;
111
+ antigravity) printf 'antigravity' ;;
112
+ *) return 1 ;;
113
+ esac
114
+ }
115
+
116
+ # acpx launches Google's Antigravity ACP runtime by name. It is a separate
117
+ # download from the agy CLI and needs its localharness_external helper from the
118
+ # same release, beside it or named by ANTIGRAVITY_HARNESS_PATH.
119
+ acpx_antigravity_helper() {
120
+ local runtime
121
+ if [ -n "${ANTIGRAVITY_HARNESS_PATH:-}" ]; then printf '%s' "$ANTIGRAVITY_HARNESS_PATH"; return 0; fi
122
+ runtime="$(command -v agy_acp_server.par 2>/dev/null)" || return 1
123
+ printf '%s/localharness_external' "$(dirname "$runtime")"
124
+ }
125
+
126
+ # The runtime reads skills and settings from GEMINI_HOME, so every run gets a
127
+ # private one, deleted with the caller's scratch. A file-backed login (no macOS
128
+ # keychain) is read only from there, so it is copied in; refreshes are not
129
+ # written back, so concurrent peers cannot clobber the user's token.
130
+ acpx_antigravity_home() { # <private-dir> -> GEMINI_HOME path
131
+ local home="$1/gemini-home"
132
+ local token="${GEMINI_HOME:-${HOME:-}/.gemini}/antigravity-acp/acp_token.json"
133
+ (umask 077; mkdir -p "$home/antigravity-acp") || return 1
134
+ printf '{"auth":{"type":"oauth-personal"}}\n' > "$home/antigravity-acp/settings.json" || return 1
135
+ if [ -f "$token" ]; then
136
+ (umask 077; cp "$token" "$home/antigravity-acp/acp_token.json") || return 1
137
+ fi
138
+ printf '%s' "$home"
139
+ }
140
+
141
+ acpx_unavailable() { # <shared|route> <reason>
142
+ ACPX_SCOPE="$1"; ACPX_UNAVAILABLE="$2"
143
+ return 1
144
+ }
145
+
146
+ # acpx lets ~/.acpx/config.json and <cwd>/.acpxrc.json replace a built-in
147
+ # agent's launch command; a route that would run something else is skipped.
148
+ # opencode launches the installed CLI by path, which no config entry overrides.
149
+ acpx_config_guard() { # <route> <cwd>
150
+ local agent file
151
+ [ "$1" = opencode ] && return 0
152
+ agent="$(acpx_agent "$1")" || return 1
153
+ for file in "${HOME:-}/.acpx/config.json" "$2/.acpxrc.json"; do
154
+ [ -e "$file" ] || continue
155
+ if ! jq -e --arg a "$agent" '(.agents // {}) | keys | map(ascii_downcase) | index($a) == null' "$file" >/dev/null 2>&1; then
156
+ acpx_unavailable route "$file overrides or cannot be checked for the '$agent' agent launch"
157
+ return 1
158
+ fi
159
+ done
160
+ }
161
+
162
+ acpx_preflight() { # <route> <cwd>; nothing is sent to a provider here
163
+ local v major minor
164
+ ACPX_UNAVAILABLE=""; ACPX_SCOPE=""
165
+ command -v node >/dev/null 2>&1 || { acpx_unavailable shared "node not found; acpx needs Node 22.13 or newer"; return 1; }
166
+ v="$(node -p 'process.versions.node' 2>/dev/null)"
167
+ major="${v%%.*}"; minor="${v#*.}"; minor="${minor%%.*}"
168
+ case "$major$minor" in ''|*[!0-9]*) acpx_unavailable shared "cannot read the Node version; acpx needs Node 22.13 or newer"; return 1 ;; esac
169
+ if [ "$major" -lt 22 ] || { [ "$major" -eq 22 ] && [ "$minor" -lt 13 ]; }; then
170
+ acpx_unavailable shared "Node $v is too old; acpx needs Node 22.13 or newer"
171
+ return 1
172
+ fi
173
+ command -v npx >/dev/null 2>&1 || { acpx_unavailable shared "npx not found; acpx runs through npx"; return 1; }
174
+ if [ "$1" = antigravity ]; then
175
+ case "$(uname -s 2>/dev/null)" in
176
+ MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "the Antigravity route is not yet supported on native Windows"; return 1 ;;
177
+ esac
178
+ route_available "$1" || { acpx_unavailable route "Antigravity's ACP runtime (agy_acp_server.par) is not on PATH; it is a separate download from the agy CLI (run ce-setup for the link)"; return 1; }
179
+ [ -x "$(acpx_antigravity_helper)" ] || { acpx_unavailable route "Antigravity's localharness_external helper is not beside agy_acp_server.par or at ANTIGRAVITY_HARNESS_PATH; install both from the same release (run ce-setup for the link)"; return 1; }
180
+ fi
181
+ route_available "$1" || { acpx_unavailable route "the agent CLI for route '$1' is not installed"; return 1; }
182
+ if [ "$1" = opencode ]; then
183
+ case "$(uname -s 2>/dev/null)" in
184
+ MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "opencode launches through acpx's raw --agent command, which acpx rejects on native Windows"; return 1 ;;
185
+ esac
186
+ fi
187
+ acpx_config_guard "$1" "$2"
188
+ }
189
+
190
+ # Claude's ACP adapter always loads project settings, so the launch goes
191
+ # through a wrapper that adds --safe-mode.
192
+ acpx_claude_wrapper() { # <private-dir> -> wrapper path
193
+ local real wrapper="$1/claude-safe-mode"
194
+ real="$(command -v claude)" || return 1
195
+ printf '#!/bin/sh\nexec %s --safe-mode "$@"\n' "$(printf '%q' "$real")" > "$wrapper" && chmod 700 "$wrapper" || return 1
196
+ printf '%s' "$wrapper"
197
+ }
198
+
199
+ # npm resolves offline-first, so cached metadata older than the acpx pin or a
200
+ # version an adapter's dependencies need fails with ETARGET before anything is
201
+ # sent. One launch with fresh metadata heals the cache. npm ignores
202
+ # prefer_online while prefer_offline is set, so the refresh turns it off.
203
+ acpx_stale_npm_metadata() { # <stderr-log> <stream-log>
204
+ [ "$ACPX_NPM_REFRESH" = true ] && return 1
205
+ grep -qE 'ETARGET|notarget|No matching version found' "$1" "$2" 2>/dev/null
206
+ }
207
+
208
+ # Prints the NUL-delimited argv prefix through `npx acpx@<pin>` and its global
209
+ # flags; the worker appends permission flags, `--model`, the agent, and exec options.
210
+ # The codex and claude adapters are pointed at the installed CLIs so the pin
211
+ # governs the transport, not which models the agent can serve.
212
+ acpx_base_argv() { # <route> <cwd> <mcp-config> <timeout-secs> <claude-wrapper> <gemini-home>
213
+ printf '%s\0' env
214
+ # An ambient Google API key or cloud project would bill it instead of the
215
+ # Antigravity subscription. env takes -u only before its first assignment.
216
+ case "$1" in
217
+ antigravity) printf '%s\0' -u GEMINI_API_KEY -u GOOGLE_API_KEY -u GOOGLE_APPLICATION_CREDENTIALS \
218
+ -u GOOGLE_CLOUD_PROJECT -u GOOGLE_CLOUD_LOCATION -u GOOGLE_GENAI_USE_VERTEXAI -u AGY_LLM_GATEWAY_URL ;;
219
+ esac
220
+ if [ "$ACPX_NPM_REFRESH" = true ]; then
221
+ printf '%s\0' npm_config_prefer_offline=false npm_config_fetch_retries=0 npm_config_prefer_online=true
222
+ else
223
+ printf '%s\0' npm_config_prefer_offline=true npm_config_fetch_retries=0
224
+ fi
225
+ case "$1" in
226
+ # codex-acp ignores app-server CLI flags.
227
+ codex) printf '%s\0' "CODEX_PATH=$(command -v codex)" 'CODEX_CONFIG={"features":{"plugins":false}}' ;;
228
+ claude) printf '%s\0' "CLAUDE_CODE_EXECUTABLE=$5" ;;
229
+ antigravity) printf '%s\0' "GEMINI_HOME=$6" "ANTIGRAVITY_HARNESS_PATH=$(acpx_antigravity_helper)" ;;
230
+ esac
231
+ printf '%s\0' npx -y "acpx@$ACPX_VERSION" --cwd "$2" --format json --mcp-config "$3" --timeout "$4"
232
+ }
233
+
234
+ acpx_agent_argv() { # <route>
235
+ # The preflight refuses opencode when it is not installed; the bare name keeps
236
+ # --emit-adapter output readable on a machine without it.
237
+ if [ "$1" = opencode ]; then printf '%s\0' --agent "$(command -v opencode || printf opencode) acp" exec
238
+ else printf '%s\0' "$(acpx_agent "$1")" exec; fi
239
+ }
240
+
241
+ # The run's outcome is the result of its own session/prompt request, matched by
242
+ # id because --model adds a session/set_model request before the prompt.
243
+ acpx_outcome() { # <stream-log> -> end_turn | <other stopReason> | error | incomplete | not-sent
244
+ local id outcome
245
+ id="$(jq -R 'fromjson? | select(.method == "session/prompt") | .id' "$1" 2>/dev/null | tail -1)"
246
+ [ -n "$id" ] || { printf 'not-sent'; return 0; }
247
+ outcome="$(jq -rR --argjson id "$id" 'fromjson? | select(.method == null and .id == $id) | if .error then "error" else (.result.stopReason // "error") end' "$1" 2>/dev/null | tail -1)"
248
+ printf '%s' "${outcome:-incomplete}"
249
+ }
250
+
251
+ # Antigravity holds session/new open while it waits for a browser sign-in it
252
+ # will never get, so a run without a usable login would otherwise sit out the
253
+ # whole idle cap. Nothing has been sent while the session is pending.
254
+ ACPX_SESSION_START_SECS="${CROSS_MODEL_SESSION_START_SECS:-60}"
255
+ acpx_session_state() { # <stream-log> -> starting | pending | open
256
+ local id
257
+ jq -eR 'fromjson? | select(.result.protocolVersion? != null)' "$1" >/dev/null 2>&1 || { printf 'starting'; return 0; }
258
+ id="$(jq -R 'fromjson? | select(.method == "session/new") | .id' "$1" 2>/dev/null | tail -1)"
259
+ if [ -n "$id" ] && jq -eR --argjson id "$id" 'fromjson? | select(.method == null and .id == $id)' "$1" >/dev/null 2>&1; then
260
+ printf 'open'
261
+ else
262
+ printf 'pending'
263
+ fi
264
+ }
265
+
266
+ acpx_text() { # <stream-log> <outfile>: the agent's reply text
267
+ jq -jR 'fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty' "$1" > "$2" 2>/dev/null
268
+ }
269
+
270
+ # Model ids the adapter reports in the prompt result's _meta. acpx documents
271
+ # _meta as adapter-defined, so these are the adapter's assertion, not proof.
272
+ acpx_served_models() { # <stream-log> -> "<model> <tokens>" lines
273
+ jq -rR 'fromjson? | select(.result.stopReason?) | .result._meta // {} |
274
+ ((.quota.model_usage // [])[] | "\(.model) \(.token_count.totalTokens // 0)"),
275
+ (.modelId // empty | "\(.) 0")' "$1" 2>/dev/null
276
+ }
277
+
278
+ # Bounded failure evidence from the stream. The stream echoes the outbound
279
+ # prompt, so only error messages and the stop reason are read from it.
280
+ acpx_failure_evidence() { # <stream-log>
281
+ local evidence
282
+ evidence="$(jq -rR 'fromjson? | (.error.message? // empty), (.result.stopReason? // empty | select(. != "end_turn") | "stopReason=" + .)' "$1" 2>/dev/null | tr '\n' ' ')"
283
+ evidence="${evidence% }"
284
+ [ "${#evidence}" -gt 300 ] && evidence="${evidence:0:147} ... ${evidence: -147}"
285
+ printf '%s' "$evidence"
286
+ }
287
+
288
+ # codex-acp streams a provider rejection (such as a model the account may not
289
+ # use) as reply text and still ends the turn with end_turn, so only the reply
290
+ # carries it. Bounded inside jq so a long reply never enters a bash string.
291
+ acpx_reply_evidence() { # <stream-log>
292
+ jq -nrR '[inputs | fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty]
293
+ | join("") | gsub("\\s+"; " ") | ltrimstr(" ") | rtrimstr(" ")
294
+ | if length > 300 then .[0:147] + " ... " + .[-147:] else . end' "$1" 2>/dev/null
295
+ }
296
+
297
+ acpx_advertised_models() { # <stream-log>
298
+ jq -nrR '[inputs | fromjson? | .result.models.availableModels[]?.modelId | sub("\\[.*$"; "")]
299
+ | reduce .[] as $m ([]; if any(.[]; . == $m) then . else . + [$m] end)
300
+ | join(", ") | if length > 300 then .[0:296] + " ..." else . end' "$1" 2>/dev/null
301
+ }
302
+
303
+ # Exit 5 is acpx's permission refusal. Paired with stopReason cancelled, the
304
+ # refusal ended the turn. Exit 5 with end_turn means the turn finished. A
305
+ # cancelled turn with any other exit is an interrupt.
306
+ acpx_permission_cancel_reason() { # <stream-log> <exit-code>
307
+ [ "$(acpx_outcome "$1")" = cancelled ] && [ "$2" = 5 ] || return 0
308
+ printf '%s' "permission request cancelled the turn"
309
+ }
310
+
311
+ acpx_node_startup_line() { # <stderr-log>
312
+ local line=""
313
+ line="$(grep -m1 -E '^Error ' "$1" 2>/dev/null)"
314
+ [ -n "$line" ] || line="$(grep -m1 -E '^node:' "$1" 2>/dev/null)"
315
+ printf '%s' "$line"
316
+ }
317
+ # --- end acpx transport -----------------------------------------------------
318
+
319
+ # Cursor routes take Cursor's ACP model ids, which may carry a bracketed preset
320
+ # (grok-4.7[context=256k,reasoning_effort=high,fast=true]); the family rule
321
+ # applies to the id before the bracket.
322
+ CURSOR_MODEL_RE='^([A-Za-z0-9][A-Za-z0-9._:/-]*)(\[[A-Za-z0-9._=,:-]*\])?$'
323
+
324
+ validate_model_override() {
325
+ local route="$1" override="${CE_WORK_MODEL_OVERRIDE:-}" override_target="${CE_WORK_MODEL_OVERRIDE_TARGET:-}" target base base_lower
326
+ [ -n "$override" ] || { [ -z "$override_target" ]; return; }
327
+ case "$override_target" in
328
+ codex|claude|grok|cursor|composer|opencode|antigravity) ;;
329
+ *) return 1 ;;
330
+ esac
331
+ target="$(route_target "$route")" || return 1
332
+ [ "$override_target" = "$target" ] || return 0
333
+ base="$override"
334
+ case "$route" in
335
+ cursor|composer|grok-cursor)
336
+ [[ "$override" =~ $CURSOR_MODEL_RE ]] || return 1
337
+ base="${BASH_REMATCH[1]}"
338
+ ;;
339
+ esac
340
+ if [ "$route" = cursor ]; then
341
+ base_lower="$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')"
342
+ case "$base_lower" in composer|composer-*|grok|grok-*|cursor-grok-*) return 1 ;; esac
343
+ return 0
344
+ fi
345
+ case "$route:$base" in
346
+ codex:gpt-*|codex:o[0-9]*|claude:fable|claude:opus|claude:sonnet|claude:haiku|claude:claude-*|grok-cli:grok-*|grok-cursor:grok-*|grok-cursor:cursor-grok-*|composer:composer-*|opencode:*/*) ;;
347
+ antigravity:gemini-*) [[ "$base" =~ ^gemini-[A-Za-z0-9._-]+$ ]] || return 1 ;;
348
+ *) return 1 ;;
349
+ esac
350
+ }
351
+
352
+ # Accept an effort only where the route's ACP adapter exposes an effort option
353
+ # and lists the value (claude effort, codex and grok reasoning_effort, checked
354
+ # 2026-10-06 through acpx 0.19.4; antigravity thinking_level, checked
355
+ # 2026-10-14; opencode 2.x effort, checked 2026-10-23). Codex and OpenCode
356
+ # levels vary per model, and OpenCode 1.x has no effort option, so a listed
357
+ # level can still be refused before the prompt is sent. Cursor fixes effort in
358
+ # the model preset, so any effort there makes the route unavailable instead of
359
+ # being dropped.
360
+ validate_effort_override() {
361
+ local route="$1" effort="${EFFORT_REQUESTED:-}"
362
+ [ -n "$effort" ] || return 0
363
+ case "$route:$effort" in
364
+ claude:low|claude:medium|claude:high|claude:xhigh|claude:max) ;;
365
+ codex:low|codex:medium|codex:high|codex:xhigh|codex:max|codex:ultra) ;;
366
+ grok-cli:low|grok-cli:medium|grok-cli:high|grok-cli:xhigh) ;;
367
+ antigravity:low|antigravity:medium|antigravity:high) ;;
368
+ opencode:none|opencode:low|opencode:medium|opencode:high|opencode:xhigh|opencode:max) ;;
369
+ *) return 1 ;;
370
+ esac
371
+ }
372
+
373
+ # The worker runs in the prepared workspace with every permission request
374
+ # approved. Each adapter runs in its write-capable mode (codex agent, claude
375
+ # default, cursor agent, opencode build, antigravity auto_edit, since its
376
+ # default mode waits on approvals that never come); none of them confines
377
+ # writes to the workspace, which references/cross-model-execution.md records
378
+ # per route. Codex, Claude, and Antigravity run at high effort and native Grok
379
+ # at xhigh unless the authorization requests another level.
380
+ adapter_argv() {
381
+ local route="$1" model
382
+ acpx_agent "$route" >/dev/null || return 1
383
+ model="$(route_model "$route")"
384
+ acpx_base_argv "$route" "$WORKSPACE" "$MCP_CONFIG" "$ACPX_TIMEOUT" "$CLAUDE_WRAPPER" "$GEMINI_HOME_DIR"
385
+ printf '%s\0' --approve-all
386
+ [ "$model" = auto ] || printf '%s\0' --model "$model"
387
+ acpx_agent_argv "$route"
388
+ case "$route" in
389
+ codex) printf '%s\0' --config-option mode=agent --config-option "reasoning_effort=${EFFORT_REQUESTED:-high}" ;;
390
+ # Claude otherwise starts in the user's default permission mode.
391
+ claude) printf '%s\0' --config-option mode=default --config-option "effort=${EFFORT_REQUESTED:-high}" ;;
392
+ grok-cli) printf '%s\0' --config-option "reasoning_effort=${EFFORT_REQUESTED:-xhigh}" ;;
393
+ cursor|composer|grok-cursor) printf '%s\0' --config-option mode=agent ;;
394
+ opencode) printf '%s\0' --config-option mode=build
395
+ [ -z "${EFFORT_REQUESTED:-}" ] || printf '%s\0' --config-option "effort=$EFFORT_REQUESTED" ;;
396
+ antigravity) printf '%s\0' --config-option mode=auto_edit --config-option "thinking_level=${EFFORT_REQUESTED:-high}" ;;
397
+ esac
398
+ printf '%s\0' --file "$PROMPT_FILE"
399
+ }
400
+
401
+ ACPX_TIMEOUT="${CE_PEER_HARD_SECS:-7200}"
402
+ case "$ACPX_TIMEOUT" in ''|0|*[!0-9]*) ACPX_TIMEOUT=7200 ;; esac
403
+
404
+ if [ "${1:-}" = "--emit-adapter" ]; then
405
+ WORKSPACE="<workspace>"
406
+ PROMPT_FILE="<prompt-file>"
407
+ MCP_CONFIG="<mcp-config>"
408
+ CLAUDE_WRAPPER="<claude-safe-mode-wrapper>"
409
+ GEMINI_HOME_DIR="<gemini-home>"
410
+ ROUTE="${2:-}"
411
+ EFFORT_REQUESTED="${CROSS_MODEL_EFFORT_OVERRIDE:-}"
412
+ validate_model_override "$ROUTE" || {
413
+ printf "model override '%s' not compatible with route '%s'\n" "${CE_WORK_MODEL_OVERRIDE:-}" "$ROUTE" >&2
414
+ exit 2
415
+ }
416
+ validate_effort_override "$ROUTE" || {
417
+ printf "effort override '%s' not compatible with route '%s'\n" "$EFFORT_REQUESTED" "$ROUTE" >&2
418
+ exit 2
419
+ }
420
+ adapter_argv "$ROUTE" >/dev/null 2>&1 || { printf "unknown route '%s'\n" "$ROUTE" >&2; exit 2; }
421
+ adapter_argv "$ROUTE" | tr '\0' ' '
422
+ printf '\n'
423
+ exit 0
424
+ fi
425
+
426
+ SIGN_IN_STALL_REASON="transport unavailable (pre-egress, route): Antigravity is waiting for a Google sign-in; the agy CLI's sign-in does not carry over, so sign the ACP runtime in once: put {\"auth\":{\"type\":\"oauth-personal\"}} in ${GEMINI_HOME:-~/.gemini}/antigravity-acp/settings.json, run npx acpx@$ACPX_VERSION antigravity exec ok in a terminal and finish the browser sign-in, then retry"
427
+
428
+ # --check-model antigravity <model>: acpx checks --model against the models the
429
+ # session advertises before any prompt, so opening a session answers whether the
430
+ # account offers the model. acpx records the session under $HOME/.acpx; the
431
+ # check deletes that record, and the directories when it created them. A scratch
432
+ # HOME is not an option: the runtime then never opens the session.
433
+ if [ "${1:-}" = "--check-model" ]; then
434
+ ROUTE="${2:-}"
435
+ CE_WORK_MODEL_OVERRIDE="${3:-}"
436
+ CE_WORK_MODEL_OVERRIDE_TARGET=antigravity
437
+ [ "$ROUTE" = antigravity ] || { printf "no model check for route '%s'\n" "$ROUTE" >&2; exit 2; }
438
+ validate_model_override antigravity && [ -n "$CE_WORK_MODEL_OVERRIDE" ] || {
439
+ printf "model '%s' not compatible with route 'antigravity'\n" "$CE_WORK_MODEL_OVERRIDE" >&2
440
+ exit 2
441
+ }
442
+ command -v jq >/dev/null 2>&1 || { echo "transport unavailable (pre-egress, shared): jq not found; the ACP stream is read with jq" >&2; exit 2; }
443
+ CHECK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/ce-work-model-check-XXXXXX")" || exit 2
444
+ CHECK_PID=""
445
+ ACPX_STATE="${HOME:-}/.acpx"
446
+ ACPX_STATE_EXISTED=false; [ -d "$ACPX_STATE" ] && ACPX_STATE_EXISTED=true
447
+ # Every exit path stops the detached acpx group (TERM, then KILL) and removes
448
+ # what the check created.
449
+ check_cleanup() {
450
+ local i
451
+ # Probe the whole group, not the leader: acpx can exit and leave the
452
+ # runtime running in its group.
453
+ if [ -n "$CHECK_PID" ] && kill -0 -- -"$CHECK_PID" 2>/dev/null; then
454
+ kill -TERM -- -"$CHECK_PID" 2>/dev/null
455
+ for i in 1 2 3 4 5; do kill -0 -- -"$CHECK_PID" 2>/dev/null || break; sleep 1; done
456
+ kill -KILL -- -"$CHECK_PID" 2>/dev/null || true
457
+ fi
458
+ rm -rf "$CHECK_DIR"
459
+ [ "$ACPX_STATE_EXISTED" = true ] || { rmdir "$ACPX_STATE/sessions" "$ACPX_STATE" 2>/dev/null || true; }
460
+ }
461
+ trap check_cleanup EXIT
462
+ trap 'exit 130' INT TERM HUP QUIT
463
+ mkdir "$CHECK_DIR/cwd" || exit 2
464
+ acpx_preflight antigravity "$CHECK_DIR/cwd" || {
465
+ printf 'transport unavailable (pre-egress, %s): %s\n' "$ACPX_SCOPE" "$ACPX_UNAVAILABLE" >&2
466
+ exit 2
467
+ }
468
+ GEMINI_HOME_DIR="$(acpx_antigravity_home "$CHECK_DIR")" || { echo "cannot prepare a private Antigravity home" >&2; exit 2; }
469
+ printf '{"mcpServers":[]}\n' > "$CHECK_DIR/mcp.json"
470
+ open_check_session() {
471
+ ARGS=()
472
+ while IFS= read -r -d '' token; do ARGS+=("$token"); done < <(
473
+ acpx_base_argv antigravity "$CHECK_DIR/cwd" "$CHECK_DIR/mcp.json" "$ACPX_TIMEOUT" "" "$GEMINI_HOME_DIR")
474
+ ARGS+=(--model "$CE_WORK_MODEL_OVERRIDE" antigravity sessions new --name "ce-work-model-check-$$")
475
+ set -m
476
+ (cd "$CHECK_DIR" && exec "${ARGS[@]}" < /dev/null > "$CHECK_DIR/stdout" 2> "$CHECK_DIR/stderr") &
477
+ CHECK_PID=$!
478
+ set +m
479
+ CHECK_DEADLINE=$(( $(date +%s) + ACPX_SESSION_START_SECS ))
480
+ while kill -0 "$CHECK_PID" 2>/dev/null && [ "$(date +%s)" -lt "$CHECK_DEADLINE" ]; do sleep 1; done
481
+ if kill -0 "$CHECK_PID" 2>/dev/null; then
482
+ echo "$SIGN_IN_STALL_REASON" >&2
483
+ exit 2
484
+ fi
485
+ wait "$CHECK_PID"
486
+ CHECK_EXIT=$?
487
+ }
488
+ open_check_session
489
+ if [ "$CHECK_EXIT" -ne 0 ] && acpx_stale_npm_metadata "$CHECK_DIR/stderr" "$CHECK_DIR/stdout"; then
490
+ log "npm resolved against stale cached package metadata; retrying the model check once with fresh metadata"
491
+ ACPX_NPM_REFRESH=true
492
+ open_check_session
493
+ fi
494
+ RECORD_ID="$(jq -r 'select(.action? == "session_ensured") | .acpxRecordId // empty' "$CHECK_DIR/stdout" 2>/dev/null | tail -1)"
495
+ case "$RECORD_ID" in ''|*[!A-Za-z0-9-]*) ;; *) rm -f "$ACPX_STATE/sessions/$RECORD_ID.json" ;; esac
496
+ [ "$CHECK_EXIT" -eq 0 ] && exit 0
497
+ REASON="$(grep -m1 '^npm error' "$CHECK_DIR/stderr" 2>/dev/null)"
498
+ if [ -n "$REASON" ]; then
499
+ printf 'transport unavailable (pre-egress, shared): %s\n' "${REASON:0:200}" >&2
500
+ else
501
+ REASON="$(acpx_failure_evidence "$CHECK_DIR/stdout")"
502
+ [ -n "$REASON" ] || REASON="$(grep -m1 -e 'did not advertise' -e 'Error' "$CHECK_DIR/stderr" 2>/dev/null)"
503
+ printf 'transport unavailable (pre-egress, route): %s\n' "${REASON:-acpx exited $CHECK_EXIT while opening a session}" >&2
504
+ fi
505
+ exit 2
506
+ fi
507
+
508
+ AUTHORIZATION="${1:-}"
509
+ WORKSPACE="${2:-}"
510
+ PACKET="${3:-}"
511
+ EXPECTED_PACKET_DIGEST="${4:-}"
512
+ RESULT_DIR="${5:-}"
513
+ [[ "$EXPECTED_PACKET_DIGEST" =~ ^[0-9a-f]{64}$ ]] || { log "expected packet digest must be lowercase SHA-256"; exit 2; }
514
+ [ -n "$AUTHORIZATION" ] || { log "controller authorization JSON path is required"; exit 2; }
515
+ [ -d "$WORKSPACE" ] || { log "workspace '$WORKSPACE' is not a directory"; exit 2; }
516
+ [ -f "$PACKET" ] && [ ! -L "$PACKET" ] || { log "unit packet '$PACKET' is not a regular non-link file"; exit 2; }
517
+ [ -d "$RESULT_DIR" ] && [ ! -L "$RESULT_DIR" ] || { log "result dir '$RESULT_DIR' is not a directory"; exit 2; }
518
+
519
+ DISPATCH_AUTHORIZATION="$AUTHORIZATION"
520
+ DISPATCH_WORKSPACE="$WORKSPACE"
521
+ DISPATCH_PACKET="$PACKET"
522
+ DISPATCH_RESULT_DIR="$RESULT_DIR"
523
+
524
+ MAX_PACKET_BYTES="${CE_WORK_MAX_PACKET_BYTES:-200000}"
525
+ case "$MAX_PACKET_BYTES" in ''|*[!0-9]*) MAX_PACKET_BYTES=200000 ;; esac
526
+
527
+ SKILL_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || exit 2
528
+ PERSONA="$SKILL_ROOT/references/agents/implementation-worker.md"
529
+ SCHEMA="$SKILL_ROOT/references/implementation-result-schema.json"
530
+ [ -f "$PERSONA" ] && [ -f "$SCHEMA" ] || { log "worker persona or result schema missing"; exit 2; }
531
+
532
+ SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/ce-work-adapter-XXXXXX")" || exit 2
533
+ chmod 700 "$SCRATCH"
534
+ PROMPT_FILE="$SCRATCH/prompt.md"
535
+ # acpx prints the ACP stream on stdout; npm and adapter diagnostics go to stderr.
536
+ RAW_STDOUT="$SCRATCH/stdout.log"
537
+ RAW_STDERR="$SCRATCH/stderr.log"
538
+ STREAM="$SCRATCH/stream.redacted"
539
+ REPLY_TEXT="$SCRATCH/reply.txt"
540
+ SERVED_MODELS="$SCRATCH/served-models"
541
+ MCP_CONFIG="$SCRATCH/mcp.json"
542
+ CLAUDE_WRAPPER=""
543
+ GEMINI_HOME_DIR=""
544
+ SESSION_STALL_MARKER="$SCRATCH/session-stalled"
545
+ RAW_LIMIT_MARKER="$SCRATCH/raw-output-limit"
546
+ PACKET_SNAPSHOT="$SCRATCH/unit-packet"
547
+ AUTH_VALUES="$SCRATCH/authorization-values"
548
+ RESULT_FILE="$RESULT_DIR/implementation-result.json"
549
+ LOG_FILE="$RESULT_DIR/adapter.log"
550
+ LOG_RETAINED=0
551
+ trap 'rm -rf "$SCRATCH"' EXIT
552
+
553
+ # The controller's create-exclusive authorization artifact is the production
554
+ # dispatch capability. Read it once through a no-follow descriptor, validate
555
+ # its exact route/model/packet contract, and derive every dispatch identity
556
+ # field from those bytes before constructing a prompt or invoking a model CLI.
557
+ "$PY" - "$AUTHORIZATION" "$EXPECTED_PACKET_DIGEST" "$AUTH_VALUES" <<'PY'
558
+ import json, os, re, stat, sys
559
+
560
+ source, expected_packet_digest, output = sys.argv[1:]
561
+ required = {
562
+ "schema_version", "run_id", "unit_id", "attempt_id", "route", "target", "harness",
563
+ "intermediaries", "model_requested", "restriction_posture",
564
+ "restrictions", "activity_posture", "packet_digest",
565
+ }
566
+ contracts = {
567
+ "codex": ("codex", "codex", [], "cooperative"),
568
+ "claude": ("claude", "claude", [], "cooperative"),
569
+ "grok-cli": ("grok", "grok", [], "cooperative"),
570
+ "cursor": ("cursor", "cursor-agent", [], "cooperative"),
571
+ "composer": ("composer", "cursor-agent", ["cursor"], "cooperative"),
572
+ "grok-cursor": ("grok", "cursor-agent", ["cursor"], "cooperative"),
573
+ "opencode": ("opencode", "opencode", [], "cooperative"),
574
+ "antigravity": ("antigravity", "antigravity", [], "cooperative"),
575
+ }
576
+
577
+ def fail(message):
578
+ raise ValueError(message)
579
+
580
+ def model_allowed(route, model):
581
+ if not isinstance(model, str) or not model or "\n" in model or "\r" in model:
582
+ return False
583
+ if route in ("cursor", "composer", "grok-cursor"):
584
+ match = re.fullmatch(r"([A-Za-z0-9][A-Za-z0-9._:/-]*)(?:\[[A-Za-z0-9._=,:-]*\])?", model)
585
+ if not match:
586
+ return False
587
+ base = match.group(1)
588
+ if route == "composer":
589
+ return bool(re.fullmatch(r"composer-[A-Za-z0-9._-]+", base))
590
+ if route == "grok-cursor":
591
+ return bool(re.fullmatch(r"(?:cursor-)?grok-[A-Za-z0-9._-]+", base))
592
+ lowered = base.lower()
593
+ return not (lowered in {"composer", "grok"} or lowered.startswith(("composer-", "grok-", "cursor-grok-")))
594
+ if route == "codex":
595
+ return model == "auto" or bool(re.fullmatch(r"(?:gpt-[A-Za-z0-9._-]+|o[0-9][A-Za-z0-9._-]*)", model))
596
+ if route == "claude":
597
+ return model in {"auto", "fable", "opus", "sonnet", "haiku"} or bool(re.fullmatch(r"claude-[A-Za-z0-9._-]+", model))
598
+ if route == "grok-cli":
599
+ return model == "auto" or bool(re.fullmatch(r"grok-[A-Za-z0-9._-]+", model))
600
+ if route == "opencode":
601
+ return model == "auto" or bool(re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9._-]+", model))
602
+ if route == "antigravity":
603
+ return bool(re.fullmatch(r"gemini-[A-Za-z0-9._-]+", model))
604
+ return False
605
+
606
+ try:
607
+ flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
608
+ fd = os.open(os.path.abspath(source), flags)
609
+ try:
610
+ before = os.fstat(fd)
611
+ if not stat.S_ISREG(before.st_mode):
612
+ fail("authorization is not a regular file")
613
+ geteuid = getattr(os, "geteuid", None)
614
+ if geteuid is not None and before.st_uid != geteuid():
615
+ fail("authorization is not owned by the current user")
616
+ if stat.S_IMODE(before.st_mode) != 0o600:
617
+ fail("authorization mode is not 0600")
618
+ if before.st_size > 64 * 1024:
619
+ fail("authorization exceeds 65536 bytes")
620
+ chunks, total = [], 0
621
+ while True:
622
+ part = os.read(fd, min(65536, 65537 - total))
623
+ if not part:
624
+ break
625
+ chunks.append(part)
626
+ total += len(part)
627
+ if total > 65536:
628
+ fail("authorization grew past 65536 bytes")
629
+ after = os.fstat(fd)
630
+ if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns, before.st_ctime_ns) != (
631
+ after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns, after.st_ctime_ns
632
+ ):
633
+ fail("authorization changed while being read")
634
+ finally:
635
+ os.close(fd)
636
+ try:
637
+ value = json.loads(b"".join(chunks))
638
+ except (ValueError, UnicodeDecodeError) as exc:
639
+ fail(f"authorization is malformed JSON: {exc}")
640
+ if not isinstance(value, dict) or set(value) - {"effort_requested"} != required:
641
+ fail("authorization keys do not match the exact controller schema")
642
+ if type(value["schema_version"]) is not int or value["schema_version"] != 1:
643
+ fail("authorization schema_version must be 1")
644
+ for key in ("run_id", "unit_id", "attempt_id"):
645
+ if not isinstance(value[key], str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,128}", value[key]) or not value[key].strip("."):
646
+ fail(f"authorization {key} is unsafe")
647
+ route = value["route"]
648
+ if route not in contracts:
649
+ fail("authorization route is unsupported")
650
+ target, harness, intermediaries, posture = contracts[route]
651
+ if (value["target"], value["harness"], value["intermediaries"], value["restriction_posture"]) != (target, harness, intermediaries, posture):
652
+ fail("authorization route identity or restriction posture is inconsistent")
653
+ if value["activity_posture"] not in {"incremental", "hard-only"}:
654
+ fail("authorization activity_posture is invalid")
655
+ restrictions = value["restrictions"]
656
+ if not isinstance(restrictions, list) or not all(isinstance(item, str) for item in restrictions):
657
+ fail("authorization restrictions must be a string list")
658
+ if not model_allowed(route, value["model_requested"]):
659
+ fail("authorization model is incompatible with the fixed route")
660
+ effort = value.get("effort_requested", "")
661
+ if "effort_requested" in value and (not isinstance(effort, str) or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,31}", effort)):
662
+ fail("authorization effort_requested is not a short plain token")
663
+ packet_digest = value["packet_digest"]
664
+ if not isinstance(packet_digest, str) or not re.fullmatch(r"[0-9a-f]{64}", packet_digest):
665
+ fail("authorization packet_digest is not lowercase SHA-256")
666
+ if packet_digest != expected_packet_digest:
667
+ fail("authorization packet digest does not match dispatch")
668
+ authorization_digest = __import__("hashlib").sha256(b"".join(chunks)).hexdigest()
669
+ fields = (
670
+ authorization_digest, value["run_id"], value["unit_id"], value["attempt_id"],
671
+ route, target, harness, value["model_requested"], value["activity_posture"], posture, effort,
672
+ )
673
+ out = os.open(output, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
674
+ try:
675
+ os.write(out, b"\0".join(item.encode() for item in fields) + b"\0")
676
+ finally:
677
+ os.close(out)
678
+ except (OSError, ValueError) as exc:
679
+ print(f"controller authorization rejected: {exc}", file=sys.stderr)
680
+ raise SystemExit(2)
681
+ PY
682
+ AUTH_EXIT=$?
683
+ [ "$AUTH_EXIT" -eq 0 ] || { log "controller authorization rejected"; exit 2; }
684
+
685
+ AUTH_FIELDS=()
686
+ while IFS= read -r -d '' field; do AUTH_FIELDS+=("$field"); done < "$AUTH_VALUES"
687
+ [ "${#AUTH_FIELDS[@]}" -eq 11 ] || { log "controller authorization projection is incomplete"; exit 2; }
688
+ OBSERVED_AUTH_DIGEST="${AUTH_FIELDS[0]}"
689
+ RUN_ID="${AUTH_FIELDS[1]}"
690
+ UNIT_ID="${AUTH_FIELDS[2]}"
691
+ ATTEMPT_ID="${AUTH_FIELDS[3]}"
692
+ ROUTE="${AUTH_FIELDS[4]}"
693
+ AUTH_TARGET="${AUTH_FIELDS[5]}"
694
+ AUTH_HARNESS="${AUTH_FIELDS[6]}"
695
+ MODEL_REQUESTED="${AUTH_FIELDS[7]}"
696
+ ACTIVITY_POSTURE="${AUTH_FIELDS[8]}"
697
+ RESTRICTION_POSTURE="${AUTH_FIELDS[9]}"
698
+ EFFORT_REQUESTED="${AUTH_FIELDS[10]}"
699
+ RUNNER_JOB_ID="${CE_PEER_JOB_ID:-}"
700
+ [[ "$RUNNER_JOB_ID" =~ ^[A-Za-z0-9._-]{1,128}$ && "$RUNNER_JOB_ID" =~ [A-Za-z0-9_-] ]] || {
701
+ log "runner job identity is missing or unsafe"
702
+ exit 2
703
+ }
704
+
705
+ # A valid JSON file is not itself dispatch authority. Prove the exact no-follow
706
+ # snapshot and every raw controller-returned path back to the controller before
707
+ # prompt construction. Only its AUTHORIZED status permits external egress.
708
+ CONTROLLER="$SKILL_ROOT/scripts/unit-workspace.py"
709
+ AUTH_RESPONSE="$("$PY" "$CONTROLLER" authorize-dispatch \
710
+ --authorization "$DISPATCH_AUTHORIZATION" \
711
+ --authorization-digest "$OBSERVED_AUTH_DIGEST" \
712
+ --workspace "$DISPATCH_WORKSPACE" \
713
+ --packet "$DISPATCH_PACKET" \
714
+ --packet-digest "$EXPECTED_PACKET_DIGEST" \
715
+ --result-dir "$DISPATCH_RESULT_DIR" \
716
+ --run-id "$RUN_ID" --unit-id "$UNIT_ID" --attempt-id "$ATTEMPT_ID" --job-id "$RUNNER_JOB_ID" 2>&1)"
717
+ CONTROLLER_EXIT=$?
718
+ AUTH_STATUS="${AUTH_RESPONSE%%$'\n'*}"
719
+ if [ "$CONTROLLER_EXIT" -ne 0 ] || [ "$AUTH_STATUS" != "AUTHORIZED" ]; then
720
+ [ -n "$AUTH_RESPONSE" ] && printf '%s\n' "$AUTH_RESPONSE" >&2
721
+ log "controller dispatch authorization failed"
722
+ exit 2
723
+ fi
724
+
725
+ # Canonicalize operational paths only after the handshake. The controller
726
+ # compares the raw paths it returned, including platform compatibility symlinks.
727
+ WORKSPACE="$(cd "$WORKSPACE" && pwd -P)" || exit 2
728
+ PACKET="$(cd "$(dirname "$PACKET")" && pwd -P)/$(basename "$PACKET")" || exit 2
729
+ RESULT_DIR="$(cd "$RESULT_DIR" && pwd -P)" || exit 2
730
+ case "$RESULT_DIR/" in "$WORKSPACE/"*) log "result dir must be outside the worker workspace"; exit 2 ;; esac
731
+ case "$PACKET" in "$WORKSPACE"/*) log "unit packet must be outside the worker workspace"; exit 2 ;; esac
732
+ git -C "$WORKSPACE" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { log "workspace is not a Git worktree"; exit 2; }
733
+ chmod 700 "$RESULT_DIR" 2>/dev/null || { log "result dir could not be made private"; exit 2; }
734
+ RESULT_DIR_IDENTITY="$("$PY" - "$RESULT_DIR" <<'PY'
735
+ import os, stat, sys
736
+
737
+ path = sys.argv[1]
738
+ flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
739
+ fd = os.open(path, flags)
740
+ try:
741
+ info = os.fstat(fd)
742
+ if not stat.S_ISDIR(info.st_mode):
743
+ raise OSError("result dir is not a directory")
744
+ print(f"{info.st_dev}:{info.st_ino}")
745
+ finally:
746
+ os.close(fd)
747
+ PY
748
+ )" || { log "result dir identity could not be captured"; exit 2; }
749
+
750
+ write_adapter_log() {
751
+ "$PY" -c '
752
+ import os, stat, sys
753
+
754
+ path, expected = sys.argv[1:]
755
+ dir_flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
756
+ try:
757
+ dir_fd = os.open(path, dir_flags)
758
+ try:
759
+ directory = os.fstat(dir_fd)
760
+ if not stat.S_ISDIR(directory.st_mode):
761
+ raise OSError("result dir is not a directory")
762
+ if f"{directory.st_dev}:{directory.st_ino}" != expected:
763
+ raise OSError("result dir identity changed during route")
764
+ file_flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
765
+ log_fd = os.open("adapter.log", file_flags, 0o600, dir_fd=dir_fd)
766
+ try:
767
+ target = os.fstat(log_fd)
768
+ if not stat.S_ISREG(target.st_mode):
769
+ raise OSError("adapter log is not a regular file")
770
+ os.fchmod(log_fd, 0o600)
771
+ while True:
772
+ chunk = sys.stdin.buffer.read(65536)
773
+ if not chunk:
774
+ break
775
+ view = memoryview(chunk)
776
+ while view:
777
+ view = view[os.write(log_fd, view):]
778
+ finally:
779
+ os.close(log_fd)
780
+ finally:
781
+ os.close(dir_fd)
782
+ except OSError as error:
783
+ print(f"adapter log retention refused: {error}", file=sys.stderr)
784
+ raise SystemExit(2)
785
+ ' "$RESULT_DIR" "$RESULT_DIR_IDENTITY"
786
+ }
787
+
788
+ write_result_receipt() {
789
+ "$PY" -c '
790
+ import os, secrets, stat, sys
791
+
792
+ path, expected = sys.argv[1:]
793
+ dir_flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
794
+ dir_fd = None
795
+ receipt_fd = None
796
+ tmp_name = None
797
+ try:
798
+ data = sys.stdin.buffer.read()
799
+ dir_fd = os.open(path, dir_flags)
800
+ directory = os.fstat(dir_fd)
801
+ if not stat.S_ISDIR(directory.st_mode):
802
+ raise OSError("result dir is not a directory")
803
+ if f"{directory.st_dev}:{directory.st_ino}" != expected:
804
+ raise OSError("result dir identity changed during route")
805
+ file_flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
806
+ for _ in range(128):
807
+ candidate = f".result-{os.getpid()}-{secrets.token_hex(8)}"
808
+ try:
809
+ receipt_fd = os.open(candidate, file_flags, 0o600, dir_fd=dir_fd)
810
+ tmp_name = candidate
811
+ break
812
+ except FileExistsError:
813
+ continue
814
+ if receipt_fd is None:
815
+ raise OSError("could not reserve a result receipt temporary file")
816
+ target = os.fstat(receipt_fd)
817
+ if not stat.S_ISREG(target.st_mode):
818
+ raise OSError("result receipt temporary file is not regular")
819
+ os.fchmod(receipt_fd, 0o600)
820
+ view = memoryview(data)
821
+ while view:
822
+ view = view[os.write(receipt_fd, view):]
823
+ os.close(receipt_fd)
824
+ receipt_fd = None
825
+ os.replace(
826
+ tmp_name,
827
+ "implementation-result.json",
828
+ src_dir_fd=dir_fd,
829
+ dst_dir_fd=dir_fd,
830
+ )
831
+ tmp_name = None
832
+ except OSError as error:
833
+ print(f"result receipt publication refused: {error}", file=sys.stderr)
834
+ raise SystemExit(2)
835
+ finally:
836
+ if receipt_fd is not None:
837
+ os.close(receipt_fd)
838
+ if tmp_name is not None and dir_fd is not None:
839
+ try:
840
+ os.unlink(tmp_name, dir_fd=dir_fd)
841
+ except OSError:
842
+ pass
843
+ if dir_fd is not None:
844
+ os.close(dir_fd)
845
+ ' "$RESULT_DIR" "$RESULT_DIR_IDENTITY"
846
+ }
847
+
848
+ # Read the packet once through a no-follow descriptor, hash those exact bytes,
849
+ # and build the prompt from the private snapshot. The controller-provided
850
+ # digest is therefore bound to the content that actually crosses the route.
851
+ OBSERVED_PACKET_DIGEST="$("$PY" - "$PACKET" "$PACKET_SNAPSHOT" "$MAX_PACKET_BYTES" <<'PY'
852
+ import hashlib, os, stat, sys
853
+
854
+ source, snapshot, raw_cap = sys.argv[1:]
855
+ cap = int(raw_cap)
856
+ flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
857
+ fd = os.open(source, flags)
858
+ try:
859
+ info = os.fstat(fd)
860
+ if not stat.S_ISREG(info.st_mode):
861
+ raise OSError("unit packet is not a regular file")
862
+ chunks, total = [], 0
863
+ while True:
864
+ chunk = os.read(fd, min(65536, cap + 1 - total))
865
+ if not chunk:
866
+ break
867
+ chunks.append(chunk)
868
+ total += len(chunk)
869
+ if total > cap:
870
+ raise OSError(f"unit packet exceeds {cap} bytes")
871
+ finally:
872
+ os.close(fd)
873
+ data = b"".join(chunks)
874
+ out = os.open(snapshot, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
875
+ try:
876
+ view = memoryview(data)
877
+ while view:
878
+ written = os.write(out, view)
879
+ view = view[written:]
880
+ finally:
881
+ os.close(out)
882
+ print(hashlib.sha256(data).hexdigest())
883
+ PY
884
+ )" || { log "unit packet could not be snapshotted safely"; exit 2; }
885
+ [ "$OBSERVED_PACKET_DIGEST" = "$EXPECTED_PACKET_DIGEST" ] || {
886
+ log "unit packet digest mismatch (expected $EXPECTED_PACKET_DIGEST, observed $OBSERVED_PACKET_DIGEST)"
887
+ exit 2
888
+ }
889
+
890
+ redact_stream() {
891
+ CE_WORK_REDACT_FILE="${CE_WORK_REDACT_FILE:-}" "$PY" -c '
892
+ import json, os, sys
893
+
894
+ # The ACP stream is JSON, so a value is also matched in its escaped forms.
895
+ def forms(value):
896
+ yield value
897
+ try:
898
+ text = value.decode("utf-8")
899
+ except UnicodeDecodeError:
900
+ return
901
+ yield json.dumps(text, ensure_ascii=False)[1:-1].encode("utf-8")
902
+ yield json.dumps(text)[1:-1].encode("ascii")
903
+
904
+ p = os.environ.get("CE_WORK_REDACT_FILE", "")
905
+ if p:
906
+ try:
907
+ values = sorted(
908
+ {form for v in open(p, "rb").read().splitlines() if v for form in forms(v)},
909
+ key=lambda value: (-len(value), value),
910
+ )
911
+ except OSError:
912
+ values = []
913
+ else:
914
+ values = []
915
+
916
+ def emit(data):
917
+ while data:
918
+ written = os.write(sys.stdout.fileno(), data)
919
+ data = data[written:]
920
+
921
+ pending = b""
922
+ max_value_bytes = max((len(value) for value in values), default=1)
923
+ try:
924
+ if not values:
925
+ while True:
926
+ chunk = os.read(sys.stdin.fileno(), 65536)
927
+ if not chunk:
928
+ break
929
+ emit(chunk)
930
+ sys.exit(0)
931
+
932
+ while True:
933
+ chunk = os.read(sys.stdin.fileno(), 65536)
934
+ if not chunk:
935
+ break
936
+ pending += chunk
937
+ offset = 0
938
+ output = bytearray()
939
+ while len(pending) - offset >= max_value_bytes:
940
+ match = next((value for value in values if pending.startswith(value, offset)), None)
941
+ if match is not None:
942
+ output.extend(b"[REDACTED]")
943
+ offset += len(match)
944
+ else:
945
+ output.append(pending[offset])
946
+ offset += 1
947
+ emit(bytes(output))
948
+ pending = pending[offset:]
949
+ offset = 0
950
+ output = bytearray()
951
+ while offset < len(pending):
952
+ match = next((value for value in values if pending.startswith(value, offset)), None)
953
+ if match is not None:
954
+ output.extend(b"[REDACTED]")
955
+ offset += len(match)
956
+ else:
957
+ output.append(pending[offset])
958
+ offset += 1
959
+ emit(bytes(output))
960
+ except BrokenPipeError:
961
+ os._exit(0)
962
+ '
963
+ }
964
+
965
+ cap_stream() {
966
+ "$PY" -c '
967
+ import os, sys
968
+
969
+ remaining = int(sys.argv[1])
970
+ while True:
971
+ chunk = os.read(sys.stdin.fileno(), 65536)
972
+ if not chunk:
973
+ break
974
+ if remaining:
975
+ retained = chunk[:remaining]
976
+ while retained:
977
+ written = os.write(sys.stdout.fileno(), retained)
978
+ retained = retained[written:]
979
+ remaining -= min(len(chunk), remaining)
980
+ ' "$MAX_RAW_BYTES"
981
+ }
982
+
983
+ {
984
+ cat "$PERSONA"
985
+ if [ "$ROUTE" = codex ]; then
986
+ printf '\n\nSocket binds, OS permission checks, peer credentials, and similar capability probes are host-owned. Preserve the host command and observed result; do not treat a sandbox EPERM as proof the host lacks the capability.\n'
987
+ fi
988
+ printf '\n\nThe required final-result JSON schema is:\n\n'
989
+ cat "$SCHEMA"
990
+ printf '\n\n--- BOUNDED IMPLEMENTATION UNIT PACKET ---\n\n'
991
+ redact_stream < "$PACKET_SNAPSHOT"
992
+ } > "$PROMPT_FILE"
993
+ chmod 600 "$PROMPT_FILE"
994
+
995
+ TARGET="$AUTH_TARGET"
996
+ HARNESS="$AUTH_HARNESS"
997
+
998
+ publish_unavailable() {
999
+ local reason terminal_status="${2:-unavailable}" actual_route="${3:-}"
1000
+ # Failure evidence can quote the stream, so it is redacted like the log.
1001
+ reason="$(printf '%s' "$1" | redact_stream)"
1002
+ if [ "$LOG_RETAINED" -ne 1 ]; then
1003
+ printf '%s\n' "$reason" | write_adapter_log || {
1004
+ log "result dir or adapter log identity changed during route"
1005
+ exit 2
1006
+ }
1007
+ LOG_RETAINED=1
1008
+ fi
1009
+ "$PY" - "$ROUTE" "$TARGET" "$HARNESS" "$MODEL_REQUESTED" "$EXPECTED_PACKET_DIGEST" "$LOG_FILE" "$reason" "$ACTIVITY_POSTURE" "$RESTRICTION_POSTURE" "$terminal_status" "$actual_route" "$EFFORT_REQUESTED" <<'PY' | write_result_receipt
1010
+ import json, sys
1011
+ route, target, harness, requested, packet_digest, log, reason, activity, restriction, terminal_status, actual_route, effort = sys.argv[1:]
1012
+ value = {
1013
+ "schema_version": 1, "terminal_status": terminal_status,
1014
+ "summary": "External route failed after launch" if terminal_status == "failed" else "External route unavailable",
1015
+ "changed_files": [], "evidence": [], "scope_expansion": None,
1016
+ "requested_route": route, "actual_route": actual_route or None, "target": target, "harness": harness,
1017
+ "intermediaries": ["cursor"] if route in ("composer", "grok-cursor") else [],
1018
+ "model_requested": requested, "model_actual": "unverified", "model_receipt_status": "unverified",
1019
+ "effort_requested": effort or None,
1020
+ "packet_digest": packet_digest,
1021
+ "activity_posture": activity, "restriction_posture": restriction,
1022
+ "failure_reason": reason, "raw_log": log,
1023
+ }
1024
+ json.dump(value, sys.stdout, indent=2)
1025
+ sys.stdout.write("\n")
1026
+ PY
1027
+ }
1028
+
1029
+ if [ "${CE_WORK_REQUIRE_ENFORCED_CONFINEMENT:-}" = "1" ] && [ "$RESTRICTION_POSTURE" != adapter-enforced ]; then
1030
+ publish_unavailable "route offers cooperative workspace restriction, not required enforceable confinement" || exit 2
1031
+ exit 2
1032
+ fi
1033
+
1034
+ validate_effort_override "$ROUTE" || {
1035
+ publish_unavailable "effort override '$EFFORT_REQUESTED' not compatible with route '$ROUTE'" || exit 2
1036
+ exit 2
1037
+ }
1038
+
1039
+ # The Codex desktop app (Codex.app, or ChatGPT.app since the July 2026 merger)
1040
+ # ships `codex` at Contents/Resources without linking it onto PATH (#1272).
1041
+ # Append, never prepend, so a PATH-installed CLI stays authoritative.
1042
+ # CROSS_MODEL_CODEX_APP_DIRS (colon-separated) overrides the probed dirs.
1043
+ if ! command -v codex >/dev/null 2>&1; then
1044
+ OLDIFS="$IFS"; IFS=':'
1045
+ for d in ${CROSS_MODEL_CODEX_APP_DIRS-"${HOME:-}/Applications/ChatGPT.app/Contents/Resources:/Applications/ChatGPT.app/Contents/Resources:${HOME:-}/Applications/Codex.app/Contents/Resources:/Applications/Codex.app/Contents/Resources"}; do
1046
+ if [ -n "$d" ] && [ -x "$d/codex" ]; then PATH="${PATH:+$PATH:}$d"; export PATH; break; fi
1047
+ done
1048
+ IFS="$OLDIFS"
1049
+ fi
1050
+
1051
+ # Nothing is sent to a provider before this point.
1052
+ if ! command -v jq >/dev/null 2>&1; then
1053
+ publish_unavailable "transport unavailable (pre-egress, shared): jq not found; the ACP stream is read with jq" || exit 2
1054
+ exit 2
1055
+ fi
1056
+ acpx_preflight "$ROUTE" "$WORKSPACE" || {
1057
+ publish_unavailable "transport unavailable (pre-egress, $ACPX_SCOPE): $ACPX_UNAVAILABLE" || exit 2
1058
+ exit 2
1059
+ }
1060
+ if [ "$ROUTE" = claude ]; then
1061
+ CLAUDE_WRAPPER="$(acpx_claude_wrapper "$SCRATCH")" || {
1062
+ publish_unavailable "transport unavailable (pre-egress, route): cannot prepare the Claude --safe-mode launcher" || exit 2
1063
+ exit 2
1064
+ }
1065
+ fi
1066
+ if [ "$ROUTE" = antigravity ]; then
1067
+ GEMINI_HOME_DIR="$(acpx_antigravity_home "$SCRATCH")" || {
1068
+ publish_unavailable "transport unavailable (pre-egress, route): cannot prepare a private Antigravity home" || exit 2
1069
+ exit 2
1070
+ }
1071
+ fi
1072
+ printf '{"mcpServers":[]}\n' > "$MCP_CONFIG"
1073
+
1074
+ ARGS=()
1075
+ while IFS= read -r -d '' token; do ARGS+=("$token"); done < <(adapter_argv "$ROUTE")
1076
+
1077
+ MIN_ENV=(env -i "PATH=$PATH" "PYTHONDONTWRITEBYTECODE=1")
1078
+ [ -n "${HOME:-}" ] && MIN_ENV+=("HOME=$HOME")
1079
+ [ -n "${USER:-}" ] && MIN_ENV+=("USER=$USER")
1080
+ [ -n "${TMPDIR:-}" ] && MIN_ENV+=("TMPDIR=$TMPDIR")
1081
+ [ -n "${LANG:-}" ] && MIN_ENV+=("LANG=$LANG")
1082
+ [ -n "${LC_ALL:-}" ] && MIN_ENV+=("LC_ALL=$LC_ALL")
1083
+ [ -n "${XDG_CONFIG_HOME:-}" ] && MIN_ENV+=("XDG_CONFIG_HOME=$XDG_CONFIG_HOME")
1084
+ # npx reaches the user's npm cache, registry, and user config by these
1085
+ # locations. npm auth token values are never forwarded; npm reads its own
1086
+ # config file for them.
1087
+ for name in npm_config_cache NPM_CONFIG_CACHE npm_config_registry NPM_CONFIG_REGISTRY npm_config_userconfig NPM_CONFIG_USERCONFIG; do
1088
+ [ -n "${!name:-}" ] && MIN_ENV+=("$name=${!name}")
1089
+ done
1090
+ # Preserve route-specific config-directory pointers so existing CLI-native login
1091
+ # remains reachable. Credential-bearing API-key variables are intentionally not
1092
+ # forwarded; the worker gets paths to the CLI's own auth store, not secrets.
1093
+ case "$ROUTE" in
1094
+ codex) [ -n "${CODEX_HOME:-}" ] && MIN_ENV+=("CODEX_HOME=$CODEX_HOME") ;;
1095
+ claude) [ -n "${CLAUDE_CONFIG_DIR:-}" ] && MIN_ENV+=("CLAUDE_CONFIG_DIR=$CLAUDE_CONFIG_DIR") ;;
1096
+ grok-cli) [ -n "${GROK_CONFIG_HOME:-}" ] && MIN_ENV+=("GROK_CONFIG_HOME=$GROK_CONFIG_HOME") ;;
1097
+ opencode)
1098
+ [ -n "${OPENCODE_CONFIG_DIR:-}" ] && MIN_ENV+=("OPENCODE_CONFIG_DIR=$OPENCODE_CONFIG_DIR")
1099
+ [ -n "${OPENCODE_CONFIG:-}" ] && MIN_ENV+=("OPENCODE_CONFIG=$OPENCODE_CONFIG")
1100
+ ;;
1101
+ cursor|composer|grok-cursor)
1102
+ [ -n "${CURSOR_CONFIG_DIR:-}" ] && MIN_ENV+=("CURSOR_CONFIG_DIR=$CURSOR_CONFIG_DIR")
1103
+ ;;
1104
+ esac
1105
+
1106
+ ACTIVITY_POLL_SECS="${CE_WORK_ACTIVITY_POLL_SECS:-15}"
1107
+ case "$ACTIVITY_POLL_SECS" in ''|*[!0-9]*) ACTIVITY_POLL_SECS=15 ;; esac
1108
+ [ "$ACTIVITY_POLL_SECS" -lt 1 ] && ACTIVITY_POLL_SECS=1
1109
+ MAX_RAW_BYTES="${CE_WORK_MAX_RAW_BYTES:-10485760}"
1110
+ case "$MAX_RAW_BYTES" in ''|*[!0-9]*) MAX_RAW_BYTES=10485760 ;; esac
1111
+ [ "$MAX_RAW_BYTES" -lt 1 ] && MAX_RAW_BYTES=10485760
1112
+
1113
+ raw_byte_count() {
1114
+ local total=0 bytes file
1115
+ for file in "$RAW_STDOUT" "$RAW_STDERR"; do
1116
+ [ -f "$file" ] || continue
1117
+ bytes="$(wc -c < "$file" | tr -d '[:space:]')"
1118
+ case "$bytes" in ''|*[!0-9]*) bytes=0 ;; esac
1119
+ total=$((total + bytes))
1120
+ done
1121
+ printf '%s' "$total"
1122
+ }
1123
+
1124
+ ACTIVE_ROUTE_PID=""
1125
+ ACTIVITY_PID=""
1126
+ # The route runs in its own process group (set -m below). npm launches acpx through
1127
+ # `sh -c`, and a shell that does not exec its command (Ubuntu's dash) would stop a
1128
+ # leader-only TERM short of acpx and the agent, so signal the whole group.
1129
+ stop_route() {
1130
+ kill -TERM -- -"$1" 2>/dev/null || kill -TERM "$1" 2>/dev/null || true
1131
+ }
1132
+ terminate_route() {
1133
+ [ -n "$ACTIVITY_PID" ] && kill "$ACTIVITY_PID" 2>/dev/null || true
1134
+ [ -n "$ACTIVE_ROUTE_PID" ] && stop_route "$ACTIVE_ROUTE_PID"
1135
+ [ -n "$ACTIVE_ROUTE_PID" ] && wait "$ACTIVE_ROUTE_PID" 2>/dev/null || true
1136
+ rm -rf "$SCRATCH"
1137
+ exit 143
1138
+ }
1139
+ trap 'terminate_route' TERM INT
1140
+
1141
+ set +e
1142
+ # npx resolves packages from its working directory's node_modules and .npmrc first;
1143
+ # acpx gets the agent's cwd from --cwd, so start it from private scratch.
1144
+ launch_route() {
1145
+ set -m
1146
+ (cd "$SCRATCH" && exec "${MIN_ENV[@]}" "${ARGS[@]}" < /dev/null > "$RAW_STDOUT" 2> "$RAW_STDERR") &
1147
+ ACTIVE_ROUTE_PID=$!
1148
+ set +m
1149
+ (
1150
+ # A foreground sleep would outlive this subshell's TERM and hold the script's
1151
+ # output open, so callers (bun 1.4 spawnSync) would wait out the poll interval.
1152
+ # Kill every background job, so a TERM between `sleep &` and `$!` still reaps it.
1153
+ trap 'kill $(jobs -p) 2>/dev/null; exit 0' TERM
1154
+ previous=0
1155
+ session=open
1156
+ [ "$ROUTE" = antigravity ] && session=starting
1157
+ started="$(date +%s)"
1158
+ while kill -0 "$ACTIVE_ROUTE_PID" 2>/dev/null; do
1159
+ current="$(raw_byte_count)"
1160
+ if [ "$current" -gt "$MAX_RAW_BYTES" ]; then
1161
+ : > "$RAW_LIMIT_MARKER"
1162
+ log "activity route=$ROUTE raw-output-limit bytes=$current cap=$MAX_RAW_BYTES"
1163
+ stop_route "$ACTIVE_ROUTE_PID"
1164
+ break
1165
+ fi
1166
+ if [ "$session" != open ] && [ $(( $(date +%s) - started )) -ge "$ACPX_SESSION_START_SECS" ]; then
1167
+ session="$(acpx_session_state "$RAW_STDOUT")"
1168
+ if [ "$session" = pending ]; then
1169
+ : > "$SESSION_STALL_MARKER"
1170
+ log "Antigravity opened no session within ${ACPX_SESSION_START_SECS}s; stopping the route"
1171
+ stop_route "$ACTIVE_ROUTE_PID"
1172
+ break
1173
+ fi
1174
+ fi
1175
+ if [ "$current" != "$previous" ]; then
1176
+ log "activity route=$ROUTE output-updated"
1177
+ previous="$current"
1178
+ fi
1179
+ sleep "$ACTIVITY_POLL_SECS" &
1180
+ wait $!
1181
+ done
1182
+ ) &
1183
+ ACTIVITY_PID=$!
1184
+ wait "$ACTIVE_ROUTE_PID"
1185
+ ROUTE_EXIT=$?
1186
+ kill "$ACTIVITY_PID" 2>/dev/null || true
1187
+ wait "$ACTIVITY_PID" 2>/dev/null || true
1188
+ ACTIVE_ROUTE_PID=""
1189
+ ACTIVITY_PID=""
1190
+ }
1191
+ LAUNCH_STARTED="$(date +%s)"
1192
+ launch_route
1193
+ if acpx_stale_npm_metadata "$RAW_STDERR" "$RAW_STDOUT" && [ "$(acpx_outcome "$RAW_STDOUT")" = not-sent ]; then
1194
+ REMAINING=$(( ACPX_TIMEOUT - ( $(date +%s) - LAUNCH_STARTED ) ))
1195
+ if [ "$REMAINING" -gt 0 ]; then
1196
+ log "npm resolved against stale cached package metadata; retrying the same route once with fresh metadata in the ${REMAINING}s left (nothing was sent)"
1197
+ ACPX_NPM_REFRESH=true
1198
+ ACPX_TIMEOUT="$REMAINING"
1199
+ ARGS=()
1200
+ while IFS= read -r -d '' token; do ARGS+=("$token"); done < <(adapter_argv "$ROUTE")
1201
+ launch_route
1202
+ fi
1203
+ fi
1204
+ RAW_BYTES="$(raw_byte_count)"
1205
+ [ "$RAW_BYTES" -gt "$MAX_RAW_BYTES" ] && : > "$RAW_LIMIT_MARKER"
1206
+ # acpx echoes the outbound prompt and the contents of files the agent reads, so
1207
+ # everything retained or published comes from the redacted copy. The protocol is
1208
+ # parsed from the private raw stream: a redaction value can collide with ACP text.
1209
+ redact_stream < "$RAW_STDOUT" > "$STREAM"
1210
+ {
1211
+ cat "$STREAM"
1212
+ redact_stream < "$RAW_STDERR"
1213
+ } | cap_stream | write_adapter_log || {
1214
+ log "result dir or adapter log identity changed during route"
1215
+ exit 2
1216
+ }
1217
+ LOG_RETAINED=1
1218
+
1219
+ if [ -f "$RAW_LIMIT_MARKER" ]; then
1220
+ publish_unavailable "fixed route raw output exceeded ${MAX_RAW_BYTES} bytes" || exit 2
1221
+ exit 1
1222
+ fi
1223
+
1224
+ # The run's outcome is its own prompt's result, not acpx's exit code.
1225
+ OUTCOME="$(acpx_outcome "$RAW_STDOUT")"
1226
+ case "$OUTCOME" in
1227
+ end_turn) ;;
1228
+ not-sent)
1229
+ # Nothing reached the provider: npm could not fetch acpx (every route fails
1230
+ # alike), or the adapter refused before the prompt (this route only).
1231
+ REASON="$(grep -m1 '^npm error' "$RAW_STDERR" 2>/dev/null)"
1232
+ if [ -n "$REASON" ]; then
1233
+ REASON="transport unavailable (pre-egress, shared): ${REASON:0:200}"
1234
+ elif [ -f "$SESSION_STALL_MARKER" ]; then
1235
+ REASON="$SIGN_IN_STALL_REASON"
1236
+ else
1237
+ node_line="$(acpx_node_startup_line "$RAW_STDERR")"
1238
+ if [ -n "$node_line" ]; then
1239
+ REASON="transport unavailable (pre-egress, route): ${node_line:0:200}"
1240
+ else
1241
+ REASON="$(acpx_failure_evidence "$STREAM")"
1242
+ REASON="transport unavailable (pre-egress, route): ${REASON:-acpx exited $ROUTE_EXIT before sending the prompt}"
1243
+ fi
1244
+ fi
1245
+ publish_unavailable "$REASON" || exit 2
1246
+ exit 2
1247
+ ;;
1248
+ *)
1249
+ REASON="$(acpx_permission_cancel_reason "$RAW_STDOUT" "$ROUTE_EXIT")"
1250
+ [ -n "$REASON" ] || REASON="$(acpx_failure_evidence "$STREAM")"
1251
+ publish_unavailable "fixed route ended with $OUTCOME (acpx exit $ROUTE_EXIT)${REASON:+: $REASON}" failed "$ROUTE" || exit 2
1252
+ exit 1
1253
+ ;;
1254
+ esac
1255
+
1256
+ # The reply and served models are redacted with the rest of the receipt below.
1257
+ acpx_text "$RAW_STDOUT" "$REPLY_TEXT"
1258
+ acpx_served_models "$RAW_STDOUT" > "$SERVED_MODELS"
1259
+ set +e
1260
+ CE_WORK_REDACT_FILE="${CE_WORK_REDACT_FILE:-}" "$PY" - \
1261
+ "$REPLY_TEXT" "$SERVED_MODELS" "$ROUTE" "$TARGET" "$HARNESS" \
1262
+ "$MODEL_REQUESTED" "$EXPECTED_PACKET_DIGEST" "$LOG_FILE" "$ACTIVITY_POSTURE" "$RESTRICTION_POSTURE" "$EFFORT_REQUESTED" \
1263
+ "$(acpx_reply_evidence "$RAW_STDOUT")" "$(acpx_advertised_models "$RAW_STDOUT")" <<'PY' | write_result_receipt
1264
+ import json, os, re, sys
1265
+ reply, served_models, route, target, harness, requested, packet_digest, log, activity, restriction, effort, reply_evidence, advertised = sys.argv[1:]
1266
+
1267
+ def redactions():
1268
+ p=os.environ.get("CE_WORK_REDACT_FILE", "")
1269
+ if not p: return []
1270
+ try: return sorted(set(v for v in open(p, encoding="utf-8").read().splitlines() if v), key=lambda value: (-len(value), value))
1271
+ except OSError: return []
1272
+
1273
+ redaction_values=redactions()
1274
+
1275
+ def redact(value):
1276
+ if isinstance(value,str):
1277
+ for secret in redaction_values: value=value.replace(secret, "[REDACTED]")
1278
+ return value
1279
+ if isinstance(value,list): return [redact(child) for child in value]
1280
+ if isinstance(value,dict): return {key:redact(child) for key,child in value.items()}
1281
+ return value
1282
+
1283
+ def parse_text(text):
1284
+ found=[]
1285
+ decoder=json.JSONDecoder()
1286
+ def inspect(value):
1287
+ if isinstance(value,dict):
1288
+ if all(k in value for k in ("terminal_status","summary","changed_files","evidence","scope_expansion")):
1289
+ found.append(value)
1290
+ for child in value.values(): inspect(child)
1291
+ elif isinstance(value,list):
1292
+ for child in value: inspect(child)
1293
+ elif isinstance(value,str):
1294
+ inner=re.sub(r"^```(?:json)?\s*|\s*```$", "", value.strip(), flags=re.S)
1295
+ for i,ch in enumerate(inner):
1296
+ if ch not in "[{": continue
1297
+ try:
1298
+ child,_=decoder.raw_decode(inner,i); inspect(child)
1299
+ except Exception: pass
1300
+ inspect(text)
1301
+ for line in text.splitlines():
1302
+ try: inspect(json.loads(line))
1303
+ except Exception: pass
1304
+ return found[-1] if found else None
1305
+
1306
+ def normalize_served_model(value):
1307
+ # Strip terminal control sequences before validating the receipt token;
1308
+ # never publish a partially sanitized or otherwise unsafe identity.
1309
+ text=str(value)
1310
+ text=re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", text)
1311
+ text=re.sub(r"\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)", "", text)
1312
+ text="".join(ch for ch in text if ord(ch) >= 0x20 and ord(ch) != 0x7f).strip()
1313
+ return text if 0 < len(text) <= 128 else "unverified"
1314
+
1315
+ try: text=open(reply, encoding="utf-8", errors="replace").read()
1316
+ except OSError: text=""
1317
+ worker=parse_text(text)
1318
+ valid=isinstance(worker,dict)
1319
+ worker_fields=("terminal_status", "summary", "changed_files", "evidence", "scope_expansion")
1320
+ if valid:
1321
+ valid=(set(worker) == set(worker_fields)
1322
+ and worker.get("terminal_status") in ("completed","blocked","scope_expansion")
1323
+ and isinstance(worker.get("summary"),str) and bool(worker["summary"])
1324
+ and isinstance(worker.get("changed_files"),list) and all(isinstance(x,str) and x for x in worker["changed_files"])
1325
+ and isinstance(worker.get("evidence"),list) and all(isinstance(x,str) and x for x in worker["evidence"])
1326
+ and ((worker["terminal_status"]=="scope_expansion" and isinstance(worker.get("scope_expansion"),dict))
1327
+ or (worker["terminal_status"]!="scope_expansion" and worker.get("scope_expansion") is None)))
1328
+
1329
+ # The adapter's _meta report is its own assertion of the served model, so a
1330
+ # match is recorded as "asserted", never "verified". Requested and served ids
1331
+ # are compared on the model id before any Cursor preset bracket; an alias
1332
+ # names its Claude family. An auxiliary model (Claude's Haiku) may also be
1333
+ # reported, so a served id in the requested family wins, else the heaviest.
1334
+ served=[]
1335
+ try:
1336
+ for line in open(served_models, encoding="utf-8", errors="replace").read().splitlines():
1337
+ model, _, tokens = line.rpartition(" ")
1338
+ model=normalize_served_model(model)
1339
+ if model != "unverified":
1340
+ served.append((model, int(tokens) if tokens.isdigit() else 0))
1341
+ except OSError:
1342
+ pass
1343
+ model_base=lambda value: value.split("[",1)[0].lower()
1344
+ family=model_base(requested)
1345
+ if family in ("fable","opus","sonnet","haiku"): family="claude-"+family
1346
+ in_family=[m for m,_ in served if model_base(m)==family or model_base(m).startswith(family+"-")]
1347
+ if not served: actual, receipt = "unverified", "unverified"
1348
+ elif requested == "auto": actual, receipt = max(served, key=lambda item: item[1])[0], "asserted"
1349
+ elif in_family: actual, receipt = in_family[0], "asserted"
1350
+ else: actual, receipt = max(served, key=lambda item: item[1])[0], "mismatch"
1351
+
1352
+ intermediaries=["cursor"] if route in ("composer","grok-cursor") else []
1353
+ base_receipt={
1354
+ "schema_version":1,
1355
+ "requested_route":route, "actual_route":route, "target":target, "harness":harness,
1356
+ "intermediaries":intermediaries, "model_requested":requested, "model_actual":actual,
1357
+ "model_receipt_status":receipt, "effort_requested":effort or None, "activity_posture":activity,
1358
+ "packet_digest":packet_digest,
1359
+ "restriction_posture":restriction,
1360
+ "failure_reason":None, "raw_log":log,
1361
+ }
1362
+ if valid:
1363
+ base_receipt.update({key:worker[key] for key in worker_fields})
1364
+ else:
1365
+ base_receipt.update({"terminal_status":"failed", "summary":"Adapter terminal output failed result schema",
1366
+ "changed_files":[], "evidence":[], "scope_expansion":None,
1367
+ "failure_reason":"; ".join(["terminal output failed implementation result schema"]
1368
+ + ([f"reply: {reply_evidence}"] if reply_evidence else [])
1369
+ + ([f"advertised models: {advertised}"] if advertised else []))})
1370
+ base_receipt=redact(base_receipt)
1371
+ json.dump(base_receipt,sys.stdout,indent=2)
1372
+ sys.stdout.write("\n")
1373
+ sys.exit(0 if valid else 4)
1374
+ PY
1375
+ NORMALIZE_STATUSES=("${PIPESTATUS[@]}")
1376
+ NORMALIZE_EXIT="${NORMALIZE_STATUSES[0]}"
1377
+ PUBLISH_EXIT="${NORMALIZE_STATUSES[1]}"
1378
+ if [ "$PUBLISH_EXIT" -ne 0 ]; then exit 2; fi
1379
+ if [ "$NORMALIZE_EXIT" -ne 0 ]; then exit 1; fi
1380
+
1381
+ TERMINAL_STATUS="$("$PY" -c 'import json,sys; print(json.load(open(sys.argv[1]))["terminal_status"])' "$RESULT_FILE")"
1382
+ case "$TERMINAL_STATUS" in
1383
+ completed|blocked|scope_expansion) exit 0 ;;
1384
+ *) exit 1 ;;
1385
+ esac