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.
- package/LICENSE +21 -0
- package/README.md +160 -2
- package/README.zh.md +161 -0
- package/cordis.patch.yml +6 -0
- package/lib/frontmatter.js +167 -0
- package/lib/index.js +46 -0
- package/lib/provider.js +201 -0
- package/package.json +62 -4
- package/skills/CODING_STANDARDS.md +68 -0
- package/skills/ce-babysit-pr/SKILL.md +59 -0
- package/skills/ce-babysit-pr/references/branch-currency.md +22 -0
- package/skills/ce-babysit-pr/references/envelope.md +44 -0
- package/skills/ce-babysit-pr/references/pipeline.md +15 -0
- package/skills/ce-babysit-pr/references/report.md +20 -0
- package/skills/ce-babysit-pr/references/settle.md +37 -0
- package/skills/ce-babysit-pr/references/setup.md +33 -0
- package/skills/ce-babysit-pr/references/stack-commands.md +39 -0
- package/skills/ce-babysit-pr/references/stack.md +43 -0
- package/skills/ce-babysit-pr/references/tick.md +102 -0
- package/skills/ce-babysit-pr/references/watch-loop.md +222 -0
- package/skills/ce-babysit-pr/scripts/pr-snapshot +3656 -0
- package/skills/ce-bakeoff/SKILL.md +48 -0
- package/skills/ce-bakeoff/references/candidates.md +37 -0
- package/skills/ce-bakeoff/references/judging.md +19 -0
- package/skills/ce-bakeoff/references/output.md +11 -0
- package/skills/ce-bakeoff/references/verification.md +15 -0
- package/skills/ce-brainstorm/SKILL.md +69 -0
- package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
- package/skills/ce-brainstorm/references/approaches.md +57 -0
- package/skills/ce-brainstorm/references/bakeoff.md +9 -0
- package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
- package/skills/ce-brainstorm/references/brainstorm-sections.md +458 -0
- package/skills/ce-brainstorm/references/dialogue.md +89 -0
- package/skills/ce-brainstorm/references/handoff.md +187 -0
- package/skills/ce-brainstorm/references/html-rendering.md +638 -0
- package/skills/ce-brainstorm/references/interaction-rules.md +33 -0
- package/skills/ce-brainstorm/references/markdown-rendering.md +242 -0
- package/skills/ce-brainstorm/references/model-tiers.md +13 -0
- package/skills/ce-brainstorm/references/output-mode.md +26 -0
- package/skills/ce-brainstorm/references/phase-0.md +104 -0
- package/skills/ce-brainstorm/references/plan-write.md +32 -0
- package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
- package/skills/ce-brainstorm/references/reasoning-elevation.md +124 -0
- package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
- package/skills/ce-brainstorm/references/synthesis-summary.md +149 -0
- package/skills/ce-brainstorm/references/universal-brainstorming.md +72 -0
- package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
- package/skills/ce-brainstorm/references/visual-probes.md +164 -0
- package/skills/ce-brainstorm/scripts/elevation-dispatch.sh +288 -0
- package/skills/ce-brainstorm/scripts/light-webserver.js +1275 -0
- package/skills/ce-brainstorm/scripts/packs-resolve.py +737 -0
- package/skills/ce-brainstorm/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-code-review/SKILL.md +40 -0
- package/skills/ce-code-review/references/action-class-rubric.md +67 -0
- package/skills/ce-code-review/references/cross-model-recovery.md +38 -0
- package/skills/ce-code-review/references/cross-model-review.md +191 -0
- package/skills/ce-code-review/references/depth-paths.md +32 -0
- package/skills/ce-code-review/references/diff-scope.md +59 -0
- package/skills/ce-code-review/references/dispatch-reviewers.md +135 -0
- package/skills/ce-code-review/references/findings-schema.json +125 -0
- package/skills/ce-code-review/references/finish-input.md +118 -0
- package/skills/ce-code-review/references/finish-review.md +202 -0
- package/skills/ce-code-review/references/intent-and-plan.md +73 -0
- package/skills/ce-code-review/references/modes-and-output.md +156 -0
- package/skills/ce-code-review/references/persona-catalog.md +66 -0
- package/skills/ce-code-review/references/personas/adversarial-reviewer.md +111 -0
- package/skills/ce-code-review/references/personas/agent-native-reviewer.md +173 -0
- package/skills/ce-code-review/references/personas/api-contract-reviewer.md +44 -0
- package/skills/ce-code-review/references/personas/correctness-reviewer.md +48 -0
- package/skills/ce-code-review/references/personas/data-migration-reviewer.md +111 -0
- package/skills/ce-code-review/references/personas/deployment-verification-agent.md +157 -0
- package/skills/ce-code-review/references/personas/julik-frontend-races-reviewer.md +45 -0
- package/skills/ce-code-review/references/personas/learnings-researcher.md +260 -0
- package/skills/ce-code-review/references/personas/maintainability-reviewer.md +85 -0
- package/skills/ce-code-review/references/personas/performance-reviewer.md +45 -0
- package/skills/ce-code-review/references/personas/previous-comments-reviewer.md +59 -0
- package/skills/ce-code-review/references/personas/project-standards-reviewer.md +77 -0
- package/skills/ce-code-review/references/personas/reliability-reviewer.md +49 -0
- package/skills/ce-code-review/references/personas/security-reviewer.md +50 -0
- package/skills/ce-code-review/references/personas/swift-ios-reviewer.md +99 -0
- package/skills/ce-code-review/references/personas/testing-reviewer.md +51 -0
- package/skills/ce-code-review/references/review-output-template.md +172 -0
- package/skills/ce-code-review/references/scope.md +176 -0
- package/skills/ce-code-review/references/select-and-route.md +93 -0
- package/skills/ce-code-review/references/subagent-template.md +206 -0
- package/skills/ce-code-review/references/validator-batch-template.md +67 -0
- package/skills/ce-code-review/scripts/cross-model-adversarial-review.sh +1275 -0
- package/skills/ce-code-review/scripts/findings-mechanics.py +274 -0
- package/skills/ce-code-review/scripts/packs-resolve.py +737 -0
- package/skills/ce-code-review/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-code-review/scripts/review-scope.py +419 -0
- package/skills/ce-code-review/scripts/run-log.py +280 -0
- package/skills/ce-commit/SKILL.md +60 -0
- package/skills/ce-commit-push-pr/SKILL.md +69 -0
- package/skills/ce-commit-push-pr/references/apply-and-handoff.md +79 -0
- package/skills/ce-commit-push-pr/references/branch-creation.md +47 -0
- package/skills/ce-commit-push-pr/references/commit-and-push.md +26 -0
- package/skills/ce-commit-push-pr/references/compose.md +29 -0
- package/skills/ce-commit-push-pr/references/context.md +42 -0
- package/skills/ce-commit-push-pr/references/gh-stack-cli.md +83 -0
- package/skills/ce-commit-push-pr/references/pr-description-writing.md +208 -0
- package/skills/ce-commit-push-pr/references/stack-submit.md +81 -0
- package/skills/ce-compound/SKILL.md +79 -0
- package/skills/ce-compound/assets/resolution-template.md +96 -0
- package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
- package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
- package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
- package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
- package/skills/ce-compound/references/agents/session-historian.md +82 -0
- package/skills/ce-compound/references/assembly.md +127 -0
- package/skills/ce-compound/references/concepts-vocabulary.md +98 -0
- package/skills/ce-compound/references/enhancement.md +42 -0
- package/skills/ce-compound/references/grounding-validation.md +89 -0
- package/skills/ce-compound/references/lightweight.md +56 -0
- package/skills/ce-compound/references/modes.md +13 -0
- package/skills/ce-compound/references/refresh-and-discoverability.md +96 -0
- package/skills/ce-compound/references/report.md +115 -0
- package/skills/ce-compound/references/research.md +144 -0
- package/skills/ce-compound/references/schema.yaml +236 -0
- package/skills/ce-compound/references/session-history.md +73 -0
- package/skills/ce-compound/references/yaml-schema.md +130 -0
- package/skills/ce-compound/scripts/packs-resolve.py +737 -0
- package/skills/ce-compound/scripts/session-history/__pycache__/dsh_session.cpython-314.pyc +0 -0
- package/skills/ce-compound/scripts/session-history/discover-sessions.sh +290 -0
- package/skills/ce-compound/scripts/session-history/discover_dsh.py +123 -0
- package/skills/ce-compound/scripts/session-history/dsh_session.py +225 -0
- package/skills/ce-compound/scripts/session-history/extract-errors.py +178 -0
- package/skills/ce-compound/scripts/session-history/extract-metadata.py +301 -0
- package/skills/ce-compound/scripts/session-history/extract-skeleton.py +346 -0
- package/skills/ce-compound/scripts/validate-doc-claims.py +497 -0
- package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-compound-refresh/SKILL.md +88 -0
- package/skills/ce-compound-refresh/assets/resolution-template.md +96 -0
- package/skills/ce-compound-refresh/references/classify.md +42 -0
- package/skills/ce-compound-refresh/references/commit.md +7 -0
- package/skills/ce-compound-refresh/references/concepts-vocabulary.md +116 -0
- package/skills/ce-compound-refresh/references/discoverability.md +15 -0
- package/skills/ce-compound-refresh/references/investigate.md +23 -0
- package/skills/ce-compound-refresh/references/modes.md +27 -0
- package/skills/ce-compound-refresh/references/per-action-flows.md +142 -0
- package/skills/ce-compound-refresh/references/report.md +23 -0
- package/skills/ce-compound-refresh/references/schema.yaml +236 -0
- package/skills/ce-compound-refresh/references/scope.md +18 -0
- package/skills/ce-compound-refresh/references/worth-audit.md +35 -0
- package/skills/ce-compound-refresh/references/yaml-schema.md +130 -0
- package/skills/ce-compound-refresh/scripts/validate-doc-claims.py +497 -0
- package/skills/ce-compound-refresh/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-debug/SKILL.md +122 -0
- package/skills/ce-debug/references/anti-patterns.md +91 -0
- package/skills/ce-debug/references/defense-in-depth.md +39 -0
- package/skills/ce-debug/references/fix.md +21 -0
- package/skills/ce-debug/references/investigate.md +114 -0
- package/skills/ce-debug/references/investigation-techniques.md +384 -0
- package/skills/ce-debug/references/pipeline-mode.md +78 -0
- package/skills/ce-debug/references/post-fix-handoff.md +60 -0
- package/skills/ce-debug/references/return-to-caller.md +57 -0
- package/skills/ce-doc-review/SKILL.md +75 -0
- package/skills/ce-doc-review/references/bulk-preview.md +146 -0
- package/skills/ce-doc-review/references/cross-model-review.md +207 -0
- package/skills/ce-doc-review/references/decision-primer.md +47 -0
- package/skills/ce-doc-review/references/dispatch.md +56 -0
- package/skills/ce-doc-review/references/document-intake.md +44 -0
- package/skills/ce-doc-review/references/findings-schema.json +85 -0
- package/skills/ce-doc-review/references/modes.md +20 -0
- package/skills/ce-doc-review/references/open-questions-defer.md +158 -0
- package/skills/ce-doc-review/references/persona-selection.md +41 -0
- package/skills/ce-doc-review/references/personas/adversarial-document-reviewer.md +109 -0
- package/skills/ce-doc-review/references/personas/coherence-reviewer.md +69 -0
- package/skills/ce-doc-review/references/personas/design-lens-reviewer.md +48 -0
- package/skills/ce-doc-review/references/personas/feasibility-reviewer.md +50 -0
- package/skills/ce-doc-review/references/personas/product-lens-reviewer.md +85 -0
- package/skills/ce-doc-review/references/personas/scope-guardian-reviewer.md +83 -0
- package/skills/ce-doc-review/references/personas/security-lens-reviewer.md +41 -0
- package/skills/ce-doc-review/references/personas/whole-doc-reviewer.md +25 -0
- package/skills/ce-doc-review/references/rendering-floor.md +175 -0
- package/skills/ce-doc-review/references/review-output-template.md +123 -0
- package/skills/ce-doc-review/references/subagent-template.md +188 -0
- package/skills/ce-doc-review/references/synthesis-and-presentation.md +350 -0
- package/skills/ce-doc-review/references/walkthrough.md +347 -0
- package/skills/ce-doc-review/scripts/cross-model-doc-review.sh +1270 -0
- package/skills/ce-doc-review/scripts/packs-resolve.py +737 -0
- package/skills/ce-doc-review/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-dogfood/SKILL.md +66 -0
- package/skills/ce-dogfood/agents/openai.yaml +2 -0
- package/skills/ce-dogfood/references/dogfood-report-template.md +97 -0
- package/skills/ce-dogfood/references/phases.md +167 -0
- package/skills/ce-dogfood/references/test-matrix-taxonomy.md +62 -0
- package/skills/ce-dogfood/scripts/packs-resolve.py +737 -0
- package/skills/ce-explain/SKILL.md +81 -0
- package/skills/ce-explain/references/agents/behavior-trace-scout.md +19 -0
- package/skills/ce-explain/references/agents/work-recap-scout.md +23 -0
- package/skills/ce-explain/references/check-in.md +18 -0
- package/skills/ce-explain/references/destinations.md +35 -0
- package/skills/ce-explain/references/explainer-html.md +29 -0
- package/skills/ce-explain/references/explainer-markdown.md +24 -0
- package/skills/ce-explain/references/intake.md +50 -0
- package/skills/ce-explain/references/orchestration.md +46 -0
- package/skills/ce-handoff/SKILL.md +54 -0
- package/skills/ce-handoff/references/create.md +88 -0
- package/skills/ce-handoff/references/resume.md +46 -0
- package/skills/ce-ideate/SKILL.md +75 -0
- package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +166 -0
- package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
- package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
- package/skills/ce-ideate/references/decomposition.md +39 -0
- package/skills/ce-ideate/references/divergent-ideation.md +112 -0
- package/skills/ce-ideate/references/grounding.md +138 -0
- package/skills/ce-ideate/references/html-rendering.md +638 -0
- package/skills/ce-ideate/references/ideation-sections.md +191 -0
- package/skills/ce-ideate/references/issue-intelligence.md +44 -0
- package/skills/ce-ideate/references/markdown-rendering.md +242 -0
- package/skills/ce-ideate/references/output-mode.md +34 -0
- package/skills/ce-ideate/references/post-ideation-workflow.md +169 -0
- package/skills/ce-ideate/references/scope-gates.md +148 -0
- package/skills/ce-ideate/references/universal-ideation.md +119 -0
- package/skills/ce-ideate/references/user-research-artifacts.md +28 -0
- package/skills/ce-ideate/references/web-research-cache.md +56 -0
- package/skills/ce-noslop/SKILL.md +45 -0
- package/skills/ce-noslop/references/patterns.md +61 -0
- package/skills/ce-noslop/references/terminology.md +3 -0
- package/skills/ce-optimize/SKILL.md +64 -0
- package/skills/ce-optimize/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-optimize/references/agents/repo-research-analyst.md +258 -0
- package/skills/ce-optimize/references/example-expensive-benchmark-spec.yaml +88 -0
- package/skills/ce-optimize/references/example-hard-spec.yaml +64 -0
- package/skills/ce-optimize/references/example-judge-spec.yaml +83 -0
- package/skills/ce-optimize/references/example-text-target-spec.yaml +80 -0
- package/skills/ce-optimize/references/experiment-log-schema.yaml +563 -0
- package/skills/ce-optimize/references/experiment-prompt-template.md +143 -0
- package/skills/ce-optimize/references/judge-prompt-template.md +117 -0
- package/skills/ce-optimize/references/loop.md +286 -0
- package/skills/ce-optimize/references/measurement.md +168 -0
- package/skills/ce-optimize/references/optimize-spec-schema.yaml +570 -0
- package/skills/ce-optimize/references/persistence.md +93 -0
- package/skills/ce-optimize/references/spec.md +107 -0
- package/skills/ce-optimize/references/text-targets.md +51 -0
- package/skills/ce-optimize/references/usage-guide.md +147 -0
- package/skills/ce-optimize/references/wrap-up.md +54 -0
- package/skills/ce-optimize/scripts/decide.mjs +676 -0
- package/skills/ce-optimize/scripts/experiment-worktree.sh +330 -0
- package/skills/ce-optimize/scripts/measure.sh +140 -0
- package/skills/ce-optimize/scripts/parallel-probe.sh +134 -0
- package/skills/ce-plan/SKILL.md +60 -0
- package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +66 -0
- package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
- package/skills/ce-plan/references/agents/best-practices-researcher.md +118 -0
- package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-plan/references/agents/framework-docs-researcher.md +97 -0
- package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
- package/skills/ce-plan/references/agents/learnings-researcher.md +268 -0
- package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
- package/skills/ce-plan/references/agents/repo-research-analyst.md +262 -0
- package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
- package/skills/ce-plan/references/agents/slack-researcher.md +131 -0
- package/skills/ce-plan/references/agents/spec-flow-analyzer.md +84 -0
- package/skills/ce-plan/references/agents/web-researcher.md +125 -0
- package/skills/ce-plan/references/approach-altitude.md +55 -0
- package/skills/ce-plan/references/bakeoff.md +9 -0
- package/skills/ce-plan/references/deepening-workflow.md +263 -0
- package/skills/ce-plan/references/final-review.md +134 -0
- package/skills/ce-plan/references/html-rendering.md +638 -0
- package/skills/ce-plan/references/intake.md +158 -0
- package/skills/ce-plan/references/markdown-rendering.md +242 -0
- package/skills/ce-plan/references/output-contracts.md +29 -0
- package/skills/ce-plan/references/output-mode.md +45 -0
- package/skills/ce-plan/references/plan-handoff.md +116 -0
- package/skills/ce-plan/references/plan-sections.md +489 -0
- package/skills/ce-plan/references/reasoning-elevation.md +124 -0
- package/skills/ce-plan/references/research.md +211 -0
- package/skills/ce-plan/references/resume.md +61 -0
- package/skills/ce-plan/references/settled-decisions.md +45 -0
- package/skills/ce-plan/references/structure.md +207 -0
- package/skills/ce-plan/references/synthesis-summary.md +260 -0
- package/skills/ce-plan/references/universal-planning.md +170 -0
- package/skills/ce-plan/scripts/elevation-dispatch.sh +288 -0
- package/skills/ce-plan/scripts/packs-resolve.py +737 -0
- package/skills/ce-plan/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-polish/SKILL.md +22 -0
- package/skills/ce-polish/agents/openai.yaml +2 -0
- package/skills/ce-polish/references/dev-server-astro.md +58 -0
- package/skills/ce-polish/references/dev-server-detection.md +40 -0
- package/skills/ce-polish/references/dev-server-next.md +61 -0
- package/skills/ce-polish/references/dev-server-nuxt.md +58 -0
- package/skills/ce-polish/references/dev-server-procfile.md +59 -0
- package/skills/ce-polish/references/dev-server-rails.md +50 -0
- package/skills/ce-polish/references/dev-server-remix.md +58 -0
- package/skills/ce-polish/references/dev-server-sveltekit.md +58 -0
- package/skills/ce-polish/references/dev-server-vite.md +48 -0
- package/skills/ce-polish/references/install-riffrec.md +35 -0
- package/skills/ce-polish/references/launch-json-schema.md +177 -0
- package/skills/ce-polish/references/live-loop.md +98 -0
- package/skills/ce-polish/references/live-remote.md +35 -0
- package/skills/ce-polish/references/live-start.md +93 -0
- package/skills/ce-polish/references/live-stream-contract.md +253 -0
- package/skills/ce-polish/references/run.md +67 -0
- package/skills/ce-polish/scripts/detect-project-type.sh +277 -0
- package/skills/ce-polish/scripts/detect-riffrec.sh +225 -0
- package/skills/ce-polish/scripts/live-endpoint.js +2841 -0
- package/skills/ce-polish/scripts/read-launch-json.sh +117 -0
- package/skills/ce-polish/scripts/resolve-package-manager.sh +95 -0
- package/skills/ce-polish/scripts/resolve-port.sh +307 -0
- package/skills/ce-pov/SKILL.md +64 -0
- package/skills/ce-pov/references/agents/external-evidence-researcher.md +26 -0
- package/skills/ce-pov/references/agents/pov-peer.md +43 -0
- package/skills/ce-pov/references/agents/precedent-activity-scout.md +21 -0
- package/skills/ce-pov/references/agents/project-grounding-scout.md +32 -0
- package/skills/ce-pov/references/boundaries.md +36 -0
- package/skills/ce-pov/references/cross-model-panel.md +596 -0
- package/skills/ce-pov/references/followup.md +9 -0
- package/skills/ce-pov/references/grounding.md +47 -0
- package/skills/ce-pov/references/intake.md +61 -0
- package/skills/ce-pov/references/invocation.md +47 -0
- package/skills/ce-pov/references/method.md +68 -0
- package/skills/ce-pov/references/pov-schema.json +56 -0
- package/skills/ce-pov/references/report.md +7 -0
- package/skills/ce-pov/scripts/cross-model-pov.sh +1110 -0
- package/skills/ce-pov/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-product-pulse/SKILL.md +81 -0
- package/skills/ce-product-pulse/agents/openai.yaml +2 -0
- package/skills/ce-product-pulse/references/config.md +20 -0
- package/skills/ce-product-pulse/references/interview.md +260 -0
- package/skills/ce-product-pulse/references/report-template.md +89 -0
- package/skills/ce-product-pulse/references/run.md +46 -0
- package/skills/ce-product-pulse/references/setup.md +43 -0
- package/skills/ce-promote/SKILL.md +90 -0
- package/skills/ce-promote/agents/openai.yaml +2 -0
- package/skills/ce-promote/references/spiral-cli.md +149 -0
- package/skills/ce-proof/SKILL.md +65 -0
- package/skills/ce-proof/references/api.md +176 -0
- package/skills/ce-proof/references/workflows.md +124 -0
- package/skills/ce-prototype/SKILL.md +59 -0
- package/skills/ce-prototype/assets/annotate.css +206 -0
- package/skills/ce-prototype/assets/annotate.js +837 -0
- package/skills/ce-prototype/references/annotation-loop.md +32 -0
- package/skills/ce-prototype/references/build.md +39 -0
- package/skills/ce-prototype/references/craft-floor.md +43 -0
- package/skills/ce-prototype/references/preview.md +118 -0
- package/skills/ce-prototype/references/scoping.md +38 -0
- package/skills/ce-prototype/references/write-back.md +37 -0
- package/skills/ce-prototype/scripts/light-webserver.js +1275 -0
- package/skills/ce-resolve-pr-feedback/SKILL.md +60 -0
- package/skills/ce-resolve-pr-feedback/references/agents/pr-comment-resolver.md +59 -0
- package/skills/ce-resolve-pr-feedback/references/evaluation-rubric.md +141 -0
- package/skills/ce-resolve-pr-feedback/references/full-mode.md +336 -0
- package/skills/ce-resolve-pr-feedback/references/pipeline-mode.md +23 -0
- package/skills/ce-resolve-pr-feedback/references/resume.md +60 -0
- package/skills/ce-resolve-pr-feedback/references/return-to-caller.md +111 -0
- package/skills/ce-resolve-pr-feedback/references/targeted-mode.md +48 -0
- package/skills/ce-resolve-pr-feedback/scripts/get-pr-comments +176 -0
- package/skills/ce-resolve-pr-feedback/scripts/get-thread-for-comment +84 -0
- package/skills/ce-resolve-pr-feedback/scripts/pending-feedback.py +287 -0
- package/skills/ce-resolve-pr-feedback/scripts/reply-to-pr-thread +57 -0
- package/skills/ce-resolve-pr-feedback/scripts/resolve-pr-thread +23 -0
- package/skills/ce-retune/SKILL.md +42 -0
- package/skills/ce-retune/agents/openai.yaml +2 -0
- package/skills/ce-retune/references/baseline-mining.md +122 -0
- package/skills/ce-retune/references/corpus-audit.md +128 -0
- package/skills/ce-retune/references/cut-passes.md +129 -0
- package/skills/ce-retune/references/halt-taxonomy.md +123 -0
- package/skills/ce-retune/references/noise-floor.md +131 -0
- package/skills/ce-retune/references/workflow-shapes.md +73 -0
- package/skills/ce-riffrec-feedback-analysis/SKILL.md +33 -0
- package/skills/ce-riffrec-feedback-analysis/references/analyzer.md +18 -0
- package/skills/ce-riffrec-feedback-analysis/references/compound-engineering-feedback-format.md +116 -0
- package/skills/ce-riffrec-feedback-analysis/references/extensive-analysis.md +98 -0
- package/skills/ce-riffrec-feedback-analysis/references/install-riffrec.md +27 -0
- package/skills/ce-riffrec-feedback-analysis/references/quick-bug-report.md +38 -0
- package/skills/ce-riffrec-feedback-analysis/scripts/analyze_riffrec_zip.py +1306 -0
- package/skills/ce-setup/SKILL.md +103 -0
- package/skills/ce-setup/agents/openai.yaml +2 -0
- package/skills/ce-setup/assets/compounding-directive.md +11 -0
- package/skills/ce-setup/assets/noslop-directive.md +7 -0
- package/skills/ce-setup/assets/pack-rule-template.md +11 -0
- package/skills/ce-setup/references/acpx-cache-warm.md +14 -0
- package/skills/ce-setup/references/config-template.yaml +210 -0
- package/skills/ce-setup/references/legacy-codex-tool-map.md +15 -0
- package/skills/ce-setup/references/pack-scaffold.md +45 -0
- package/skills/ce-setup/references/repo-fixes.md +121 -0
- package/skills/ce-setup/scripts/check-health +929 -0
- package/skills/ce-setup/scripts/packs-resolve.py +737 -0
- package/skills/ce-simplify-code/SKILL.md +76 -0
- package/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
- package/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
- package/skills/ce-simplify-code/references/personas/comment-reviewer.md +24 -0
- package/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
- package/skills/ce-simplify-code/references/personas/test-value-reviewer.md +23 -0
- package/skills/ce-simplify-code/references/reviewer-dispatch.md +11 -0
- package/skills/ce-simplify-code/references/standalone-report.md +25 -0
- package/skills/ce-strategy/SKILL.md +61 -0
- package/skills/ce-strategy/references/grounding.md +22 -0
- package/skills/ce-strategy/references/interview.md +179 -0
- package/skills/ce-strategy/references/strategy-template.md +94 -0
- package/skills/ce-strategy/references/update-run.md +33 -0
- package/skills/ce-sweep/SKILL.md +93 -0
- package/skills/ce-sweep/agents/openai.yaml +2 -0
- package/skills/ce-sweep/references/agents/media-analyzer.md +54 -0
- package/skills/ce-sweep/references/interview.md +208 -0
- package/skills/ce-sweep/references/model-tiers.md +13 -0
- package/skills/ce-sweep/references/plan-template.md +62 -0
- package/skills/ce-sweep/references/run.md +126 -0
- package/skills/ce-sweep/references/sources/email.md +57 -0
- package/skills/ce-sweep/references/sources/github-issues.md +59 -0
- package/skills/ce-sweep/references/sources/slack.md +61 -0
- package/skills/ce-sweep/references/state-schema.md +157 -0
- package/skills/ce-sweep/references/subagent-template.md +60 -0
- package/skills/ce-sweep/scripts/analyze_riffrec_zip.py +1306 -0
- package/skills/ce-sweep/scripts/sweep-state.py +793 -0
- package/skills/ce-test-browser/SKILL.md +50 -0
- package/skills/ce-test-browser/references/agent-browser-driver.md +47 -0
- package/skills/ce-test-browser/references/pipeline-orchestration.md +45 -0
- package/skills/ce-test-browser/references/route-and-report.md +129 -0
- package/skills/ce-test-browser/scripts/resolve-port.sh +41 -0
- package/skills/ce-test-xcode/SKILL.md +26 -0
- package/skills/ce-test-xcode/agents/openai.yaml +2 -0
- package/skills/ce-test-xcode/references/setup-and-build.md +32 -0
- package/skills/ce-test-xcode/references/test-and-report.md +64 -0
- package/skills/ce-work/SKILL.md +62 -0
- package/skills/ce-work/references/agents/figma-design-sync.md +27 -0
- package/skills/ce-work/references/agents/implementation-worker.md +17 -0
- package/skills/ce-work/references/cross-model-execution.md +191 -0
- package/skills/ce-work/references/execution-engines.md +64 -0
- package/skills/ce-work/references/execution-strategy.md +73 -0
- package/skills/ce-work/references/implementation-loop.md +107 -0
- package/skills/ce-work/references/implementation-result-schema.json +52 -0
- package/skills/ce-work/references/input-triage.md +50 -0
- package/skills/ce-work/references/non-code-execution.md +23 -0
- package/skills/ce-work/references/return-to-caller.md +33 -0
- package/skills/ce-work/references/review-findings-followup.md +82 -0
- package/skills/ce-work/references/shipping-workflow.md +89 -0
- package/skills/ce-work/references/tracker-defer.md +150 -0
- package/skills/ce-work/references/work-intake.md +51 -0
- package/skills/ce-work/references/workspace-setup.md +27 -0
- package/skills/ce-work/scripts/__pycache__/unit_workspace_jobs.cpython-314.pyc +0 -0
- package/skills/ce-work/scripts/__pycache__/unit_workspace_state.cpython-314.pyc +0 -0
- package/skills/ce-work/scripts/cross-model-work.sh +1385 -0
- package/skills/ce-work/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-work/scripts/unit-workspace.py +215 -0
- package/skills/ce-work/scripts/unit_workspace_ignored.py +93 -0
- package/skills/ce-work/scripts/unit_workspace_integration.py +677 -0
- package/skills/ce-work/scripts/unit_workspace_jobs.py +1216 -0
- package/skills/ce-work/scripts/unit_workspace_lifecycle.py +981 -0
- package/skills/ce-work/scripts/unit_workspace_state.py +1379 -0
- package/skills/ce-work/scripts/unit_workspace_transaction.py +612 -0
- package/skills/ce-worktree/SKILL.md +54 -0
- package/skills/lfg/SKILL.md +59 -0
- package/skills/lfg/references/debug-return.md +25 -0
- package/skills/lfg/references/intake.md +30 -0
- package/skills/lfg/references/next-work-handoff.md +96 -0
- package/skills/lfg/references/plan-brief.md +36 -0
- package/skills/lfg/references/review-followup.md +78 -0
- package/skills/lfg/references/shipping.md +65 -0
- package/skills/lfg/references/stage-routing.md +49 -0
- package/skills/lfg/references/task-visibility.md +11 -0
- package/skills/lfg/references/tracker-defer.md +150 -0
- package/skills/lfg/references/work-return.md +27 -0
- package/skills/wtf/SKILL.md +33 -0
- package/skills/wtf/agents/openai.yaml +2 -0
|
@@ -0,0 +1,1110 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cross-model-pov.sh
|
|
3
|
+
#
|
|
4
|
+
# Runs one pre-sanctioned different-model route in a read-only, least-privilege
|
|
5
|
+
# process and writes its POV as JSON into the run dir.
|
|
6
|
+
# Every peer receives the canonical POV persona, schema, and a caller-prepared
|
|
7
|
+
# subject payload. The peer also receives the caller-declared repository read
|
|
8
|
+
# scope; private prompt/result scratch stays outside that repository.
|
|
9
|
+
#
|
|
10
|
+
# Independence is by PROVIDER, not CLI brand. A provider is reached by a ROUTE:
|
|
11
|
+
# its dedicated CLI, or (for the fixed grok-cursor / composer routes) cursor-agent. All
|
|
12
|
+
# peer runs on ONE model at HIGH reasoning (composer's
|
|
13
|
+
# -fast tier is its ceiling, an accepted exception).
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# cross-model-pov.sh <host-serving-family> <fixed-route> <subject-payload> <run-dir>
|
|
17
|
+
#
|
|
18
|
+
# <host-serving-family>
|
|
19
|
+
# the peer-key of the host's OWN serving family, attested by
|
|
20
|
+
# the calling skill (it knows its harness). A peer-key, never
|
|
21
|
+
# a provider name: openai->codex, anthropic->claude,
|
|
22
|
+
# xai->grok, cursor/composer->composer, google->gemini.
|
|
23
|
+
# Used to verify independence. `unknown` is allowed for an
|
|
24
|
+
# explicitly named peer, but its receipt remains unverified;
|
|
25
|
+
# automatic discovery must exclude it before calling this worker.
|
|
26
|
+
# <fixed-route> one host-resolved and pre-sanctioned route: codex, claude,
|
|
27
|
+
# grok-cli, grok-cursor, cursor, composer, opencode, or
|
|
28
|
+
# antigravity. A route failure
|
|
29
|
+
# returns no artifact; only the host may disclose and retry a
|
|
30
|
+
# different recipient.
|
|
31
|
+
# <subject-payload> framed question plus any conversation-only subject material.
|
|
32
|
+
# Point to repository files instead of copying their contents;
|
|
33
|
+
# the peer grounds itself from the shared working tree.
|
|
34
|
+
# <run-dir> existing private dir outside the repository; output ->
|
|
35
|
+
# <run-dir>/pov-<target>.json, where <target> is the resolved
|
|
36
|
+
# <fixed-route> target (grok-cli/grok-cursor both collapse to
|
|
37
|
+
# grok) -- NOT the <host-serving-family> key.
|
|
38
|
+
#
|
|
39
|
+
# Test/introspection mode (no model call, no side effects):
|
|
40
|
+
# cross-model-pov.sh --emit-adapter <route>
|
|
41
|
+
# prints the exact argv the given route would run (route in:
|
|
42
|
+
# codex | claude | grok-cli | grok-cursor | cursor | composer | opencode |
|
|
43
|
+
# antigravity). Both this mode and the
|
|
44
|
+
# live run build their argv from adapter_argv(), so the U7 route-safety test
|
|
45
|
+
# asserts on the same command string the peer actually runs.
|
|
46
|
+
#
|
|
47
|
+
# Self-locates its sibling reference files via BASH_SOURCE (NOT the CWD, which is
|
|
48
|
+
# the user's project on every host). The agent passes the values above.
|
|
49
|
+
#
|
|
50
|
+
# NON-BLOCKING BY DESIGN: every failure logs to stderr and exits 0 without an
|
|
51
|
+
# output file. The cross-model pass is additive and must never fail the POV;
|
|
52
|
+
# the caller detects success purely by the presence of the output file(s).
|
|
53
|
+
#
|
|
54
|
+
# DATA-EGRESS NOTE: this embeds the prepared subject payload into an external
|
|
55
|
+
# model CLI prompt. The caller must disclose its content scope and actual provider
|
|
56
|
+
# before launch; route receipts let it reconcile the fixed target afterward.
|
|
57
|
+
|
|
58
|
+
set -uo pipefail
|
|
59
|
+
|
|
60
|
+
# Survive SIGHUP when the orchestrator backgrounds this script and the parent
|
|
61
|
+
# shell exits (common on Cursor/Codex Bash tools). Without this, a detached
|
|
62
|
+
# peer process group can keep running while this script dies
|
|
63
|
+
# before normalize — leaving fold-in files without route/model receipts.
|
|
64
|
+
trap '' HUP
|
|
65
|
+
|
|
66
|
+
# Filled while a peer process group is live; TERM/INT handler (installed after
|
|
67
|
+
# reap() is defined) reaps it so an orchestrator kill cannot leave orphans.
|
|
68
|
+
ACTIVE_PEER_PID=""
|
|
69
|
+
PEER_WORKDIR=""
|
|
70
|
+
PROMPT_FILE=""
|
|
71
|
+
PEERLOG=""
|
|
72
|
+
PEERERR=""
|
|
73
|
+
RAW_OUT=""
|
|
74
|
+
RUN_SUCCEEDED=false
|
|
75
|
+
PEER_ROUTE=""
|
|
76
|
+
SESSION_STALLED=false
|
|
77
|
+
|
|
78
|
+
cleanup_private_scratch() {
|
|
79
|
+
[ -n "${PEER_WORKDIR:-}" ] && rm -rf "$PEER_WORKDIR"
|
|
80
|
+
PEER_WORKDIR=""
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
log() { printf '[cross-model-pov] %s\n' "$*" >&2; }
|
|
84
|
+
skip() { log "$*"; exit 0; } # non-blocking: announce reason, exit clean, no output
|
|
85
|
+
|
|
86
|
+
# --- model + reasoning per provider ----------------------------------------
|
|
87
|
+
# ONE model per provider at its editorial tier (native Grok is xhigh; Codex and Claude stay high). Concrete IDs are the CURRENT instance of the tier principle
|
|
88
|
+
# and the single maintenance point when model families change.
|
|
89
|
+
M_CODEX="gpt-6.1-sol" # codex (reasoning_effort=high)
|
|
90
|
+
M_CLAUDE="claude-opus-5-5" # claude (effort=high)
|
|
91
|
+
M_GROK="grok-4.7" # grok CLI (reasoning_effort=xhigh)
|
|
92
|
+
# Cursor's ACP server offers one preset per model and rejects effort variants,
|
|
93
|
+
# so grok-cursor runs at high/fast and composer at its fast tier.
|
|
94
|
+
M_GROK_CURSOR="grok-4.7[context=256k,reasoning_effort=high,fast=true]"
|
|
95
|
+
M_COMPOSER="composer-2.5[fast=true]"
|
|
96
|
+
# Antigravity offers only the models the account is entitled to. Argon is
|
|
97
|
+
# preferred; an account without it falls back once to Flash before any prompt,
|
|
98
|
+
# and the non-final retry reuses whichever model was selected.
|
|
99
|
+
M_ANTIGRAVITY="gemini-4-argon" # antigravity (thinking_level=high)
|
|
100
|
+
M_ANTIGRAVITY_FALLBACK="gemini-3.8-flash"
|
|
101
|
+
ANTIGRAVITY_MODEL="" # set by the fallback; empty requests M_ANTIGRAVITY
|
|
102
|
+
|
|
103
|
+
route_model() { # <route> -> the M_* constant that route requests
|
|
104
|
+
local target
|
|
105
|
+
target="$(route_target "$1")"
|
|
106
|
+
if [ -n "${CROSS_MODEL_MODEL_OVERRIDE:-}" ] &&
|
|
107
|
+
[ "${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" = "$target" ] &&
|
|
108
|
+
[ "$target" != "cursor" ]; then
|
|
109
|
+
printf '%s' "$CROSS_MODEL_MODEL_OVERRIDE"
|
|
110
|
+
return 0
|
|
111
|
+
fi
|
|
112
|
+
case "$1" in
|
|
113
|
+
codex) printf '%s' "$M_CODEX" ;;
|
|
114
|
+
claude) printf '%s' "$M_CLAUDE" ;;
|
|
115
|
+
grok-cli) printf '%s' "$M_GROK" ;;
|
|
116
|
+
grok-cursor) printf '%s' "$M_GROK_CURSOR" ;;
|
|
117
|
+
cursor) printf 'auto' ;;
|
|
118
|
+
composer) printf '%s' "$M_COMPOSER" ;;
|
|
119
|
+
opencode) printf 'auto' ;;
|
|
120
|
+
antigravity) printf '%s' "${ANTIGRAVITY_MODEL:-$M_ANTIGRAVITY}" ;;
|
|
121
|
+
esac
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
route_target() {
|
|
125
|
+
case "$1" in
|
|
126
|
+
codex|claude|cursor|composer|antigravity) printf '%s' "$1" ;;
|
|
127
|
+
grok-cli|grok-cursor) printf 'grok' ;;
|
|
128
|
+
opencode) printf 'opencode' ;;
|
|
129
|
+
esac
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
route_harness() {
|
|
133
|
+
case "$1" in
|
|
134
|
+
codex) printf 'codex' ;;
|
|
135
|
+
claude) printf 'claude' ;;
|
|
136
|
+
grok-cli) printf 'grok' ;;
|
|
137
|
+
grok-cursor|cursor|composer) printf 'cursor-agent' ;;
|
|
138
|
+
opencode) printf 'opencode' ;;
|
|
139
|
+
antigravity) printf 'antigravity' ;;
|
|
140
|
+
esac
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
target_serving_family() {
|
|
144
|
+
case "$1" in
|
|
145
|
+
codex|claude|grok|composer) printf '%s' "$1" ;;
|
|
146
|
+
antigravity) printf 'gemini' ;;
|
|
147
|
+
cursor) printf 'unknown' ;;
|
|
148
|
+
opencode) printf 'unknown' ;;
|
|
149
|
+
esac
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
route_available() {
|
|
153
|
+
case "$1" in
|
|
154
|
+
codex) command -v codex >/dev/null 2>&1 ;;
|
|
155
|
+
claude) command -v claude >/dev/null 2>&1 ;;
|
|
156
|
+
grok-cli) command -v grok >/dev/null 2>&1 ;;
|
|
157
|
+
grok-cursor|cursor|composer) command -v cursor-agent >/dev/null 2>&1 ;;
|
|
158
|
+
opencode) command -v opencode >/dev/null 2>&1 ;;
|
|
159
|
+
antigravity) command -v agy_acp_server.par >/dev/null 2>&1 ;;
|
|
160
|
+
*) return 1 ;;
|
|
161
|
+
esac
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# --- acpx transport (keep byte-identical across migrated peer workers) -----
|
|
165
|
+
# Every route reaches its agent through acpx, which speaks the Agent Client
|
|
166
|
+
# Protocol and prints the JSON-RPC stream, so one parser covers every CLI.
|
|
167
|
+
# The calling worker supplies route_available, route_model, and log.
|
|
168
|
+
ACPX_VERSION="0.21.0"
|
|
169
|
+
ACPX_UNAVAILABLE="" # pre-egress reason; ACPX_SCOPE says whether every route shares it
|
|
170
|
+
ACPX_SCOPE=""
|
|
171
|
+
ACPX_NPM_REFRESH=false
|
|
172
|
+
|
|
173
|
+
acpx_agent() { # <route> -> acpx built-in agent name
|
|
174
|
+
case "$1" in
|
|
175
|
+
codex) printf 'codex' ;;
|
|
176
|
+
claude) printf 'claude' ;;
|
|
177
|
+
grok-cli) printf 'grok-build' ;;
|
|
178
|
+
grok-cursor|cursor|composer) printf 'cursor' ;;
|
|
179
|
+
opencode) printf 'opencode' ;;
|
|
180
|
+
antigravity) printf 'antigravity' ;;
|
|
181
|
+
*) return 1 ;;
|
|
182
|
+
esac
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# acpx launches Google's Antigravity ACP runtime by name. It is a separate
|
|
186
|
+
# download from the agy CLI and needs its localharness_external helper from the
|
|
187
|
+
# same release, beside it or named by ANTIGRAVITY_HARNESS_PATH.
|
|
188
|
+
acpx_antigravity_helper() {
|
|
189
|
+
local runtime
|
|
190
|
+
if [ -n "${ANTIGRAVITY_HARNESS_PATH:-}" ]; then printf '%s' "$ANTIGRAVITY_HARNESS_PATH"; return 0; fi
|
|
191
|
+
runtime="$(command -v agy_acp_server.par 2>/dev/null)" || return 1
|
|
192
|
+
printf '%s/localharness_external' "$(dirname "$runtime")"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# The runtime reads skills and settings from GEMINI_HOME, so every run gets a
|
|
196
|
+
# private one, deleted with the caller's scratch. A file-backed login (no macOS
|
|
197
|
+
# keychain) is read only from there, so it is copied in; refreshes are not
|
|
198
|
+
# written back, so concurrent peers cannot clobber the user's token.
|
|
199
|
+
acpx_antigravity_home() { # <private-dir> -> GEMINI_HOME path
|
|
200
|
+
local home="$1/gemini-home"
|
|
201
|
+
local token="${GEMINI_HOME:-${HOME:-}/.gemini}/antigravity-acp/acp_token.json"
|
|
202
|
+
(umask 077; mkdir -p "$home/antigravity-acp") || return 1
|
|
203
|
+
printf '{"auth":{"type":"oauth-personal"}}\n' > "$home/antigravity-acp/settings.json" || return 1
|
|
204
|
+
if [ -f "$token" ]; then
|
|
205
|
+
(umask 077; cp "$token" "$home/antigravity-acp/acp_token.json") || return 1
|
|
206
|
+
fi
|
|
207
|
+
printf '%s' "$home"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
acpx_unavailable() { # <shared|route> <reason>
|
|
211
|
+
ACPX_SCOPE="$1"; ACPX_UNAVAILABLE="$2"
|
|
212
|
+
return 1
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# acpx lets ~/.acpx/config.json and <cwd>/.acpxrc.json replace a built-in
|
|
216
|
+
# agent's launch command; a route that would run something else is skipped.
|
|
217
|
+
# opencode launches the installed CLI by path, which no config entry overrides.
|
|
218
|
+
acpx_config_guard() { # <route> <cwd>
|
|
219
|
+
local agent file
|
|
220
|
+
[ "$1" = opencode ] && return 0
|
|
221
|
+
agent="$(acpx_agent "$1")" || return 1
|
|
222
|
+
for file in "${HOME:-}/.acpx/config.json" "$2/.acpxrc.json"; do
|
|
223
|
+
[ -e "$file" ] || continue
|
|
224
|
+
if ! jq -e --arg a "$agent" '(.agents // {}) | keys | map(ascii_downcase) | index($a) == null' "$file" >/dev/null 2>&1; then
|
|
225
|
+
acpx_unavailable route "$file overrides or cannot be checked for the '$agent' agent launch"
|
|
226
|
+
return 1
|
|
227
|
+
fi
|
|
228
|
+
done
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
acpx_preflight() { # <route> <cwd>; nothing is sent to a provider here
|
|
232
|
+
local v major minor
|
|
233
|
+
ACPX_UNAVAILABLE=""; ACPX_SCOPE=""
|
|
234
|
+
command -v node >/dev/null 2>&1 || { acpx_unavailable shared "node not found; acpx needs Node 22.13 or newer"; return 1; }
|
|
235
|
+
v="$(node -p 'process.versions.node' 2>/dev/null)"
|
|
236
|
+
major="${v%%.*}"; minor="${v#*.}"; minor="${minor%%.*}"
|
|
237
|
+
case "$major$minor" in ''|*[!0-9]*) acpx_unavailable shared "cannot read the Node version; acpx needs Node 22.13 or newer"; return 1 ;; esac
|
|
238
|
+
if [ "$major" -lt 22 ] || { [ "$major" -eq 22 ] && [ "$minor" -lt 13 ]; }; then
|
|
239
|
+
acpx_unavailable shared "Node $v is too old; acpx needs Node 22.13 or newer"
|
|
240
|
+
return 1
|
|
241
|
+
fi
|
|
242
|
+
command -v npx >/dev/null 2>&1 || { acpx_unavailable shared "npx not found; acpx runs through npx"; return 1; }
|
|
243
|
+
if [ "$1" = antigravity ]; then
|
|
244
|
+
case "$(uname -s 2>/dev/null)" in
|
|
245
|
+
MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "the Antigravity route is not yet supported on native Windows"; return 1 ;;
|
|
246
|
+
esac
|
|
247
|
+
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; }
|
|
248
|
+
[ -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; }
|
|
249
|
+
fi
|
|
250
|
+
route_available "$1" || { acpx_unavailable route "the agent CLI for route '$1' is not installed"; return 1; }
|
|
251
|
+
if [ "$1" = opencode ]; then
|
|
252
|
+
case "$(uname -s 2>/dev/null)" in
|
|
253
|
+
MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "opencode launches through acpx's raw --agent command, which acpx rejects on native Windows"; return 1 ;;
|
|
254
|
+
esac
|
|
255
|
+
fi
|
|
256
|
+
acpx_config_guard "$1" "$2"
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# Claude's ACP adapter always loads project settings, so the launch goes
|
|
260
|
+
# through a wrapper that adds --safe-mode.
|
|
261
|
+
acpx_claude_wrapper() { # <private-dir> -> wrapper path
|
|
262
|
+
local real wrapper="$1/claude-safe-mode"
|
|
263
|
+
real="$(command -v claude)" || return 1
|
|
264
|
+
printf '#!/bin/sh\nexec %s --safe-mode "$@"\n' "$(printf '%q' "$real")" > "$wrapper" && chmod 700 "$wrapper" || return 1
|
|
265
|
+
printf '%s' "$wrapper"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# npm resolves offline-first, so cached metadata older than the acpx pin or a
|
|
269
|
+
# version an adapter's dependencies need fails with ETARGET before anything is
|
|
270
|
+
# sent. One launch with fresh metadata heals the cache. npm ignores
|
|
271
|
+
# prefer_online while prefer_offline is set, so the refresh turns it off.
|
|
272
|
+
acpx_stale_npm_metadata() { # <stderr-log> <stream-log>
|
|
273
|
+
[ "$ACPX_NPM_REFRESH" = true ] && return 1
|
|
274
|
+
grep -qE 'ETARGET|notarget|No matching version found' "$1" "$2" 2>/dev/null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Prints the NUL-delimited argv prefix through `npx acpx@<pin>` and its global
|
|
278
|
+
# flags; the worker appends permission flags, `--model`, the agent, and exec options.
|
|
279
|
+
# The codex and claude adapters are pointed at the installed CLIs so the pin
|
|
280
|
+
# governs the transport, not which models the agent can serve.
|
|
281
|
+
acpx_base_argv() { # <route> <cwd> <mcp-config> <timeout-secs> <claude-wrapper> <gemini-home>
|
|
282
|
+
printf '%s\0' env
|
|
283
|
+
# An ambient Google API key or cloud project would bill it instead of the
|
|
284
|
+
# Antigravity subscription. env takes -u only before its first assignment.
|
|
285
|
+
case "$1" in
|
|
286
|
+
antigravity) printf '%s\0' -u GEMINI_API_KEY -u GOOGLE_API_KEY -u GOOGLE_APPLICATION_CREDENTIALS \
|
|
287
|
+
-u GOOGLE_CLOUD_PROJECT -u GOOGLE_CLOUD_LOCATION -u GOOGLE_GENAI_USE_VERTEXAI -u AGY_LLM_GATEWAY_URL ;;
|
|
288
|
+
esac
|
|
289
|
+
if [ "$ACPX_NPM_REFRESH" = true ]; then
|
|
290
|
+
printf '%s\0' npm_config_prefer_offline=false npm_config_fetch_retries=0 npm_config_prefer_online=true
|
|
291
|
+
else
|
|
292
|
+
printf '%s\0' npm_config_prefer_offline=true npm_config_fetch_retries=0
|
|
293
|
+
fi
|
|
294
|
+
case "$1" in
|
|
295
|
+
# codex-acp ignores app-server CLI flags.
|
|
296
|
+
codex) printf '%s\0' "CODEX_PATH=$(command -v codex)" 'CODEX_CONFIG={"features":{"plugins":false}}' ;;
|
|
297
|
+
claude) printf '%s\0' "CLAUDE_CODE_EXECUTABLE=$5" ;;
|
|
298
|
+
antigravity) printf '%s\0' "GEMINI_HOME=$6" "ANTIGRAVITY_HARNESS_PATH=$(acpx_antigravity_helper)" ;;
|
|
299
|
+
esac
|
|
300
|
+
printf '%s\0' npx -y "acpx@$ACPX_VERSION" --cwd "$2" --format json --mcp-config "$3" --timeout "$4"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
acpx_agent_argv() { # <route>
|
|
304
|
+
# The preflight refuses opencode when it is not installed; the bare name keeps
|
|
305
|
+
# --emit-adapter output readable on a machine without it.
|
|
306
|
+
if [ "$1" = opencode ]; then printf '%s\0' --agent "$(command -v opencode || printf opencode) acp" exec
|
|
307
|
+
else printf '%s\0' "$(acpx_agent "$1")" exec; fi
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
# The run's outcome is the result of its own session/prompt request, matched by
|
|
311
|
+
# id because --model adds a session/set_model request before the prompt.
|
|
312
|
+
acpx_outcome() { # <stream-log> -> end_turn | <other stopReason> | error | incomplete | not-sent
|
|
313
|
+
local id outcome
|
|
314
|
+
id="$(jq -R 'fromjson? | select(.method == "session/prompt") | .id' "$1" 2>/dev/null | tail -1)"
|
|
315
|
+
[ -n "$id" ] || { printf 'not-sent'; return 0; }
|
|
316
|
+
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)"
|
|
317
|
+
printf '%s' "${outcome:-incomplete}"
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
# Antigravity holds session/new open while it waits for a browser sign-in it
|
|
321
|
+
# will never get, so a run without a usable login would otherwise sit out the
|
|
322
|
+
# whole idle cap. Nothing has been sent while the session is pending.
|
|
323
|
+
ACPX_SESSION_START_SECS="${CROSS_MODEL_SESSION_START_SECS:-60}"
|
|
324
|
+
acpx_session_state() { # <stream-log> -> starting | pending | open
|
|
325
|
+
local id
|
|
326
|
+
jq -eR 'fromjson? | select(.result.protocolVersion? != null)' "$1" >/dev/null 2>&1 || { printf 'starting'; return 0; }
|
|
327
|
+
id="$(jq -R 'fromjson? | select(.method == "session/new") | .id' "$1" 2>/dev/null | tail -1)"
|
|
328
|
+
if [ -n "$id" ] && jq -eR --argjson id "$id" 'fromjson? | select(.method == null and .id == $id)' "$1" >/dev/null 2>&1; then
|
|
329
|
+
printf 'open'
|
|
330
|
+
else
|
|
331
|
+
printf 'pending'
|
|
332
|
+
fi
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
acpx_text() { # <stream-log> <outfile>: the agent's reply text
|
|
336
|
+
jq -jR 'fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty' "$1" > "$2" 2>/dev/null
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# Model ids the adapter reports in the prompt result's _meta. acpx documents
|
|
340
|
+
# _meta as adapter-defined, so these are the adapter's assertion, not proof.
|
|
341
|
+
acpx_served_models() { # <stream-log> -> "<model> <tokens>" lines
|
|
342
|
+
jq -rR 'fromjson? | select(.result.stopReason?) | .result._meta // {} |
|
|
343
|
+
((.quota.model_usage // [])[] | "\(.model) \(.token_count.totalTokens // 0)"),
|
|
344
|
+
(.modelId // empty | "\(.) 0")' "$1" 2>/dev/null
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# Bounded failure evidence from the stream. The stream echoes the outbound
|
|
348
|
+
# prompt, so only error messages and the stop reason are read from it.
|
|
349
|
+
acpx_failure_evidence() { # <stream-log>
|
|
350
|
+
local evidence
|
|
351
|
+
evidence="$(jq -rR 'fromjson? | (.error.message? // empty), (.result.stopReason? // empty | select(. != "end_turn") | "stopReason=" + .)' "$1" 2>/dev/null | tr '\n' ' ')"
|
|
352
|
+
evidence="${evidence% }"
|
|
353
|
+
[ "${#evidence}" -gt 300 ] && evidence="${evidence:0:147} ... ${evidence: -147}"
|
|
354
|
+
printf '%s' "$evidence"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# codex-acp streams a provider rejection (such as a model the account may not
|
|
358
|
+
# use) as reply text and still ends the turn with end_turn, so only the reply
|
|
359
|
+
# carries it. Bounded inside jq so a long reply never enters a bash string.
|
|
360
|
+
acpx_reply_evidence() { # <stream-log>
|
|
361
|
+
jq -nrR '[inputs | fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty]
|
|
362
|
+
| join("") | gsub("\\s+"; " ") | ltrimstr(" ") | rtrimstr(" ")
|
|
363
|
+
| if length > 300 then .[0:147] + " ... " + .[-147:] else . end' "$1" 2>/dev/null
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
acpx_advertised_models() { # <stream-log>
|
|
367
|
+
jq -nrR '[inputs | fromjson? | .result.models.availableModels[]?.modelId | sub("\\[.*$"; "")]
|
|
368
|
+
| reduce .[] as $m ([]; if any(.[]; . == $m) then . else . + [$m] end)
|
|
369
|
+
| join(", ") | if length > 300 then .[0:296] + " ..." else . end' "$1" 2>/dev/null
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
# Exit 5 is acpx's permission refusal. Paired with stopReason cancelled, the
|
|
373
|
+
# refusal ended the turn. Exit 5 with end_turn means the turn finished. A
|
|
374
|
+
# cancelled turn with any other exit is an interrupt.
|
|
375
|
+
acpx_permission_cancel_reason() { # <stream-log> <exit-code>
|
|
376
|
+
[ "$(acpx_outcome "$1")" = cancelled ] && [ "$2" = 5 ] || return 0
|
|
377
|
+
printf '%s' "permission request cancelled the turn"
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
acpx_node_startup_line() { # <stderr-log>
|
|
381
|
+
local line=""
|
|
382
|
+
line="$(grep -m1 -E '^Error ' "$1" 2>/dev/null)"
|
|
383
|
+
[ -n "$line" ] || line="$(grep -m1 -E '^node:' "$1" 2>/dev/null)"
|
|
384
|
+
printf '%s' "$line"
|
|
385
|
+
}
|
|
386
|
+
# --- end acpx transport -----------------------------------------------------
|
|
387
|
+
|
|
388
|
+
# --- model-identity receipt ---------------------------------------------------
|
|
389
|
+
# "Which model ran" comes from the adapter's _meta report, recorded as the
|
|
390
|
+
# served id; it is never filled from the requested value. Prefer the served id
|
|
391
|
+
# in the requested family (Claude also reports an auxiliary Haiku); otherwise
|
|
392
|
+
# record the model that used the most tokens and warn about the mismatch.
|
|
393
|
+
MODEL_ACTUAL="unverified"
|
|
394
|
+
extract_model_receipt() { # <route>; reads $PEERLOG, sets MODEL_ACTUAL
|
|
395
|
+
local requested family served m tokens most=-1 heaviest=""
|
|
396
|
+
MODEL_ACTUAL="unverified"
|
|
397
|
+
requested="$(route_model "$1")"
|
|
398
|
+
family="${requested%%\[*}"
|
|
399
|
+
case "$family" in
|
|
400
|
+
fable|opus|sonnet|haiku) family="claude-$family" ;;
|
|
401
|
+
esac
|
|
402
|
+
served="$(acpx_served_models "$PEERLOG")"
|
|
403
|
+
[ -n "$served" ] || return 0
|
|
404
|
+
while read -r m tokens; do
|
|
405
|
+
[ -n "$m" ] || continue
|
|
406
|
+
case "$m" in
|
|
407
|
+
"$family"|"$family"-*|"$family"\[*) MODEL_ACTUAL="$m"; return 0 ;;
|
|
408
|
+
esac
|
|
409
|
+
if [ "${tokens:-0}" -gt "$most" ]; then most="${tokens:-0}"; heaviest="$m"; fi
|
|
410
|
+
done <<EOF
|
|
411
|
+
$served
|
|
412
|
+
EOF
|
|
413
|
+
MODEL_ACTUAL="$heaviest"
|
|
414
|
+
# A route that requested no model (Cursor default, OpenCode auto) has nothing to mismatch.
|
|
415
|
+
[ "$requested" = auto ] && return 0
|
|
416
|
+
log "WARNING: model mismatch - requested $requested, adapter reported $MODEL_ACTUAL; reconcile must surface this"
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
# --- adapter argv (single source of truth for route flags) -----------------
|
|
420
|
+
# Read-only intent on every route: reads are approved, every other permission
|
|
421
|
+
# request is denied, and each adapter runs in its read-only mode where it has
|
|
422
|
+
# one (codex mode=read-only, claude mode=default, cursor mode=ask, opencode
|
|
423
|
+
# mode=plan with edits denied, antigravity mode=default). Claude's --allowed-tools is an auto-approve
|
|
424
|
+
# list, not a restriction: its other tools still ask and are denied. The
|
|
425
|
+
# codex adapter does not enforce its read-only mode, so write denial is best
|
|
426
|
+
# effort. PEER_WORKDIR / PROMPT_FILE / MCP_CONFIG / CLAUDE_WRAPPER / GEMINI_HOME_DIR are resolved
|
|
427
|
+
# by the caller (placeholders in --emit-adapter mode).
|
|
428
|
+
adapter_argv() {
|
|
429
|
+
local route="$1" model
|
|
430
|
+
acpx_agent "$route" >/dev/null || return 1
|
|
431
|
+
model="$(route_model "$route")"
|
|
432
|
+
case "$route" in
|
|
433
|
+
# OpenCode's plan mode keeps the peer read-only; denying bash outright makes
|
|
434
|
+
# its free tier reject the session over ACP.
|
|
435
|
+
opencode) printf '%s\0' env OPENCODE_DISABLE_PROJECT_CONFIG=1 \
|
|
436
|
+
'OPENCODE_CONFIG_CONTENT={"permission":{"edit":"deny","webfetch":"deny","task":"deny"}}' ;;
|
|
437
|
+
esac
|
|
438
|
+
acpx_base_argv "$route" "$READ_ROOT" "$MCP_CONFIG" "$HARD_SECS" "$CLAUDE_WRAPPER" "$GEMINI_HOME_DIR"
|
|
439
|
+
printf '%s\0' --approve-reads --non-interactive-permissions deny
|
|
440
|
+
case "$route" in
|
|
441
|
+
claude) printf '%s\0' --allowed-tools Read,Glob,Grep,WebSearch,WebFetch --max-turns 15 ;;
|
|
442
|
+
esac
|
|
443
|
+
[ "$model" = auto ] || printf '%s\0' --model "$model"
|
|
444
|
+
acpx_agent_argv "$route"
|
|
445
|
+
case "$route" in
|
|
446
|
+
codex) printf '%s\0' --config-option mode=read-only --config-option reasoning_effort=high ;;
|
|
447
|
+
claude) printf '%s\0' --config-option mode=default --config-option effort=high ;;
|
|
448
|
+
grok-cli) printf '%s\0' --config-option reasoning_effort=xhigh ;;
|
|
449
|
+
grok-cursor|cursor|composer) printf '%s\0' --config-option mode=ask ;;
|
|
450
|
+
opencode) printf '%s\0' --config-option mode=plan ;;
|
|
451
|
+
antigravity) printf '%s\0' --config-option mode=default --config-option thinking_level=high ;;
|
|
452
|
+
esac
|
|
453
|
+
printf '%s\0' --file "$PROMPT_FILE"
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# The host may replace a stale concrete model only within the fixed route's
|
|
457
|
+
# target family. Values are passed as one argv token; they never enter eval.
|
|
458
|
+
# A codex id may carry the serving provider's own namespace (openai.gpt-...)
|
|
459
|
+
# when the CLI routes through a non-default model_provider.
|
|
460
|
+
apply_model_override() {
|
|
461
|
+
local route="$1" override="${CROSS_MODEL_MODEL_OVERRIDE:-}" override_target="${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" target
|
|
462
|
+
[ -n "$override" ] || { [ -z "$override_target" ]; return; }
|
|
463
|
+
target="$(route_target "$route")" || return 1
|
|
464
|
+
[ "$override_target" = "$target" ] || return 1
|
|
465
|
+
[ "$target" != "cursor" ] || return 1
|
|
466
|
+
case "$route:$override" in
|
|
467
|
+
codex:gpt-*|codex:o[0-9]*|codex:*[./]gpt-*|codex:*[./]o[0-9]* ) ;;
|
|
468
|
+
claude:fable|claude:opus|claude:sonnet|claude:haiku|claude:claude-* ) ;;
|
|
469
|
+
grok-cli:grok-* ) ;;
|
|
470
|
+
grok-cursor:grok-*|grok-cursor:cursor-grok-* ) ;;
|
|
471
|
+
composer:composer-* ) ;;
|
|
472
|
+
opencode:*/* ) ;;
|
|
473
|
+
antigravity:gemini-* ) ;;
|
|
474
|
+
*) return 1 ;;
|
|
475
|
+
esac
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
# --- --emit-adapter <route>: print the argv, no model call, no side effects --
|
|
479
|
+
if [ "${1:-}" = "--emit-adapter" ]; then
|
|
480
|
+
READ_ROOT="<read-root>"
|
|
481
|
+
PROMPT_FILE="<prompt-file>"; MCP_CONFIG="<mcp-config>"; CLAUDE_WRAPPER="<claude-safe-mode-wrapper>"; GEMINI_HOME_DIR="<gemini-home>"
|
|
482
|
+
HARD_SECS="${CROSS_MODEL_HARD_SECS:-600}"
|
|
483
|
+
route="${2:-}"
|
|
484
|
+
apply_model_override "$route" 2>/dev/null || { echo "model override '${CROSS_MODEL_MODEL_OVERRIDE:-}' not compatible with route '$route'" >&2; exit 2; }
|
|
485
|
+
# adapter_argv emits NUL-delimited argv (can't be captured in a shell var), so
|
|
486
|
+
# validate the route first, then render for humans with NUL -> space.
|
|
487
|
+
adapter_argv "$route" >/dev/null 2>&1 || { echo "unknown route '$route' (want codex|claude|grok-cli|grok-cursor|cursor|composer|opencode|antigravity)" >&2; exit 2; }
|
|
488
|
+
adapter_argv "$route" | tr '\0' ' '; echo
|
|
489
|
+
exit 0
|
|
490
|
+
fi
|
|
491
|
+
|
|
492
|
+
HOST_PROVIDER="${1:-unknown}"
|
|
493
|
+
HOST_HARNESS="${CROSS_MODEL_HOST_HARNESS:-unknown}"
|
|
494
|
+
FIXED_ROUTE="${2:-}"
|
|
495
|
+
PAYLOAD_PATH="${3:-}"
|
|
496
|
+
RUN_DIR="${4:-}"
|
|
497
|
+
|
|
498
|
+
# --- validate inputs -------------------------------------------------------
|
|
499
|
+
[ -n "$PAYLOAD_PATH" ] && [ -f "$PAYLOAD_PATH" ] || skip "subject payload '${PAYLOAD_PATH:-<empty>}' not readable on disk; skipping"
|
|
500
|
+
READ_ROOT="${CROSS_MODEL_READ_ROOT:-$(pwd -P)}"
|
|
501
|
+
[ -d "$READ_ROOT" ] || skip "declared repository/read root '$READ_ROOT' is not a directory"
|
|
502
|
+
READ_ROOT="$(cd "$READ_ROOT" && pwd -P)" || skip "cannot resolve repository/read root '$READ_ROOT'"
|
|
503
|
+
if [ -n "${CROSS_MODEL_REPO_ROOT:-}" ]; then
|
|
504
|
+
REPO_ROOT="$CROSS_MODEL_REPO_ROOT"
|
|
505
|
+
elif command -v git >/dev/null 2>&1 && _git_root="$(git -C "$READ_ROOT" rev-parse --show-toplevel 2>/dev/null)"; then
|
|
506
|
+
REPO_ROOT="$_git_root"
|
|
507
|
+
else
|
|
508
|
+
REPO_ROOT="$(pwd -P)"
|
|
509
|
+
fi
|
|
510
|
+
[ -d "$REPO_ROOT" ] || skip "declared repository root '$REPO_ROOT' is not a directory"
|
|
511
|
+
REPO_ROOT="$(cd "$REPO_ROOT" && pwd -P)" || skip "cannot resolve repository root '$REPO_ROOT'"
|
|
512
|
+
case "$READ_ROOT/" in "$REPO_ROOT/"*) ;; *) skip "read root '$READ_ROOT' is outside repository root '$REPO_ROOT'" ;; esac
|
|
513
|
+
|
|
514
|
+
[ -n "$RUN_DIR" ] || skip "run-dir not given; skipping"
|
|
515
|
+
if [ -d "$RUN_DIR" ]; then
|
|
516
|
+
RUN_DIR_RESOLVED="$(cd "$RUN_DIR" && pwd -P)" || skip "cannot resolve run-dir '$RUN_DIR'"
|
|
517
|
+
else
|
|
518
|
+
RUN_PARENT="$(dirname "$RUN_DIR")"
|
|
519
|
+
RUN_BASENAME="$(basename "$RUN_DIR")"
|
|
520
|
+
[ -d "$RUN_PARENT" ] || skip "run-dir parent '$RUN_PARENT' is not a directory"
|
|
521
|
+
RUN_PARENT="$(cd "$RUN_PARENT" && pwd -P)" || skip "cannot resolve run-dir parent '$RUN_PARENT'"
|
|
522
|
+
RUN_DIR_RESOLVED="$RUN_PARENT/$RUN_BASENAME"
|
|
523
|
+
fi
|
|
524
|
+
case "$RUN_DIR_RESOLVED/" in "$REPO_ROOT/"*) skip "run-dir must be outside the repository" ;; esac
|
|
525
|
+
[ -d "$RUN_DIR_RESOLVED" ] || skip "run-dir '$RUN_DIR' must already exist"
|
|
526
|
+
RUN_DIR="$RUN_DIR_RESOLVED"
|
|
527
|
+
chmod 700 "$RUN_DIR" 2>/dev/null || skip "run-dir '$RUN_DIR' could not be made private"
|
|
528
|
+
command -v jq >/dev/null 2>&1 || skip "jq not installed; skipping"
|
|
529
|
+
INCLUDE_PATHS="${CROSS_MODEL_INCLUDE_PATHS:-}"
|
|
530
|
+
EXCLUDE_PATHS="${CROSS_MODEL_EXCLUDE_PATHS:-}"
|
|
531
|
+
|
|
532
|
+
case "$HOST_PROVIDER" in
|
|
533
|
+
codex|claude|grok|composer|gemini|deepseek|unknown) ;;
|
|
534
|
+
*) skip "host serving family '$HOST_PROVIDER' invalid (want codex|claude|grok|composer|gemini|deepseek|unknown)" ;;
|
|
535
|
+
esac
|
|
536
|
+
case "$HOST_HARNESS" in
|
|
537
|
+
codex|claude|grok|cursor|opencode|antigravity|omp|dsh|unknown) ;;
|
|
538
|
+
*) skip "host harness '$HOST_HARNESS' invalid (want codex|claude|grok|cursor|opencode|antigravity|omp|dsh|unknown)" ;;
|
|
539
|
+
esac
|
|
540
|
+
|
|
541
|
+
case "$FIXED_ROUTE" in
|
|
542
|
+
codex|claude|grok-cli|grok-cursor|cursor|composer|opencode|antigravity) ;;
|
|
543
|
+
*) skip "unknown fixed route '${FIXED_ROUTE:-<empty>}'; host must resolve one route before egress" ;;
|
|
544
|
+
esac
|
|
545
|
+
TARGET="$(route_target "$FIXED_ROUTE")" || skip "unknown fixed route '${FIXED_ROUTE:-<empty>}'; host must resolve one route before egress"
|
|
546
|
+
apply_model_override "$FIXED_ROUTE" || skip "model override '${CROSS_MODEL_MODEL_OVERRIDE:-}' not compatible with route '$FIXED_ROUTE'"
|
|
547
|
+
# An Antigravity host can serve several families; never send it to itself.
|
|
548
|
+
if [ "$FIXED_ROUTE" = antigravity ] && { [ "$HOST_PROVIDER" = gemini ] || [ "$HOST_HARNESS" = antigravity ]; }; then
|
|
549
|
+
skip "fixed route 'antigravity' is the host's own Gemini family or Antigravity harness; skipping before egress"
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
# --- self-locate skill root + canonical sibling files ----------------------
|
|
553
|
+
SKILL_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || skip "cannot resolve skill root; skipping"
|
|
554
|
+
PERSONA="$SKILL_ROOT/references/agents/pov-peer.md"
|
|
555
|
+
SCHEMA="$SKILL_ROOT/references/pov-schema.json"
|
|
556
|
+
[ -f "$PERSONA" ] || skip "persona brief not found at $PERSONA; skipping"
|
|
557
|
+
[ -f "$SCHEMA" ] || skip "POV schema not found at $SCHEMA; skipping"
|
|
558
|
+
SCHEMA_CONTENT="$(cat "$SCHEMA")" || skip "cannot read POV schema; skipping"
|
|
559
|
+
|
|
560
|
+
# --- validate the host-resolved fixed route and egress allowlist -------------
|
|
561
|
+
ALLOW="${CROSS_MODEL_PEERS:-}" # optional egress allowlist (R19)
|
|
562
|
+
|
|
563
|
+
in_csv() { case ",$2," in *",$1,"*) return 0 ;; *) return 1 ;; esac; }
|
|
564
|
+
# Require a usable POV, not merely valid JSON. Error envelopes and incomplete
|
|
565
|
+
# objects fail the fixed route and return control to the host without publishing
|
|
566
|
+
# a cross-check artifact.
|
|
567
|
+
pov_shaped() { # <file>: schema-shaped POV (finality is a separate gate)
|
|
568
|
+
[ -s "$1" ] && jq -e \
|
|
569
|
+
'(.voice|type)=="string" and (.voice|length)>0 and (.position|type)=="string" and (.position|length)>0 and (.reasoning|type)=="string" and (.reasoning|length)>0 and (.evidence|type)=="array" and all(.evidence[]; type=="string" and length>0) and (.external_check=="ran" or .external_check=="unavailable") and (.mode=="independent" or .mode=="skeptic") and (.movement=="initial" or .movement=="moved" or .movement=="held")' \
|
|
570
|
+
"$1" >/dev/null 2>&1
|
|
571
|
+
}
|
|
572
|
+
out_missing_or_invalid() { ! pov_shaped "$RAW_OUT"; }
|
|
573
|
+
|
|
574
|
+
# A usable position is a settled answer to the framed question. The peer
|
|
575
|
+
# declares that itself through the schema's required `final` boolean: a
|
|
576
|
+
# schema-shaped artifact whose `final` is not true is non-final (a placeholder
|
|
577
|
+
# emitted before the peer finished inspecting -- observed on grok-cli in the
|
|
578
|
+
# #1402 panel, where the model returned its final schema object on turn one)
|
|
579
|
+
# and must not be published as a peer voice. Finality lives in the owned output
|
|
580
|
+
# contract, never in a phrase list over model prose.
|
|
581
|
+
out_final() { [ -s "$RAW_OUT" ] && jq -e '.final == true' "$RAW_OUT" >/dev/null 2>&1; }
|
|
582
|
+
|
|
583
|
+
# Backward-compatible matrix: legacy `composer` continues to sanction Cursor as
|
|
584
|
+
# the Grok intermediary, while the distinct Cursor-default target requires the
|
|
585
|
+
# new `cursor` key. Composer itself remains sanctioned by `composer`.
|
|
586
|
+
route_allowlisted() {
|
|
587
|
+
[ -z "$ALLOW" ] && return 0
|
|
588
|
+
case "$1" in
|
|
589
|
+
codex|claude|grok-cli) in_csv "$(route_target "$1")" "$ALLOW" ;;
|
|
590
|
+
cursor) in_csv cursor "$ALLOW" ;;
|
|
591
|
+
composer) in_csv composer "$ALLOW" ;;
|
|
592
|
+
grok-cursor)
|
|
593
|
+
in_csv grok "$ALLOW" && { in_csv cursor "$ALLOW" || in_csv composer "$ALLOW"; }
|
|
594
|
+
;;
|
|
595
|
+
opencode|antigravity) in_csv "$1" "$ALLOW" ;;
|
|
596
|
+
*) return 1 ;;
|
|
597
|
+
esac
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
# Soft size gate: peer prompt embeds the full subject payload. Over-budget payloads skip
|
|
601
|
+
# cleanly (R11) rather than collapsing silently inside the provider context window.
|
|
602
|
+
MAX_PAYLOAD_CHARS="${CROSS_MODEL_MAX_PAYLOAD_CHARS:-200000}"
|
|
603
|
+
case "$MAX_PAYLOAD_CHARS" in ''|*[!0-9]*) MAX_PAYLOAD_CHARS=200000 ;; esac
|
|
604
|
+
PAYLOAD_CHARS="$(wc -c <"$PAYLOAD_PATH" | tr -d '[:space:]')"
|
|
605
|
+
if [ "$PAYLOAD_CHARS" -gt "$MAX_PAYLOAD_CHARS" ]; then
|
|
606
|
+
skip "subject payload is ${PAYLOAD_CHARS} bytes (limit ${MAX_PAYLOAD_CHARS}); skipping cross-model pass rather than truncating"
|
|
607
|
+
fi
|
|
608
|
+
|
|
609
|
+
# The Codex desktop app (Codex.app, or ChatGPT.app since the July 2026 merger)
|
|
610
|
+
# ships `codex` at Contents/Resources without linking it onto PATH (#1272).
|
|
611
|
+
# Append, never prepend, so a PATH-installed CLI stays authoritative.
|
|
612
|
+
# CROSS_MODEL_CODEX_APP_DIRS (colon-separated) overrides the probed dirs.
|
|
613
|
+
if ! command -v codex >/dev/null 2>&1; then
|
|
614
|
+
OLDIFS="$IFS"; IFS=':'
|
|
615
|
+
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
|
|
616
|
+
if [ -n "$d" ] && [ -x "$d/codex" ]; then PATH="${PATH:+$PATH:}$d"; export PATH; break; fi
|
|
617
|
+
done
|
|
618
|
+
IFS="$OLDIFS"
|
|
619
|
+
fi
|
|
620
|
+
|
|
621
|
+
route_allowlisted "$FIXED_ROUTE" || skip "fixed route '$FIXED_ROUTE' is not fully sanctioned by CROSS_MODEL_PEERS; skipping before egress"
|
|
622
|
+
acpx_preflight "$FIXED_ROUTE" "$READ_ROOT" || skip "transport unavailable (pre-egress, $ACPX_SCOPE): $ACPX_UNAVAILABLE"
|
|
623
|
+
log "fixed cross-model POV route: target=$TARGET route=$FIXED_ROUTE (host $HOST_PROVIDER excluded)"
|
|
624
|
+
|
|
625
|
+
# --- compose the peer prompt from the canonical persona (single source) ----
|
|
626
|
+
# The payload is prepared by ce-pov and embeds the framed question plus any
|
|
627
|
+
# conversation-only subject material needed for this round. Repository evidence
|
|
628
|
+
# stays in the shared working tree for the peer to inspect directly.
|
|
629
|
+
SCRATCH_PARENT="${CROSS_MODEL_SCRATCH_PARENT:-${TMPDIR:-/tmp}}"
|
|
630
|
+
[ -d "$SCRATCH_PARENT" ] || mkdir -p "$SCRATCH_PARENT" 2>/dev/null || skip "private scratch parent '$SCRATCH_PARENT' unavailable"
|
|
631
|
+
SCRATCH_PARENT="$(cd "$SCRATCH_PARENT" && pwd -P)" || skip "cannot resolve private scratch parent"
|
|
632
|
+
case "$SCRATCH_PARENT/" in "$REPO_ROOT/"*) skip "private scratch parent must be outside the repository" ;; esac
|
|
633
|
+
if ! PEER_WORKDIR="$(mktemp -d "$SCRATCH_PARENT/xmodel-pov-peer-XXXXXX")"; then
|
|
634
|
+
skip "provider $TARGET workspace isolation unavailable; skipping provider"
|
|
635
|
+
fi
|
|
636
|
+
trap 'cleanup_private_scratch' EXIT
|
|
637
|
+
chmod 700 "$PEER_WORKDIR" 2>/dev/null || { cleanup_private_scratch; skip "cannot make peer scratch private"; }
|
|
638
|
+
PROMPT_FILE="$PEER_WORKDIR/prompt.md"
|
|
639
|
+
PEERLOG="$PEER_WORKDIR/stdout.log"
|
|
640
|
+
# acpx prints the ACP stream on stdout; npm and adapter diagnostics go to stderr.
|
|
641
|
+
PEERERR="$PEER_WORKDIR/stderr.log"
|
|
642
|
+
RAW_OUT="$PEER_WORKDIR/pov-$TARGET.raw.json"
|
|
643
|
+
TEXT_OUT="$PEER_WORKDIR/reply.txt"
|
|
644
|
+
MCP_CONFIG="$PEER_WORKDIR/mcp.json"
|
|
645
|
+
: > "$PROMPT_FILE"; : > "$PEERLOG"; : > "$PEERERR"
|
|
646
|
+
printf '{"mcpServers":[]}\n' > "$MCP_CONFIG"
|
|
647
|
+
chmod 600 "$PROMPT_FILE" "$PEERLOG" "$PEERERR" "$MCP_CONFIG" 2>/dev/null || { cleanup_private_scratch; skip "cannot make peer scratch files private"; }
|
|
648
|
+
CLAUDE_WRAPPER=""
|
|
649
|
+
if [ "$FIXED_ROUTE" = claude ]; then
|
|
650
|
+
CLAUDE_WRAPPER="$(acpx_claude_wrapper "$PEER_WORKDIR")" || { cleanup_private_scratch; skip "transport unavailable (pre-egress, route): cannot prepare the Claude --safe-mode launcher"; }
|
|
651
|
+
fi
|
|
652
|
+
GEMINI_HOME_DIR=""
|
|
653
|
+
if [ "$FIXED_ROUTE" = antigravity ]; then
|
|
654
|
+
GEMINI_HOME_DIR="$(acpx_antigravity_home "$PEER_WORKDIR")" || { cleanup_private_scratch; skip "transport unavailable (pre-egress, route): cannot prepare a private Antigravity home"; }
|
|
655
|
+
fi
|
|
656
|
+
{
|
|
657
|
+
cat "$PERSONA"
|
|
658
|
+
printf '\n\n---\n\n'
|
|
659
|
+
printf 'This is an authorized, read-only point-of-view cross-check on the maintainer\047s own project.\n'
|
|
660
|
+
printf 'Return ONE JSON object and nothing else (no prose, no code fence) matching this schema:\n\n'
|
|
661
|
+
printf '%s' "$SCHEMA_CONTENT"
|
|
662
|
+
printf '\n\nSet the top-level "voice" field to "peer" (it will be namespaced to the provider on fold-in).\n'
|
|
663
|
+
printf '\n<repository-read-scope enforcement="cooperative-unless-adapter-supported">\n'
|
|
664
|
+
printf 'root: %s\nincludes: %s\nexcludes: %s\n' "$READ_ROOT" "${INCLUDE_PATHS:-<all>}" "${EXCLUDE_PATHS:-<none>}"
|
|
665
|
+
printf '</repository-read-scope>\n'
|
|
666
|
+
printf '\n<subject-payload>\n'
|
|
667
|
+
cat "$PAYLOAD_PATH"
|
|
668
|
+
printf '\n</subject-payload>\n'
|
|
669
|
+
} > "$PROMPT_FILE"
|
|
670
|
+
# codex-acp's only refusal option is cancel, which ends the turn.
|
|
671
|
+
if [ "$FIXED_ROUTE" = codex ]; then
|
|
672
|
+
printf '\nYour sandbox is read-only and no approval is available: never request escalated or out-of-sandbox permissions (for example to launch a browser, use the network, or write outside the repository). Any permission request ends this review with no result. When a check needs more access than the sandbox gives, reason from the source instead and name the unrun check in reasoning.\n' >> "$PROMPT_FILE"
|
|
673
|
+
fi
|
|
674
|
+
|
|
675
|
+
# --- run machinery -----------------------------------------------------------
|
|
676
|
+
# Every route streams the ACP session, so the idle cap is the liveness guard and
|
|
677
|
+
# HARD_SECS backstops a peer that stays productive past any useful budget. acpx
|
|
678
|
+
# gets the same budget as --timeout, but it applies that per phase, so this
|
|
679
|
+
# script's wall clock stays authoritative. This skill's default HARD_SECS stays
|
|
680
|
+
# at 600s because its codex route runs the lower sol/high tier -- ce-code-review
|
|
681
|
+
# and ce-doc-review run luna/xhigh and default higher. `CROSS_MODEL_HARD_SECS`
|
|
682
|
+
# is shared across all three, and the orchestrator's aggregate deadline derives
|
|
683
|
+
# from it (see references/cross-model-panel.md), so a raised knob raises both windows.
|
|
684
|
+
IDLE_SECS="${CROSS_MODEL_IDLE_SECS:-180}"
|
|
685
|
+
HARD_SECS="${CROSS_MODEL_HARD_SECS:-600}"
|
|
686
|
+
RETRY_MIN_SECS="${CROSS_MODEL_RETRY_MIN_SECS:-60}" # least window worth spending on a non-final retry
|
|
687
|
+
|
|
688
|
+
# Reap a backgrounded job's whole process group: TERM, then KILL after a grace.
|
|
689
|
+
# True while $1 is a live (non-zombie) process. kill -0 succeeds on zombies
|
|
690
|
+
# until wait reaps them, so idle polls must not treat zombies as still running.
|
|
691
|
+
# macOS/BSD often report defunct state as "Z+" (not bare "Z").
|
|
692
|
+
# Match peer-job-runner._pid_running: empty state after ps means not alive
|
|
693
|
+
# (avoids zombie spin). Fall back to kill -0 only when ps itself is missing.
|
|
694
|
+
peer_alive() {
|
|
695
|
+
local st
|
|
696
|
+
kill -0 "$1" 2>/dev/null || return 1
|
|
697
|
+
if ! command -v ps >/dev/null 2>&1; then
|
|
698
|
+
return 0
|
|
699
|
+
fi
|
|
700
|
+
# Git Bash's ps has no -o; fall back to its -p lookup, which has no zombie state.
|
|
701
|
+
if ! st="$(ps -o state= -p "$1" 2>/dev/null)"; then
|
|
702
|
+
ps -p "$1" >/dev/null 2>&1
|
|
703
|
+
return
|
|
704
|
+
fi
|
|
705
|
+
st="$(printf '%s' "$st" | tr -d ' \n')"
|
|
706
|
+
[ -n "$st" ] || return 1
|
|
707
|
+
[ "${st#Z}" = "$st" ]
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
reap() {
|
|
711
|
+
# Signal the process group and grace-poll without wait(). The caller alone
|
|
712
|
+
# wait()s the leader so PEER_EXIT is the real exit status — a second
|
|
713
|
+
# wait here would fail after we already reaped and mark healthy exits as
|
|
714
|
+
# timed-out (#1270 Bugbot). No background KILL timer: orphaned timers can
|
|
715
|
+
# hit recycled PIDs under bun --parallel.
|
|
716
|
+
local pid="$1" grp
|
|
717
|
+
if kill -TERM -- -"$pid" 2>/dev/null; then grp=1; else kill -TERM "$pid" 2>/dev/null || true; grp=0; fi
|
|
718
|
+
for _ in 1 2 3 4 5; do
|
|
719
|
+
if ! peer_alive "$pid"; then
|
|
720
|
+
# Leader exited/zombied — sweep any group survivors; caller wait()s.
|
|
721
|
+
[ "$grp" = 1 ] && kill -KILL -- -"$pid" 2>/dev/null || true
|
|
722
|
+
return 0
|
|
723
|
+
fi
|
|
724
|
+
sleep 1
|
|
725
|
+
done
|
|
726
|
+
if [ "$grp" = 1 ]; then kill -KILL -- -"$pid" 2>/dev/null; else kill -KILL "$pid" 2>/dev/null; fi
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
# TERM/INT: reap the live peer group, then exit cleanly (HUP remains ignored).
|
|
730
|
+
on_term() {
|
|
731
|
+
if [ -n "${_HEARTBEAT_PID:-}" ]; then
|
|
732
|
+
stop_heartbeat
|
|
733
|
+
fi
|
|
734
|
+
if [ -n "${ACTIVE_PEER_PID:-}" ]; then
|
|
735
|
+
log "received TERM/INT; reaping peer process group $ACTIVE_PEER_PID"
|
|
736
|
+
_term_peer="$ACTIVE_PEER_PID"
|
|
737
|
+
reap "$_term_peer" 2>/dev/null || true
|
|
738
|
+
# reap only signals the group; wait reaps the leader so it cannot orphan.
|
|
739
|
+
wait "$_term_peer" 2>/dev/null || true
|
|
740
|
+
ACTIVE_PEER_PID=""
|
|
741
|
+
fi
|
|
742
|
+
exit 0
|
|
743
|
+
}
|
|
744
|
+
trap 'on_term' TERM INT
|
|
745
|
+
|
|
746
|
+
# Build the CMD array for a route (bash 3.2-safe: no mapfile).
|
|
747
|
+
build_cmd() {
|
|
748
|
+
CMD=()
|
|
749
|
+
# NUL-delimited so a token containing spaces or newlines stays ONE argv element.
|
|
750
|
+
while IFS= read -r -d '' tok; do CMD+=("$tok"); done < <(adapter_argv "$1")
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
# --- liveness heartbeat -----------------------------------------------------
|
|
754
|
+
# The peer CLI streams into $PEERLOG (private), so nothing reaches this script's
|
|
755
|
+
# own stdout/stderr during a long model call. An outer supervisor that watches
|
|
756
|
+
# THIS process's output for liveness (the peer-job runner's out.log byte-growth
|
|
757
|
+
# idle window) would mistake a healthy multi-minute run for a wedge. A background
|
|
758
|
+
# writer emits one stderr line every CROSS_MODEL_HEARTBEAT_SECS (default 60s) so
|
|
759
|
+
# that liveness is visible; it is torn down as soon as the foreground wait returns,
|
|
760
|
+
# so it adds no latency to a fast run. Keep this block byte-identical across
|
|
761
|
+
# the peer worker scripts (lifecycle parity).
|
|
762
|
+
_HEARTBEAT_PID=""
|
|
763
|
+
start_heartbeat() {
|
|
764
|
+
local every="${CROSS_MODEL_HEARTBEAT_SECS:-60}" parent_pid="$$"
|
|
765
|
+
# Floor to 1s: a non-numeric or 0 value would make `sleep` return instantly and
|
|
766
|
+
# spin the loop, flooding out.log into the runner's byte cap.
|
|
767
|
+
case "$every" in ''|*[!0-9]*) every=60 ;; esac; [ "$every" -lt 1 ] && every=1
|
|
768
|
+
_HEARTBEAT_READY=0
|
|
769
|
+
trap '_HEARTBEAT_READY=1' USR1
|
|
770
|
+
# Callers restore set +m after launching the peer, so without this the
|
|
771
|
+
# heartbeat inherits the worker pgid and kill -- -PID cannot reach the sleep.
|
|
772
|
+
local prev_m; case "$-" in *m*) prev_m=1;; *) prev_m=0;; esac
|
|
773
|
+
set -m
|
|
774
|
+
( local t0 n sleeper=""
|
|
775
|
+
trap 'kill "${sleeper:-}" 2>/dev/null || true; exit 0' TERM INT
|
|
776
|
+
kill -USR1 "$parent_pid"
|
|
777
|
+
t0="$(date +%s)"
|
|
778
|
+
while kill -0 "$parent_pid" 2>/dev/null; do
|
|
779
|
+
sleep "$every" & sleeper=$!
|
|
780
|
+
wait "$sleeper" 2>/dev/null || exit 0
|
|
781
|
+
sleeper=""
|
|
782
|
+
kill -0 "$parent_pid" 2>/dev/null || break
|
|
783
|
+
n="$(date +%s)"; log "peer alive ($(( n - t0 ))s elapsed)"
|
|
784
|
+
done ) &
|
|
785
|
+
_HEARTBEAT_PID=$!
|
|
786
|
+
[ "$prev_m" = 0 ] && set +m
|
|
787
|
+
while [ "$_HEARTBEAT_READY" != 1 ] && kill -0 "$_HEARTBEAT_PID" 2>/dev/null; do sleep 0.01 || true; done
|
|
788
|
+
trap - USR1
|
|
789
|
+
}
|
|
790
|
+
stop_heartbeat() {
|
|
791
|
+
if [ -n "$_HEARTBEAT_PID" ]; then
|
|
792
|
+
# Leader-only TERM is deferred until the inner `wait $sleeper` returns, so
|
|
793
|
+
# the default 60s interval would block this wait. Signal the process group.
|
|
794
|
+
kill -- -"$_HEARTBEAT_PID" 2>/dev/null || kill "$_HEARTBEAT_PID" 2>/dev/null || true
|
|
795
|
+
wait "$_HEARTBEAT_PID" 2>/dev/null || true
|
|
796
|
+
fi
|
|
797
|
+
_HEARTBEAT_PID=""
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
run_peer_cmd() { # CMD already built for PEER_ROUTE; streams to PEERLOG, diagnostics to PEERERR
|
|
801
|
+
local prev; case "$-" in *m*) prev=1;; *) prev=0;; esac
|
|
802
|
+
SESSION_STALLED=false
|
|
803
|
+
set -m
|
|
804
|
+
# Start npx from private scratch: npx resolves packages from its working
|
|
805
|
+
# directory's node_modules and .npmrc first, and acpx gets the agent's cwd from --cwd.
|
|
806
|
+
( cd "$(dirname "$PEERLOG")" && exec "${CMD[@]}" ) < /dev/null > "$PEERLOG" 2>"$PEERERR" &
|
|
807
|
+
local pid=$!
|
|
808
|
+
ACTIVE_PEER_PID="$pid"
|
|
809
|
+
[ "$prev" = 0 ] && set +m
|
|
810
|
+
start_heartbeat
|
|
811
|
+
local start last=-1 lastchg now size session=open
|
|
812
|
+
[ "$PEER_ROUTE" = antigravity ] && session=starting
|
|
813
|
+
start="$(date +%s)"; lastchg="$start"
|
|
814
|
+
while peer_alive "$pid"; do
|
|
815
|
+
now="$(date +%s)"; size="$(wc -c <"$PEERLOG" 2>/dev/null || echo 0)"
|
|
816
|
+
[ "$size" != "$last" ] && { last="$size"; lastchg="$now"; }
|
|
817
|
+
if [ $(( now - lastchg )) -ge "$IDLE_SECS" ]; then
|
|
818
|
+
log "peer output idle ${IDLE_SECS}s; reaping peer process group"; reap "$pid"; break
|
|
819
|
+
fi
|
|
820
|
+
if [ $(( now - start )) -ge "$HARD_SECS" ]; then
|
|
821
|
+
log "peer exceeded hard cap ${HARD_SECS}s; reaping peer process group"; reap "$pid"; break
|
|
822
|
+
fi
|
|
823
|
+
if [ "$session" != open ] && [ $(( now - start )) -ge "$ACPX_SESSION_START_SECS" ]; then
|
|
824
|
+
session="$(acpx_session_state "$PEERLOG")"
|
|
825
|
+
if [ "$session" = pending ]; then
|
|
826
|
+
SESSION_STALLED=true
|
|
827
|
+
log "Antigravity opened no session within ${ACPX_SESSION_START_SECS}s; reaping peer process group"; reap "$pid"; break
|
|
828
|
+
fi
|
|
829
|
+
fi
|
|
830
|
+
sleep 1
|
|
831
|
+
done
|
|
832
|
+
wait "$pid" 2>/dev/null
|
|
833
|
+
PEER_EXIT=$?
|
|
834
|
+
# Sweep any survivor the provider left in its OWN process group. `set -m` puts
|
|
835
|
+
# the provider in a separate pgid, and on a clean worker exit the runner's
|
|
836
|
+
# final sweep only kills the worker's pgid while a group-orphan reparents off
|
|
837
|
+
# the worker's process tree -- so it must be reaped here, where the pgid is
|
|
838
|
+
# known. reap() returns immediately when the group is already empty.
|
|
839
|
+
reap "$pid" 2>/dev/null || true
|
|
840
|
+
stop_heartbeat
|
|
841
|
+
ACTIVE_PEER_PID=""
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
# Recover a POV object from the agent's reply text, which may wrap it in prose or a fence.
|
|
845
|
+
recover_pov_json() { # <logfile> <outfile>
|
|
846
|
+
# Probe execution, not just PATH presence — Windows Store's python3 stub
|
|
847
|
+
# satisfies `command -v` then exits nonzero (see resolve-python convention).
|
|
848
|
+
local py
|
|
849
|
+
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)"
|
|
850
|
+
[ -n "$py" ] || return 1
|
|
851
|
+
"$py" - "$1" "$2" <<'PY' 2>/dev/null
|
|
852
|
+
import sys, json
|
|
853
|
+
txt = open(sys.argv[1], encoding="utf-8", errors="replace").read()
|
|
854
|
+
best = None
|
|
855
|
+
best_score = -1
|
|
856
|
+
decoder = json.JSONDecoder()
|
|
857
|
+
def shaped(d):
|
|
858
|
+
# Mirror of pov_shaped() in the shell: the same field types and enums,
|
|
859
|
+
# so ranking cannot promote a fully keyed but invalid draft.
|
|
860
|
+
return (
|
|
861
|
+
isinstance(d.get("voice"), str) and d["voice"] != ""
|
|
862
|
+
and isinstance(d.get("position"), str) and d["position"] != ""
|
|
863
|
+
and isinstance(d.get("reasoning"), str) and d["reasoning"] != ""
|
|
864
|
+
and isinstance(d.get("evidence"), list)
|
|
865
|
+
and all(isinstance(e, str) and e != "" for e in d["evidence"])
|
|
866
|
+
and d.get("external_check") in ("ran", "unavailable")
|
|
867
|
+
and d.get("mode") in ("independent", "skeptic")
|
|
868
|
+
and d.get("movement") in ("initial", "moved", "held")
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
def score(d):
|
|
872
|
+
# Prefer a schema-shaped final POV over a shaped non-final one over any
|
|
873
|
+
# dict that merely carries a position; ties go to the later candidate.
|
|
874
|
+
if shaped(d) and d.get("final") is True:
|
|
875
|
+
return 2
|
|
876
|
+
if shaped(d):
|
|
877
|
+
return 1
|
|
878
|
+
return 0
|
|
879
|
+
|
|
880
|
+
def inspect(value):
|
|
881
|
+
global best, best_score
|
|
882
|
+
if isinstance(value, dict):
|
|
883
|
+
if "position" in value:
|
|
884
|
+
sc = score(value)
|
|
885
|
+
if sc >= best_score:
|
|
886
|
+
best, best_score = value, sc
|
|
887
|
+
for child in value.values():
|
|
888
|
+
inspect(child)
|
|
889
|
+
elif isinstance(value, list):
|
|
890
|
+
for child in value:
|
|
891
|
+
inspect(child)
|
|
892
|
+
elif isinstance(value, str):
|
|
893
|
+
for i, ch in enumerate(value):
|
|
894
|
+
if ch not in "{[":
|
|
895
|
+
continue
|
|
896
|
+
try:
|
|
897
|
+
child, _ = decoder.raw_decode(value, i)
|
|
898
|
+
inspect(child)
|
|
899
|
+
except Exception:
|
|
900
|
+
pass
|
|
901
|
+
|
|
902
|
+
inspect(txt)
|
|
903
|
+
if best is not None: open(sys.argv[2], "w").write(json.dumps(best))
|
|
904
|
+
PY
|
|
905
|
+
[ -s "$2" ]
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
bounded_failure_evidence() { # <logfile>; bounded head+tail of a plain-text log
|
|
909
|
+
local path="$1" evidence
|
|
910
|
+
# bash 3.2 rewrites newlines in a large string superlinearly, so read only the
|
|
911
|
+
# ends of a long log instead of the whole file.
|
|
912
|
+
if [ "$(wc -c <"$path")" -le 600 ]; then evidence="$(cat "$path")"
|
|
913
|
+
else IFS= read -r -d '' -n 300 evidence <"$path"; evidence="$evidence ... $(tail -c 300 "$path")"; fi
|
|
914
|
+
evidence="${evidence//$'\n'/ }"
|
|
915
|
+
if [ "${#evidence}" -gt 300 ]; then
|
|
916
|
+
evidence="${evidence:0:147} ... ${evidence: -147}"
|
|
917
|
+
fi
|
|
918
|
+
printf '%s' "$evidence"
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
# Run one route for a provider; leaves a schema-shaped (pre-normalization) $RAW_OUT on success.
|
|
922
|
+
# Success is the prompt's own end_turn result, not acpx's exit status.
|
|
923
|
+
attempt_route() { # <provider> <route>
|
|
924
|
+
local provider="$1" route="$2" outcome reason started remaining node_line cancel_reason
|
|
925
|
+
: > "$PEERLOG"; : > "$PEERERR"; rm -f "$RAW_OUT" "$OUT" "$TEXT_OUT"
|
|
926
|
+
RUN_SUCCEEDED=false
|
|
927
|
+
build_cmd "$route"
|
|
928
|
+
PEER_ROUTE="$route"
|
|
929
|
+
log "peer run: provider=$provider route=$route model=$(route_model "$route") transport=acpx@$ACPX_VERSION POV read-only least-privilege (idle ${IDLE_SECS}s / hard ${HARD_SECS}s)"
|
|
930
|
+
started="$(date +%s)"
|
|
931
|
+
run_peer_cmd
|
|
932
|
+
if acpx_stale_npm_metadata "$PEERERR" "$PEERLOG" && [ "$(acpx_outcome "$PEERLOG")" = not-sent ]; then
|
|
933
|
+
remaining=$(( HARD_SECS - ( $(date +%s) - started ) ))
|
|
934
|
+
if [ "$remaining" -gt 0 ]; then
|
|
935
|
+
log "npm resolved against stale cached package metadata; retrying the same route once with fresh metadata in the ${remaining}s left (nothing was sent)"
|
|
936
|
+
ACPX_NPM_REFRESH=true
|
|
937
|
+
HARD_SECS="$remaining"
|
|
938
|
+
ROUTE_STARTED_AT="$(date +%s)"
|
|
939
|
+
build_cmd "$route"
|
|
940
|
+
run_peer_cmd
|
|
941
|
+
fi
|
|
942
|
+
fi
|
|
943
|
+
outcome="$(acpx_outcome "$PEERLOG")"
|
|
944
|
+
case "$outcome" in
|
|
945
|
+
end_turn)
|
|
946
|
+
RUN_SUCCEEDED=true
|
|
947
|
+
acpx_text "$PEERLOG" "$TEXT_OUT" && recover_pov_json "$TEXT_OUT" "$RAW_OUT"
|
|
948
|
+
extract_model_receipt "$route"
|
|
949
|
+
;;
|
|
950
|
+
not-sent)
|
|
951
|
+
# Nothing reached the provider: npm could not fetch acpx (every route
|
|
952
|
+
# fails alike), or the adapter refused before the prompt (this route only).
|
|
953
|
+
if grep -q '^npm error' "$PEERERR" 2>/dev/null; then
|
|
954
|
+
reason="$(grep -m1 '^npm error' "$PEERERR")"
|
|
955
|
+
PRE_EGRESS="transport unavailable (pre-egress, shared): ${reason:0:200}"
|
|
956
|
+
elif [ "$SESSION_STALLED" = true ]; then
|
|
957
|
+
PRE_EGRESS="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"
|
|
958
|
+
else
|
|
959
|
+
node_line="$(acpx_node_startup_line "$PEERERR")"
|
|
960
|
+
if [ -n "$node_line" ]; then
|
|
961
|
+
PRE_EGRESS="transport unavailable (pre-egress, route): ${node_line:0:200}"
|
|
962
|
+
else
|
|
963
|
+
reason="$(acpx_failure_evidence "$PEERLOG")"
|
|
964
|
+
PRE_EGRESS="transport unavailable (pre-egress, route): ${reason:-acpx exited $PEER_EXIT before sending the prompt}"
|
|
965
|
+
fi
|
|
966
|
+
fi
|
|
967
|
+
;;
|
|
968
|
+
*)
|
|
969
|
+
cancel_reason="$(acpx_permission_cancel_reason "$PEERLOG" "$PEER_EXIT")"
|
|
970
|
+
if [ -n "$cancel_reason" ]; then
|
|
971
|
+
log "peer run ended: $cancel_reason (acpx exit $PEER_EXIT)"
|
|
972
|
+
else
|
|
973
|
+
log "peer run ended with $outcome (acpx exit $PEER_EXIT)"
|
|
974
|
+
fi
|
|
975
|
+
;;
|
|
976
|
+
esac
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
# Run the one fixed route. Any failure returns control to the host without
|
|
980
|
+
# trying a different target, provider, or intermediary.
|
|
981
|
+
run_fixed_route() {
|
|
982
|
+
local provider="$TARGET"
|
|
983
|
+
OUT="$RUN_DIR/pov-$provider.json"
|
|
984
|
+
ACTUAL_ROUTE="$FIXED_ROUTE"
|
|
985
|
+
ROUTE_STARTED_AT="$(date +%s)"
|
|
986
|
+
PRE_EGRESS=""
|
|
987
|
+
attempt_route "$provider" "$FIXED_ROUTE"
|
|
988
|
+
# A configured model is exact; only the editorial Argon request falls back.
|
|
989
|
+
if [ "$FIXED_ROUTE" = antigravity ] && [ "$(route_model antigravity)" = "$M_ANTIGRAVITY" ] &&
|
|
990
|
+
[ "${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" != antigravity ]; then
|
|
991
|
+
case "$PRE_EGRESS" in
|
|
992
|
+
*"did not advertise"*)
|
|
993
|
+
# The fallback shares the route's window; the non-final retry below
|
|
994
|
+
# then measures what is left from this restart.
|
|
995
|
+
remaining=$(( HARD_SECS - ( $(date +%s) - ROUTE_STARTED_AT ) ))
|
|
996
|
+
if [ "$remaining" -gt 0 ]; then
|
|
997
|
+
log "Antigravity does not offer $M_ANTIGRAVITY on this account; falling back to $M_ANTIGRAVITY_FALLBACK"
|
|
998
|
+
ANTIGRAVITY_MODEL="$M_ANTIGRAVITY_FALLBACK"
|
|
999
|
+
PRE_EGRESS=""
|
|
1000
|
+
HARD_SECS="$remaining"
|
|
1001
|
+
ROUTE_STARTED_AT="$(date +%s)"
|
|
1002
|
+
attempt_route "$provider" "$FIXED_ROUTE"
|
|
1003
|
+
fi
|
|
1004
|
+
;;
|
|
1005
|
+
esac
|
|
1006
|
+
fi
|
|
1007
|
+
[ -z "$PRE_EGRESS" ] || { cleanup_private_scratch; skip "$PRE_EGRESS"; }
|
|
1008
|
+
# One bounded retry on the same route, target, model, and scope; the only
|
|
1009
|
+
# change is a final-answer instruction. The retry gets only what is left of
|
|
1010
|
+
# this worker's HARD_SECS window so both attempts stay inside the panel's
|
|
1011
|
+
# aggregate deadline (cross-model-panel.md: CROSS_MODEL_HARD_SECS + 10s);
|
|
1012
|
+
# too little left means no retry. A second non-final position drops the
|
|
1013
|
+
# voice with skip evidence -- no route hopping.
|
|
1014
|
+
nonfinal_position=""
|
|
1015
|
+
if [ "$RUN_SUCCEEDED" = true ] && ! out_missing_or_invalid && ! out_final; then
|
|
1016
|
+
position="$(jq -r '.position' "$RAW_OUT" 2>/dev/null)"
|
|
1017
|
+
remaining=$(( HARD_SECS - ( $(date +%s) - ROUTE_STARTED_AT ) ))
|
|
1018
|
+
if [ "$remaining" -lt "$RETRY_MIN_SECS" ]; then
|
|
1019
|
+
log "peer returned a non-final position (\"${position:0:120}\") with ${remaining}s of the ${HARD_SECS}s window left; not retrying"
|
|
1020
|
+
nonfinal_position="$position"
|
|
1021
|
+
rm -f "$RAW_OUT"
|
|
1022
|
+
else
|
|
1023
|
+
log "peer returned a non-final position (\"${position:0:120}\"); retrying once on the same route with a final-answer requirement (${remaining}s left)"
|
|
1024
|
+
printf '\n\nYour previous response set final to false. This response is the final one: inspect the subject and shared working tree now, then return the settled position with its evidence and final set to true.\n' >> "$PROMPT_FILE"
|
|
1025
|
+
HARD_SECS="$remaining"
|
|
1026
|
+
attempt_route "$provider" "$FIXED_ROUTE"
|
|
1027
|
+
if [ "$RUN_SUCCEEDED" = true ] && ! out_missing_or_invalid && ! out_final; then
|
|
1028
|
+
nonfinal_position="$(jq -r '.position' "$RAW_OUT" 2>/dev/null)"
|
|
1029
|
+
rm -f "$RAW_OUT"
|
|
1030
|
+
fi
|
|
1031
|
+
fi
|
|
1032
|
+
fi
|
|
1033
|
+
|
|
1034
|
+
# --- normalize + validate against the peer POV contract ------------------
|
|
1035
|
+
# Force voice = peer-<provider>, preserve the POV fields, and add route/model
|
|
1036
|
+
# receipts from the route that actually ran. The peer never self-attributes an
|
|
1037
|
+
# unverifiable serving model.
|
|
1038
|
+
# Publish ONLY the normalized OUT into RUN_DIR. RAW_OUT lives in the per-peer
|
|
1039
|
+
# workspace and is never a fold-in artifact — if this script dies before normalize
|
|
1040
|
+
# (orphaned launch), synthesis finds no .json in RUN_DIR.
|
|
1041
|
+
rm -f "$OUT"
|
|
1042
|
+
if [ -s "$RAW_OUT" ]; then
|
|
1043
|
+
_norm="$PEER_WORKDIR/normalized.json"
|
|
1044
|
+
case "$ACTUAL_ROUTE:$MODEL_ACTUAL" in
|
|
1045
|
+
cursor:*) serving_family="unknown" ;;
|
|
1046
|
+
composer:unverified|grok-cursor:unverified) serving_family="unknown" ;;
|
|
1047
|
+
*) serving_family="$(target_serving_family "$provider")" ;;
|
|
1048
|
+
esac
|
|
1049
|
+
independence=false
|
|
1050
|
+
[ "$HOST_PROVIDER" != "unknown" ] && [ "$serving_family" != "unknown" ] && [ "$HOST_PROVIDER" != "$serving_family" ] && independence=true
|
|
1051
|
+
if jq --arg v "peer-$provider" --arg route "$ACTUAL_ROUTE" \
|
|
1052
|
+
--arg target "$provider" --arg harness "$(route_harness "$ACTUAL_ROUTE")" \
|
|
1053
|
+
--arg family "$serving_family" \
|
|
1054
|
+
--arg mreq "$(route_model "$ACTUAL_ROUTE")" --arg mact "$MODEL_ACTUAL" \
|
|
1055
|
+
--argjson independent "$independence" \
|
|
1056
|
+
'if ((.voice|type)=="string" and (.voice|length)>0 and (.position|type)=="string" and (.position|length)>0 and (.reasoning|type)=="string" and (.reasoning|length)>0 and (.evidence|type)=="array" and all(.evidence[]; type=="string" and length>0) and (.external_check=="ran" or .external_check=="unavailable") and (.mode=="independent" or .mode=="skeptic") and (.movement=="initial" or .movement=="moved" or .movement=="held") and .final==true)
|
|
1057
|
+
then { voice: $v,
|
|
1058
|
+
cross_model_route: $route,
|
|
1059
|
+
cross_model_target: $target,
|
|
1060
|
+
cross_model_harness: $harness,
|
|
1061
|
+
serving_family: $family,
|
|
1062
|
+
model_requested: $mreq,
|
|
1063
|
+
model_actual: $mact,
|
|
1064
|
+
independence_verified: $independent,
|
|
1065
|
+
position: .position,
|
|
1066
|
+
reasoning: .reasoning,
|
|
1067
|
+
evidence: .evidence,
|
|
1068
|
+
external_check: .external_check,
|
|
1069
|
+
mode: .mode,
|
|
1070
|
+
movement: .movement,
|
|
1071
|
+
final: true }
|
|
1072
|
+
else empty end' \
|
|
1073
|
+
"$RAW_OUT" > "$_norm" 2>/dev/null; then
|
|
1074
|
+
mv "$_norm" "$OUT"
|
|
1075
|
+
chmod 600 "$OUT" 2>/dev/null || { rm -f "$OUT"; log "could not make result artifact private"; }
|
|
1076
|
+
else
|
|
1077
|
+
rm -f "$_norm"
|
|
1078
|
+
fi
|
|
1079
|
+
rm -f "$RAW_OUT"
|
|
1080
|
+
fi
|
|
1081
|
+
if [ -s "$OUT" ] && jq -e \
|
|
1082
|
+
'(.voice|type)=="string" and (.position|type)=="string" and (.position|length)>0 and (.reasoning|type)=="string" and (.reasoning|length)>0 and (.evidence|type)=="array" and all(.evidence[]; type=="string" and length>0) and (.external_check=="ran" or .external_check=="unavailable") and (.mode=="independent" or .mode=="skeptic") and (.movement=="initial" or .movement=="moved" or .movement=="held") and (.independence_verified|type)=="boolean"' \
|
|
1083
|
+
"$OUT" >/dev/null 2>&1; then
|
|
1084
|
+
log "wrote peer POV to $OUT (voice peer-$provider)"
|
|
1085
|
+
else
|
|
1086
|
+
log "provider $provider produced no usable schema-shaped output; skipping fold-in"
|
|
1087
|
+
[ -n "$nonfinal_position" ] && log " peer skip evidence: non-final position: ${nonfinal_position:0:200}"
|
|
1088
|
+
# Surface bounded, actionable peer evidence so the orchestrator can
|
|
1089
|
+
# reason about WHY it was skipped (quota/usage-limit exhaustion vs an ordinary
|
|
1090
|
+
# empty review) and, in a repeated-pass session, deprioritize an exhausted
|
|
1091
|
+
# route. Provider errors arrive as ACP error messages or as reply text on
|
|
1092
|
+
# stdout; npm and adapter diagnostics on stderr.
|
|
1093
|
+
_pt="$(acpx_permission_cancel_reason "$PEERLOG" "$PEER_EXIT")"
|
|
1094
|
+
[ -n "$_pt" ] || _pt="$(acpx_failure_evidence "$PEERLOG")"
|
|
1095
|
+
[ -n "$_pt" ] && log " peer skip evidence: $_pt"
|
|
1096
|
+
_pr="$(acpx_reply_evidence "$PEERLOG")"
|
|
1097
|
+
[ -n "$_pr" ] && log " peer skip evidence (reply): $_pr"
|
|
1098
|
+
_pm="$(acpx_advertised_models "$PEERLOG")"
|
|
1099
|
+
[ -n "$_pm" ] && log " peer skip evidence (advertised models): $_pm"
|
|
1100
|
+
if [ -s "$PEERERR" ]; then
|
|
1101
|
+
_pe="$(bounded_failure_evidence "$PEERERR")"
|
|
1102
|
+
log " peer skip evidence (stderr): $_pe"
|
|
1103
|
+
fi
|
|
1104
|
+
rm -f "$OUT" "$RAW_OUT"
|
|
1105
|
+
fi
|
|
1106
|
+
cleanup_private_scratch
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
run_fixed_route
|
|
1110
|
+
exit 0
|