raga-cli 0.0.0-stage → 0.8.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/CHANGELOG.md +103 -0
- package/LICENSE +21 -0
- package/README.md +139 -2
- package/bundled-skills/AGENTS.md +5 -0
- package/bundled-skills/README.md +25 -0
- package/bundled-skills/agents/comment-sicko.json +9 -0
- package/bundled-skills/agents/comment-sicko.md +28 -0
- package/bundled-skills/agents/model-roles.json +41 -0
- package/bundled-skills/agents/poteto-agent.json +9 -0
- package/bundled-skills/agents/poteto-agent.md +4 -0
- package/bundled-skills/agents/worker.json +8 -0
- package/bundled-skills/agents/worker.md +1 -0
- package/bundled-skills/bundle.json +1448 -0
- package/bundled-skills/dependencies.json +40 -0
- package/bundled-skills/docs/guide/01-setup.md +3 -0
- package/bundled-skills/docs/guide/02-peak.md +135 -0
- package/bundled-skills/docs/guide/03-understand.md +79 -0
- package/bundled-skills/docs/guide/04-design.md +133 -0
- package/bundled-skills/docs/guide/05-build-and-clean.md +83 -0
- package/bundled-skills/docs/guide/06-verify-and-ship.md +166 -0
- package/bundled-skills/docs/guide/07-overnight.md +118 -0
- package/bundled-skills/docs/guide/08-principles.md +72 -0
- package/bundled-skills/docs/guide/09-make-it-yours.md +96 -0
- package/bundled-skills/docs/guide/10-recipes-and-pitfalls.md +180 -0
- package/bundled-skills/docs/guide/README.md +38 -0
- package/bundled-skills/docs/guide/images/design.jpg +0 -0
- package/bundled-skills/docs/guide/images/overnight.jpg +0 -0
- package/bundled-skills/docs/guide/images/recipes.jpg +0 -0
- package/bundled-skills/docs/guide/images/router.jpg +0 -0
- package/bundled-skills/docs/guide/images/understanding.jpg +0 -0
- package/bundled-skills/docs/guide/images/verification.jpg +0 -0
- package/bundled-skills/licenses/commander +22 -0
- package/bundled-skills/licenses/cursor-team-kit +21 -0
- package/bundled-skills/licenses/no-mistakes +21 -0
- package/bundled-skills/licenses/pstack +21 -0
- package/bundled-skills/skills/peak/SKILL.md +184 -0
- package/bundled-skills/skills/peak/playbooks/authoring-a-skill.md +12 -0
- package/bundled-skills/skills/peak/playbooks/autonomous-run.md +13 -0
- package/bundled-skills/skills/peak/playbooks/autopilot-full.md +25 -0
- package/bundled-skills/skills/peak/playbooks/autopilot-stack.md +12 -0
- package/bundled-skills/skills/peak/playbooks/babysit.md +18 -0
- package/bundled-skills/skills/peak/playbooks/bug-fix.md +15 -0
- package/bundled-skills/skills/peak/playbooks/eval.md +25 -0
- package/bundled-skills/skills/peak/playbooks/feature.md +21 -0
- package/bundled-skills/skills/peak/playbooks/hillclimb.md +21 -0
- package/bundled-skills/skills/peak/playbooks/investigation.md +14 -0
- package/bundled-skills/skills/peak/playbooks/multi-phase-plan.md +155 -0
- package/bundled-skills/skills/peak/playbooks/opening-a-pr.md +34 -0
- package/bundled-skills/skills/peak/playbooks/orchestrate.md +112 -0
- package/bundled-skills/skills/peak/playbooks/pause-safely.md +10 -0
- package/bundled-skills/skills/peak/playbooks/perf-issue.md +25 -0
- package/bundled-skills/skills/peak/playbooks/prototype.md +14 -0
- package/bundled-skills/skills/peak/playbooks/refactoring.md +16 -0
- package/bundled-skills/skills/peak/playbooks/runtime-forensics.md +11 -0
- package/bundled-skills/skills/peak/playbooks/session-pickup.md +11 -0
- package/bundled-skills/skills/peak/playbooks/shipping.md +12 -0
- package/bundled-skills/skills/peak/playbooks/trace-forensics.md +14 -0
- package/bundled-skills/skills/peak/playbooks/visual-parity.md +11 -0
- package/bundled-skills/skills/peak/playbooks/worktree-cleanup.md +6 -0
- package/bundled-skills/skills/peak/references/architect/SKILL.md +81 -0
- package/bundled-skills/skills/peak/references/architect/references/design-red-flags.md +57 -0
- package/bundled-skills/skills/peak/references/architect/references/rationale-template.md +35 -0
- package/bundled-skills/skills/peak/references/architect/references/runner-prompt.md +20 -0
- package/bundled-skills/skills/peak/references/arena/SKILL.md +50 -0
- package/bundled-skills/skills/peak/references/automate-me/SKILL.md +106 -0
- package/bundled-skills/skills/peak/references/benchmark-checklist/SKILL.md +41 -0
- package/bundled-skills/skills/peak/references/blast-radius/SKILL.md +52 -0
- package/bundled-skills/skills/peak/references/bro/SKILL.md +9 -0
- package/bundled-skills/skills/peak/references/control-cli/SKILL.md +111 -0
- package/bundled-skills/skills/peak/references/control-ui/SKILL.md +111 -0
- package/bundled-skills/skills/peak/references/correct/SKILL.md +34 -0
- package/bundled-skills/skills/peak/references/create-formal-verification-skill/SKILL.md +43 -0
- package/bundled-skills/skills/peak/references/create-skill/SKILL.md +14 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/SKILL.md +46 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/bundled-skills/skills/peak/references/deslop/SKILL.md +24 -0
- package/bundled-skills/skills/peak/references/figure-it-out/SKILL.md +54 -0
- package/bundled-skills/skills/peak/references/how/SKILL.md +38 -0
- package/bundled-skills/skills/peak/references/how/references/explainer-prompt.md +55 -0
- package/bundled-skills/skills/peak/references/how/references/explorer-prompt.md +35 -0
- package/bundled-skills/skills/peak/references/in-your-own-words/SKILL.md +9 -0
- package/bundled-skills/skills/peak/references/interrogate/SKILL.md +76 -0
- package/bundled-skills/skills/peak/references/interrogate/references/code-quality-review.md +47 -0
- package/bundled-skills/skills/peak/references/interrogate/references/lead-judgment.md +39 -0
- package/bundled-skills/skills/peak/references/interrogate/references/reviewer-prompt.md +62 -0
- package/bundled-skills/skills/peak/references/interrogate/references/rubric.md +77 -0
- package/bundled-skills/skills/peak/references/ketchup/SKILL.md +45 -0
- package/bundled-skills/skills/peak/references/maintain-formal-verification-skill/SKILL.md +60 -0
- package/bundled-skills/skills/peak/references/maintain-verification-skill/SKILL.md +41 -0
- package/bundled-skills/skills/peak/references/no-comments/SKILL.md +26 -0
- package/bundled-skills/skills/peak/references/no-mistakes/SKILL.md +20 -0
- package/bundled-skills/skills/peak/references/no-mistakes/upstream/SKILL.md +394 -0
- package/bundled-skills/skills/peak/references/peak-setup/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/poteto-help/SKILL.md +16 -0
- package/bundled-skills/skills/peak/references/poteto-help/references/prompting.md +51 -0
- package/bundled-skills/skills/peak/references/poteto-help/references/recipes.md +51 -0
- package/bundled-skills/skills/peak/references/principle-attack-the-premise/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-boundary-discipline/SKILL.md +36 -0
- package/bundled-skills/skills/peak/references/principle-build-the-lever/SKILL.md +25 -0
- package/bundled-skills/skills/peak/references/principle-encode-lessons-in-structure/SKILL.md +13 -0
- package/bundled-skills/skills/peak/references/principle-exhaust-the-design-space/SKILL.md +11 -0
- package/bundled-skills/skills/peak/references/principle-experience-first/SKILL.md +21 -0
- package/bundled-skills/skills/peak/references/principle-explain-the-number/SKILL.md +19 -0
- package/bundled-skills/skills/peak/references/principle-fix-root-causes/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-foundational-thinking/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-guard-the-context-window/SKILL.md +14 -0
- package/bundled-skills/skills/peak/references/principle-laziness-protocol/SKILL.md +12 -0
- package/bundled-skills/skills/peak/references/principle-make-operations-idempotent/SKILL.md +24 -0
- package/bundled-skills/skills/peak/references/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +16 -0
- package/bundled-skills/skills/peak/references/principle-minimize-reader-load/SKILL.md +20 -0
- package/bundled-skills/skills/peak/references/principle-model-the-domain/SKILL.md +26 -0
- package/bundled-skills/skills/peak/references/principle-never-block-on-the-human/SKILL.md +17 -0
- package/bundled-skills/skills/peak/references/principle-outcome-oriented-execution/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-prove-it-works/SKILL.md +24 -0
- package/bundled-skills/skills/peak/references/principle-redesign-from-first-principles/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/principle-separate-before-serializing-shared-state/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/principle-sequence-verifiable-units/SKILL.md +19 -0
- package/bundled-skills/skills/peak/references/principle-subtract-before-you-add/SKILL.md +20 -0
- package/bundled-skills/skills/peak/references/principle-test-behavior-not-implementation/SKILL.md +22 -0
- package/bundled-skills/skills/peak/references/principle-type-system-discipline/SKILL.md +15 -0
- package/bundled-skills/skills/peak/references/raga-runtime/SKILL.md +37 -0
- package/bundled-skills/skills/peak/references/raga-runtime/managed-tools.mjs +34 -0
- package/bundled-skills/skills/peak/references/raga-runtime/raga_runtime.py +44 -0
- package/bundled-skills/skills/peak/references/recall/SKILL.md +37 -0
- package/bundled-skills/skills/peak/references/reflect/SKILL.md +63 -0
- package/bundled-skills/skills/peak/references/reflect/references/divergent-reviewer.md +38 -0
- package/bundled-skills/skills/peak/references/reflect/references/judgment-reviewer.md +37 -0
- package/bundled-skills/skills/peak/references/reflect/references/synthesizer.md +56 -0
- package/bundled-skills/skills/peak/references/reflect/references/tooling-reviewer.md +48 -0
- package/bundled-skills/skills/peak/references/reviewer-triage.md +142 -0
- package/bundled-skills/skills/peak/references/sharpen/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/show-me-your-work/SKILL.md +84 -0
- package/bundled-skills/skills/peak/references/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/bundled-skills/skills/peak/references/show-me-your-work/scripts/log.sh +42 -0
- package/bundled-skills/skills/peak/references/statechart/SKILL.md +38 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/chart-to-tla.mjs +236 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/compare-charts.mjs +104 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/lib.mjs +725 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.cfg +6 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.map.mjs +106 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.tla +176 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/statechart.mjs +749 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/test/statechart.test.mjs +2790 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/test/sync.mmd +19 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/test/upload.mmd +16 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/tlc-to-chart.mjs +1347 -0
- package/bundled-skills/skills/peak/references/swarm/SKILL.md +43 -0
- package/bundled-skills/skills/peak/references/tdd/SKILL.md +44 -0
- package/bundled-skills/skills/peak/references/teach/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/technical-writing/SKILL.md +108 -0
- package/bundled-skills/skills/peak/references/typescript-best-practices/SKILL.md +33 -0
- package/bundled-skills/skills/peak/references/typescript-best-practices/references/patterns.md +328 -0
- package/bundled-skills/skills/peak/references/unslop/SKILL.md +64 -0
- package/bundled-skills/skills/peak/references/verify-this/SKILL.md +76 -0
- package/bundled-skills/skills/peak/references/why/SKILL.md +100 -0
- package/bundled-skills/skills/peak/references/why/references/epistemics.md +71 -0
- package/bundled-skills/skills/peak/references/why/references/investigator-prompt.md +84 -0
- package/bundled-skills/skills/peak/references/why/references/source-playbook.md +17 -0
- package/bundled-skills/skills/peak/references/why/references/sources/code-archaeology.md +51 -0
- package/bundled-skills/skills/peak/references/why/references/sources/databricks.md +70 -0
- package/bundled-skills/skills/peak/references/why/references/sources/datadog.md +97 -0
- package/bundled-skills/skills/peak/references/why/references/sources/incident-postmortem.md +5 -0
- package/bundled-skills/skills/peak/references/why/references/sources/linear.md +48 -0
- package/bundled-skills/skills/peak/references/why/references/sources/notion.md +55 -0
- package/bundled-skills/skills/peak/references/why/references/sources/sentry.md +66 -0
- package/bundled-skills/skills/peak/references/why/references/sources/slack.md +17 -0
- package/bundled-skills/skills/peak/references/why/references/synthesizer-prompt.md +129 -0
- package/bundled-skills/skills/peak/scripts/check-plan.mjs +205 -0
- package/bundled-skills/skills/peak/scripts/check-plan.test.ts +96 -0
- package/bundled-skills/skills/peak/scripts/orch/orch.test.ts +761 -0
- package/bundled-skills/skills/peak/scripts/orch/orch.ts +588 -0
- package/bundled-skills/skills/peak/scripts/orch/store.ts +1676 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/LICENSE +22 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/esm.mjs +16 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/index.js +24 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/argument.js +149 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/command.js +2778 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/error.js +39 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/help.js +747 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/option.js +379 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/suggestSimilar.js +101 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/package.json +1 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/typings/esm.d.mts +3 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/typings/index.d.ts +1113 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/cli.test.ts +224 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/cli.ts +223 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/fakes.test-helper.ts +129 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/github.test.ts +436 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/github.ts +718 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/policy.test.ts +420 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/policy.ts +836 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/render.ts +169 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/tsconfig.json +13 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/types.compile.ts +93 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/types.ts +413 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/watch-pr +4 -0
- package/bundled-skills/skills/peak/scripts/worktree-audit.sh +4 -0
- package/dist/bundle/chunks/anthropic-messages-BMDYDGHW.js +6 -0
- package/dist/bundle/chunks/anthropic.js +75 -0
- package/dist/bundle/chunks/azure-openai-responses-Q7EZTEHK.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +86 -0
- package/dist/bundle/chunks/chunk-3EGDWOKT.js +2 -0
- package/dist/bundle/chunks/chunk-3V45WARH.js +7 -0
- package/dist/bundle/chunks/chunk-5AGIVGSV.js +6 -0
- package/dist/bundle/chunks/chunk-6QGV3IWD.js +6 -0
- package/dist/bundle/chunks/chunk-7CBJINYJ.js +20 -0
- package/dist/bundle/chunks/chunk-7DDKXS2Q.js +2 -0
- package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
- package/dist/bundle/chunks/chunk-AI2NMHE7.js +1567 -0
- package/dist/bundle/chunks/chunk-C4IJNE7U.js +2 -0
- package/dist/bundle/chunks/chunk-CF5GWZHW.js +31 -0
- package/dist/bundle/chunks/chunk-G54UBZTF.js +44 -0
- package/dist/bundle/chunks/chunk-H76KZMOI.js +2 -0
- package/dist/bundle/chunks/chunk-IOPE2TJ4.js +131 -0
- package/dist/bundle/chunks/chunk-IXDQLPKS.js +11 -0
- package/dist/bundle/chunks/chunk-J22MF7YW.js +64 -0
- package/dist/bundle/chunks/chunk-JLV25LHE.js +2 -0
- package/dist/bundle/chunks/chunk-JXWJOIH4.js +2 -0
- package/dist/bundle/chunks/chunk-KNY3PXQI.js +2 -0
- package/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
- package/dist/bundle/chunks/chunk-LPXNMA2W.js +2 -0
- package/dist/bundle/chunks/chunk-LVVZS2NO.js +12 -0
- package/dist/bundle/chunks/chunk-LX24DVQ5.js +2 -0
- package/dist/bundle/chunks/chunk-P2QCWBFM.js +9 -0
- package/dist/bundle/chunks/chunk-P7FPUEYA.js +2 -0
- package/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
- package/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
- package/dist/bundle/chunks/chunk-PMRZO46Y.js +2 -0
- package/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
- package/dist/bundle/chunks/chunk-Q4U25BSY.js +2 -0
- package/dist/bundle/chunks/chunk-RDZVAGPR.js +2 -0
- package/dist/bundle/chunks/chunk-RFPFZTK2.js +7 -0
- package/dist/bundle/chunks/chunk-RP2WKNT7.js +4 -0
- package/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
- package/dist/bundle/chunks/chunk-TMTOQ7H7.js +2 -0
- package/dist/bundle/chunks/chunk-UDT647VB.js +82 -0
- package/dist/bundle/chunks/chunk-UKVNB6C4.js +2 -0
- package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
- package/dist/bundle/chunks/chunk-UTB4DTYQ.js +2 -0
- package/dist/bundle/chunks/chunk-WJGLPWUE.js +16 -0
- package/dist/bundle/chunks/chunk-YMZUNB25.js +2 -0
- package/dist/bundle/chunks/cli-4P6FVVLT.js +51 -0
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-S6CT2FXS.js +2 -0
- package/dist/bundle/chunks/codemode-worker.js +478 -0
- package/dist/bundle/chunks/easter-egg-3d-65VIC4G2.js +2 -0
- package/dist/bundle/chunks/execute-3H5WKMA3.js +505 -0
- package/dist/bundle/chunks/github-copilot.js +2 -0
- package/dist/bundle/chunks/google-generative-ai-35NZKG6S.js +2 -0
- package/dist/bundle/chunks/google-vertex-P33AHYYX.js +2 -0
- package/dist/bundle/chunks/https-proxy-agent-LVOO5TPL.js +13 -0
- package/dist/bundle/chunks/image-resize-worker.js +2 -0
- package/dist/bundle/chunks/jiti-loader-GL4HJFAD.js +21 -0
- package/dist/bundle/chunks/jiti-static-loader-2RCDI6B4.js +2 -0
- package/dist/bundle/chunks/kimi-coding.js +2 -0
- package/dist/bundle/chunks/lib-4AVRLBHM.js +9 -0
- package/dist/bundle/chunks/llama-cpp-classify-U4EH6TAM.js +22 -0
- package/dist/bundle/chunks/meta.js +2 -0
- package/dist/bundle/chunks/mistral-conversations-J62DPS2P.js +5 -0
- package/dist/bundle/chunks/multipart-parser-FMGQ7342.js +3 -0
- package/dist/bundle/chunks/node-AUOGOBCN.js +2 -0
- package/dist/bundle/chunks/node-KCMANYC2.js +16 -0
- package/dist/bundle/chunks/openai-chatgpt.js +75 -0
- package/dist/bundle/chunks/openai-codex-responses-4HXATL2M.js +10 -0
- package/dist/bundle/chunks/openai-codex.js +75 -0
- package/dist/bundle/chunks/openai-completions-75YYY35N.js +7 -0
- package/dist/bundle/chunks/openai-decisions-HEBEVGA2.js +5 -0
- package/dist/bundle/chunks/openai-responses-UWNAKTBV.js +4 -0
- package/dist/bundle/chunks/openrouter-images-HVOPXKHS.js +2 -0
- package/dist/bundle/chunks/openrouter.js +75 -0
- package/dist/bundle/chunks/pi-messages-DG7CALOV.js +8 -0
- package/dist/bundle/chunks/radius.js +75 -0
- package/dist/bundle/chunks/runtime-435R6IKY.js +2 -0
- package/dist/bundle/chunks/src-QKGKDFSU.js +4 -0
- package/dist/bundle/chunks/typesafe-system-one-24HEGHNN.js +2 -0
- package/dist/bundle/chunks/virtual-modules-U3JBUDP7.js +2 -0
- package/dist/bundle/chunks/xai.js +2 -0
- package/dist/bundle/cli-runtime.js +3 -0
- package/dist/bundle/cli.js +5 -0
- package/dist/bundle/index.js +2 -0
- package/dist/bundle/raga-runtime.js +3 -0
- package/dist/bundle/raga.js +5 -0
- package/dist/bundle/rpc-entry.js +3 -0
- package/dist/core/export-html/template.css +1085 -0
- package/dist/core/export-html/template.html +56 -0
- package/dist/core/export-html/template.js +2044 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked-cjk-friendly.min.js +6 -0
- package/dist/core/export-html/vendor/marked.min.js +79 -0
- package/dist/modes/interactive/assets/clankolas.png +0 -0
- package/dist/modes/interactive/theme/dark.json +78 -0
- package/dist/modes/interactive/theme/light.json +78 -0
- package/docs/cli-integration.md +106 -0
- package/docs/cli.md +337 -0
- package/docs/codemode.md +230 -0
- package/docs/compaction.md +463 -0
- package/docs/configuration.md +62 -0
- package/docs/containerization.md +183 -0
- package/docs/custom-provider.md +227 -0
- package/docs/docs.json +220 -0
- package/docs/environment-variables.md +101 -0
- package/docs/extensions.md +400 -0
- package/docs/how-pi-works.md +49 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/exy.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/index.md +39 -0
- package/docs/json.md +226 -0
- package/docs/keybindings.md +195 -0
- package/docs/llama-cpp.md +121 -0
- package/docs/mcp.md +272 -0
- package/docs/message-types.md +279 -0
- package/docs/models-configuration.md +196 -0
- package/docs/models.md +225 -0
- package/docs/nvidia-rtx-llama-cpp.md +96 -0
- package/docs/packages.md +129 -0
- package/docs/prompt-templates.md +59 -0
- package/docs/providers.md +247 -0
- package/docs/quickstart.md +136 -0
- package/docs/rpc-commands.md +860 -0
- package/docs/rpc-extension-ui.md +200 -0
- package/docs/rpc.md +193 -0
- package/docs/sdk.md +148 -0
- package/docs/security.md +99 -0
- package/docs/session-format.md +293 -0
- package/docs/sessions.md +66 -0
- package/docs/settings.md +184 -0
- package/docs/shell-aliases.md +93 -0
- package/docs/skills.md +93 -0
- package/docs/slash-commands.md +61 -0
- package/docs/terminal-setup.md +233 -0
- package/docs/termux.md +117 -0
- package/docs/themes.md +138 -0
- package/docs/tmux.md +55 -0
- package/docs/tui.md +195 -0
- package/docs/usage.md +94 -0
- package/docs/virtual-models.md +114 -0
- package/docs/windows.md +81 -0
- package/examples/README.md +36 -0
- package/examples/extensions/README.md +214 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bash-spawn-hook.ts +30 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/border-status-editor.ts +150 -0
- package/examples/extensions/built-in-tool-renderer.ts +225 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/commands.ts +72 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +117 -0
- package/examples/extensions/custom-footer.ts +64 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +405 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/examples/extensions/debug-provider.ts +90 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
- package/examples/extensions/dynamic-resources/SKILL.md +8 -0
- package/examples/extensions/dynamic-resources/dynamic.json +81 -0
- package/examples/extensions/dynamic-resources/dynamic.md +5 -0
- package/examples/extensions/dynamic-resources/index.ts +15 -0
- package/examples/extensions/dynamic-tools.ts +74 -0
- package/examples/extensions/entry-renderer.ts +41 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/examples/extensions/github-issue-autocomplete.ts +185 -0
- package/examples/extensions/gondolin/index.ts +531 -0
- package/examples/extensions/gondolin/package-lock.json +175 -0
- package/examples/extensions/gondolin/package.json +19 -0
- package/examples/extensions/handoff.ts +190 -0
- package/examples/extensions/hello.ts +26 -0
- package/examples/extensions/hidden-thinking-label.ts +53 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform-streaming.ts +39 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/jev-router.ts +113 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +59 -0
- package/examples/extensions/minimal-mode.ts +353 -0
- package/examples/extensions/modal-editor.ts +85 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +57 -0
- package/examples/extensions/overlay-qa-tests.ts +1450 -0
- package/examples/extensions/overlay-test.ts +153 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +66 -0
- package/examples/extensions/plan-mode/index.ts +394 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +436 -0
- package/examples/extensions/project-trust.ts +64 -0
- package/examples/extensions/prompt-customizer.ts +49 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/provider-payload.ts +18 -0
- package/examples/extensions/qna.ts +118 -0
- package/examples/extensions/question.ts +286 -0
- package/examples/extensions/questionnaire.ts +448 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/rpc-demo.ts +118 -0
- package/examples/extensions/sandbox/index.ts +326 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +343 -0
- package/examples/extensions/space-invaders.ts +560 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +32 -0
- package/examples/extensions/structured-output.ts +65 -0
- package/examples/extensions/subagent/README.md +177 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +157 -0
- package/examples/extensions/subagent/index.ts +1038 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +199 -0
- package/examples/extensions/system-prompt-header.ts +17 -0
- package/examples/extensions/tic-tac-toe.ts +1008 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/titlebar-spinner.ts +58 -0
- package/examples/extensions/todo.ts +297 -0
- package/examples/extensions/tool-override.ts +144 -0
- package/examples/extensions/tools.ts +146 -0
- package/examples/extensions/trigger-compact.ts +50 -0
- package/examples/extensions/truncated-tool.ts +195 -0
- package/examples/extensions/widget-placement.ts +9 -0
- package/examples/extensions/with-deps/index.ts +32 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/extensions/working-indicator.ts +123 -0
- package/examples/extensions/working-message-test.ts +25 -0
- package/examples/rpc-client.ts +35 -0
- package/examples/rpc-extension-ui.ts +661 -0
- package/examples/sdk/01-minimal.ts +26 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +75 -0
- package/examples/sdk/04-skills.ts +55 -0
- package/examples/sdk/05-tools.ts +48 -0
- package/examples/sdk/06-extensions.ts +99 -0
- package/examples/sdk/07-context-files.ts +47 -0
- package/examples/sdk/08-prompt-templates.ts +51 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
- package/examples/sdk/10-settings.ts +53 -0
- package/examples/sdk/11-sessions.ts +52 -0
- package/examples/sdk/12-full-control.ts +74 -0
- package/examples/sdk/13-session-runtime.ts +67 -0
- package/examples/sdk/14-codemode-mcp.ts +61 -0
- package/examples/sdk/README.md +141 -0
- package/package.json +21 -5
- package/python/raga_rlm/__init__.py +336 -0
- package/python/raga_rlm/repl.py +920 -0
- package/schemas/keybindings.schema.json +393 -0
- package/schemas/models.schema.json +1211 -0
- package/schemas/settings.schema.json +815 -0
- package/schemas/theme.schema.json +365 -0
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
# Adapted from Prime Agent (MIT), github.com/PrimeIntellect-ai/prime-agent
|
|
2
|
+
"""RLM kernel: a CPython REPL speaking newline-delimited JSON over stdio.
|
|
3
|
+
|
|
4
|
+
Entry point: `python -m raga_rlm.repl`. docs/RLM.md documents protocol 1.
|
|
5
|
+
Cells run with top-level await in one persistent `__main__` namespace on a
|
|
6
|
+
single asyncio event loop.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import asyncio
|
|
13
|
+
import codecs
|
|
14
|
+
import contextvars
|
|
15
|
+
import inspect
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import linecache
|
|
19
|
+
import os
|
|
20
|
+
import pickle
|
|
21
|
+
import platform
|
|
22
|
+
import signal
|
|
23
|
+
import sys
|
|
24
|
+
import tempfile
|
|
25
|
+
import threading
|
|
26
|
+
import traceback
|
|
27
|
+
import types
|
|
28
|
+
import uuid
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import resource
|
|
33
|
+
except ImportError: # Windows
|
|
34
|
+
resource = None # type: ignore[assignment]
|
|
35
|
+
|
|
36
|
+
import raga_rlm
|
|
37
|
+
|
|
38
|
+
PROTOCOL_VERSION = 1
|
|
39
|
+
|
|
40
|
+
DEFAULT_SNAPSHOT_MAX_BYTES = 256 * 1024 * 1024
|
|
41
|
+
DEFAULT_SNAPSHOT_MAX_VARIABLE_BYTES = 16 * 1024 * 1024
|
|
42
|
+
_SNAPSHOT_MAGIC = b"RAGA-RLM-SNAPSHOT-V1\n"
|
|
43
|
+
|
|
44
|
+
_STREAM_FRAME_TEXT_CAP = 64 * 1024
|
|
45
|
+
# Bounds a pathological repr or exception text in transit; the host truncates further.
|
|
46
|
+
_RESULT_TEXT_CAP = 1024 * 1024
|
|
47
|
+
_HOST_REQUEST_CAP = 16 * 1024 * 1024
|
|
48
|
+
# After stdin EOF a synchronous cell can block the queued shutdown forever.
|
|
49
|
+
_EOF_EXIT_GRACE_SECONDS = 10.0
|
|
50
|
+
# The host passes the `rlm.memoryLimitMb` setting; unset or 0 means no limit.
|
|
51
|
+
_MEMORY_LIMIT_ENV = "RAGA_RLM_MEMORY_LIMIT_MB"
|
|
52
|
+
|
|
53
|
+
_RUNTIME_FILES = {os.path.abspath(__file__), os.path.abspath(raga_rlm.__file__)}
|
|
54
|
+
|
|
55
|
+
_protocol_fd = -1
|
|
56
|
+
_host_stderr_fd = -1
|
|
57
|
+
_write_lock = threading.Lock()
|
|
58
|
+
_loop: asyncio.AbstractEventLoop | None = None
|
|
59
|
+
_serve_task: asyncio.Task[Any] | None = None
|
|
60
|
+
|
|
61
|
+
# Asyncio tasks copy the context at creation, so background tasks keep their cell id.
|
|
62
|
+
_current_cell: contextvars.ContextVar[str | None] = contextvars.ContextVar("_current_cell", default=None)
|
|
63
|
+
# Full stdout/stderr per running cell, for the `cells` record.
|
|
64
|
+
_cell_output: dict[str, dict[str, list[str]]] = {}
|
|
65
|
+
_cell_counter = 0
|
|
66
|
+
_pending_host: dict[str, asyncio.Future[dict[str, Any]]] = {}
|
|
67
|
+
_host_closed = False
|
|
68
|
+
|
|
69
|
+
# Interrupt bookkeeping shared by the reader thread and the loop thread.
|
|
70
|
+
_interrupt_lock = threading.Lock()
|
|
71
|
+
_active: dict[str, Any] = {"task": None, "rid": None, "interrupted": False}
|
|
72
|
+
_inflight: set[str] = set() # execute ids received and not yet finished
|
|
73
|
+
_pending_interrupts: dict[str, Any] = {"ids": set(), "any": False}
|
|
74
|
+
_sigint_target: str | None = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _send(event: dict[str, Any]) -> None:
|
|
78
|
+
"""Write one protocol frame; the lock keeps frames whole."""
|
|
79
|
+
data = (json.dumps(event, separators=(",", ":")) + "\n").encode()
|
|
80
|
+
with _write_lock:
|
|
81
|
+
view = memoryview(data)
|
|
82
|
+
try:
|
|
83
|
+
while view:
|
|
84
|
+
view = view[os.write(_protocol_fd, view) :]
|
|
85
|
+
except OSError as err:
|
|
86
|
+
try:
|
|
87
|
+
os.write(_host_stderr_fd, f"raga_rlm.repl: dropped protocol frame: {err}\n".encode())
|
|
88
|
+
except OSError:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _protocol_error(message: str) -> None:
|
|
93
|
+
_send({"event": "error", "id": None, "ename": "ProtocolError", "evalue": message, "traceback": []})
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# --- host requests -----------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def host_request(data: dict[str, Any]) -> dict[str, Any]:
|
|
100
|
+
"""Send one host_request and await the raw reply dict."""
|
|
101
|
+
if _loop is None:
|
|
102
|
+
raise RuntimeError("the RLM kernel is not serving")
|
|
103
|
+
if _host_closed:
|
|
104
|
+
raise RuntimeError("host connection closed; host_request cannot be answered")
|
|
105
|
+
# allow_nan=False: NaN would serialize as invalid JSON and break the host's parser.
|
|
106
|
+
if len(json.dumps(data, allow_nan=False)) > _HOST_REQUEST_CAP:
|
|
107
|
+
raise ValueError(f"host_request payload exceeds {_HOST_REQUEST_CAP} characters")
|
|
108
|
+
rid = uuid.uuid4().hex
|
|
109
|
+
future: asyncio.Future[dict[str, Any]] = _loop.create_future()
|
|
110
|
+
_pending_host[rid] = future
|
|
111
|
+
try:
|
|
112
|
+
_send({"event": "host_request", "id": rid, "cell": _current_cell.get(), "data": data})
|
|
113
|
+
return await future
|
|
114
|
+
finally:
|
|
115
|
+
_pending_host.pop(rid, None)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _fail_pending_host_requests() -> None:
|
|
119
|
+
"""No reply can arrive after EOF or shutdown; unblock every waiting cell."""
|
|
120
|
+
global _host_closed
|
|
121
|
+
_host_closed = True
|
|
122
|
+
for future in _pending_host.values():
|
|
123
|
+
if not future.done():
|
|
124
|
+
future.set_exception(RuntimeError("host connection closed; host_request cannot be answered"))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _resolve_host_reply(rid: str, data: dict[str, Any]) -> None:
|
|
128
|
+
"""Reader-thread half of the host bridge; replies to unknown ids are dropped."""
|
|
129
|
+
assert _loop is not None
|
|
130
|
+
|
|
131
|
+
def deliver() -> None:
|
|
132
|
+
future = _pending_host.get(rid)
|
|
133
|
+
if future is not None and not future.done():
|
|
134
|
+
future.set_result(data)
|
|
135
|
+
|
|
136
|
+
_loop.call_soon_threadsafe(deliver)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# --- output capture ----------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class _Pump:
|
|
143
|
+
"""Ships the bytes of one captured fd pipe as `id: null` stream events."""
|
|
144
|
+
|
|
145
|
+
def __init__(self, read_fd: int, write_fd: int, stream: str) -> None:
|
|
146
|
+
self._read_fd = read_fd
|
|
147
|
+
# Private write end: a cell closing fd 1/2 cannot break drain tokens.
|
|
148
|
+
self._token_fd = os.dup(write_fd)
|
|
149
|
+
self._stream = stream
|
|
150
|
+
self._decoder = codecs.getincrementaldecoder("utf-8")("replace")
|
|
151
|
+
self._lock = threading.Lock()
|
|
152
|
+
self._watch: tuple[bytes, threading.Event] | None = None
|
|
153
|
+
self._buf = b""
|
|
154
|
+
self._thread = threading.Thread(target=self._run, daemon=True)
|
|
155
|
+
self._thread.start()
|
|
156
|
+
|
|
157
|
+
def drain(self) -> None:
|
|
158
|
+
"""Block until every byte written to the fd so far has been shipped."""
|
|
159
|
+
token = b"\xff<drain:" + uuid.uuid4().hex.encode() + b">\xff"
|
|
160
|
+
seen = threading.Event()
|
|
161
|
+
with self._lock:
|
|
162
|
+
self._watch = (token, seen)
|
|
163
|
+
try:
|
|
164
|
+
os.write(self._token_fd, token)
|
|
165
|
+
while not seen.wait(0.1):
|
|
166
|
+
if not self._thread.is_alive():
|
|
167
|
+
return
|
|
168
|
+
except OSError:
|
|
169
|
+
return
|
|
170
|
+
finally:
|
|
171
|
+
with self._lock:
|
|
172
|
+
self._watch = None
|
|
173
|
+
|
|
174
|
+
def _run(self) -> None:
|
|
175
|
+
while True:
|
|
176
|
+
try:
|
|
177
|
+
chunk = os.read(self._read_fd, 65536)
|
|
178
|
+
except OSError:
|
|
179
|
+
break
|
|
180
|
+
if not chunk:
|
|
181
|
+
break
|
|
182
|
+
self._feed(chunk)
|
|
183
|
+
|
|
184
|
+
def _feed(self, chunk: bytes) -> None:
|
|
185
|
+
data = self._buf + chunk
|
|
186
|
+
self._buf = b""
|
|
187
|
+
with self._lock:
|
|
188
|
+
watch = self._watch
|
|
189
|
+
if watch is None:
|
|
190
|
+
self._emit(data)
|
|
191
|
+
return
|
|
192
|
+
token, seen = watch
|
|
193
|
+
while True:
|
|
194
|
+
i = data.find(token)
|
|
195
|
+
if i == -1:
|
|
196
|
+
break
|
|
197
|
+
self._emit(data[:i])
|
|
198
|
+
self._finish_decode()
|
|
199
|
+
seen.set()
|
|
200
|
+
data = data[i + len(token) :]
|
|
201
|
+
# Hold back a tail that may be the start of a token split across reads.
|
|
202
|
+
hold = 0
|
|
203
|
+
for k in range(min(len(data), len(token) - 1), 0, -1):
|
|
204
|
+
if data.endswith(token[:k]):
|
|
205
|
+
hold = k
|
|
206
|
+
break
|
|
207
|
+
if hold:
|
|
208
|
+
self._buf = data[len(data) - hold :]
|
|
209
|
+
data = data[: len(data) - hold]
|
|
210
|
+
self._emit(data)
|
|
211
|
+
|
|
212
|
+
def _emit(self, data: bytes) -> None:
|
|
213
|
+
if data:
|
|
214
|
+
text = self._decoder.decode(data)
|
|
215
|
+
if text:
|
|
216
|
+
_send({"event": self._stream, "id": None, "text": text})
|
|
217
|
+
|
|
218
|
+
def _finish_decode(self) -> None:
|
|
219
|
+
text = self._decoder.decode(b"", final=True)
|
|
220
|
+
self._decoder = codecs.getincrementaldecoder("utf-8")("replace")
|
|
221
|
+
if text:
|
|
222
|
+
_send({"event": self._stream, "id": None, "text": text})
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class _RawBuffer(io.RawIOBase):
|
|
226
|
+
"""`sys.stdout.buffer`: bytes go to the captured fd pipe (id null)."""
|
|
227
|
+
|
|
228
|
+
def __init__(self, fd: int) -> None:
|
|
229
|
+
self._fd = fd
|
|
230
|
+
|
|
231
|
+
def write(self, data: Any) -> int:
|
|
232
|
+
view = memoryview(data).cast("B")
|
|
233
|
+
total = len(view)
|
|
234
|
+
while view:
|
|
235
|
+
view = view[os.write(self._fd, view) :]
|
|
236
|
+
return total
|
|
237
|
+
|
|
238
|
+
def fileno(self) -> int:
|
|
239
|
+
return self._fd
|
|
240
|
+
|
|
241
|
+
def writable(self) -> bool:
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class _TaggedWriter(io.TextIOBase):
|
|
246
|
+
"""`sys.stdout`/`sys.stderr` replacement that tags writes with the writer's cell id."""
|
|
247
|
+
|
|
248
|
+
def __init__(self, stream: str, fd: int) -> None:
|
|
249
|
+
self._stream = stream
|
|
250
|
+
self._fd = fd
|
|
251
|
+
self._frame_lock = threading.Lock()
|
|
252
|
+
self._buffer = _RawBuffer(fd)
|
|
253
|
+
|
|
254
|
+
def write(self, text: str) -> int:
|
|
255
|
+
if not isinstance(text, str):
|
|
256
|
+
raise TypeError(f"write() argument must be str, not {type(text).__name__}")
|
|
257
|
+
if text:
|
|
258
|
+
cell_id = _current_cell.get()
|
|
259
|
+
with self._frame_lock:
|
|
260
|
+
record = _cell_output.get(cell_id) if cell_id is not None else None
|
|
261
|
+
if record is not None:
|
|
262
|
+
record[self._stream].append(text)
|
|
263
|
+
for start in range(0, len(text), _STREAM_FRAME_TEXT_CAP):
|
|
264
|
+
chunk = text[start : start + _STREAM_FRAME_TEXT_CAP]
|
|
265
|
+
_send({"event": self._stream, "id": cell_id, "text": chunk})
|
|
266
|
+
return len(text)
|
|
267
|
+
|
|
268
|
+
def flush(self) -> None:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
def fileno(self) -> int:
|
|
272
|
+
return self._fd
|
|
273
|
+
|
|
274
|
+
def writable(self) -> bool:
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
def isatty(self) -> bool:
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def buffer(self) -> _RawBuffer:
|
|
282
|
+
return self._buffer
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def encoding(self) -> str:
|
|
286
|
+
return "utf-8"
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def errors(self) -> str:
|
|
290
|
+
return "replace"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
_pump_out: _Pump
|
|
294
|
+
_pump_err: _Pump
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _drain_output() -> None:
|
|
298
|
+
for stream in (sys.stdout, sys.stderr):
|
|
299
|
+
try:
|
|
300
|
+
stream.flush()
|
|
301
|
+
except (OSError, ValueError, AttributeError):
|
|
302
|
+
pass
|
|
303
|
+
_pump_out.drain()
|
|
304
|
+
_pump_err.drain()
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# --- interrupts --------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _consume_task_exception(task: asyncio.Task[Any]) -> None:
|
|
311
|
+
if not task.cancelled():
|
|
312
|
+
task.exception()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _sigint_handler(signum: int, frame: types.FrameType | None) -> None:
|
|
316
|
+
task = _active["task"]
|
|
317
|
+
# No lock here (the main thread may hold it). The rid check drops a SIGINT
|
|
318
|
+
# that arrives after its cell finished, so it never hits a later cell.
|
|
319
|
+
if task is None or task.done() or _active["rid"] != _sigint_target:
|
|
320
|
+
return
|
|
321
|
+
_active["interrupted"] = True
|
|
322
|
+
running = asyncio.current_task(_loop) if _loop is not None else None
|
|
323
|
+
if running is task:
|
|
324
|
+
# The cell's own step (sync code or a blocking syscall): raise into it.
|
|
325
|
+
raise KeyboardInterrupt
|
|
326
|
+
# The loop is idle in select() or another task is mid-step: cancel the cell.
|
|
327
|
+
task.cancel()
|
|
328
|
+
if running is not None and running is not _serve_task:
|
|
329
|
+
# A background task blocked in sync code holds the only thread; unwind it.
|
|
330
|
+
running.add_done_callback(_consume_task_exception)
|
|
331
|
+
raise KeyboardInterrupt
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _request_interrupt(target: str | None) -> None:
|
|
335
|
+
"""Reader thread: deliver an interrupt to the running cell or park it for a queued one."""
|
|
336
|
+
global _sigint_target
|
|
337
|
+
with _interrupt_lock:
|
|
338
|
+
rid = _active["rid"]
|
|
339
|
+
if rid is not None and (target is None or target == rid):
|
|
340
|
+
_sigint_target = rid
|
|
341
|
+
elif target is not None:
|
|
342
|
+
if target in _inflight:
|
|
343
|
+
_pending_interrupts["ids"].add(target)
|
|
344
|
+
return
|
|
345
|
+
else:
|
|
346
|
+
if _inflight:
|
|
347
|
+
_pending_interrupts["any"] = True
|
|
348
|
+
return
|
|
349
|
+
# Cells run on the main thread, so SIGINT must land there.
|
|
350
|
+
signal.pthread_kill(threading.main_thread().ident, signal.SIGINT)
|
|
351
|
+
if _loop is not None:
|
|
352
|
+
# Wake the selector so a cancel scheduled by the handler runs promptly.
|
|
353
|
+
_loop.call_soon_threadsafe(lambda: None)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _finish_request(rid: str) -> None:
|
|
357
|
+
global _sigint_target
|
|
358
|
+
with _interrupt_lock:
|
|
359
|
+
if _sigint_target == rid:
|
|
360
|
+
_sigint_target = None
|
|
361
|
+
_inflight.discard(rid)
|
|
362
|
+
_pending_interrupts["ids"].discard(rid)
|
|
363
|
+
if not _inflight:
|
|
364
|
+
_pending_interrupts["any"] = False
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# --- execution ---------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _safe_str(exc: BaseException) -> str:
|
|
371
|
+
try:
|
|
372
|
+
return str(exc)
|
|
373
|
+
except BaseException: # noqa: BLE001 - a broken __str__ must not kill the kernel
|
|
374
|
+
return "<exception str() failed>"
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _cap_text(text: str) -> str:
|
|
378
|
+
if len(text) > _RESULT_TEXT_CAP:
|
|
379
|
+
return text[:_RESULT_TEXT_CAP] + f"\n[... truncated at {_RESULT_TEXT_CAP} characters ...]"
|
|
380
|
+
return text
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _cell_stack(stack: traceback.StackSummary) -> traceback.StackSummary | None:
|
|
384
|
+
"""Frames from the first cell frame on, without the kernel's own frames."""
|
|
385
|
+
start = next((i for i, f in enumerate(stack) if f.filename.startswith("<cell-")), None)
|
|
386
|
+
if start is None:
|
|
387
|
+
return None
|
|
388
|
+
frames = [f for f in stack[start:] if os.path.abspath(f.filename) not in _RUNTIME_FILES]
|
|
389
|
+
return traceback.StackSummary.from_list(frames)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _error_event(cell_id: str, exc: BaseException) -> dict[str, Any]:
|
|
393
|
+
te = traceback.TracebackException.from_exception(exc)
|
|
394
|
+
stack = _cell_stack(te.stack)
|
|
395
|
+
if stack is None:
|
|
396
|
+
# No cell frame (a SyntaxError): exception-only keeps the source line and caret.
|
|
397
|
+
lines = traceback.format_exception_only(type(exc), exc)
|
|
398
|
+
else:
|
|
399
|
+
te.stack = stack
|
|
400
|
+
lines = list(te.format())
|
|
401
|
+
return {
|
|
402
|
+
"event": "error",
|
|
403
|
+
"id": cell_id,
|
|
404
|
+
"ename": type(exc).__name__,
|
|
405
|
+
"evalue": _cap_text(_safe_str(exc)),
|
|
406
|
+
"traceback": [_cap_text(line) for line in lines],
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _interrupt_event(cell_id: str, exc: BaseException) -> dict[str, Any]:
|
|
411
|
+
"""Report a cell cancelled at an await as a KeyboardInterrupt."""
|
|
412
|
+
stack = _cell_stack(traceback.extract_tb(exc.__traceback__))
|
|
413
|
+
lines = []
|
|
414
|
+
if stack:
|
|
415
|
+
lines = ["Traceback (most recent call last):\n", *stack.format()]
|
|
416
|
+
lines.append("KeyboardInterrupt\n")
|
|
417
|
+
return {"event": "error", "id": cell_id, "ename": "KeyboardInterrupt", "evalue": "", "traceback": lines}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _compile_cell(code: str, filename: str) -> tuple[list[types.CodeType], bool]:
|
|
421
|
+
"""Compile a cell; a trailing expression compiles separately in eval mode."""
|
|
422
|
+
linecache.cache[filename] = (len(code), None, code.splitlines(keepends=True), filename)
|
|
423
|
+
tree = ast.parse(code, filename)
|
|
424
|
+
trailing: ast.Expression | None = None
|
|
425
|
+
if tree.body and isinstance(tree.body[-1], ast.Expr):
|
|
426
|
+
trailing = ast.Expression(tree.body.pop().value)
|
|
427
|
+
flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
|
|
428
|
+
codes: list[types.CodeType] = []
|
|
429
|
+
if tree.body:
|
|
430
|
+
codes.append(compile(tree, filename, "exec", flags=flags, dont_inherit=True))
|
|
431
|
+
if trailing is not None:
|
|
432
|
+
codes.append(compile(trailing, filename, "eval", flags=flags, dont_inherit=True))
|
|
433
|
+
return codes, trailing is not None
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
async def _run_codes(codes: list[types.CodeType], ns: dict[str, Any]) -> Any:
|
|
437
|
+
value: Any = None
|
|
438
|
+
for code_obj in codes:
|
|
439
|
+
value = eval(code_obj, ns) # noqa: S307 - running the model's cell is the kernel's job
|
|
440
|
+
if code_obj.co_flags & inspect.CO_COROUTINE:
|
|
441
|
+
value = await value
|
|
442
|
+
return value
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
async def _run_guarded(task: asyncio.Task[Any], rid: str) -> tuple[str, Any, dict[str, Any] | None]:
|
|
446
|
+
"""Await a cell task; returns (status, value, error event or None)."""
|
|
447
|
+
with _interrupt_lock:
|
|
448
|
+
_active.update(task=task, rid=rid, interrupted=False)
|
|
449
|
+
if _pending_interrupts["any"] or rid in _pending_interrupts["ids"]:
|
|
450
|
+
_pending_interrupts["any"] = False
|
|
451
|
+
_pending_interrupts["ids"].discard(rid)
|
|
452
|
+
# Interrupt parked before the cell started: cancel before its first step.
|
|
453
|
+
_active["interrupted"] = True
|
|
454
|
+
task.cancel()
|
|
455
|
+
try:
|
|
456
|
+
return "ok", await task, None
|
|
457
|
+
except asyncio.CancelledError as exc:
|
|
458
|
+
if _active["interrupted"]:
|
|
459
|
+
return "error", None, _interrupt_event(rid, exc)
|
|
460
|
+
return "error", None, _error_event(rid, exc)
|
|
461
|
+
except BaseException as exc: # noqa: BLE001 - every cell failure becomes an error event
|
|
462
|
+
return "error", None, _error_event(rid, exc)
|
|
463
|
+
finally:
|
|
464
|
+
with _interrupt_lock:
|
|
465
|
+
_active.update(task=None, rid=None)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
async def _handle_execute(req: dict[str, Any], ns: dict[str, Any]) -> None:
|
|
469
|
+
global _cell_counter
|
|
470
|
+
rid: str = req["id"]
|
|
471
|
+
code: str = req["code"]
|
|
472
|
+
_cell_counter += 1
|
|
473
|
+
filename = f"<cell-{_cell_counter}>"
|
|
474
|
+
output: dict[str, list[str]] = {"stdout": [], "stderr": []}
|
|
475
|
+
_cell_output[rid] = output
|
|
476
|
+
token = _current_cell.set(rid)
|
|
477
|
+
status, result_text, error = "ok", None, None
|
|
478
|
+
try:
|
|
479
|
+
try:
|
|
480
|
+
codes, has_trailing = _compile_cell(code, filename)
|
|
481
|
+
except BaseException as exc: # noqa: BLE001 - SyntaxError, RecursionError, ...
|
|
482
|
+
status, error = "error", _error_event(rid, exc)
|
|
483
|
+
else:
|
|
484
|
+
assert _loop is not None
|
|
485
|
+
status, value, error = await _run_guarded(_loop.create_task(_run_codes(codes, ns)), rid)
|
|
486
|
+
if status == "ok" and has_trailing and value is not None:
|
|
487
|
+
try:
|
|
488
|
+
ns["_"] = value
|
|
489
|
+
result_text = _cap_text(repr(value))
|
|
490
|
+
except BaseException as exc: # noqa: BLE001 - a broken __repr__ is a cell error
|
|
491
|
+
status, error = "error", _error_event(rid, exc)
|
|
492
|
+
_drain_output()
|
|
493
|
+
finally:
|
|
494
|
+
_current_cell.reset(token)
|
|
495
|
+
_cell_output.pop(rid, None)
|
|
496
|
+
_finish_request(rid)
|
|
497
|
+
if result_text is not None:
|
|
498
|
+
_send({"event": "result", "id": rid, "text": result_text})
|
|
499
|
+
if error is not None:
|
|
500
|
+
_send(error)
|
|
501
|
+
raga_rlm.record_cell(
|
|
502
|
+
{
|
|
503
|
+
"id": rid,
|
|
504
|
+
"code": code,
|
|
505
|
+
"stdout": "".join(output["stdout"]),
|
|
506
|
+
"stderr": "".join(output["stderr"]),
|
|
507
|
+
"result": result_text,
|
|
508
|
+
"error": "".join(error["traceback"]) if error else None,
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
done: dict[str, Any] = {"event": "done", "id": rid, "status": status}
|
|
512
|
+
rss_mb = _rss_mb()
|
|
513
|
+
if rss_mb is not None:
|
|
514
|
+
done["rss_mb"] = rss_mb
|
|
515
|
+
_send(done)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
# --- memory ------------------------------------------------------------------
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _apply_memory_limit() -> None:
|
|
522
|
+
"""Cap the data segment (heap and private mappings) at the host's limit.
|
|
523
|
+
|
|
524
|
+
On Linux an allocation past RLIMIT_DATA fails with MemoryError in the cell,
|
|
525
|
+
and the kernel keeps serving. macOS does not enforce RLIMIT_DATA, so there
|
|
526
|
+
is no hard limit there.
|
|
527
|
+
"""
|
|
528
|
+
raw = os.environ.get(_MEMORY_LIMIT_ENV, "")
|
|
529
|
+
limit_mb = int(raw) if raw else 0
|
|
530
|
+
if limit_mb <= 0 or resource is None or not sys.platform.startswith("linux"):
|
|
531
|
+
return
|
|
532
|
+
limit = limit_mb * 1024 * 1024
|
|
533
|
+
_, hard = resource.getrlimit(resource.RLIMIT_DATA)
|
|
534
|
+
if hard != resource.RLIM_INFINITY:
|
|
535
|
+
limit = min(limit, hard)
|
|
536
|
+
resource.setrlimit(resource.RLIMIT_DATA, (limit, limit))
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _rss_mb() -> int | None:
|
|
540
|
+
"""Current RSS in MiB on Linux; peak RSS elsewhere, where getrusage has no current value."""
|
|
541
|
+
try:
|
|
542
|
+
with open("/proc/self/statm", "rb") as fh:
|
|
543
|
+
pages = int(fh.read().split()[1])
|
|
544
|
+
return pages * os.sysconf("SC_PAGE_SIZE") // (1024 * 1024)
|
|
545
|
+
except (OSError, ValueError, IndexError):
|
|
546
|
+
pass
|
|
547
|
+
if resource is None:
|
|
548
|
+
return None
|
|
549
|
+
peak = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
550
|
+
# ru_maxrss is bytes on macOS and KiB on the other Unixes.
|
|
551
|
+
return peak // (1024 * 1024) if sys.platform == "darwin" else peak // 1024
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
# --- snapshot and restore ----------------------------------------------------
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class _TooLarge(Exception):
|
|
558
|
+
pass
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
class _CappedWriter:
|
|
562
|
+
def __init__(self, sink: io.BytesIO, limit: int) -> None:
|
|
563
|
+
self._sink = sink
|
|
564
|
+
self._limit = limit
|
|
565
|
+
self._written = 0
|
|
566
|
+
|
|
567
|
+
def write(self, chunk: Any) -> int:
|
|
568
|
+
size = len(chunk)
|
|
569
|
+
if self._written + size > self._limit:
|
|
570
|
+
raise _TooLarge()
|
|
571
|
+
self._sink.write(chunk)
|
|
572
|
+
self._written += size
|
|
573
|
+
return size
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _user_names(ns: dict[str, Any]) -> list[str]:
|
|
577
|
+
return sorted(
|
|
578
|
+
name
|
|
579
|
+
for name, value in ns.items()
|
|
580
|
+
if isinstance(name, str)
|
|
581
|
+
and not name.startswith("_")
|
|
582
|
+
and name not in raga_rlm.BOOTSTRAP_NAMES
|
|
583
|
+
and not isinstance(value, types.ModuleType)
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _snapshot(ns: dict[str, Any], path: str, max_bytes: int, max_variable_bytes: int) -> dict[str, Any]:
|
|
588
|
+
try:
|
|
589
|
+
import dill
|
|
590
|
+
except ImportError:
|
|
591
|
+
return {"status": "error", "reason": "dill not installed"}
|
|
592
|
+
# Format: magic, then per name a 4-byte name length, the UTF-8 name, an
|
|
593
|
+
# 8-byte blob length, and the dill blob. One blob per name lets restore
|
|
594
|
+
# load each name on its own.
|
|
595
|
+
saved: list[str] = []
|
|
596
|
+
skipped: list[str] = []
|
|
597
|
+
directory = os.path.dirname(os.path.abspath(path))
|
|
598
|
+
os.makedirs(directory, exist_ok=True)
|
|
599
|
+
fd, tmp = tempfile.mkstemp(dir=directory, prefix=os.path.basename(path) + ".", suffix=".tmp")
|
|
600
|
+
try:
|
|
601
|
+
with os.fdopen(fd, "wb") as fh:
|
|
602
|
+
total = fh.write(_SNAPSHOT_MAGIC)
|
|
603
|
+
for name in _user_names(ns):
|
|
604
|
+
value = ns[name]
|
|
605
|
+
# A large buffer arrives in one write, after dill has copied it whole.
|
|
606
|
+
if _estimated_size(value) > max_variable_bytes:
|
|
607
|
+
skipped.append(name)
|
|
608
|
+
continue
|
|
609
|
+
try:
|
|
610
|
+
encoded = name.encode("utf-8")
|
|
611
|
+
buffer = io.BytesIO()
|
|
612
|
+
dill.dump(value, _CappedWriter(buffer, max_variable_bytes))
|
|
613
|
+
except Exception: # noqa: BLE001 - _TooLarge or unpicklable: skip this name only
|
|
614
|
+
skipped.append(name)
|
|
615
|
+
continue
|
|
616
|
+
blob = buffer.getvalue()
|
|
617
|
+
size = 12 + len(encoded) + len(blob)
|
|
618
|
+
if total + size > max_bytes:
|
|
619
|
+
skipped.append(name)
|
|
620
|
+
continue
|
|
621
|
+
fh.write(len(encoded).to_bytes(4, "little"))
|
|
622
|
+
fh.write(encoded)
|
|
623
|
+
fh.write(len(blob).to_bytes(8, "little"))
|
|
624
|
+
fh.write(blob)
|
|
625
|
+
total += size
|
|
626
|
+
saved.append(name)
|
|
627
|
+
os.replace(tmp, path)
|
|
628
|
+
except OSError as err:
|
|
629
|
+
try:
|
|
630
|
+
os.remove(tmp)
|
|
631
|
+
except OSError:
|
|
632
|
+
pass
|
|
633
|
+
return {"status": "error", "reason": f"snapshot write failed: {err}"}
|
|
634
|
+
return {"status": "ok", "saved": saved, "skipped": skipped, "bytes": total}
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _estimated_size(value: Any) -> int:
|
|
638
|
+
"""A cheap lower bound on a value's pickle size, without pickling it; 0 when unknown."""
|
|
639
|
+
if isinstance(value, (str, bytes, bytearray)):
|
|
640
|
+
return len(value)
|
|
641
|
+
if isinstance(value, memoryview):
|
|
642
|
+
return value.nbytes
|
|
643
|
+
try:
|
|
644
|
+
# numpy arrays and similar buffers, without importing numpy.
|
|
645
|
+
nbytes = getattr(value, "nbytes", None)
|
|
646
|
+
except Exception: # noqa: BLE001 - a broken __getattr__ only loses the estimate
|
|
647
|
+
return 0
|
|
648
|
+
return nbytes if isinstance(nbytes, int) and not isinstance(nbytes, bool) else 0
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _index_records(fh: io.BufferedReader) -> dict[str, tuple[int, int]]:
|
|
652
|
+
"""Map each name to its blob's (offset, length), reading only the record headers."""
|
|
653
|
+
if fh.read(len(_SNAPSHOT_MAGIC)) != _SNAPSHOT_MAGIC:
|
|
654
|
+
raise ValueError("not a raga RLM snapshot")
|
|
655
|
+
size = os.fstat(fh.fileno()).st_size
|
|
656
|
+
records: dict[str, tuple[int, int]] = {}
|
|
657
|
+
pos = len(_SNAPSHOT_MAGIC)
|
|
658
|
+
while pos < size:
|
|
659
|
+
header = fh.read(4)
|
|
660
|
+
if len(header) < 4:
|
|
661
|
+
raise ValueError("truncated snapshot record")
|
|
662
|
+
name_len = int.from_bytes(header, "little")
|
|
663
|
+
pos += 4
|
|
664
|
+
if pos + name_len + 8 > size:
|
|
665
|
+
raise ValueError("truncated snapshot record")
|
|
666
|
+
name = fh.read(name_len).decode("utf-8")
|
|
667
|
+
blob_len = int.from_bytes(fh.read(8), "little")
|
|
668
|
+
pos += name_len + 8
|
|
669
|
+
if pos + blob_len > size:
|
|
670
|
+
raise ValueError("truncated snapshot record")
|
|
671
|
+
records[name] = (pos, blob_len)
|
|
672
|
+
pos += blob_len
|
|
673
|
+
fh.seek(pos)
|
|
674
|
+
return records
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _snapshot_unpickler(dill: Any) -> type:
|
|
678
|
+
"""dill pickles a pipe-backed file as its fd number; reopening that number
|
|
679
|
+
here would later close whatever owns the fd now (e.g. the loop's self-pipe)."""
|
|
680
|
+
create_filehandle = dill._dill._create_filehandle
|
|
681
|
+
|
|
682
|
+
def refuse_raw_fd(name: Any, *args: Any) -> Any:
|
|
683
|
+
if isinstance(name, int):
|
|
684
|
+
raise pickle.UnpicklingError(f"refusing to reopen raw file descriptor {name} from a snapshot")
|
|
685
|
+
return create_filehandle(name, *args)
|
|
686
|
+
|
|
687
|
+
class GuardedUnpickler(dill.Unpickler):
|
|
688
|
+
def find_class(self, module: str, name: str) -> Any:
|
|
689
|
+
target = super().find_class(module, name)
|
|
690
|
+
return refuse_raw_fd if target is create_filehandle else target
|
|
691
|
+
|
|
692
|
+
return GuardedUnpickler
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def _restore(ns: dict[str, Any], path: str) -> dict[str, Any]:
|
|
696
|
+
if not os.path.exists(path):
|
|
697
|
+
return {"status": "ok", "restored": [], "failed": [], "reason": "snapshot not found"}
|
|
698
|
+
try:
|
|
699
|
+
import dill
|
|
700
|
+
except ImportError:
|
|
701
|
+
return {"status": "error", "reason": "dill not installed"}
|
|
702
|
+
unpickler = _snapshot_unpickler(dill)
|
|
703
|
+
restored: list[str] = []
|
|
704
|
+
failed: list[str] = []
|
|
705
|
+
try:
|
|
706
|
+
with open(path, "rb") as fh:
|
|
707
|
+
records = _index_records(fh)
|
|
708
|
+
# One blob in memory at a time.
|
|
709
|
+
for name, (offset, length) in records.items():
|
|
710
|
+
if name.startswith("_") or name in raga_rlm.BOOTSTRAP_NAMES:
|
|
711
|
+
continue
|
|
712
|
+
try:
|
|
713
|
+
fh.seek(offset)
|
|
714
|
+
ns[name] = unpickler(io.BytesIO(fh.read(length))).load()
|
|
715
|
+
except Exception: # noqa: BLE001 - one broken name must not abort the restore
|
|
716
|
+
failed.append(name)
|
|
717
|
+
continue
|
|
718
|
+
restored.append(name)
|
|
719
|
+
except (OSError, ValueError) as err:
|
|
720
|
+
# Only opening the file and indexing its headers raise here, before any name loads.
|
|
721
|
+
return {"status": "error", "reason": f"snapshot load failed: {err}"}
|
|
722
|
+
return {"status": "ok", "restored": sorted(restored), "failed": sorted(failed)}
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def _answer(ns: dict[str, Any]) -> dict[str, Any]:
|
|
726
|
+
"""`answer` from the namespace as {content: str, ready: bool}; the model may rebind it."""
|
|
727
|
+
value = ns.get("answer")
|
|
728
|
+
if not isinstance(value, dict):
|
|
729
|
+
return {"content": "", "ready": False}
|
|
730
|
+
content = value.get("content", "")
|
|
731
|
+
if not isinstance(content, str):
|
|
732
|
+
try:
|
|
733
|
+
content = json.dumps(content, allow_nan=False)
|
|
734
|
+
except (TypeError, ValueError):
|
|
735
|
+
content = _safe_str(content)
|
|
736
|
+
return {"content": _cap_text(content), "ready": bool(value.get("ready"))}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def _int_field(req: dict[str, Any], field: str, default: int) -> int:
|
|
740
|
+
value = req.get(field, default)
|
|
741
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
742
|
+
raise ValueError(f"{field} must be a non-negative integer")
|
|
743
|
+
return value
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
# --- serving -----------------------------------------------------------------
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
async def _serve(queue: asyncio.Queue[dict[str, Any]], ns: dict[str, Any]) -> None:
|
|
750
|
+
while True:
|
|
751
|
+
req = await queue.get()
|
|
752
|
+
# A cell may have rebound SIGINT; the kernel must own it for every request.
|
|
753
|
+
signal.signal(signal.SIGINT, _sigint_handler)
|
|
754
|
+
rtype = req["type"]
|
|
755
|
+
rid = req.get("id")
|
|
756
|
+
if rtype == "shutdown":
|
|
757
|
+
if isinstance(rid, str):
|
|
758
|
+
_send({"event": "done", "id": rid, "status": "ok"})
|
|
759
|
+
return
|
|
760
|
+
try:
|
|
761
|
+
if rtype == "execute":
|
|
762
|
+
await _handle_execute(req, ns)
|
|
763
|
+
continue
|
|
764
|
+
if rtype == "snapshot":
|
|
765
|
+
reply = _snapshot(
|
|
766
|
+
ns,
|
|
767
|
+
req["path"],
|
|
768
|
+
_int_field(req, "max_bytes", DEFAULT_SNAPSHOT_MAX_BYTES),
|
|
769
|
+
_int_field(req, "max_variable_bytes", DEFAULT_SNAPSHOT_MAX_VARIABLE_BYTES),
|
|
770
|
+
)
|
|
771
|
+
elif rtype == "restore":
|
|
772
|
+
reply = _restore(ns, req["path"])
|
|
773
|
+
elif rtype == "set_context":
|
|
774
|
+
raga_rlm.set_context(req["task"], req.get("inputs", {}), req.get("data"))
|
|
775
|
+
reply = {"status": "ok"}
|
|
776
|
+
elif rtype == "get_answer":
|
|
777
|
+
reply = {"status": "ok", "answer": _answer(ns)}
|
|
778
|
+
else:
|
|
779
|
+
reply = {"status": "ok", "names": _user_names(ns)}
|
|
780
|
+
_send({"event": "done", "id": rid, **reply})
|
|
781
|
+
except BaseException as exc: # noqa: BLE001 - one broken request fails alone
|
|
782
|
+
_send({"event": "done", "id": rid, "status": "error", "reason": f"{type(exc).__name__}: {_safe_str(exc)}"})
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
_REQUIRED_FIELDS: dict[str, tuple[str, ...]] = {
|
|
786
|
+
"execute": ("id", "code"),
|
|
787
|
+
"snapshot": ("id", "path"),
|
|
788
|
+
"restore": ("id", "path"),
|
|
789
|
+
"list_names": ("id",),
|
|
790
|
+
"get_answer": ("id",),
|
|
791
|
+
"set_context": ("id", "task"),
|
|
792
|
+
"shutdown": (),
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def _handle_line(raw: bytes, queue: asyncio.Queue[dict[str, Any]]) -> None:
|
|
797
|
+
assert _loop is not None
|
|
798
|
+
req = json.loads(raw)
|
|
799
|
+
if not isinstance(req, dict):
|
|
800
|
+
raise ValueError("request is not a JSON object")
|
|
801
|
+
rtype = req.get("type")
|
|
802
|
+
if rtype == "interrupt":
|
|
803
|
+
target = req.get("id")
|
|
804
|
+
if target is not None and not isinstance(target, str):
|
|
805
|
+
_protocol_error("interrupt id must be a string")
|
|
806
|
+
return
|
|
807
|
+
_request_interrupt(target)
|
|
808
|
+
return
|
|
809
|
+
if rtype == "host_reply":
|
|
810
|
+
# Bypasses the queue: the waiting cell is the running execute.
|
|
811
|
+
rid, data = req.get("id"), req.get("data")
|
|
812
|
+
if isinstance(rid, str) and isinstance(data, dict):
|
|
813
|
+
_resolve_host_reply(rid, data)
|
|
814
|
+
else:
|
|
815
|
+
_protocol_error("host_reply needs a string id and a dict data")
|
|
816
|
+
return
|
|
817
|
+
if not isinstance(rtype, str) or rtype not in _REQUIRED_FIELDS:
|
|
818
|
+
_protocol_error(f"unknown request type: {rtype!r}")
|
|
819
|
+
return
|
|
820
|
+
missing = [field for field in _REQUIRED_FIELDS[rtype] if not isinstance(req.get(field), str)]
|
|
821
|
+
if missing:
|
|
822
|
+
_protocol_error(f"{rtype} request needs string fields: {', '.join(missing)}")
|
|
823
|
+
return
|
|
824
|
+
if rtype == "shutdown" and "id" in req and not isinstance(req["id"], str):
|
|
825
|
+
_protocol_error("shutdown id must be a string")
|
|
826
|
+
return
|
|
827
|
+
if rtype == "execute":
|
|
828
|
+
with _interrupt_lock:
|
|
829
|
+
duplicate = req["id"] in _inflight
|
|
830
|
+
if not duplicate:
|
|
831
|
+
_inflight.add(req["id"])
|
|
832
|
+
if duplicate:
|
|
833
|
+
_protocol_error(f"duplicate in-flight execute id: {req['id']!r}")
|
|
834
|
+
return
|
|
835
|
+
if rtype == "shutdown":
|
|
836
|
+
# A cell waiting on a host reply would block the queued shutdown forever.
|
|
837
|
+
_loop.call_soon_threadsafe(_fail_pending_host_requests)
|
|
838
|
+
_loop.call_soon_threadsafe(queue.put_nowait, req)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _read_requests(stdin_fd: int, queue: asyncio.Queue[dict[str, Any]]) -> None:
|
|
842
|
+
assert _loop is not None
|
|
843
|
+
with os.fdopen(stdin_fd, "rb") as stream:
|
|
844
|
+
for raw in stream:
|
|
845
|
+
raw = raw.strip()
|
|
846
|
+
if not raw:
|
|
847
|
+
continue
|
|
848
|
+
try:
|
|
849
|
+
_handle_line(raw, queue)
|
|
850
|
+
except BaseException as err: # noqa: BLE001 - hostile input must not kill the reader
|
|
851
|
+
_protocol_error(f"{type(err).__name__}: {_safe_str(err)}")
|
|
852
|
+
# stdin closed: same as shutdown.
|
|
853
|
+
_loop.call_soon_threadsafe(_fail_pending_host_requests)
|
|
854
|
+
_loop.call_soon_threadsafe(queue.put_nowait, {"type": "shutdown"})
|
|
855
|
+
threading.Timer(_EOF_EXIT_GRACE_SECONDS, os._exit, args=(0,)).start()
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _setup_fds() -> int:
|
|
859
|
+
"""Reserve the original fd 1 for protocol frames; route fds 1 and 2 through pumped pipes."""
|
|
860
|
+
global _protocol_fd, _host_stderr_fd, _pump_out, _pump_err
|
|
861
|
+
_protocol_fd = os.dup(1)
|
|
862
|
+
os.set_inheritable(_protocol_fd, False)
|
|
863
|
+
_host_stderr_fd = os.dup(2)
|
|
864
|
+
os.set_inheritable(_host_stderr_fd, False)
|
|
865
|
+
out_r, out_w = os.pipe()
|
|
866
|
+
err_r, err_w = os.pipe()
|
|
867
|
+
os.dup2(out_w, 1)
|
|
868
|
+
os.dup2(err_w, 2)
|
|
869
|
+
os.close(out_w)
|
|
870
|
+
os.close(err_w)
|
|
871
|
+
sys.stdout = _TaggedWriter("stdout", os.dup(1))
|
|
872
|
+
sys.stderr = _TaggedWriter("stderr", os.dup(2))
|
|
873
|
+
stdin_fd = os.dup(0)
|
|
874
|
+
os.set_inheritable(stdin_fd, False)
|
|
875
|
+
devnull = os.open(os.devnull, os.O_RDONLY)
|
|
876
|
+
os.dup2(devnull, 0)
|
|
877
|
+
os.close(devnull)
|
|
878
|
+
sys.stdin = open(os.devnull) # noqa: SIM115 - input() sees EOF, never protocol frames
|
|
879
|
+
_pump_out = _Pump(out_r, 1, "stdout")
|
|
880
|
+
_pump_err = _Pump(err_r, 2, "stderr")
|
|
881
|
+
return stdin_fd
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def main() -> None:
|
|
885
|
+
global _loop, _serve_task
|
|
886
|
+
_apply_memory_limit()
|
|
887
|
+
stdin_fd = _setup_fds()
|
|
888
|
+
|
|
889
|
+
# `python -m` runs this file as `__main__`; alias it so `raga_rlm.repl` is this module.
|
|
890
|
+
sys.modules["raga_rlm.repl"] = sys.modules[__name__]
|
|
891
|
+
# A real `__main__` module makes dill pickle cell functions and classes by value.
|
|
892
|
+
user_module = types.ModuleType("__main__")
|
|
893
|
+
user_module.__dict__["__builtins__"] = __builtins__
|
|
894
|
+
sys.modules["__main__"] = user_module
|
|
895
|
+
ns = user_module.__dict__
|
|
896
|
+
raga_rlm.bootstrap(ns)
|
|
897
|
+
|
|
898
|
+
_loop = asyncio.new_event_loop()
|
|
899
|
+
asyncio.set_event_loop(_loop)
|
|
900
|
+
queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()
|
|
901
|
+
signal.signal(signal.SIGINT, _sigint_handler)
|
|
902
|
+
threading.Thread(target=_read_requests, args=(stdin_fd, queue), daemon=True).start()
|
|
903
|
+
|
|
904
|
+
_send({"event": "ready", "protocol": PROTOCOL_VERSION, "python": platform.python_version()})
|
|
905
|
+
|
|
906
|
+
_serve_task = _loop.create_task(_serve(queue, ns))
|
|
907
|
+
# A KeyboardInterrupt raised into a cell or background task escapes
|
|
908
|
+
# run_until_complete; the cell already recorded it, so keep serving.
|
|
909
|
+
while not _serve_task.done():
|
|
910
|
+
try:
|
|
911
|
+
_loop.run_until_complete(_serve_task)
|
|
912
|
+
except KeyboardInterrupt:
|
|
913
|
+
continue
|
|
914
|
+
_drain_output()
|
|
915
|
+
# Hard exit: pending background tasks and user threads must not hold the process.
|
|
916
|
+
os._exit(0)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
if __name__ == "__main__":
|
|
920
|
+
main()
|