dsh-aris-panel 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 +98 -0
- package/README_CN.md +87 -0
- package/dsh/checkout.patch.yml +38 -0
- package/dsh/client.js +634 -0
- package/dsh/cordis.patch.yml +44 -0
- package/dsh/index.mjs +76 -0
- package/dsh/run-status.mjs +182 -0
- package/dsh/scope-limits.mjs +50 -0
- package/dsh/workbench.mjs +291 -0
- package/mcp-servers/claude-review/README.md +93 -0
- package/mcp-servers/claude-review/run_with_claude_aws.sh +49 -0
- package/mcp-servers/claude-review/server.py +718 -0
- package/mcp-servers/codex-image2/README.md +65 -0
- package/mcp-servers/codex-image2/server.py +893 -0
- package/mcp-servers/feishu-bridge/requirements.txt +1 -0
- package/mcp-servers/feishu-bridge/server.py +240 -0
- package/mcp-servers/gemini-review/README.md +171 -0
- package/mcp-servers/gemini-review/server.py +1856 -0
- package/mcp-servers/llm-chat/requirements.txt +1 -0
- package/mcp-servers/llm-chat/server.py +664 -0
- package/mcp-servers/manual-review/README.md +133 -0
- package/mcp-servers/manual-review/server.py +910 -0
- package/mcp-servers/manual-review/ui.html +279 -0
- package/mcp-servers/minimax-chat/requirements.txt +1 -0
- package/mcp-servers/minimax-chat/server.py +381 -0
- package/package.json +51 -0
- package/skills/ablation-planner/SKILL.md +123 -0
- package/skills/alphaxiv/SKILL.md +196 -0
- package/skills/analyze-results/SKILL.md +46 -0
- package/skills/arxiv/SKILL.md +248 -0
- package/skills/auto-paper-improvement-loop/SKILL.md +651 -0
- package/skills/auto-review-loop/SKILL.md +1137 -0
- package/skills/auto-review-loop-llm/SKILL.md +259 -0
- package/skills/auto-review-loop-minimax/SKILL.md +302 -0
- package/skills/citation-audit/SKILL.md +502 -0
- package/skills/claims-drafting/SKILL.md +227 -0
- package/skills/comm-lit-review/SKILL.md +297 -0
- package/skills/deepxiv/SKILL.md +263 -0
- package/skills/dse-loop/SKILL.md +296 -0
- package/skills/embodiment-description/SKILL.md +129 -0
- package/skills/exa-search/SKILL.md +205 -0
- package/skills/experiment-audit/SKILL.md +311 -0
- package/skills/experiment-bridge/SKILL.md +376 -0
- package/skills/experiment-plan/SKILL.md +249 -0
- package/skills/experiment-queue/SKILL.md +431 -0
- package/skills/experiment-queue/scripts/build_manifest.py +142 -0
- package/skills/experiment-queue/scripts/queue_manager.py +433 -0
- package/skills/feishu-notify/SKILL.md +156 -0
- package/skills/figure-description/SKILL.md +138 -0
- package/skills/figure-spec/SKILL.md +262 -0
- package/skills/figure-spec/scripts/figure_renderer.py +799 -0
- package/skills/formula-derivation/SKILL.md +280 -0
- package/skills/gemini-search/SKILL.md +231 -0
- package/skills/grant-proposal/SKILL.md +698 -0
- package/skills/idea-creator/SKILL.md +542 -0
- package/skills/idea-discovery/SKILL.md +521 -0
- package/skills/idea-discovery-robot/SKILL.md +363 -0
- package/skills/integrity-forensics/SKILL.md +284 -0
- package/skills/interview-cheatsheet/SKILL.md +245 -0
- package/skills/invention-structuring/SKILL.md +188 -0
- package/skills/jurisdiction-format/SKILL.md +192 -0
- package/skills/kill-argument/SKILL.md +437 -0
- package/skills/mermaid-diagram/SKILL.md +419 -0
- package/skills/meta-apply/SKILL.md +141 -0
- package/skills/meta-optimize/SKILL.md +437 -0
- package/skills/monitor-experiment/SKILL.md +140 -0
- package/skills/novelty-check/SKILL.md +101 -0
- package/skills/openalex/SKILL.md +237 -0
- package/skills/overleaf-sync/SKILL.md +220 -0
- package/skills/paper-claim-audit/SKILL.md +348 -0
- package/skills/paper-compile/SKILL.md +266 -0
- package/skills/paper-figure/SKILL.md +312 -0
- package/skills/paper-illustration/SKILL.md +736 -0
- package/skills/paper-illustration-image2/SKILL.md +391 -0
- package/skills/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
- package/skills/paper-plan/SKILL.md +386 -0
- package/skills/paper-poster/SKILL.md +19 -0
- package/skills/paper-poster-html/DESIGN_FINAL.md +176 -0
- package/skills/paper-poster-html/IMPLEMENTATION_CONVENTIONS.md +161 -0
- package/skills/paper-poster-html/LICENSES/posterly-MIT.txt +21 -0
- package/skills/paper-poster-html/NOTICE.md +57 -0
- package/skills/paper-poster-html/SKILL.md +323 -0
- package/skills/paper-poster-html/scripts/_posterly/__init__.py +0 -0
- package/skills/paper-poster-html/scripts/_posterly/canvas.py +200 -0
- package/skills/paper-poster-html/scripts/_posterly/measure.py +588 -0
- package/skills/paper-poster-html/scripts/_posterly/polish.py +498 -0
- package/skills/paper-poster-html/scripts/_posterly/preflight.py +489 -0
- package/skills/paper-poster-html/scripts/_posterly/render.py +215 -0
- package/skills/paper-poster-html/scripts/_posterly/textutil.py +16 -0
- package/skills/paper-poster-html/scripts/_posterly/verify_final.py +171 -0
- package/skills/paper-poster-html/scripts/asset_check.py +897 -0
- package/skills/paper-poster-html/scripts/extract_pdf_figures.py +666 -0
- package/skills/paper-poster-html/scripts/poster_check.py +251 -0
- package/skills/paper-poster-html/scripts/preprocess_figures.py +238 -0
- package/skills/paper-poster-html/scripts/render_preview.py +217 -0
- package/skills/paper-poster-html/scripts/run_gates.py +556 -0
- package/skills/paper-poster-html/scripts/style_check.py +1324 -0
- package/skills/paper-poster-html/templates/COMPONENTS.md +462 -0
- package/skills/paper-poster-html/templates/README.md +170 -0
- package/skills/paper-poster-html/templates/landscape_4col.html +1032 -0
- package/skills/paper-poster-html/templates/landscape_hero.html +1046 -0
- package/skills/paper-poster-html/templates/portrait_2col.html +947 -0
- package/skills/paper-poster-html/templates/tokens/acl.json +9 -0
- package/skills/paper-poster-html/templates/tokens/cvpr.json +9 -0
- package/skills/paper-poster-html/templates/tokens/generic.json +9 -0
- package/skills/paper-poster-html/templates/tokens/iclr.json +9 -0
- package/skills/paper-poster-html/templates/tokens/icml.json +9 -0
- package/skills/paper-poster-html/templates/tokens/neurips.json +9 -0
- package/skills/paper-slides/SKILL.md +635 -0
- package/skills/paper-talk/SKILL.md +381 -0
- package/skills/paper-write/SKILL.md +604 -0
- package/skills/paper-write/templates/IEEEtran.bst +2409 -0
- package/skills/paper-write/templates/IEEEtran.cls +6347 -0
- package/skills/paper-write/templates/iclr2026.tex +84 -0
- package/skills/paper-write/templates/icml2025.tex +87 -0
- package/skills/paper-write/templates/ieee_conference.tex +89 -0
- package/skills/paper-write/templates/ieee_journal.tex +93 -0
- package/skills/paper-write/templates/math_commands.tex +48 -0
- package/skills/paper-write/templates/neurips2025.tex +80 -0
- package/skills/paper-writing/SKILL.md +916 -0
- package/skills/patent-novelty-check/SKILL.md +153 -0
- package/skills/patent-pipeline/SKILL.md +344 -0
- package/skills/patent-review/SKILL.md +203 -0
- package/skills/pixel-art/SKILL.md +137 -0
- package/skills/prior-art-search/SKILL.md +146 -0
- package/skills/proof-checker/SKILL.md +866 -0
- package/skills/proof-orchestrator/NOTICE.md +24 -0
- package/skills/proof-orchestrator/SKILL.md +254 -0
- package/skills/proof-orchestrator/references/audit-output-contract.md +126 -0
- package/skills/proof-orchestrator/references/deepseek-routing.md +74 -0
- package/skills/proof-orchestrator/references/dispatch-prompts.md +227 -0
- package/skills/proof-orchestrator/references/notation-audit.md +135 -0
- package/skills/proof-orchestrator/references/proof-audit-rubric.md +70 -0
- package/skills/proof-orchestrator/references/stress-tests.md +38 -0
- package/skills/proof-writer/SKILL.md +223 -0
- package/skills/qzcli/SKILL.md +324 -0
- package/skills/rebuttal/SKILL.md +376 -0
- package/skills/render-html/SKILL.md +316 -0
- package/skills/render-html/scripts/render_html.py +1006 -0
- package/skills/render-html/scripts/templates/academic.html +703 -0
- package/skills/render-html/scripts/templates/dashboard.html +333 -0
- package/skills/research-lit/SKILL.md +756 -0
- package/skills/research-pipeline/SKILL.md +384 -0
- package/skills/research-refine/SKILL.md +770 -0
- package/skills/research-refine-pipeline/SKILL.md +186 -0
- package/skills/research-review/SKILL.md +198 -0
- package/skills/research-wiki/SKILL.md +461 -0
- package/skills/resubmit-pipeline/SKILL.md +447 -0
- package/skills/result-to-claim/SKILL.md +311 -0
- package/skills/run-experiment/SKILL.md +313 -0
- package/skills/semantic-scholar/SKILL.md +236 -0
- package/skills/serverless-modal/SKILL.md +335 -0
- package/skills/shared-references/acceptance-gate.md +324 -0
- package/skills/shared-references/assurance-contract.md +248 -0
- package/skills/shared-references/capture-antipatterns.md +78 -0
- package/skills/shared-references/citation-discipline.md +583 -0
- package/skills/shared-references/compute-env-contract.md +163 -0
- package/skills/shared-references/effort-contract.md +183 -0
- package/skills/shared-references/evidence-precheck.md +65 -0
- package/skills/shared-references/experiment-integrity.md +49 -0
- package/skills/shared-references/external-cadence.md +326 -0
- package/skills/shared-references/fan-out-pattern.md +366 -0
- package/skills/shared-references/injection-hygiene.md +127 -0
- package/skills/shared-references/integration-contract.md +461 -0
- package/skills/shared-references/output-composition.md +93 -0
- package/skills/shared-references/output-language.md +45 -0
- package/skills/shared-references/output-manifest.md +49 -0
- package/skills/shared-references/output-versioning.md +111 -0
- package/skills/shared-references/patent-format-cn.md +199 -0
- package/skills/shared-references/patent-format-ep.md +173 -0
- package/skills/shared-references/patent-format-us.md +161 -0
- package/skills/shared-references/patent-writing-principles.md +197 -0
- package/skills/shared-references/prior-art-databases.md +141 -0
- package/skills/shared-references/resumable-runs.md +109 -0
- package/skills/shared-references/review-scope-limits.md +81 -0
- package/skills/shared-references/review-tracing.md +391 -0
- package/skills/shared-references/reviewer-independence.md +79 -0
- package/skills/shared-references/reviewer-routing.md +852 -0
- package/skills/shared-references/skill-governance.md +104 -0
- package/skills/shared-references/taste-calibration.md +85 -0
- package/skills/shared-references/venue-checklists.md +114 -0
- package/skills/shared-references/wiki-helper-resolution.md +134 -0
- package/skills/shared-references/writing-principles.md +525 -0
- package/skills/skills-codex/README.md +102 -0
- package/skills/skills-codex/README_CN.md +100 -0
- package/skills/skills-codex/ablation-planner/SKILL.md +126 -0
- package/skills/skills-codex/alphaxiv/SKILL.md +186 -0
- package/skills/skills-codex/analyze-results/SKILL.md +45 -0
- package/skills/skills-codex/arxiv/SKILL.md +210 -0
- package/skills/skills-codex/auto-paper-improvement-loop/SKILL.md +574 -0
- package/skills/skills-codex/auto-review-loop/SKILL.md +500 -0
- package/skills/skills-codex/auto-review-loop-llm/SKILL.md +247 -0
- package/skills/skills-codex/auto-review-loop-minimax/SKILL.md +290 -0
- package/skills/skills-codex/citation-audit/SKILL.md +504 -0
- package/skills/skills-codex/claims-drafting/SKILL.md +239 -0
- package/skills/skills-codex/comm-lit-review/SKILL.md +299 -0
- package/skills/skills-codex/comm-lit-review/references/domain-taxonomy.md +57 -0
- package/skills/skills-codex/comm-lit-review/references/output-template.md +37 -0
- package/skills/skills-codex/comm-lit-review/references/source-policy.md +99 -0
- package/skills/skills-codex/comm-lit-review/references/venue-tiering.md +112 -0
- package/skills/skills-codex/deepxiv/SKILL.md +142 -0
- package/skills/skills-codex/dse-loop/SKILL.md +285 -0
- package/skills/skills-codex/embodiment-description/SKILL.md +129 -0
- package/skills/skills-codex/exa-search/SKILL.md +192 -0
- package/skills/skills-codex/experiment-audit/SKILL.md +286 -0
- package/skills/skills-codex/experiment-bridge/SKILL.md +356 -0
- package/skills/skills-codex/experiment-plan/SKILL.md +249 -0
- package/skills/skills-codex/experiment-queue/SKILL.md +401 -0
- package/skills/skills-codex/feishu-notify/SKILL.md +155 -0
- package/skills/skills-codex/figure-description/SKILL.md +138 -0
- package/skills/skills-codex/figure-spec/SKILL.md +252 -0
- package/skills/skills-codex/formula-derivation/SKILL.md +280 -0
- package/skills/skills-codex/gemini-search/SKILL.md +205 -0
- package/skills/skills-codex/grant-proposal/SKILL.md +626 -0
- package/skills/skills-codex/idea-creator/SKILL.md +405 -0
- package/skills/skills-codex/idea-discovery/SKILL.md +475 -0
- package/skills/skills-codex/idea-discovery-robot/SKILL.md +362 -0
- package/skills/skills-codex/integrity-forensics/SKILL.md +106 -0
- package/skills/skills-codex/interview-cheatsheet/SKILL.md +245 -0
- package/skills/skills-codex/invention-structuring/SKILL.md +188 -0
- package/skills/skills-codex/jurisdiction-format/SKILL.md +192 -0
- package/skills/skills-codex/kill-argument/SKILL.md +403 -0
- package/skills/skills-codex/mermaid-diagram/SKILL.md +379 -0
- package/skills/skills-codex/meta-apply/SKILL.md +154 -0
- package/skills/skills-codex/meta-optimize/SKILL.md +348 -0
- package/skills/skills-codex/monitor-experiment/SKILL.md +98 -0
- package/skills/skills-codex/novelty-check/SKILL.md +89 -0
- package/skills/skills-codex/openalex/SKILL.md +228 -0
- package/skills/skills-codex/overleaf-sync/SKILL.md +220 -0
- package/skills/skills-codex/paper-claim-audit/SKILL.md +350 -0
- package/skills/skills-codex/paper-compile/SKILL.md +253 -0
- package/skills/skills-codex/paper-figure/SKILL.md +311 -0
- package/skills/skills-codex/paper-illustration/SKILL.md +690 -0
- package/skills/skills-codex/paper-illustration-image2/SKILL.md +383 -0
- package/skills/skills-codex/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
- package/skills/skills-codex/paper-plan/SKILL.md +278 -0
- package/skills/skills-codex/paper-poster/SKILL.md +19 -0
- package/skills/skills-codex/paper-poster-html/SKILL.md +377 -0
- package/skills/skills-codex/paper-slides/SKILL.md +571 -0
- package/skills/skills-codex/paper-talk/SKILL.md +381 -0
- package/skills/skills-codex/paper-write/SKILL.md +411 -0
- package/skills/skills-codex/paper-write/templates/IEEEtran.bst +2409 -0
- package/skills/skills-codex/paper-write/templates/IEEEtran.cls +6347 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.bst +1493 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.sty +315 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.tex +952 -0
- package/skills/skills-codex/paper-write/templates/acl.sty +312 -0
- package/skills/skills-codex/paper-write/templates/acl2026.tex +377 -0
- package/skills/skills-codex/paper-write/templates/acl_natbib.bst +1940 -0
- package/skills/skills-codex/paper-write/templates/acm.bst +3081 -0
- package/skills/skills-codex/paper-write/templates/acm_mm2026.tex +204 -0
- package/skills/skills-codex/paper-write/templates/acmart.cls +3520 -0
- package/skills/skills-codex/paper-write/templates/cvpr.bst +1448 -0
- package/skills/skills-codex/paper-write/templates/cvpr.sty +508 -0
- package/skills/skills-codex/paper-write/templates/cvpr2026.tex +63 -0
- package/skills/skills-codex/paper-write/templates/iclr2026.tex +84 -0
- package/skills/skills-codex/paper-write/templates/iclr2026_conference.bst +1440 -0
- package/skills/skills-codex/paper-write/templates/iclr2026_conference.sty +246 -0
- package/skills/skills-codex/paper-write/templates/icml2026.sty +767 -0
- package/skills/skills-codex/paper-write/templates/icml2026.tex +662 -0
- package/skills/skills-codex/paper-write/templates/ieee_conference.tex +89 -0
- package/skills/skills-codex/paper-write/templates/ieee_journal.tex +93 -0
- package/skills/skills-codex/paper-write/templates/math_commands.tex +48 -0
- package/skills/skills-codex/paper-write/templates/neurips2026.tex +493 -0
- package/skills/skills-codex/paper-write/templates/neurips_2026.sty +437 -0
- package/skills/skills-codex/paper-writing/SKILL.md +731 -0
- package/skills/skills-codex/patent-novelty-check/SKILL.md +153 -0
- package/skills/skills-codex/patent-pipeline/SKILL.md +344 -0
- package/skills/skills-codex/patent-review/SKILL.md +202 -0
- package/skills/skills-codex/pixel-art/SKILL.md +139 -0
- package/skills/skills-codex/prior-art-search/SKILL.md +146 -0
- package/skills/skills-codex/proof-checker/SKILL.md +554 -0
- package/skills/skills-codex/proof-orchestrator/SKILL.md +260 -0
- package/skills/skills-codex/proof-orchestrator/references/audit-output-contract.md +126 -0
- package/skills/skills-codex/proof-orchestrator/references/deepseek-routing.md +76 -0
- package/skills/skills-codex/proof-orchestrator/references/dispatch-prompts.md +227 -0
- package/skills/skills-codex/proof-orchestrator/references/notation-audit.md +135 -0
- package/skills/skills-codex/proof-orchestrator/references/proof-audit-rubric.md +70 -0
- package/skills/skills-codex/proof-orchestrator/references/stress-tests.md +38 -0
- package/skills/skills-codex/proof-writer/SKILL.md +222 -0
- package/skills/skills-codex/qzcli/SKILL.md +324 -0
- package/skills/skills-codex/rebuttal/SKILL.md +305 -0
- package/skills/skills-codex/render-html/SKILL.md +305 -0
- package/skills/skills-codex/render-html/scripts/__pycache__/render_html.cpython-314.pyc +0 -0
- package/skills/skills-codex/render-html/scripts/render_html.py +909 -0
- package/skills/skills-codex/render-html/scripts/templates/academic.html +342 -0
- package/skills/skills-codex/render-html/scripts/templates/dashboard.html +333 -0
- package/skills/skills-codex/research-lit/SKILL.md +464 -0
- package/skills/skills-codex/research-pipeline/SKILL.md +340 -0
- package/skills/skills-codex/research-refine/SKILL.md +721 -0
- package/skills/skills-codex/research-refine-pipeline/SKILL.md +186 -0
- package/skills/skills-codex/research-review/SKILL.md +135 -0
- package/skills/skills-codex/research-wiki/SKILL.md +421 -0
- package/skills/skills-codex/resubmit-pipeline/SKILL.md +444 -0
- package/skills/skills-codex/result-to-claim/SKILL.md +246 -0
- package/skills/skills-codex/run-experiment/SKILL.md +236 -0
- package/skills/skills-codex/semantic-scholar/SKILL.md +219 -0
- package/skills/skills-codex/serverless-modal/SKILL.md +335 -0
- package/skills/skills-codex/shared-references/acceptance-gate.md +336 -0
- package/skills/skills-codex/shared-references/assurance-contract.md +139 -0
- package/skills/skills-codex/shared-references/capture-antipatterns.md +84 -0
- package/skills/skills-codex/shared-references/citation-discipline.md +452 -0
- package/skills/skills-codex/shared-references/compute-env-contract.md +163 -0
- package/skills/skills-codex/shared-references/effort-contract.md +143 -0
- package/skills/skills-codex/shared-references/evidence-precheck.md +73 -0
- package/skills/skills-codex/shared-references/experiment-integrity.md +49 -0
- package/skills/skills-codex/shared-references/external-cadence.md +334 -0
- package/skills/skills-codex/shared-references/fan-out-pattern.md +375 -0
- package/skills/skills-codex/shared-references/injection-hygiene.md +132 -0
- package/skills/skills-codex/shared-references/integration-contract.md +372 -0
- package/skills/skills-codex/shared-references/output-composition.md +98 -0
- package/skills/skills-codex/shared-references/output-language.md +45 -0
- package/skills/skills-codex/shared-references/output-manifest.md +40 -0
- package/skills/skills-codex/shared-references/output-versioning.md +111 -0
- package/skills/skills-codex/shared-references/patent-format-cn.md +199 -0
- package/skills/skills-codex/shared-references/patent-format-ep.md +173 -0
- package/skills/skills-codex/shared-references/patent-format-us.md +161 -0
- package/skills/skills-codex/shared-references/patent-writing-principles.md +197 -0
- package/skills/skills-codex/shared-references/prior-art-databases.md +141 -0
- package/skills/skills-codex/shared-references/resumable-runs.md +125 -0
- package/skills/skills-codex/shared-references/review-scope-limits.md +81 -0
- package/skills/skills-codex/shared-references/review-tracing.md +144 -0
- package/skills/skills-codex/shared-references/reviewer-independence.md +66 -0
- package/skills/skills-codex/shared-references/reviewer-routing.md +128 -0
- package/skills/skills-codex/shared-references/skill-governance.md +119 -0
- package/skills/skills-codex/shared-references/taste-calibration.md +90 -0
- package/skills/skills-codex/shared-references/venue-checklists.md +73 -0
- package/skills/skills-codex/shared-references/wiki-helper-resolution.md +69 -0
- package/skills/skills-codex/shared-references/writing-principles.md +525 -0
- package/skills/skills-codex/slides-polish/SKILL.md +563 -0
- package/skills/skills-codex/specification-writing/SKILL.md +211 -0
- package/skills/skills-codex/system-profile/SKILL.md +103 -0
- package/skills/skills-codex/training-check/SKILL.md +83 -0
- package/skills/skills-codex/vast-gpu/SKILL.md +394 -0
- package/skills/skills-codex/web-debug-search/SKILL.md +334 -0
- package/skills/skills-codex/wiki-enrich/SKILL.md +255 -0
- package/skills/skills-codex/writing-systems-papers/SKILL.md +184 -0
- package/skills/skills-codex-claude-review/README.md +79 -0
- package/skills/skills-codex-claude-review/README_CN.md +78 -0
- package/skills/skills-codex-claude-review/auto-paper-improvement-loop/SKILL.md +581 -0
- package/skills/skills-codex-claude-review/auto-review-loop/SKILL.md +510 -0
- package/skills/skills-codex-claude-review/novelty-check/SKILL.md +102 -0
- package/skills/skills-codex-claude-review/paper-figure/SKILL.md +319 -0
- package/skills/skills-codex-claude-review/paper-plan/SKILL.md +287 -0
- package/skills/skills-codex-claude-review/paper-write/SKILL.md +420 -0
- package/skills/skills-codex-claude-review/research-refine/SKILL.md +732 -0
- package/skills/skills-codex-claude-review/research-review/SKILL.md +149 -0
- package/skills/skills-codex-gemini-review/README.md +176 -0
- package/skills/skills-codex-gemini-review/README_CN.md +175 -0
- package/skills/skills-codex-gemini-review/auto-paper-improvement-loop/SKILL.md +331 -0
- package/skills/skills-codex-gemini-review/auto-review-loop/SKILL.md +304 -0
- package/skills/skills-codex-gemini-review/grant-proposal/SKILL.md +630 -0
- package/skills/skills-codex-gemini-review/idea-creator/SKILL.md +263 -0
- package/skills/skills-codex-gemini-review/idea-discovery/SKILL.md +275 -0
- package/skills/skills-codex-gemini-review/idea-discovery-robot/SKILL.md +365 -0
- package/skills/skills-codex-gemini-review/novelty-check/SKILL.md +92 -0
- package/skills/skills-codex-gemini-review/paper-figure/SKILL.md +289 -0
- package/skills/skills-codex-gemini-review/paper-plan/SKILL.md +265 -0
- package/skills/skills-codex-gemini-review/paper-poster-html/SKILL.md +102 -0
- package/skills/skills-codex-gemini-review/paper-slides/SKILL.md +582 -0
- package/skills/skills-codex-gemini-review/paper-write/SKILL.md +346 -0
- package/skills/skills-codex-gemini-review/paper-writing/SKILL.md +312 -0
- package/skills/skills-codex-gemini-review/research-refine/SKILL.md +674 -0
- package/skills/skills-codex-gemini-review/research-review/SKILL.md +112 -0
- package/skills/slides-polish/SKILL.md +565 -0
- package/skills/specification-writing/SKILL.md +211 -0
- package/skills/system-profile/SKILL.md +103 -0
- package/skills/training-check/SKILL.md +132 -0
- package/skills/vast-gpu/SKILL.md +394 -0
- package/skills/web-debug-search/SKILL.md +334 -0
- package/skills/wiki-enrich/SKILL.md +257 -0
- package/skills/writing-systems-papers/SKILL.md +184 -0
- package/templates/CLAUDE_MD_TEMPLATE.md +29 -0
- package/templates/EXPERIMENT_LOG_TEMPLATE.md +47 -0
- package/templates/EXPERIMENT_PLAN_TEMPLATE.md +51 -0
- package/templates/EXPERIMENT_PLAN_TEMPLATE_CN.md +53 -0
- package/templates/FINDINGS_TEMPLATE.md +52 -0
- package/templates/IDEA_CANDIDATES_TEMPLATE.md +47 -0
- package/templates/IDEA_CANDIDATES_TEMPLATE_CN.md +47 -0
- package/templates/INVENTION_BRIEF_TEMPLATE.md +87 -0
- package/templates/MANIFEST_TEMPLATE.md +7 -0
- package/templates/NARRATIVE_REPORT_TEMPLATE.md +49 -0
- package/templates/PAPER_PLAN_TEMPLATE.md +47 -0
- package/templates/PATENT_CLAIMS_TEMPLATE.md +78 -0
- package/templates/PATENT_SPECIFICATION_TEMPLATE.md +68 -0
- package/templates/README.md +57 -0
- package/templates/RESEARCH_BRIEF_TEMPLATE.md +35 -0
- package/templates/RESEARCH_BRIEF_TEMPLATE_CN.md +41 -0
- package/templates/RESEARCH_CONTRACT_TEMPLATE.md +60 -0
- package/templates/claude-hooks/corpus_write_guard.json +16 -0
- package/templates/claude-hooks/corpus_write_guard.py +85 -0
- package/templates/claude-hooks/meta_logging.json +74 -0
- package/templates/gitignore-trace.txt +3 -0
- package/tools/__pycache__/check_skills_inventory.cpython-314.pyc +0 -0
- package/tools/arxiv_fetch.py +311 -0
- package/tools/capture_filter.py +126 -0
- package/tools/check_skills_inventory.py +273 -0
- package/tools/convert_skills_to_llm_chat.py +282 -0
- package/tools/copilot_native_evidence.py +818 -0
- package/tools/deepxiv_fetch.py +213 -0
- package/tools/evidence_check.py +212 -0
- package/tools/exa_search.py +425 -0
- package/tools/experiment_queue/README.md +118 -0
- package/tools/experiment_queue/build_manifest.py +44 -0
- package/tools/experiment_queue/queue_manager.py +44 -0
- package/tools/extract_paper_style.py +560 -0
- package/tools/figure_renderer.py +69 -0
- package/tools/forensics_gate.py +669 -0
- package/tools/generate_codex_claude_review_overrides.py +299 -0
- package/tools/idea_discovery_gate.py +256 -0
- package/tools/install_aris.ps1 +1372 -0
- package/tools/install_aris.sh +1370 -0
- package/tools/install_aris_codex.sh +1023 -0
- package/tools/install_aris_copilot.sh +1052 -0
- package/tools/iteration_log.py +143 -0
- package/tools/lint_skills_helpers.sh +84 -0
- package/tools/meta_opt/check_ready.sh +80 -0
- package/tools/meta_opt/log_event.sh +91 -0
- package/tools/meta_opt/trigger_eval.py +280 -0
- package/tools/meta_opt/trigger_evals.sample.json +28 -0
- package/tools/openalex_fetch.py +326 -0
- package/tools/overleaf_audit.sh +104 -0
- package/tools/overleaf_setup.sh +150 -0
- package/tools/paper_illustration_image2.py +62 -0
- package/tools/provenance.py +294 -0
- package/tools/research_wiki.py +1720 -0
- package/tools/review_gate.py +502 -0
- package/tools/run_state.py +399 -0
- package/tools/save_trace.sh +477 -0
- package/tools/semantic_scholar_fetch.py +438 -0
- package/tools/skill-groups.tsv +116 -0
- package/tools/skill_picker.py +238 -0
- package/tools/smart_update.ps1 +521 -0
- package/tools/smart_update.sh +591 -0
- package/tools/smart_update_codex.sh +419 -0
- package/tools/smart_update_copilot.sh +605 -0
- package/tools/threat_scan.py +222 -0
- package/tools/verify_paper_audits.sh +487 -0
- package/tools/verify_papers.py +613 -0
- package/tools/verify_wiki_coverage.sh +176 -0
- package/tools/watchdog.py +485 -0
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Codex app-server image bridge for Claude-first ARIS workflows.
|
|
3
|
+
|
|
4
|
+
This server exposes a narrow MCP interface that asks the local Codex desktop
|
|
5
|
+
app to generate an image through the app-server path. It is intentionally kept
|
|
6
|
+
small and dependency-free so users can copy it into `~/.claude/mcp-servers/`
|
|
7
|
+
and register it with Claude Code.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import base64
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
import traceback
|
|
20
|
+
import uuid
|
|
21
|
+
from datetime import datetime, timedelta, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_stdio_initialized = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _init_stdio() -> None:
|
|
30
|
+
"""Rebind stdio to raw unbuffered binary streams for MCP framing.
|
|
31
|
+
|
|
32
|
+
Deferred into a function (called at the top of main()) so that merely
|
|
33
|
+
IMPORTING this module has no stdio side effects. os.fdopen(fileno) defaults
|
|
34
|
+
to closefd=True and thus seizes ownership of the fd; doing that at import
|
|
35
|
+
time under a test harness that captures stdio (pytest fd-capture) closes the
|
|
36
|
+
harness's capture fd and corrupts capture for every subsequent test. Real
|
|
37
|
+
server launch (python server.py) still calls this first via main(), so
|
|
38
|
+
runtime behavior is unchanged. Idempotent."""
|
|
39
|
+
global _stdio_initialized
|
|
40
|
+
if _stdio_initialized:
|
|
41
|
+
return
|
|
42
|
+
sys.stdout = os.fdopen(sys.stdout.fileno(), "wb", buffering=0)
|
|
43
|
+
sys.stdin = os.fdopen(sys.stdin.fileno(), "rb", buffering=0)
|
|
44
|
+
_stdio_initialized = True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
SERVER_NAME = os.environ.get("CODEX_IMAGE2_SERVER_NAME", "codex-image2")
|
|
48
|
+
CODEX_BIN = os.environ.get("CODEX_IMAGE2_CODEX_BIN", "codex")
|
|
49
|
+
DEFAULT_TIMEOUT_SEC = int(os.environ.get("CODEX_IMAGE2_TIMEOUT_SEC", "600"))
|
|
50
|
+
DEFAULT_JOB_EXPIRY_GRACE_SEC = int(
|
|
51
|
+
os.environ.get("CODEX_IMAGE2_JOB_EXPIRY_GRACE_SEC", "60")
|
|
52
|
+
)
|
|
53
|
+
MAX_STATUS_WAIT_SEC = int(os.environ.get("CODEX_IMAGE2_MAX_STATUS_WAIT_SEC", "30"))
|
|
54
|
+
DEFAULT_MODEL = os.environ.get("CODEX_IMAGE2_MODEL", "")
|
|
55
|
+
DEBUG_LOG_RAW = os.environ.get("CODEX_IMAGE2_DEBUG_LOG", "").strip()
|
|
56
|
+
DEBUG_LOG = Path(DEBUG_LOG_RAW).expanduser() if DEBUG_LOG_RAW else None
|
|
57
|
+
SAVE_RUN_LOGS = os.environ.get("CODEX_IMAGE2_SAVE_RUN_LOGS", "").strip().lower() in {
|
|
58
|
+
"1",
|
|
59
|
+
"true",
|
|
60
|
+
"yes",
|
|
61
|
+
"on",
|
|
62
|
+
}
|
|
63
|
+
STATE_DIR = Path(
|
|
64
|
+
os.environ.get(
|
|
65
|
+
"CODEX_IMAGE2_STATE_DIR",
|
|
66
|
+
str(Path.home() / ".claude" / "state" / SERVER_NAME),
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
JOBS_DIR = STATE_DIR / "jobs"
|
|
70
|
+
RUNS_DIR = STATE_DIR / "runs"
|
|
71
|
+
|
|
72
|
+
TERMINAL_JOB_STATES = {"completed", "failed"}
|
|
73
|
+
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
|
|
74
|
+
_use_ndjson = False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def debug_log(message: str) -> None:
|
|
78
|
+
if DEBUG_LOG is None:
|
|
79
|
+
return
|
|
80
|
+
try:
|
|
81
|
+
DEBUG_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
with DEBUG_LOG.open("a", encoding="utf-8") as fh:
|
|
83
|
+
fh.write(f"{message}\n")
|
|
84
|
+
except OSError:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def send_response(response: dict[str, Any]) -> None:
|
|
89
|
+
global _use_ndjson
|
|
90
|
+
|
|
91
|
+
payload = json.dumps(response, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
|
92
|
+
debug_log(f"SEND {payload.decode('utf-8', errors='replace')}")
|
|
93
|
+
if _use_ndjson:
|
|
94
|
+
sys.stdout.write(payload + b"\n")
|
|
95
|
+
else:
|
|
96
|
+
header = f"Content-Length: {len(payload)}\r\n\r\n".encode("utf-8")
|
|
97
|
+
sys.stdout.write(header + payload)
|
|
98
|
+
sys.stdout.flush()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def read_message() -> dict[str, Any] | None:
|
|
102
|
+
global _use_ndjson
|
|
103
|
+
|
|
104
|
+
line = sys.stdin.readline()
|
|
105
|
+
if not line:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
line_text = line.decode("utf-8").rstrip("\r\n")
|
|
109
|
+
if line_text.lower().startswith("content-length:"):
|
|
110
|
+
try:
|
|
111
|
+
content_length = int(line_text.split(":", 1)[1].strip())
|
|
112
|
+
except ValueError:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
while True:
|
|
116
|
+
header_line = sys.stdin.readline()
|
|
117
|
+
if not header_line:
|
|
118
|
+
return None
|
|
119
|
+
if header_line in {b"\r\n", b"\n"}:
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
body = sys.stdin.read(content_length)
|
|
123
|
+
try:
|
|
124
|
+
return json.loads(body.decode("utf-8"))
|
|
125
|
+
except json.JSONDecodeError:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
if line_text.startswith("{") or line_text.startswith("["):
|
|
129
|
+
_use_ndjson = True
|
|
130
|
+
try:
|
|
131
|
+
return json.loads(line_text)
|
|
132
|
+
except json.JSONDecodeError:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def utc_now() -> str:
|
|
139
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def parse_utc_timestamp(raw_value: Any) -> datetime | None:
|
|
143
|
+
if not isinstance(raw_value, str) or not raw_value:
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
return datetime.fromisoformat(raw_value.replace("Z", "+00:00"))
|
|
147
|
+
except ValueError:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def utc_after_seconds(seconds: int) -> str:
|
|
152
|
+
return (datetime.now(timezone.utc) + timedelta(seconds=seconds)).replace(
|
|
153
|
+
microsecond=0
|
|
154
|
+
).isoformat().replace("+00:00", "Z")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
158
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
temp_path = path.with_suffix(path.suffix + ".tmp")
|
|
160
|
+
temp_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
161
|
+
temp_path.replace(path)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def read_json(path: Path) -> dict[str, Any]:
|
|
165
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def job_state_path(job_id: str) -> Path:
|
|
169
|
+
return JOBS_DIR / f"{job_id}.json"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def classify_worker_state(pid: int | None) -> str:
|
|
173
|
+
if not pid or pid <= 0:
|
|
174
|
+
return "missing"
|
|
175
|
+
try:
|
|
176
|
+
waited_pid, _ = os.waitpid(pid, os.WNOHANG)
|
|
177
|
+
except ChildProcessError:
|
|
178
|
+
try:
|
|
179
|
+
os.kill(pid, 0)
|
|
180
|
+
except OSError:
|
|
181
|
+
return "exited"
|
|
182
|
+
return "running"
|
|
183
|
+
except OSError:
|
|
184
|
+
return "exited"
|
|
185
|
+
if waited_pid == 0:
|
|
186
|
+
return "running"
|
|
187
|
+
return "exited"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def find_codex_bin() -> str | None:
|
|
191
|
+
if Path(CODEX_BIN).is_file():
|
|
192
|
+
return CODEX_BIN
|
|
193
|
+
return shutil.which(CODEX_BIN)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def normalize_string_list(raw_value: Any) -> tuple[list[str], str | None]:
|
|
197
|
+
if raw_value is None:
|
|
198
|
+
return [], None
|
|
199
|
+
if isinstance(raw_value, str):
|
|
200
|
+
candidate = raw_value.strip()
|
|
201
|
+
return ([candidate] if candidate else []), None
|
|
202
|
+
if not isinstance(raw_value, list):
|
|
203
|
+
return [], "referenceImagePaths must be a string or an array of strings"
|
|
204
|
+
|
|
205
|
+
values: list[str] = []
|
|
206
|
+
for item in raw_value:
|
|
207
|
+
if not isinstance(item, str):
|
|
208
|
+
return [], "referenceImagePaths entries must be strings"
|
|
209
|
+
candidate = item.strip()
|
|
210
|
+
if candidate:
|
|
211
|
+
values.append(candidate)
|
|
212
|
+
return values, None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def resolve_cwd(raw_cwd: str | None) -> tuple[Path, str | None]:
|
|
216
|
+
if raw_cwd:
|
|
217
|
+
cwd = Path(raw_cwd).expanduser()
|
|
218
|
+
else:
|
|
219
|
+
cwd = Path.cwd()
|
|
220
|
+
try:
|
|
221
|
+
cwd = cwd.resolve()
|
|
222
|
+
except OSError as exc:
|
|
223
|
+
return cwd, f"failed to resolve cwd {raw_cwd!r}: {exc}"
|
|
224
|
+
if not cwd.exists():
|
|
225
|
+
return cwd, f"working directory does not exist: {cwd}"
|
|
226
|
+
if not cwd.is_dir():
|
|
227
|
+
return cwd, f"working directory is not a directory: {cwd}"
|
|
228
|
+
return cwd, None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def resolve_output_path(raw_output_path: str | None, *, cwd: Path, job_id: str) -> Path:
|
|
232
|
+
if raw_output_path:
|
|
233
|
+
path = Path(raw_output_path).expanduser()
|
|
234
|
+
if not path.is_absolute():
|
|
235
|
+
path = cwd / path
|
|
236
|
+
else:
|
|
237
|
+
path = cwd / "figures" / "ai_generated" / f"codex-image2-{job_id}.png"
|
|
238
|
+
return path.resolve()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def allowed_output_root(*, cwd: Path) -> Path:
|
|
242
|
+
return (cwd / "figures" / "ai_generated").resolve()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def validate_output_path(output_path: Path, *, cwd: Path) -> str | None:
|
|
246
|
+
root = allowed_output_root(cwd=cwd)
|
|
247
|
+
try:
|
|
248
|
+
output_path.relative_to(root)
|
|
249
|
+
except ValueError:
|
|
250
|
+
return f"outputPath must stay under {root}"
|
|
251
|
+
if output_path == root:
|
|
252
|
+
return f"outputPath must be a file under {root}, not the directory itself"
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def parse_timeout_seconds(raw_value: Any) -> tuple[int | None, str | None]:
|
|
257
|
+
if raw_value is None:
|
|
258
|
+
return DEFAULT_TIMEOUT_SEC, None
|
|
259
|
+
try:
|
|
260
|
+
timeout_sec = int(raw_value)
|
|
261
|
+
except (TypeError, ValueError):
|
|
262
|
+
return None, "timeoutSeconds must be an integer"
|
|
263
|
+
if timeout_sec <= 0:
|
|
264
|
+
return None, "timeoutSeconds must be positive"
|
|
265
|
+
return timeout_sec, None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def is_png_bytes(raw_bytes: bytes) -> bool:
|
|
269
|
+
return raw_bytes.startswith(PNG_SIGNATURE)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def maybe_run_log_path(run_id: str) -> Path | None:
|
|
273
|
+
if not SAVE_RUN_LOGS:
|
|
274
|
+
return None
|
|
275
|
+
return RUNS_DIR / f"{run_id}.log"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def scrub_job_request(job: dict[str, Any]) -> None:
|
|
279
|
+
request = job.get("request")
|
|
280
|
+
if not isinstance(request, dict):
|
|
281
|
+
return
|
|
282
|
+
job["request"] = {
|
|
283
|
+
"cwd": request.get("cwd"),
|
|
284
|
+
"outputPath": request.get("outputPath"),
|
|
285
|
+
"timeoutSec": request.get("timeoutSec"),
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def fail_job(job_path: Path, job: dict[str, Any], message: str) -> dict[str, Any]:
|
|
290
|
+
finished_at = utc_now()
|
|
291
|
+
job["status"] = "failed"
|
|
292
|
+
job["error"] = message
|
|
293
|
+
job["completedAt"] = finished_at
|
|
294
|
+
job["updatedAt"] = finished_at
|
|
295
|
+
job["result"] = None
|
|
296
|
+
scrub_job_request(job)
|
|
297
|
+
write_json(job_path, job)
|
|
298
|
+
return job
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def build_bridge_prompt(
|
|
302
|
+
prompt: str,
|
|
303
|
+
*,
|
|
304
|
+
system: str | None,
|
|
305
|
+
reference_image_paths: list[str],
|
|
306
|
+
) -> str:
|
|
307
|
+
sections: list[str] = [
|
|
308
|
+
"You are operating behind a Codex native image-generation MCP bridge.",
|
|
309
|
+
"Use native image generation through the Codex app-server.",
|
|
310
|
+
"Do not use shell commands, Python, SVG, HTML, Canvas, or manual bitmap encoding.",
|
|
311
|
+
"Do not fabricate success. If native image generation is unavailable, reply exactly NATIVE_IMAGE_UNAVAILABLE.",
|
|
312
|
+
"Generate exactly one publication-quality raster image unless the user explicitly requests multiple variants.",
|
|
313
|
+
"",
|
|
314
|
+
]
|
|
315
|
+
selected_system = (system or "").strip()
|
|
316
|
+
if selected_system:
|
|
317
|
+
sections.extend(["## System Instructions", selected_system, ""])
|
|
318
|
+
if reference_image_paths:
|
|
319
|
+
sections.append("## Reference Images")
|
|
320
|
+
sections.append(
|
|
321
|
+
"These local image files are available. If image viewing tools are available in this Codex session, inspect them before generating."
|
|
322
|
+
)
|
|
323
|
+
for path in reference_image_paths:
|
|
324
|
+
sections.append(f"- {path}")
|
|
325
|
+
sections.append("")
|
|
326
|
+
sections.extend(["## User Request", prompt.strip()])
|
|
327
|
+
return "\n".join(sections).strip()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def parse_debug_json_messages(raw_stdout: str) -> list[dict[str, Any]]:
|
|
331
|
+
messages: list[dict[str, Any]] = []
|
|
332
|
+
buffer: list[str] = []
|
|
333
|
+
collecting = False
|
|
334
|
+
|
|
335
|
+
for raw_line in raw_stdout.splitlines():
|
|
336
|
+
if not collecting:
|
|
337
|
+
if raw_line.startswith("< {"):
|
|
338
|
+
collecting = True
|
|
339
|
+
buffer = [raw_line[2:]]
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
if not raw_line.startswith("< "):
|
|
343
|
+
collecting = False
|
|
344
|
+
buffer = []
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
buffer.append(raw_line[2:])
|
|
348
|
+
candidate = "\n".join(buffer)
|
|
349
|
+
try:
|
|
350
|
+
payload = json.loads(candidate)
|
|
351
|
+
except json.JSONDecodeError:
|
|
352
|
+
continue
|
|
353
|
+
messages.append(payload)
|
|
354
|
+
collecting = False
|
|
355
|
+
buffer = []
|
|
356
|
+
|
|
357
|
+
return messages
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def extract_run_summary(messages: list[dict[str, Any]]) -> dict[str, Any]:
|
|
361
|
+
thread_id: str | None = None
|
|
362
|
+
agent_messages: list[str] = []
|
|
363
|
+
image_items: list[dict[str, Any]] = []
|
|
364
|
+
command_items: list[dict[str, Any]] = []
|
|
365
|
+
|
|
366
|
+
for message in messages:
|
|
367
|
+
if not isinstance(message, dict):
|
|
368
|
+
continue
|
|
369
|
+
params = message.get("params")
|
|
370
|
+
if isinstance(params, dict):
|
|
371
|
+
candidate_thread_id = params.get("threadId")
|
|
372
|
+
if isinstance(candidate_thread_id, str) and candidate_thread_id:
|
|
373
|
+
thread_id = candidate_thread_id
|
|
374
|
+
item = params.get("item")
|
|
375
|
+
if isinstance(item, dict):
|
|
376
|
+
item_type = item.get("type")
|
|
377
|
+
if item_type == "agentMessage":
|
|
378
|
+
text = item.get("text")
|
|
379
|
+
if isinstance(text, str):
|
|
380
|
+
agent_messages.append(text)
|
|
381
|
+
elif item_type == "imageGeneration":
|
|
382
|
+
image_items.append(item)
|
|
383
|
+
elif item_type == "commandExecution":
|
|
384
|
+
command_items.append(item)
|
|
385
|
+
|
|
386
|
+
result = message.get("result")
|
|
387
|
+
if isinstance(result, dict):
|
|
388
|
+
thread = result.get("thread")
|
|
389
|
+
if isinstance(thread, dict):
|
|
390
|
+
candidate_thread_id = thread.get("id")
|
|
391
|
+
if isinstance(candidate_thread_id, str) and candidate_thread_id:
|
|
392
|
+
thread_id = candidate_thread_id
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
"threadId": thread_id,
|
|
396
|
+
"agentMessages": agent_messages,
|
|
397
|
+
"imageItems": image_items,
|
|
398
|
+
"commandItems": command_items,
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def materialize_generated_image(
|
|
403
|
+
image_item: dict[str, Any],
|
|
404
|
+
output_path: Path,
|
|
405
|
+
) -> tuple[Path | None, str | None, str | None, str | None]:
|
|
406
|
+
saved_path_value = image_item.get("savedPath")
|
|
407
|
+
revised_prompt = image_item.get("revisedPrompt")
|
|
408
|
+
revised_prompt_text = revised_prompt if isinstance(revised_prompt, str) else None
|
|
409
|
+
|
|
410
|
+
if isinstance(saved_path_value, str) and saved_path_value:
|
|
411
|
+
source_path = Path(saved_path_value).expanduser()
|
|
412
|
+
if source_path.is_file():
|
|
413
|
+
source_bytes = source_path.read_bytes()
|
|
414
|
+
if not is_png_bytes(source_bytes):
|
|
415
|
+
return None, None, None, "imageGeneration savedPath did not contain a PNG image"
|
|
416
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
417
|
+
shutil.copy2(source_path, output_path)
|
|
418
|
+
return output_path, str(source_path), revised_prompt_text, None
|
|
419
|
+
|
|
420
|
+
raw_result = image_item.get("result")
|
|
421
|
+
if isinstance(raw_result, str) and raw_result.strip():
|
|
422
|
+
try:
|
|
423
|
+
decoded = base64.b64decode(raw_result)
|
|
424
|
+
except ValueError as exc:
|
|
425
|
+
return None, None, None, f"imageGeneration result was not valid base64: {exc}"
|
|
426
|
+
if not is_png_bytes(decoded):
|
|
427
|
+
return None, None, None, "imageGeneration result did not decode to a PNG image"
|
|
428
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
429
|
+
output_path.write_bytes(decoded)
|
|
430
|
+
return output_path, None, revised_prompt_text, None
|
|
431
|
+
|
|
432
|
+
return None, None, None, "imageGeneration item did not contain a savedPath or decodable result"
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def run_codex_image(
|
|
436
|
+
prompt: str,
|
|
437
|
+
*,
|
|
438
|
+
cwd: Path,
|
|
439
|
+
output_path: Path,
|
|
440
|
+
system: str | None = None,
|
|
441
|
+
model: str | None = None,
|
|
442
|
+
reference_image_paths: list[str] | None = None,
|
|
443
|
+
timeout_sec: int | None = None,
|
|
444
|
+
run_log_path: Path | None = None,
|
|
445
|
+
) -> tuple[dict[str, Any] | None, str | None]:
|
|
446
|
+
bin_path = find_codex_bin()
|
|
447
|
+
if not bin_path:
|
|
448
|
+
return None, f"Codex CLI not found: {CODEX_BIN}"
|
|
449
|
+
|
|
450
|
+
output_path = output_path.resolve()
|
|
451
|
+
output_path_error = validate_output_path(output_path, cwd=cwd)
|
|
452
|
+
if output_path_error:
|
|
453
|
+
return None, output_path_error
|
|
454
|
+
|
|
455
|
+
normalized_refs = reference_image_paths or []
|
|
456
|
+
prompt_text = build_bridge_prompt(
|
|
457
|
+
prompt,
|
|
458
|
+
system=system,
|
|
459
|
+
reference_image_paths=normalized_refs,
|
|
460
|
+
)
|
|
461
|
+
cmd = [bin_path]
|
|
462
|
+
selected_model = model or DEFAULT_MODEL
|
|
463
|
+
if selected_model:
|
|
464
|
+
cmd.extend(["-c", f'model="{selected_model}"'])
|
|
465
|
+
cmd.extend(["debug", "app-server", "send-message-v2", prompt_text])
|
|
466
|
+
|
|
467
|
+
effective_timeout = timeout_sec or DEFAULT_TIMEOUT_SEC
|
|
468
|
+
debug_log(f"RUN {' '.join(cmd)} cwd={cwd}")
|
|
469
|
+
try:
|
|
470
|
+
started = time.monotonic()
|
|
471
|
+
result = subprocess.run(
|
|
472
|
+
cmd,
|
|
473
|
+
capture_output=True,
|
|
474
|
+
text=True,
|
|
475
|
+
stdin=subprocess.DEVNULL,
|
|
476
|
+
cwd=str(cwd),
|
|
477
|
+
timeout=effective_timeout,
|
|
478
|
+
check=False,
|
|
479
|
+
)
|
|
480
|
+
duration_ms = int((time.monotonic() - started) * 1000)
|
|
481
|
+
except subprocess.TimeoutExpired:
|
|
482
|
+
return None, f"Codex image generation timed out after {effective_timeout} seconds"
|
|
483
|
+
|
|
484
|
+
if run_log_path is not None:
|
|
485
|
+
run_log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
486
|
+
run_log_path.write_text(
|
|
487
|
+
result.stdout + ("\n[stderr]\n" + result.stderr if result.stderr else ""),
|
|
488
|
+
encoding="utf-8",
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
messages = parse_debug_json_messages(result.stdout)
|
|
492
|
+
summary = extract_run_summary(messages)
|
|
493
|
+
|
|
494
|
+
if summary["commandItems"]:
|
|
495
|
+
return None, (
|
|
496
|
+
"Codex attempted shell-based image creation instead of native image generation. "
|
|
497
|
+
"This bridge only accepts native imageGeneration events."
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
image_items = summary["imageItems"]
|
|
501
|
+
if not image_items:
|
|
502
|
+
final_message = ""
|
|
503
|
+
for candidate in reversed(summary["agentMessages"]):
|
|
504
|
+
if candidate and candidate.strip():
|
|
505
|
+
final_message = candidate.strip()
|
|
506
|
+
break
|
|
507
|
+
stderr_text = result.stderr.strip()
|
|
508
|
+
if final_message == "NATIVE_IMAGE_UNAVAILABLE":
|
|
509
|
+
return None, "Codex app-server reported that native image generation is unavailable in this session."
|
|
510
|
+
if final_message:
|
|
511
|
+
return None, f"Codex did not emit an imageGeneration item. Final message: {final_message}"
|
|
512
|
+
if stderr_text:
|
|
513
|
+
return None, f"Codex did not emit an imageGeneration item. stderr: {stderr_text}"
|
|
514
|
+
return None, "Codex did not emit an imageGeneration item."
|
|
515
|
+
|
|
516
|
+
image_item = image_items[-1]
|
|
517
|
+
generated_output, source_saved_path, revised_prompt, materialize_error = materialize_generated_image(
|
|
518
|
+
image_item,
|
|
519
|
+
output_path,
|
|
520
|
+
)
|
|
521
|
+
if materialize_error:
|
|
522
|
+
return None, materialize_error
|
|
523
|
+
if generated_output is None:
|
|
524
|
+
return None, "Codex image bridge failed to materialize the generated image"
|
|
525
|
+
|
|
526
|
+
response_text = ""
|
|
527
|
+
for candidate in reversed(summary["agentMessages"]):
|
|
528
|
+
if candidate is not None:
|
|
529
|
+
response_text = candidate
|
|
530
|
+
break
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
"threadId": summary["threadId"],
|
|
534
|
+
"response": response_text,
|
|
535
|
+
"model": selected_model or None,
|
|
536
|
+
"duration_ms": duration_ms,
|
|
537
|
+
"nativeToolConfirmed": True,
|
|
538
|
+
"imageCount": len(image_items),
|
|
539
|
+
"outputPath": str(generated_output),
|
|
540
|
+
"sourceSavedPath": source_saved_path,
|
|
541
|
+
"revisedPrompt": revised_prompt,
|
|
542
|
+
"runLogPath": str(run_log_path) if run_log_path is not None else None,
|
|
543
|
+
}, None
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def serialize_job(job: dict[str, Any]) -> dict[str, Any]:
|
|
547
|
+
result = job.get("result") or {}
|
|
548
|
+
return {
|
|
549
|
+
"jobId": job.get("jobId"),
|
|
550
|
+
"status": job.get("status"),
|
|
551
|
+
"done": job.get("status") in TERMINAL_JOB_STATES,
|
|
552
|
+
"threadId": result.get("threadId"),
|
|
553
|
+
"response": result.get("response"),
|
|
554
|
+
"model": result.get("model"),
|
|
555
|
+
"duration_ms": result.get("duration_ms"),
|
|
556
|
+
"nativeToolConfirmed": result.get("nativeToolConfirmed"),
|
|
557
|
+
"imageCount": result.get("imageCount"),
|
|
558
|
+
"outputPath": result.get("outputPath"),
|
|
559
|
+
"sourceSavedPath": result.get("sourceSavedPath"),
|
|
560
|
+
"revisedPrompt": result.get("revisedPrompt"),
|
|
561
|
+
"runLogPath": result.get("runLogPath"),
|
|
562
|
+
"error": job.get("error"),
|
|
563
|
+
"createdAt": job.get("createdAt"),
|
|
564
|
+
"startedAt": job.get("startedAt"),
|
|
565
|
+
"completedAt": job.get("completedAt"),
|
|
566
|
+
"updatedAt": job.get("updatedAt"),
|
|
567
|
+
"expiresAt": job.get("expiresAt"),
|
|
568
|
+
"resumeHint": "Call generate_status with this jobId until done=true.",
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def start_async_generate(
|
|
573
|
+
prompt: str,
|
|
574
|
+
*,
|
|
575
|
+
cwd: str | None = None,
|
|
576
|
+
output_path: str | None = None,
|
|
577
|
+
system: str | None = None,
|
|
578
|
+
model: str | None = None,
|
|
579
|
+
reference_image_paths: Any = None,
|
|
580
|
+
timeout_seconds: Any = None,
|
|
581
|
+
) -> tuple[dict[str, Any] | None, str | None]:
|
|
582
|
+
resolved_cwd, cwd_error = resolve_cwd(cwd)
|
|
583
|
+
if cwd_error:
|
|
584
|
+
return None, cwd_error
|
|
585
|
+
|
|
586
|
+
refs, refs_error = normalize_string_list(reference_image_paths)
|
|
587
|
+
if refs_error:
|
|
588
|
+
return None, refs_error
|
|
589
|
+
|
|
590
|
+
timeout_sec, timeout_error = parse_timeout_seconds(timeout_seconds)
|
|
591
|
+
if timeout_error:
|
|
592
|
+
return None, timeout_error
|
|
593
|
+
|
|
594
|
+
job_id = uuid.uuid4().hex
|
|
595
|
+
resolved_output_path = resolve_output_path(output_path, cwd=resolved_cwd, job_id=job_id)
|
|
596
|
+
output_path_error = validate_output_path(resolved_output_path, cwd=resolved_cwd)
|
|
597
|
+
if output_path_error:
|
|
598
|
+
return None, output_path_error
|
|
599
|
+
created_at = utc_now()
|
|
600
|
+
job = {
|
|
601
|
+
"jobId": job_id,
|
|
602
|
+
"status": "queued",
|
|
603
|
+
"createdAt": created_at,
|
|
604
|
+
"startedAt": None,
|
|
605
|
+
"completedAt": None,
|
|
606
|
+
"updatedAt": created_at,
|
|
607
|
+
"expiresAt": utc_after_seconds(timeout_sec + DEFAULT_JOB_EXPIRY_GRACE_SEC),
|
|
608
|
+
"error": None,
|
|
609
|
+
"result": None,
|
|
610
|
+
"workerPid": None,
|
|
611
|
+
"request": {
|
|
612
|
+
"prompt": prompt,
|
|
613
|
+
"cwd": str(resolved_cwd),
|
|
614
|
+
"outputPath": str(resolved_output_path),
|
|
615
|
+
"system": system,
|
|
616
|
+
"model": model,
|
|
617
|
+
"referenceImagePaths": refs,
|
|
618
|
+
"timeoutSec": timeout_sec,
|
|
619
|
+
},
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
job_path = job_state_path(job_id)
|
|
623
|
+
write_json(job_path, job)
|
|
624
|
+
|
|
625
|
+
try:
|
|
626
|
+
worker = subprocess.Popen(
|
|
627
|
+
[sys.executable, str(Path(__file__).resolve()), "--run-job", job_id],
|
|
628
|
+
stdin=subprocess.DEVNULL,
|
|
629
|
+
stdout=subprocess.DEVNULL,
|
|
630
|
+
stderr=subprocess.DEVNULL,
|
|
631
|
+
close_fds=True,
|
|
632
|
+
start_new_session=True,
|
|
633
|
+
)
|
|
634
|
+
except OSError as exc:
|
|
635
|
+
job["status"] = "failed"
|
|
636
|
+
job["completedAt"] = utc_now()
|
|
637
|
+
job["updatedAt"] = job["completedAt"]
|
|
638
|
+
job["error"] = f"Failed to launch background image worker: {exc}"
|
|
639
|
+
write_json(job_path, job)
|
|
640
|
+
return None, job["error"]
|
|
641
|
+
|
|
642
|
+
job["workerPid"] = worker.pid
|
|
643
|
+
job["updatedAt"] = utc_now()
|
|
644
|
+
write_json(job_path, job)
|
|
645
|
+
debug_log(f"JOB_START job_id={job_id} worker_pid={worker.pid}")
|
|
646
|
+
return serialize_job(job), None
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def get_generate_status(job_id: str, *, wait_seconds: int = 0) -> tuple[dict[str, Any] | None, str | None]:
|
|
650
|
+
job_path = job_state_path(job_id)
|
|
651
|
+
if not job_path.exists():
|
|
652
|
+
return None, f"Unknown jobId: {job_id}"
|
|
653
|
+
|
|
654
|
+
deadline = time.monotonic() + max(wait_seconds, 0)
|
|
655
|
+
while True:
|
|
656
|
+
job = read_json(job_path)
|
|
657
|
+
if job.get("status") in {"queued", "running"}:
|
|
658
|
+
expires_at = parse_utc_timestamp(job.get("expiresAt"))
|
|
659
|
+
if expires_at is not None and datetime.now(timezone.utc) > expires_at:
|
|
660
|
+
job = fail_job(job_path, job, "Background image worker exceeded its deadline before writing a final result")
|
|
661
|
+
else:
|
|
662
|
+
worker_state = classify_worker_state(job.get("workerPid"))
|
|
663
|
+
if worker_state in {"missing", "exited"}:
|
|
664
|
+
job = fail_job(job_path, job, "Background image worker exited before writing a final result")
|
|
665
|
+
if job.get("status") in TERMINAL_JOB_STATES:
|
|
666
|
+
return serialize_job(job), None
|
|
667
|
+
if time.monotonic() >= deadline:
|
|
668
|
+
return serialize_job(job), None
|
|
669
|
+
time.sleep(min(0.5, max(deadline - time.monotonic(), 0.0)))
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def run_async_job(job_id: str) -> int:
|
|
673
|
+
job_path = job_state_path(job_id)
|
|
674
|
+
if not job_path.exists():
|
|
675
|
+
debug_log(f"JOB_MISSING job_id={job_id}")
|
|
676
|
+
return 1
|
|
677
|
+
|
|
678
|
+
job = read_json(job_path)
|
|
679
|
+
job["status"] = "running"
|
|
680
|
+
job["startedAt"] = utc_now()
|
|
681
|
+
job["updatedAt"] = job["startedAt"]
|
|
682
|
+
job["workerPid"] = os.getpid()
|
|
683
|
+
write_json(job_path, job)
|
|
684
|
+
debug_log(f"JOB_RUNNING job_id={job_id} worker_pid={os.getpid()}")
|
|
685
|
+
|
|
686
|
+
request = job.get("request") or {}
|
|
687
|
+
run_log_path = maybe_run_log_path(job_id)
|
|
688
|
+
try:
|
|
689
|
+
payload, error = run_codex_image(
|
|
690
|
+
str(request.get("prompt", "")),
|
|
691
|
+
cwd=Path(str(request.get("cwd"))),
|
|
692
|
+
output_path=Path(str(request.get("outputPath"))),
|
|
693
|
+
system=request.get("system"),
|
|
694
|
+
model=request.get("model"),
|
|
695
|
+
reference_image_paths=request.get("referenceImagePaths") or [],
|
|
696
|
+
timeout_sec=request.get("timeoutSec"),
|
|
697
|
+
run_log_path=run_log_path,
|
|
698
|
+
)
|
|
699
|
+
except Exception as exc:
|
|
700
|
+
payload = None
|
|
701
|
+
error = f"Background image generation crashed: {exc}"
|
|
702
|
+
debug_log(traceback.format_exc())
|
|
703
|
+
|
|
704
|
+
finished_at = utc_now()
|
|
705
|
+
job = read_json(job_path)
|
|
706
|
+
job["updatedAt"] = finished_at
|
|
707
|
+
job["completedAt"] = finished_at
|
|
708
|
+
if error:
|
|
709
|
+
fail_job(job_path, job, error)
|
|
710
|
+
debug_log(f"JOB_FAILED job_id={job_id} error={error}")
|
|
711
|
+
return 1
|
|
712
|
+
|
|
713
|
+
job["status"] = "completed"
|
|
714
|
+
job["error"] = None
|
|
715
|
+
job["result"] = payload
|
|
716
|
+
scrub_job_request(job)
|
|
717
|
+
write_json(job_path, job)
|
|
718
|
+
debug_log(f"JOB_COMPLETED job_id={job_id} output={(payload or {}).get('outputPath')}")
|
|
719
|
+
return 0
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def tool_success(request_id: Any, payload: dict[str, Any]) -> dict[str, Any]:
|
|
723
|
+
return {
|
|
724
|
+
"jsonrpc": "2.0",
|
|
725
|
+
"id": request_id,
|
|
726
|
+
"result": {
|
|
727
|
+
"content": [{"type": "text", "text": json.dumps(payload, ensure_ascii=False)}],
|
|
728
|
+
},
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def tool_error(request_id: Any, message: str) -> dict[str, Any]:
|
|
733
|
+
return {
|
|
734
|
+
"jsonrpc": "2.0",
|
|
735
|
+
"id": request_id,
|
|
736
|
+
"result": {
|
|
737
|
+
"content": [{"type": "text", "text": json.dumps({"error": message}, ensure_ascii=False)}],
|
|
738
|
+
"isError": True,
|
|
739
|
+
},
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def handle_request(request: dict[str, Any]) -> dict[str, Any] | None:
|
|
744
|
+
request_id = request.get("id")
|
|
745
|
+
method = request.get("method", "")
|
|
746
|
+
params = request.get("params", {})
|
|
747
|
+
if DEBUG_LOG is not None:
|
|
748
|
+
param_keys = sorted(params.keys()) if isinstance(params, dict) else []
|
|
749
|
+
debug_log(f"REQUEST id={request_id!r} method={method} param_keys={param_keys}")
|
|
750
|
+
|
|
751
|
+
if request_id is None:
|
|
752
|
+
if method in {"notifications/initialized", "initialized"}:
|
|
753
|
+
return None
|
|
754
|
+
return None
|
|
755
|
+
|
|
756
|
+
if method == "initialize":
|
|
757
|
+
return {
|
|
758
|
+
"jsonrpc": "2.0",
|
|
759
|
+
"id": request_id,
|
|
760
|
+
"result": {
|
|
761
|
+
"protocolVersion": "2024-11-05",
|
|
762
|
+
"capabilities": {"tools": {}},
|
|
763
|
+
"serverInfo": {"name": SERVER_NAME, "version": "0.1.0"},
|
|
764
|
+
},
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if method == "ping":
|
|
768
|
+
return {"jsonrpc": "2.0", "id": request_id, "result": {}}
|
|
769
|
+
|
|
770
|
+
if method == "resources/list":
|
|
771
|
+
return {"jsonrpc": "2.0", "id": request_id, "result": {"resources": []}}
|
|
772
|
+
|
|
773
|
+
if method == "resources/templates/list":
|
|
774
|
+
return {"jsonrpc": "2.0", "id": request_id, "result": {"resourceTemplates": []}}
|
|
775
|
+
|
|
776
|
+
if method in {"notifications/initialized", "initialized"}:
|
|
777
|
+
return {"jsonrpc": "2.0", "id": request_id, "result": {}}
|
|
778
|
+
|
|
779
|
+
if method == "tools/list":
|
|
780
|
+
return {
|
|
781
|
+
"jsonrpc": "2.0",
|
|
782
|
+
"id": request_id,
|
|
783
|
+
"result": {
|
|
784
|
+
"tools": [
|
|
785
|
+
{
|
|
786
|
+
"name": "generate_start",
|
|
787
|
+
"description": "Start a background native image generation job through the Codex app-server.",
|
|
788
|
+
"inputSchema": {
|
|
789
|
+
"type": "object",
|
|
790
|
+
"properties": {
|
|
791
|
+
"prompt": {"type": "string", "description": "Image generation prompt"},
|
|
792
|
+
"cwd": {"type": "string", "description": "Optional working directory"},
|
|
793
|
+
"outputPath": {"type": "string", "description": "Optional output file path; defaults to figures/ai_generated"},
|
|
794
|
+
"system": {"type": "string", "description": "Optional extra bridge instructions"},
|
|
795
|
+
"model": {"type": "string", "description": "Optional Codex text model override"},
|
|
796
|
+
"referenceImagePaths": {
|
|
797
|
+
"oneOf": [
|
|
798
|
+
{"type": "string"},
|
|
799
|
+
{"type": "array", "items": {"type": "string"}},
|
|
800
|
+
],
|
|
801
|
+
"description": "Optional local reference image paths that Codex may inspect before generating",
|
|
802
|
+
},
|
|
803
|
+
"timeoutSeconds": {
|
|
804
|
+
"type": "integer",
|
|
805
|
+
"description": "Optional positive timeout for the underlying Codex image call",
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
"required": ["prompt"],
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
"name": "generate_status",
|
|
813
|
+
"description": "Check whether a background image generation job has finished and fetch the output path when available.",
|
|
814
|
+
"inputSchema": {
|
|
815
|
+
"type": "object",
|
|
816
|
+
"properties": {
|
|
817
|
+
"jobId": {"type": "string", "description": "Background image job id"},
|
|
818
|
+
"job_id": {"type": "string", "description": "Alias of jobId"},
|
|
819
|
+
"waitSeconds": {
|
|
820
|
+
"type": "integer",
|
|
821
|
+
"description": "Optional bounded wait before returning status; capped to keep the MCP server responsive",
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
"required": ["jobId"],
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
]
|
|
828
|
+
},
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if method == "tools/call":
|
|
832
|
+
name = params.get("name", "")
|
|
833
|
+
args = params.get("arguments", {})
|
|
834
|
+
if not isinstance(args, dict):
|
|
835
|
+
return tool_error(request_id, "tool arguments must be an object")
|
|
836
|
+
|
|
837
|
+
if name == "generate_start":
|
|
838
|
+
prompt = str(args.get("prompt", "")).strip()
|
|
839
|
+
if not prompt:
|
|
840
|
+
return tool_error(request_id, "prompt is required")
|
|
841
|
+
payload, error = start_async_generate(
|
|
842
|
+
prompt,
|
|
843
|
+
cwd=args.get("cwd"),
|
|
844
|
+
output_path=args.get("outputPath"),
|
|
845
|
+
system=args.get("system"),
|
|
846
|
+
model=args.get("model"),
|
|
847
|
+
reference_image_paths=args.get("referenceImagePaths"),
|
|
848
|
+
timeout_seconds=args.get("timeoutSeconds"),
|
|
849
|
+
)
|
|
850
|
+
if error:
|
|
851
|
+
return tool_error(request_id, error)
|
|
852
|
+
return tool_success(request_id, payload or {})
|
|
853
|
+
|
|
854
|
+
if name == "generate_status":
|
|
855
|
+
job_id = args.get("jobId") or args.get("job_id")
|
|
856
|
+
if not job_id:
|
|
857
|
+
return tool_error(request_id, "jobId or job_id is required")
|
|
858
|
+
wait_seconds = args.get("waitSeconds", 0)
|
|
859
|
+
try:
|
|
860
|
+
wait_seconds = int(wait_seconds)
|
|
861
|
+
except (TypeError, ValueError):
|
|
862
|
+
wait_seconds = 0
|
|
863
|
+
wait_seconds = min(max(wait_seconds, 0), MAX_STATUS_WAIT_SEC)
|
|
864
|
+
payload, error = get_generate_status(str(job_id), wait_seconds=wait_seconds)
|
|
865
|
+
if error:
|
|
866
|
+
return tool_error(request_id, error)
|
|
867
|
+
return tool_success(request_id, payload or {})
|
|
868
|
+
|
|
869
|
+
return tool_error(request_id, f"unknown tool: {name}")
|
|
870
|
+
|
|
871
|
+
return {
|
|
872
|
+
"jsonrpc": "2.0",
|
|
873
|
+
"id": request_id,
|
|
874
|
+
"error": {"code": -32601, "message": f"Unknown method: {method}"},
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def main() -> int:
|
|
879
|
+
_init_stdio()
|
|
880
|
+
if len(sys.argv) == 3 and sys.argv[1] == "--run-job":
|
|
881
|
+
return run_async_job(sys.argv[2])
|
|
882
|
+
|
|
883
|
+
while True:
|
|
884
|
+
request = read_message()
|
|
885
|
+
if request is None:
|
|
886
|
+
return 0
|
|
887
|
+
response = handle_request(request)
|
|
888
|
+
if response is not None:
|
|
889
|
+
send_response(response)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
if __name__ == "__main__":
|
|
893
|
+
raise SystemExit(main())
|