super-dsh 0.1.12 → 0.3.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/README.md +50 -13
- package/agent-agy/dist/world-plugin.d.ts +1 -1
- package/agent-agy/dist/world-plugin.js +14 -27
- package/agent-agy-sdk/dist/world-plugin.d.ts +1 -1
- package/agent-agy-sdk/dist/world-plugin.js +14 -27
- package/agent-codex/dist/world-plugin.js +14 -27
- package/agent-codex-sdk/dist/world-plugin.js +14 -43
- package/agent-hermes/dist/world-plugin.js +14 -27
- package/agent-omp/dist/world-plugin.js +14 -39
- package/agent-omp/lib/client/index.js +258 -0
- package/agent-omp-sdk/dist/world-plugin.d.ts +1 -1
- package/agent-omp-sdk/dist/world-plugin.js +14 -43
- package/agent-pi/dist/world-plugin.js +14 -27
- package/bundles/better-dsh.tgz +0 -0
- package/bundles/manifest.json +10 -0
- package/client/index.js +434 -307
- package/cordis.patch.yml +7 -16
- package/node_modules/better-dsh/LICENSE +24 -0
- package/node_modules/better-dsh/README.md +323 -0
- package/node_modules/better-dsh/THIRD_PARTY_NOTICES.md +52 -0
- package/node_modules/better-dsh/cordis.patch.yml +139 -0
- package/node_modules/better-dsh/docs/00_adr/0001-bridge-tool-layer-not-service-layer.md +14 -0
- package/node_modules/better-dsh/docs/00_adr/0002-masking-is-presentation-only.md +15 -0
- package/node_modules/better-dsh/docs/10_plans/2026-09-23-remote-execution-framework.md +236 -0
- package/node_modules/better-dsh/docs/10_plans/2026-09-23-remote-framework-impl.md +2083 -0
- package/node_modules/better-dsh/docs/10_plans/2026-09-26-lsp-ast-reminder.md +64 -0
- package/node_modules/better-dsh/docs/10_plans/A2A-messaging-channel-test-archive.md +256 -0
- package/node_modules/better-dsh/docs/10_plans/code-mode-vs-rlm-ipython-comparison.md +137 -0
- package/node_modules/better-dsh/docs/10_plans/dashr-blueprint-review.md +201 -0
- package/node_modules/better-dsh/docs/10_plans/dashr-blueprint.md +561 -0
- package/node_modules/better-dsh/docs/10_plans/dashr-compaction-window-and-archive.md +307 -0
- package/node_modules/better-dsh/docs/10_plans/dashr-profile-layer-feasibility.md +367 -0
- package/node_modules/better-dsh/docs/10_plans/dashr-sandbox-escalation-semantics-gap.md +171 -0
- package/node_modules/better-dsh/docs/10_plans/dashr-security-sandbox-analysis.md +187 -0
- package/node_modules/better-dsh/docs/10_plans/dashr-surface-invariant-and-omp-imports.md +97 -0
- package/node_modules/better-dsh/docs/10_plans/ipython-kernel-interactive-interface-test-report.md +152 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +146 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +50 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +79 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +138 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +161 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/V0.1.5-development-plan.md +109 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +50 -0
- package/node_modules/better-dsh/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +113 -0
- package/node_modules/better-dsh/docs/10_plans/recallable-compaction.md +147 -0
- package/node_modules/better-dsh/docs/10_plans/spike-tag-repro.mjs +102 -0
- package/node_modules/better-dsh/docs/10_plans/upstream-analysis.md +128 -0
- package/node_modules/better-dsh/docs/20_specs/agent/spec.md +54 -0
- package/node_modules/better-dsh/docs/20_specs/ast/spec.md +57 -0
- package/node_modules/better-dsh/docs/20_specs/compaction-recall/spec.md +46 -0
- package/node_modules/better-dsh/docs/20_specs/ctx/spec.md +107 -0
- package/node_modules/better-dsh/docs/20_specs/dsh/spec.md +47 -0
- package/node_modules/better-dsh/docs/20_specs/dvc/spec.md +106 -0
- package/node_modules/better-dsh/docs/20_specs/escalation-guidance/spec.md +44 -0
- package/node_modules/better-dsh/docs/20_specs/fs-scheme-resolution/spec.md +37 -0
- package/node_modules/better-dsh/docs/20_specs/hash-edit/spec.md +41 -0
- package/node_modules/better-dsh/docs/20_specs/http-read/spec.md +73 -0
- package/node_modules/better-dsh/docs/20_specs/kernel-provisioning/spec.md +53 -0
- package/node_modules/better-dsh/docs/20_specs/lsp/spec.md +121 -0
- package/node_modules/better-dsh/docs/20_specs/mobile-layout/spec.md +108 -0
- package/node_modules/better-dsh/docs/20_specs/model-failover/spec.md +20 -0
- package/node_modules/better-dsh/docs/20_specs/plugins-page-components/spec.md +79 -0
- package/node_modules/better-dsh/docs/20_specs/preact-ui-shell/spec.md +22 -0
- package/node_modules/better-dsh/docs/20_specs/repl-dispatch-resilience/spec.md +21 -0
- package/node_modules/better-dsh/docs/20_specs/skill/spec.md +58 -0
- package/node_modules/better-dsh/docs/20_specs/tool-surface/spec.md +222 -0
- package/node_modules/better-dsh/docs/20_specs/url-schema/spec.md +148 -0
- package/node_modules/better-dsh/docs/20_specs/web-trust-fence/spec.md +43 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-06-write/345/267/245/345/205/267sandbox/345/215/207/347/272/247/351/200/217/344/274/240bug/345/244/215/345/217/221/345/217/212/346/214/202/350/265/267-/344/272/213/344/273/266/346/212/245/345/221/212.md +176 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-08-hashline-edit-E_RANGE_UNVERIFIED/350/267/250/350/275/256/344/274/232/350/257/235/351/224/256/345/244/261/346/225/210-/350/257/212/346/226/255/346/212/245/345/221/212.md +226 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-11-control-prompt-into-eval-description/345/256/236/346/265/213/346/212/245/345/221/212.md +222 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-12-fs-scheme-resolution-/345/256/236/346/265/213/346/212/245/345/221/212.md +81 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-12-url-schemes-grammar-matrix/344/270/216catalog-centralize-/345/256/236/346/265/213/346/212/245/345/221/212.md +234 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-12-url-schemes-recallable-context-design/351/252/214/350/257/201/346/212/245/345/221/212.md +160 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-12-url-schemes-recallable-context-/345/256/236/346/265/213/345/211/247/346/234/254.md +44 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-12-url-schemes-recallable-context-/345/256/236/346/265/213/346/212/245/345/221/212.md +89 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-12-url-schemes-/345/205/255scheme/345/206/222/347/203/237/344/270/216/350/276/271/347/225/214/345/256/236/346/265/213/346/212/245/345/221/212.md +198 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-13-hashline-off/344/270/213scheme/345/217/257/350/276/276/346/200/247/345/267/245/345/205/267/351/235/242/344/270/215/345/257/271/347/247/260-/345/256/236/346/265/213/346/212/245/345/221/212.md +246 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-13-preact-ui-shell/345/256/236/346/265/213/346/212/245/345/221/212.md +50 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-14-4999-skill/346/270/205/345/215/225/344/270/216lsp-gate/345/256/236/346/265/213/346/212/245/345/221/212.md +63 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-17-plugins/351/241/265/345/205/255/347/273/204/344/273/266/346/213/206/345/210/206/345/256/236/346/265/213/346/212/245/345/221/212.md +76 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-22-4988/345/217/214/345/271/263/351/235/242/345/220/257/345/212/250/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +76 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-22-better-dsh/347/273/204/344/273/266/347/203/255/346/217/222/346/213/224/344/270/216/344/276/235/350/265/226/347/272/247/350/201/224/345/256/236/346/265/213/346/212/245/345/221/212.md +134 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-23-remote-framework/345/256/236/346/265/213/346/212/245/345/221/212.md +92 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-23-remote-framework/347/254/254/344/270/200/344/272/272/347/247/260/345/244/215/346/265/213/346/212/245/345/221/212.md +125 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-23-ssh/345/267/245/345/205/267assisted-pty/344/274/232/350/257/235framing/345/256/236/346/265/213/346/212/245/345/221/212.md +207 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-24-v0.1.7-rc.1/345/257/271/351/275/220/350/275/256-compaction/344/270/216failover/350/256/276/347/275/256/351/235/242/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +65 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-27-lsp-ast-reminder-4999/345/256/236/346/265/213/346/212/245/345/221/212.md +53 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-09-28-ctx-scheme/351/225/277/344/274/232/350/257/235utility/345/256/236/346/265/213/346/212/245/345/221/212.md +155 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-01-v0.2.4d-web-password-gate/345/256/236/346/265/213/346/212/245/345/221/212.md +105 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-02-better-dsh-schemastery/344/276/235/350/265/226/345/243/260/346/230/216/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +62 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-02-ctx-navigation/345/256/236/346/265/213/346/212/245/345/221/212.md +137 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-02-v0.2.0-rc.2/345/257/271/351/275/220/350/275/256/345/256/236/346/265/213/346/212/245/345/221/212.md +38 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-ast-engine-deomp-4999/345/256/236/346/265/213/346/212/245/345/221/212.md +325 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-better-dsh-tool-surface-repl-dvc-browser-ast-lsp-4999/345/256/236/346/265/213/346/212/245/345/221/212.md +185 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-docs-packaging-trim/345/256/236/346/265/213/346/212/245/345/221/212.md +82 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-physical-import/344/270/216llm-completion-ast/347/233/270/345/257/271/350/267/257/345/276/204/344/277/256/345/244/215/345/256/236/346/265/213/346/212/245/345/221/212.md +101 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-pnpm11/345/271/264/351/276/204/351/227/250/351/224/201/346/226/207/344/273/266/347/233/270/344/275/215/345/256/236/350/257/201.md +54 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-remote-BYO-PTY-label/351/207/215/346/236/204/347/254/254/344/270/200/344/272/272/347/247/260/345/256/236/346/265/213/346/212/245/345/221/212.md +447 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-schemastery-cosmokit/346/272/220/347/240/201/345/206/205/350/201/224/345/256/236/346/265/213/346/212/245/345/221/212.md +63 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-03-zero-npm-runtime-deps/345/256/236/346/265/213/346/212/245/345/221/212.md +82 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-04-ast-engine-deomp-0.2.5-d/345/217/221/345/270/203/351/252/214/346/224/266/346/212/245/345/221/212.md +220 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-04-ast_grep/344/270/216ast_edit/345/245/221/347/272/246/344/270/216/350/276/271/347/225/214/345/256/236/346/265/213/346/212/245/345/221/212.md +151 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-06-authentication-only-web-admission-4999.md +63 -0
- package/node_modules/better-dsh/docs/50_test-reports/2026-10-07-better-dsh-welcome-self-contained-4999.md +52 -0
- package/node_modules/better-dsh/docs/50_test-reports/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +110 -0
- package/node_modules/better-dsh/docs/50_test-reports/kernel-provisioning.md +44 -0
- package/node_modules/better-dsh/docs/50_test-reports/repl-kernel-provisioning-test-report.md +87 -0
- package/node_modules/better-dsh/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-local-test-report.md +81 -0
- package/node_modules/better-dsh/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-report.md +93 -0
- package/node_modules/better-dsh/docs/50_test-reports/upstream-dsh-0.1.3-alpha.2-local-test-report.md +44 -0
- package/node_modules/better-dsh/docs/50_test-reports/upstream-dsh-0.1.3-alpha.2-report.md +110 -0
- package/node_modules/better-dsh/docs/50_test-reports/upstream-dsh-0.1.5-rc.2-local-test-report.md +79 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +142 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +193 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +96 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +127 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +150 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +224 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +168 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +123 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +110 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +86 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +66 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.1d-/345/256/236/346/265/213/346/212/245/345/221/212.md +67 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.1e-P1-/345/256/236/346/265/213/346/212/245/345/221/212.md +136 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.1ef-dev-audit-report.md +73 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.1f-plugin-shipped-ui-patches/345/256/236/346/265/213/346/212/245/345/221/212.md +102 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.3b-hashline-content-locator/345/256/236/346/265/213/346/212/245/345/221/212.md +73 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.3c-mobile-wave/345/256/236/346/265/213/346/212/245/345/221/212.md +47 -0
- package/node_modules/better-dsh/docs/50_test-reports/v0.2.4-ios-focus-zoom-suppression/345/256/236/346/265/213/346/212/245/345/221/212.md +158 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/SKILL.md +147 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch01-user-guide.md +123 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch02-plugin-basics.md +127 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch03-plugin-framework-practice.md +120 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch04-cordis-fundamentals.md +129 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch05-cordis-api.md +115 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch06-architecture.md +109 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch07-capability-seams.md +97 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch08-internals-pipelines.md +111 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch09-api-gateway.md +106 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch10-config-catalog.md +97 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch11-tool-catalog.md +121 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch12-persistence-catalog.md +109 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch13-subsystems-core-session.md +106 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch14-subsystems-agent-loop.md +111 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch15-subsystems-tool-execution.md +106 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch16-subsystems-session-data.md +132 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch17-subsystems-llm-streaming.md +128 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch18-subsystems-web-ui.md +142 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch19-cookbook.md +149 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch20-development-testing.md +87 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch21-postmortems.md +82 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/chapters/ch22-i18n.md +86 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/cheatsheet.md +67 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/glossary.md +57 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/skill/patterns.md +88 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/AGENTS.md +75 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/agent-lifecycle.md +84 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/agent-lifecycle.zh.md +86 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/api-gateway.md +164 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/api-gateway.zh.md +164 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/architecture.md +150 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/architecture.zh.md +154 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/capability-seams.md +543 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/capability-seams.zh.md +545 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/config-catalog.md +3473 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/config-catalog.zh.md +3474 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-package.md +117 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-package.zh.md +119 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-remote-api.md +197 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-remote-api.zh.md +197 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-settings-card.md +102 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-settings-card.zh.md +102 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-tool.md +101 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-tool.zh.md +103 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-vendored-package.md +59 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-a-vendored-package.zh.md +59 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-an-llm-adapter.md +43 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/adding-an-llm-adapter.zh.md +43 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/extension-cookbook.md +132 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/extension-cookbook.zh.md +136 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/maintaining-dsh-code-review.md +64 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/maintaining-dsh-code-review.zh.md +64 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/context.md +364 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/context.zh.md +366 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/events.md +207 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/events.zh.md +209 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/fiber.md +375 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/fiber.zh.md +377 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/inherited.md +39 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/registry.md +152 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/registry.zh.md +154 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/service.md +102 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-api/service.zh.md +104 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-primer.md +45 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-primer.zh.md +51 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/01-first-plugin.md +95 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/01-first-plugin.zh.md +95 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/02-lifecycle-and-effects.zh.md +98 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/03-services.md +98 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/03-services.zh.md +98 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/04-events.md +144 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/04-events.zh.md +144 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/05-config.md +84 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/05-config.zh.md +84 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/06-composition-and-hmr.zh.md +113 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/07-into-the-harness.md +108 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/07-into-the-harness.zh.md +108 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/index.md +60 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/cordis-tutorial/index.zh.md +62 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/deepseek-llm-api-wire-extensions.md +163 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/deepseek-llm-api-wire-extensions.zh.md +163 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/defensive-patterns.md +33 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/defensive-patterns.zh.md +35 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/development.md +167 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/development.zh.md +173 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/event-producer-consumer.md +86 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/event-producer-consumer.zh.md +88 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/glossary.md +45 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/glossary.zh.md +45 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/graph-atlas.md +22 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/graph-atlas.zh.md +24 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/README.md +60 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/README.zh.md +62 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/style-samples.md +87 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/terminology.md +214 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/translation-prompt.md +263 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/translation-rules.md +69 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/i18n/translation-rules.zh.md +69 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/module-graph.md +1411 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/module-graph.zh.md +1413 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/persistence-catalog.md +1075 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/persistence-catalog.zh.md +1077 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0001-acp-default-export-drops-inject.md +113 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/README.md +18 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/postmortem/README.zh.md +18 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/rescope.md +53 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/rescope.zh.md +53 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/README.md +61 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/README.zh.md +61 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/agent-team.md +207 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/agent-team.zh.md +207 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/approval.md +170 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/approval.zh.md +170 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/attachment.md +351 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/attachment.zh.md +351 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/client-modules.md +168 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/client-modules.zh.md +168 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/code-runtime.md +195 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/code-runtime.zh.md +195 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/commands.md +219 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/commands.zh.md +219 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/compaction.md +238 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/compaction.zh.md +238 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/conversation.md +258 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/conversation.zh.md +258 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/core.md +1209 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/core.zh.md +1219 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/credentials.md +329 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/credentials.zh.md +329 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/extensions.md +382 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/extensions.zh.md +382 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/feedback.md +266 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/feedback.zh.md +266 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/filesystem.md +505 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/filesystem.zh.md +505 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/goal.md +277 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/goal.zh.md +277 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/invariants.md +88 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/invariants.zh.md +88 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/jobs.md +290 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/jobs.zh.md +290 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/llm-streaming.md +1080 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/llm-streaming.zh.md +1086 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/lsp.md +202 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/lsp.zh.md +202 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/permission-presets.md +131 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/permission-presets.zh.md +131 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/persistence.md +395 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/persistence.zh.md +395 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/plan.md +87 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/plan.zh.md +87 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/sandbox.md +220 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/sandbox.zh.md +220 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/schedule.md +192 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/schedule.zh.md +192 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/scope.md +59 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/scope.zh.md +59 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-projection.md +354 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-projection.zh.md +354 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-query.md +509 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-query.zh.md +509 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-reference.md +219 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-reference.zh.md +219 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-telemetry.md +194 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-telemetry.zh.md +194 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-title.md +204 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session-title.zh.md +204 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session.md +1155 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/session.zh.md +1159 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/settings.md +405 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/settings.zh.md +405 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/shell.md +303 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/shell.zh.md +303 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/skills.md +354 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/skills.zh.md +354 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/slots.md +175 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/slots.zh.md +175 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/spill.md +117 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/spill.zh.md +117 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/storage.md +260 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/storage.zh.md +260 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subagent.md +766 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subagent.zh.md +770 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subprocess.md +324 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/subprocess.zh.md +324 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/system-prompt.md +220 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/system-prompt.zh.md +220 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/terminal.md +184 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/terminal.zh.md +184 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/todo.md +32 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/todo.zh.md +32 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/token-meter.md +105 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/token-meter.zh.md +105 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/tools.md +720 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/tools.zh.md +720 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/typert.md +343 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/typert.zh.md +343 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/user-questions.md +178 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/user-questions.zh.md +178 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-client.md +95 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-client.zh.md +95 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-server.md +154 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web-server.zh.md +154 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web.md +206 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/web.zh.md +206 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/webhook.md +70 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/webhook.zh.md +70 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workflow.md +278 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workflow.zh.md +278 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workspace.md +321 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/subsystems/workspace.zh.md +321 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/testing.md +54 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/testing.zh.md +54 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-catalog.md +2225 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-catalog.zh.md +2233 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-execution-pipeline.md +62 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/tool-execution-pipeline.zh.md +64 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/config.md +106 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/config.zh.md +106 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/index.md +144 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/index.zh.md +144 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/publish.md +183 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/publish.zh.md +183 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/tool.md +52 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/basic/tool.zh.md +52 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/events.md +143 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/events.zh.md +143 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/index.md +137 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/index.zh.md +137 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/service.md +148 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/framework/service.zh.md +150 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/dynamic-cordis.md +15 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/dynamic-cordis.zh.md +15 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/index.md +155 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/index.zh.md +155 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/llm-adapter.md +189 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/develop/practice/llm-adapter.zh.md +189 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/github-review.md +102 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/github-review.zh.md +102 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/index.md +30 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/index.zh.md +30 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/mcp-memory.md +101 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/mcp-memory.zh.md +101 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/network-proxy.md +85 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/network-proxy.zh.md +85 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/providers.md +190 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/providers.zh.md +190 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/python-sdk.md +150 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/python-sdk.zh.md +150 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/schedule.md +21 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/guide/schedule.zh.md +21 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/index.md +11 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/user/index.zh.md +11 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/web-styling.md +29 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/docs/web-styling.zh.md +29 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/site-pages-map.txt +234 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src/site-pages.json +232 -0
- package/node_modules/better-dsh/docs/80_upstream-docs-and-skills/dsh-dev-skill/src-README.md +63 -0
- package/node_modules/better-dsh/docs/dev-test-environment-notes.md +137 -0
- package/node_modules/better-dsh/docs/distro-blueprint.md +81 -0
- package/node_modules/better-dsh/docs/dsh-webUI-with-rlm-mode.png +0 -0
- package/node_modules/better-dsh/docs/repositioning-and-rebranding.md +102 -0
- package/node_modules/better-dsh/docs/specs/agent/spec.md +54 -0
- package/node_modules/better-dsh/docs/specs/ast/spec.md +57 -0
- package/node_modules/better-dsh/docs/specs/ast-engine-deomp/spec.md +170 -0
- package/node_modules/better-dsh/docs/specs/authentication-only-web-admission/spec.md +51 -0
- package/node_modules/better-dsh/docs/specs/compaction-recall/spec.md +46 -0
- package/node_modules/better-dsh/docs/specs/ctx/spec.md +179 -0
- package/node_modules/better-dsh/docs/specs/docs-packaging-trim/spec.md +110 -0
- package/node_modules/better-dsh/docs/specs/dsh/spec.md +47 -0
- package/node_modules/better-dsh/docs/specs/dvc/spec.md +106 -0
- package/node_modules/better-dsh/docs/specs/escalation-guidance/spec.md +44 -0
- package/node_modules/better-dsh/docs/specs/fs-scheme-resolution/spec.md +37 -0
- package/node_modules/better-dsh/docs/specs/hash-edit/spec.md +41 -0
- package/node_modules/better-dsh/docs/specs/http-read/spec.md +73 -0
- package/node_modules/better-dsh/docs/specs/kernel-provisioning/spec.md +53 -0
- package/node_modules/better-dsh/docs/specs/lsp/spec.md +121 -0
- package/node_modules/better-dsh/docs/specs/mobile-layout/spec.md +108 -0
- package/node_modules/better-dsh/docs/specs/model-failover/spec.md +20 -0
- package/node_modules/better-dsh/docs/specs/model-picker-ios-blur/spec.md +168 -0
- package/node_modules/better-dsh/docs/specs/plugins-page-components/spec.md +79 -0
- package/node_modules/better-dsh/docs/specs/preact-ui-shell/spec.md +22 -0
- package/node_modules/better-dsh/docs/specs/remote-pty-label/spec.md +126 -0
- package/node_modules/better-dsh/docs/specs/repl-dispatch-resilience/spec.md +21 -0
- package/node_modules/better-dsh/docs/specs/skill/spec.md +58 -0
- package/node_modules/better-dsh/docs/specs/tool-surface/spec.md +222 -0
- package/node_modules/better-dsh/docs/specs/url-schema/spec.md +148 -0
- package/node_modules/better-dsh/docs/specs/web-trust-fence/spec.md +43 -0
- package/node_modules/better-dsh/docs/specs/welcome-self-contained/spec.md +13 -0
- package/node_modules/better-dsh/docs/specs/zero-npm-runtime-deps/spec.md +85 -0
- package/node_modules/better-dsh/docs/superpowers/plans/2026-10-02-ctx-navigation.md +319 -0
- package/node_modules/better-dsh/docs/superpowers/plans/2026-10-03-ast-engine-deomp.md +1214 -0
- package/node_modules/better-dsh/docs/superpowers/plans/2026-10-03-docs-packaging-trim.md +181 -0
- package/node_modules/better-dsh/docs/superpowers/specs/2026-10-02-ctx-navigation-design.md +209 -0
- package/node_modules/better-dsh/docs/upstream-dsh-0.1.5-rc.2-report.md +156 -0
- package/node_modules/better-dsh/dsh-docs/AGENTS.md +75 -0
- package/node_modules/better-dsh/dsh-docs/agent-lifecycle.md +84 -0
- package/node_modules/better-dsh/dsh-docs/agent-lifecycle.zh.md +86 -0
- package/node_modules/better-dsh/dsh-docs/api-gateway.md +164 -0
- package/node_modules/better-dsh/dsh-docs/api-gateway.zh.md +164 -0
- package/node_modules/better-dsh/dsh-docs/architecture.md +150 -0
- package/node_modules/better-dsh/dsh-docs/architecture.zh.md +154 -0
- package/node_modules/better-dsh/dsh-docs/capability-seams.md +543 -0
- package/node_modules/better-dsh/dsh-docs/capability-seams.zh.md +545 -0
- package/node_modules/better-dsh/dsh-docs/config-catalog.md +3473 -0
- package/node_modules/better-dsh/dsh-docs/config-catalog.zh.md +3474 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-package.md +117 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-package.zh.md +119 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-remote-api.md +197 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-remote-api.zh.md +197 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-session-format-version.md +109 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-session-format-version.zh.md +109 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-settings-card.md +102 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-settings-card.zh.md +102 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-tool.md +101 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-tool.zh.md +103 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-vendored-package.md +59 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-a-vendored-package.zh.md +59 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-an-llm-adapter.md +43 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/adding-an-llm-adapter.zh.md +43 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/extension-cookbook.md +132 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/extension-cookbook.zh.md +136 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/maintaining-dsh-code-review.md +64 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/maintaining-dsh-code-review.zh.md +64 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
- package/node_modules/better-dsh/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/context.md +364 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/context.zh.md +366 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/events.md +207 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/events.zh.md +209 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/fiber.md +375 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/fiber.zh.md +377 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/inherited.md +39 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/registry.md +152 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/registry.zh.md +154 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/service.md +102 -0
- package/node_modules/better-dsh/dsh-docs/cordis-api/service.zh.md +104 -0
- package/node_modules/better-dsh/dsh-docs/cordis-primer.md +45 -0
- package/node_modules/better-dsh/dsh-docs/cordis-primer.zh.md +51 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/01-first-plugin.md +95 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/01-first-plugin.zh.md +95 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.zh.md +98 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/03-services.md +98 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/03-services.zh.md +98 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/04-events.md +144 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/04-events.zh.md +144 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/05-config.md +84 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/05-config.zh.md +84 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/06-composition-and-hmr.zh.md +113 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/07-into-the-harness.md +108 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/07-into-the-harness.zh.md +108 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/index.md +60 -0
- package/node_modules/better-dsh/dsh-docs/cordis-tutorial/index.zh.md +62 -0
- package/node_modules/better-dsh/dsh-docs/deepseek-llm-api-wire-extensions.md +163 -0
- package/node_modules/better-dsh/dsh-docs/deepseek-llm-api-wire-extensions.zh.md +163 -0
- package/node_modules/better-dsh/dsh-docs/defensive-patterns.md +33 -0
- package/node_modules/better-dsh/dsh-docs/defensive-patterns.zh.md +35 -0
- package/node_modules/better-dsh/dsh-docs/development.md +167 -0
- package/node_modules/better-dsh/dsh-docs/development.zh.md +173 -0
- package/node_modules/better-dsh/dsh-docs/event-producer-consumer.md +86 -0
- package/node_modules/better-dsh/dsh-docs/event-producer-consumer.zh.md +88 -0
- package/node_modules/better-dsh/dsh-docs/glossary.md +45 -0
- package/node_modules/better-dsh/dsh-docs/glossary.zh.md +45 -0
- package/node_modules/better-dsh/dsh-docs/graph-atlas.md +22 -0
- package/node_modules/better-dsh/dsh-docs/graph-atlas.zh.md +24 -0
- package/node_modules/better-dsh/dsh-docs/i18n/README.md +60 -0
- package/node_modules/better-dsh/dsh-docs/i18n/README.zh.md +62 -0
- package/node_modules/better-dsh/dsh-docs/i18n/style-samples.md +87 -0
- package/node_modules/better-dsh/dsh-docs/i18n/terminology.md +214 -0
- package/node_modules/better-dsh/dsh-docs/i18n/translation-prompt.md +263 -0
- package/node_modules/better-dsh/dsh-docs/i18n/translation-rules.md +69 -0
- package/node_modules/better-dsh/dsh-docs/i18n/translation-rules.zh.md +69 -0
- package/node_modules/better-dsh/dsh-docs/module-graph.md +1411 -0
- package/node_modules/better-dsh/dsh-docs/module-graph.zh.md +1413 -0
- package/node_modules/better-dsh/dsh-docs/persistence-catalog.md +1075 -0
- package/node_modules/better-dsh/dsh-docs/persistence-catalog.zh.md +1077 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0001-acp-default-export-drops-inject.md +113 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/README.md +18 -0
- package/node_modules/better-dsh/dsh-docs/postmortem/README.zh.md +18 -0
- package/node_modules/better-dsh/dsh-docs/rescope.md +53 -0
- package/node_modules/better-dsh/dsh-docs/rescope.zh.md +53 -0
- package/node_modules/better-dsh/dsh-docs/session-format-status.md +47 -0
- package/node_modules/better-dsh/dsh-docs/session-format-status.zh.md +47 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/README.md +61 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/README.zh.md +61 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/agent-team.md +207 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/agent-team.zh.md +207 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/approval.md +170 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/approval.zh.md +170 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/attachment.md +351 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/attachment.zh.md +351 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/client-modules.md +168 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/client-modules.zh.md +168 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/client-resources.md +91 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/client-resources.zh.md +91 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/code-runtime.md +195 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/code-runtime.zh.md +195 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/commands.md +219 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/commands.zh.md +219 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/compaction.md +238 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/compaction.zh.md +238 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/conversation.md +258 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/conversation.zh.md +258 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/core.md +1209 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/core.zh.md +1219 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/credentials.md +329 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/credentials.zh.md +329 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/extensions.md +382 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/extensions.zh.md +382 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/feedback.md +266 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/feedback.zh.md +266 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/filesystem.md +505 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/filesystem.zh.md +505 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/goal.md +277 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/goal.zh.md +277 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/invariants.md +88 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/invariants.zh.md +88 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/jobs.md +290 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/jobs.zh.md +290 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/llm-streaming.md +1080 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/llm-streaming.zh.md +1086 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/lsp.md +202 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/lsp.zh.md +202 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/permission-presets.md +131 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/permission-presets.zh.md +131 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/persistence.md +395 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/persistence.zh.md +395 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/plan.md +87 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/plan.zh.md +87 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/sandbox.md +220 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/sandbox.zh.md +220 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/schedule.md +192 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/schedule.zh.md +192 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/scope.md +59 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/scope.zh.md +59 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-projection.md +354 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-projection.zh.md +354 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-query.md +509 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-query.zh.md +509 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-reference.md +219 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-reference.zh.md +219 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-telemetry.md +194 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-telemetry.zh.md +194 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-title.md +204 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session-title.zh.md +204 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session.md +1155 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/session.zh.md +1159 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/settings.md +405 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/settings.zh.md +405 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/shell.md +303 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/shell.zh.md +303 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/sidebar-right.md +148 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/sidebar-right.zh.md +148 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/skills.md +354 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/skills.zh.md +354 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/slots.md +175 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/slots.zh.md +175 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/spill.md +117 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/spill.zh.md +117 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/storage.md +260 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/storage.zh.md +260 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/subagent.md +766 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/subagent.zh.md +770 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/subprocess.md +324 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/subprocess.zh.md +324 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/system-prompt.md +220 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/system-prompt.zh.md +220 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/terminal.md +184 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/terminal.zh.md +184 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/todo.md +32 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/todo.zh.md +32 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/token-meter.md +105 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/token-meter.zh.md +105 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/tools.md +720 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/tools.zh.md +720 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/typert.md +343 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/typert.zh.md +343 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/user-questions.md +178 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/user-questions.zh.md +178 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/web-client.md +95 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/web-client.zh.md +95 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/web-server.md +154 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/web-server.zh.md +154 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/web.md +206 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/web.zh.md +206 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/webhook.md +70 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/webhook.zh.md +70 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/workflow.md +278 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/workflow.zh.md +278 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/workspace.md +321 -0
- package/node_modules/better-dsh/dsh-docs/subsystems/workspace.zh.md +321 -0
- package/node_modules/better-dsh/dsh-docs/testing.md +54 -0
- package/node_modules/better-dsh/dsh-docs/testing.zh.md +54 -0
- package/node_modules/better-dsh/dsh-docs/tool-catalog.md +2225 -0
- package/node_modules/better-dsh/dsh-docs/tool-catalog.zh.md +2233 -0
- package/node_modules/better-dsh/dsh-docs/tool-execution-pipeline.md +62 -0
- package/node_modules/better-dsh/dsh-docs/tool-execution-pipeline.zh.md +64 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/config.md +106 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/config.zh.md +106 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/index.md +144 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/index.zh.md +144 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/publish.md +183 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/publish.zh.md +183 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/tool.md +52 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/basic/tool.zh.md +52 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/framework/events.md +143 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/framework/events.zh.md +143 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/framework/index.md +137 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/framework/index.zh.md +137 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/framework/service.md +148 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/framework/service.zh.md +150 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/practice/dynamic-cordis.md +15 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/practice/dynamic-cordis.zh.md +15 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/practice/index.md +155 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/practice/index.zh.md +155 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/practice/llm-adapter.md +189 -0
- package/node_modules/better-dsh/dsh-docs/user/develop/practice/llm-adapter.zh.md +189 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/github-review.md +102 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/github-review.zh.md +102 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/index.md +30 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/index.zh.md +30 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/mcp-memory.md +101 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/mcp-memory.zh.md +101 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/network-proxy.md +85 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/network-proxy.zh.md +85 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/providers.md +190 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/providers.zh.md +190 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/python-sdk.md +150 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/python-sdk.zh.md +150 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/schedule.md +21 -0
- package/node_modules/better-dsh/dsh-docs/user/guide/schedule.zh.md +21 -0
- package/node_modules/better-dsh/dsh-docs/user/index.md +11 -0
- package/node_modules/better-dsh/dsh-docs/user/index.zh.md +11 -0
- package/node_modules/better-dsh/dsh-docs/web-styling.md +29 -0
- package/node_modules/better-dsh/dsh-docs/web-styling.zh.md +29 -0
- package/node_modules/better-dsh/eval-description.md +33 -0
- package/node_modules/better-dsh/lib/BrowserWebSocketTransport-D1aFReu_.js +4 -0
- package/node_modules/better-dsh/lib/BrowserWebSocketTransport-wm99h94r.js +39 -0
- package/node_modules/better-dsh/lib/LaunchOptions-B874eBu2.js +4 -0
- package/node_modules/better-dsh/lib/LaunchOptions-kGXcZOe0.js +17 -0
- package/node_modules/better-dsh/lib/NodeWebSocketTransport-BRLjmu2R.js +4 -0
- package/node_modules/better-dsh/lib/NodeWebSocketTransport-D9ysYGLM.js +3634 -0
- package/node_modules/better-dsh/lib/ast-assets/ast-grep.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-bash.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-c.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-cpp.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-css.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-go.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-html.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-javascript.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-json.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-python.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-ruby.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-rust.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-tsx.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-typescript.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter-yaml.wasm +0 -0
- package/node_modules/better-dsh/lib/ast-assets/tree-sitter.wasm +0 -0
- package/node_modules/better-dsh/lib/bidi-BalpKaJc.js +16106 -0
- package/node_modules/better-dsh/lib/bundle-delivery.d.ts +20 -0
- package/node_modules/better-dsh/lib/bundle-delivery.js +209 -0
- package/node_modules/better-dsh/lib/chunk-DQxbwPpZ.js +30 -0
- package/node_modules/better-dsh/lib/client/index.js +712 -0
- package/node_modules/better-dsh/lib/compaction/index.d.ts +57 -0
- package/node_modules/better-dsh/lib/compaction/index.js +145 -0
- package/node_modules/better-dsh/lib/failover/index.d.ts +59 -0
- package/node_modules/better-dsh/lib/failover/index.js +147 -0
- package/node_modules/better-dsh/lib/fs-aware/sandbox-plugin.d.ts +71 -0
- package/node_modules/better-dsh/lib/fs-aware/sandbox-plugin.js +249 -0
- package/node_modules/better-dsh/lib/hmr.d.ts +32 -0
- package/node_modules/better-dsh/lib/hmr.js +49 -0
- package/node_modules/better-dsh/lib/index-DTDXUe6m.d.ts +208 -0
- package/node_modules/better-dsh/lib/index-browser-CysrjMyM.js +21061 -0
- package/node_modules/better-dsh/lib/index.d.ts +705 -0
- package/node_modules/better-dsh/lib/index.js +3816 -0
- package/node_modules/better-dsh/lib/kernel-bridge.py +172 -0
- package/node_modules/better-dsh/lib/kernel-env-D8UOWeji.js +195 -0
- package/node_modules/better-dsh/lib/kernel-env.d.ts +80 -0
- package/node_modules/better-dsh/lib/kernel-env.js +3 -0
- package/node_modules/better-dsh/lib/lib-C8_jXvwA.js +894 -0
- package/node_modules/better-dsh/lib/lsp-server-registry-BXvUTBY7.js +3 -0
- package/node_modules/better-dsh/lib/lsp-server-registry-CH9WNFFt.js +972 -0
- package/node_modules/better-dsh/lib/main-BQeFrBM1.js +3 -0
- package/node_modules/better-dsh/lib/main-CupOhMp5.js +1432 -0
- package/node_modules/better-dsh/lib/mobile/plugin.d.ts +29 -0
- package/node_modules/better-dsh/lib/mobile/plugin.js +54 -0
- package/node_modules/better-dsh/lib/native-capture-BEByDXYM.js +68 -0
- package/node_modules/better-dsh/lib/puppeteer-core-CSk2PtSm.js +1136 -0
- package/node_modules/better-dsh/lib/py-sdk-D7klhpWb.js +178 -0
- package/node_modules/better-dsh/lib/py-sdk.d.ts +19 -0
- package/node_modules/better-dsh/lib/py-sdk.js +3 -0
- package/node_modules/better-dsh/lib/remote/plugin.d.ts +24 -0
- package/node_modules/better-dsh/lib/remote/plugin.js +1190 -0
- package/node_modules/better-dsh/lib/url-schemes/index.d.ts +88 -0
- package/node_modules/better-dsh/lib/url-schemes/index.js +12229 -0
- package/node_modules/better-dsh/lib/util-BHL_dfV_.js +2953 -0
- package/node_modules/better-dsh/lib/wasm_bg-CNXhHjVg.js +1291 -0
- package/node_modules/better-dsh/lib/web-password.d.ts +156 -0
- package/node_modules/better-dsh/lib/web-password.js +562 -0
- package/node_modules/better-dsh/lib/web-tree-sitter-CPrPD4J2.js +3440 -0
- package/node_modules/better-dsh/lib/web-trust-D6gJpUMf.js +393 -0
- package/node_modules/better-dsh/lib/web-trust-Dej_RI2B.d.ts +113 -0
- package/node_modules/better-dsh/lib/web-trust.d.ts +3 -0
- package/node_modules/better-dsh/lib/web-trust.js +4 -0
- package/node_modules/better-dsh/lib/webserver.d.ts +10 -0
- package/node_modules/better-dsh/lib/webserver.js +19 -0
- package/node_modules/better-dsh/lib/welcome-notice.d.ts +18 -0
- package/node_modules/better-dsh/lib/welcome-notice.js +59 -0
- package/node_modules/better-dsh/lib/wrap-T4I0nfPM.js +750 -0
- package/node_modules/better-dsh/package.json +430 -0
- package/node_modules/better-dsh/scripts/kernel-provision.mjs +35 -0
- package/node_modules/better-dsh/scripts/register-bundle.mjs +80 -0
- package/node_modules/better-dsh/url-schemes-instruction.md +22 -0
- package/package.json +21 -6
- package/superd-hub/assets/icons/README.md +20 -0
- package/superd-hub/assets/icons/amp.png +0 -0
- package/superd-hub/assets/icons/ante.png +0 -0
- package/superd-hub/assets/icons/antigravity.png +0 -0
- package/superd-hub/assets/icons/aug.png +0 -0
- package/superd-hub/assets/icons/autohand.png +0 -0
- package/superd-hub/assets/icons/claude.svg +1 -0
- package/superd-hub/assets/icons/cline.png +0 -0
- package/superd-hub/assets/icons/codebuddy.png +0 -0
- package/superd-hub/assets/icons/codebuff.png +0 -0
- package/superd-hub/assets/icons/codex.svg +1 -0
- package/superd-hub/assets/icons/command-code.png +0 -0
- package/superd-hub/assets/icons/continue.png +0 -0
- package/superd-hub/assets/icons/copilot.png +0 -0
- package/superd-hub/assets/icons/crush.png +0 -0
- package/superd-hub/assets/icons/cursor.png +0 -0
- package/superd-hub/assets/icons/devin.png +0 -0
- package/superd-hub/assets/icons/droid.png +0 -0
- package/superd-hub/assets/icons/dsh-fish.svg +3 -0
- package/superd-hub/assets/icons/dsh.png +0 -0
- package/superd-hub/assets/icons/freebuff.png +0 -0
- package/superd-hub/assets/icons/gemini.png +0 -0
- package/superd-hub/assets/icons/goose.png +0 -0
- package/superd-hub/assets/icons/grok.png +0 -0
- package/superd-hub/assets/icons/hermes.png +0 -0
- package/superd-hub/assets/icons/jcode.png +0 -0
- package/superd-hub/assets/icons/kilo.png +0 -0
- package/superd-hub/assets/icons/kimi.png +0 -0
- package/superd-hub/assets/icons/kiro.png +0 -0
- package/superd-hub/assets/icons/manifest.json +20 -0
- package/superd-hub/assets/icons/mimo-code.png +0 -0
- package/superd-hub/assets/icons/mistral-vibe.png +0 -0
- package/superd-hub/assets/icons/muse.png +0 -0
- package/superd-hub/assets/icons/omp.svg +1 -0
- package/superd-hub/assets/icons/openclaude.png +0 -0
- package/superd-hub/assets/icons/openclaw.png +0 -0
- package/superd-hub/assets/icons/opencode.png +0 -0
- package/superd-hub/assets/icons/pi.svg +1 -0
- package/superd-hub/assets/icons/prime-agent.png +0 -0
- package/superd-hub/assets/icons/qoder-cn.png +0 -0
- package/superd-hub/assets/icons/qoder.png +0 -0
- package/superd-hub/assets/icons/qwen-code.png +0 -0
- package/superd-hub/assets/icons/rovo.png +0 -0
- package/superd-hub/assets/icons/trae.png +0 -0
- package/superd-hub/assets/icons/zcode.png +0 -0
- package/superd-hub/dist/a2a-row.js +1 -1
- package/superd-hub/dist/agent-roster.d.ts +8 -3
- package/superd-hub/dist/agent-roster.js +19 -7
- package/superd-hub/dist/bundle-setup.d.ts +2 -0
- package/superd-hub/dist/bundle-setup.js +2 -0
- package/superd-hub/dist/client/icon-assets.gen.d.ts +16 -0
- package/superd-hub/dist/client/icon-assets.gen.js +54 -0
- package/superd-hub/dist/client/mesh-api.d.ts +28 -17
- package/superd-hub/dist/client/mesh-api.js +44 -31
- package/superd-hub/dist/client/mesh-groups.d.ts +13 -11
- package/superd-hub/dist/client/mesh-groups.js +30 -10
- package/superd-hub/dist/client/roster.d.ts +7 -16
- package/superd-hub/dist/client/roster.js +5 -3
- package/superd-hub/dist/fusion-transport.d.ts +11 -10
- package/superd-hub/dist/fusion-transport.js +21 -11
- package/superd-hub/dist/index.d.ts +0 -1
- package/superd-hub/dist/index.js +0 -1
- package/superd-hub/dist/machines.d.ts +6 -3
- package/superd-hub/dist/machines.js +15 -3
- package/superd-hub/dist/mesh-federate.d.ts +18 -8
- package/superd-hub/dist/mesh-federate.js +14 -9
- package/superd-hub/dist/mesh-link.d.ts +37 -3
- package/superd-hub/dist/mesh-link.js +49 -4
- package/superd-hub/dist/mesh-rpc.d.ts +48 -16
- package/superd-hub/dist/mesh-rpc.js +139 -14
- package/superd-hub/dist/roster.d.ts +20 -0
- package/superd-hub/dist/roster.js +24 -0
- package/superd-hub/dist/switch-core.d.ts +3 -2
- package/superd-hub/dist/switch-core.js +1 -1
- package/superd-hub/dist/world-entry.d.ts +3 -0
- package/superd-hub/dist/world-entry.js +4 -1
- package/superd-hub/dist/world-host.d.ts +5 -2
- package/superd-hub/dist/world-host.js +6 -2
- package/superd-hub/dist/world-join.d.ts +1 -1
- package/superd-hub/dist/world-join.js +14 -32
- package/superd-hub/dist/world-lifecycle.d.ts +20 -0
- package/superd-hub/dist/world-lifecycle.js +69 -0
- package/superd-hub/dist/world-mount.d.ts +1 -1
- package/superd-hub/dist/world-mount.js +2 -2
- package/superd-hub/lib/client/index.js +434 -307
- package/superd-hub/package.json +3 -2
- package/superd-hub/dist/connection-trust.d.ts +0 -8
- package/superd-hub/dist/connection-trust.js +0 -16
- package/superd-hub/dist/web-trust.d.ts +0 -84
- package/superd-hub/dist/web-trust.js +0 -101
|
@@ -0,0 +1,3816 @@
|
|
|
1
|
+
import { t as Schema } from "./lib-C8_jXvwA.js";
|
|
2
|
+
import { a as RESERVED_ERROR_MEMBERS, i as RESERVED_BINDING_GLOBALS, n as DUNDER_MEMBER, o as ReplRuntime, r as PORTABLE_RESERVED_WORDS, t as isFlatBindableName } from "./py-sdk-D7klhpWb.js";
|
|
3
|
+
import { s as resolveKernelEnv } from "./kernel-env-D8UOWeji.js";
|
|
4
|
+
import { t as captureAllTools } from "./native-capture-BEByDXYM.js";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
11
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
|
+
import { createInterface } from "node:readline";
|
|
15
|
+
import { TOOL_RUNTIME_SCHEDULER, defineTool } from "@deepseek-ai/dsh-tools";
|
|
16
|
+
import * as dshLlm from "@deepseek-ai/dsh-llm";
|
|
17
|
+
import { BlockAssembler, HarnessError, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
18
|
+
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
19
|
+
|
|
20
|
+
//#region src/snapshot-json.ts
|
|
21
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
22
|
+
function hasIntrinsicConstructor(prototype, name$1) {
|
|
23
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
24
|
+
if (typeof constructor !== "function") return false;
|
|
25
|
+
try {
|
|
26
|
+
return constructor.name === name$1 && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name$1}() { [native code] }`;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
32
|
+
function isIntrinsicObjectPrototype(value) {
|
|
33
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
34
|
+
}
|
|
35
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
36
|
+
function hasPlainArrayPrototype(value) {
|
|
37
|
+
const prototype = Object.getPrototypeOf(value);
|
|
38
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
39
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
40
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
41
|
+
}
|
|
42
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
43
|
+
function hasPlainObjectPrototype(value) {
|
|
44
|
+
const prototype = Object.getPrototypeOf(value);
|
|
45
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
46
|
+
}
|
|
47
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
48
|
+
function enumerableStringKeys(value) {
|
|
49
|
+
const keys = Reflect.ownKeys(value);
|
|
50
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
51
|
+
return keys;
|
|
52
|
+
}
|
|
53
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
54
|
+
function walkJsonValue(value, detach) {
|
|
55
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
56
|
+
let root;
|
|
57
|
+
const assign = (destination, item) => {
|
|
58
|
+
if (destination === void 0) return;
|
|
59
|
+
if (destination.kind === "root") root = item;
|
|
60
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
61
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
62
|
+
value: item,
|
|
63
|
+
enumerable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
writable: true
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const tasks = [{
|
|
69
|
+
kind: "visit",
|
|
70
|
+
value,
|
|
71
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
72
|
+
}];
|
|
73
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
74
|
+
if (task.kind === "leave") {
|
|
75
|
+
ancestors.delete(task.source);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (task.kind === "array-item") {
|
|
79
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
80
|
+
tasks.push({
|
|
81
|
+
kind: "visit",
|
|
82
|
+
value: task.source[task.index],
|
|
83
|
+
...task.target === void 0 ? {} : { destination: {
|
|
84
|
+
kind: "array",
|
|
85
|
+
target: task.target,
|
|
86
|
+
index: task.index
|
|
87
|
+
} }
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (task.kind === "object-property") {
|
|
92
|
+
tasks.push({
|
|
93
|
+
kind: "visit",
|
|
94
|
+
value: task.source[task.key],
|
|
95
|
+
...task.target === void 0 ? {} : { destination: {
|
|
96
|
+
kind: "object",
|
|
97
|
+
target: task.target,
|
|
98
|
+
key: task.key
|
|
99
|
+
} }
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const current = task.value;
|
|
104
|
+
if (current === null) {
|
|
105
|
+
assign(task.destination, null);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
109
|
+
assign(task.destination, current);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (typeof current === "number") {
|
|
113
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
114
|
+
assign(task.destination, current);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (typeof current !== "object") return void 0;
|
|
118
|
+
if (ancestors.has(current)) return void 0;
|
|
119
|
+
if (Array.isArray(current)) {
|
|
120
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
121
|
+
const length = current.length;
|
|
122
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
123
|
+
const target$1 = detach ? [] : void 0;
|
|
124
|
+
if (target$1 !== void 0) assign(task.destination, target$1);
|
|
125
|
+
ancestors.add(current);
|
|
126
|
+
tasks.push({
|
|
127
|
+
kind: "leave",
|
|
128
|
+
source: current
|
|
129
|
+
});
|
|
130
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
131
|
+
kind: "array-item",
|
|
132
|
+
source: current,
|
|
133
|
+
index,
|
|
134
|
+
...target$1 === void 0 ? {} : { target: target$1 }
|
|
135
|
+
});
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
139
|
+
const keys = enumerableStringKeys(current);
|
|
140
|
+
if (keys === void 0) return void 0;
|
|
141
|
+
const target = detach ? {} : void 0;
|
|
142
|
+
if (target !== void 0) assign(task.destination, target);
|
|
143
|
+
ancestors.add(current);
|
|
144
|
+
tasks.push({
|
|
145
|
+
kind: "leave",
|
|
146
|
+
source: current
|
|
147
|
+
});
|
|
148
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
149
|
+
const key = keys[index];
|
|
150
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
151
|
+
if (key === void 0) return void 0;
|
|
152
|
+
tasks.push({
|
|
153
|
+
kind: "object-property",
|
|
154
|
+
source: current,
|
|
155
|
+
key,
|
|
156
|
+
...target === void 0 ? {} : { target }
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return detach ? root : true;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Validate and detach lossless JSON in one read per property, so a stateful
|
|
164
|
+
* getter cannot change between validation and copying. Traversal is iterative,
|
|
165
|
+
* so valid nesting is bounded by available memory rather than the JavaScript
|
|
166
|
+
* call stack. Accepts ordinary arrays, plain or null-prototype objects, and JSON
|
|
167
|
+
* scalars; rejects sparse, cyclic, exotic, negative-zero, and non-finite values.
|
|
168
|
+
* Getter throws propagate.
|
|
169
|
+
*
|
|
170
|
+
* @param value - the candidate value to validate and detach.
|
|
171
|
+
* @returns the detached snapshot, or `undefined` when the value is not
|
|
172
|
+
* losslessly JSON-serializable.
|
|
173
|
+
*/
|
|
174
|
+
function snapshotJsonValue(value) {
|
|
175
|
+
return walkJsonValue(value, true);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/kernel-transport.ts
|
|
180
|
+
/** The bridge script shipped beside the built entry (`lib/kernel-bridge.py`). */
|
|
181
|
+
const BRIDGE_SCRIPT = fileURLToPath(new URL("./kernel-bridge.py", import.meta.url));
|
|
182
|
+
/**
|
|
183
|
+
* One bridge subprocess speaking JSON lines. Lifecycle is owned by the caller
|
|
184
|
+
* (the kernel bridge registers it as an `ctx.effect`); this type installs no
|
|
185
|
+
* process-level signal handlers.
|
|
186
|
+
*/
|
|
187
|
+
var KernelTransport = class {
|
|
188
|
+
child;
|
|
189
|
+
reader;
|
|
190
|
+
startup = false;
|
|
191
|
+
closed = false;
|
|
192
|
+
readySettled = false;
|
|
193
|
+
readyResolve;
|
|
194
|
+
readyReject;
|
|
195
|
+
bridgeStderr = "";
|
|
196
|
+
handlers = /* @__PURE__ */ new Set();
|
|
197
|
+
waiters = /* @__PURE__ */ new Set();
|
|
198
|
+
constructor(options) {
|
|
199
|
+
this.options = options;
|
|
200
|
+
}
|
|
201
|
+
/** Spawn the bridge and resolve once it reports the channels are up. */
|
|
202
|
+
async start() {
|
|
203
|
+
if (this.reader !== void 0) return;
|
|
204
|
+
if (this.closed) throw new Error("kernel transport is closed");
|
|
205
|
+
const child = spawn(this.options.python, [BRIDGE_SCRIPT, this.options.connectionFile], {
|
|
206
|
+
...this.options.cwd === void 0 ? {} : { cwd: this.options.cwd },
|
|
207
|
+
env: {
|
|
208
|
+
PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
209
|
+
LANG: "C.UTF-8",
|
|
210
|
+
HOME: process.env.HOME ?? tmpdir(),
|
|
211
|
+
PYTHONUNBUFFERED: "1"
|
|
212
|
+
},
|
|
213
|
+
stdio: [
|
|
214
|
+
"pipe",
|
|
215
|
+
"pipe",
|
|
216
|
+
"pipe"
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
this.child = child;
|
|
220
|
+
this.startup = true;
|
|
221
|
+
const ready = new Promise((resolve$1, reject) => {
|
|
222
|
+
this.readyResolve = resolve$1;
|
|
223
|
+
this.readyReject = reject;
|
|
224
|
+
});
|
|
225
|
+
const timer = setTimeout(() => {
|
|
226
|
+
this.failReady(/* @__PURE__ */ new Error(`kernel bridge did not report ready within ${this.options.readyTimeoutMs}ms; stderr tail:\n${this.stderrTail()}`));
|
|
227
|
+
}, this.options.readyTimeoutMs);
|
|
228
|
+
timer.unref?.();
|
|
229
|
+
child.stderr?.on("data", (buffer) => {
|
|
230
|
+
this.bridgeStderr += buffer.toString();
|
|
231
|
+
});
|
|
232
|
+
child.stdin?.on("error", (error) => {
|
|
233
|
+
this.bridgeStderr += `stdin error: ${error.code ?? error.message}\n`;
|
|
234
|
+
this.closed = true;
|
|
235
|
+
this.failWaiters();
|
|
236
|
+
});
|
|
237
|
+
child.stdout?.on("error", (error) => {
|
|
238
|
+
this.bridgeStderr += `stdout error: ${error.code ?? error.message}\n`;
|
|
239
|
+
});
|
|
240
|
+
child.on("error", (error) => {
|
|
241
|
+
this.bridgeStderr += `spawn error: ${error.message}\n`;
|
|
242
|
+
this.failReady(/* @__PURE__ */ new Error(`kernel bridge failed to spawn: ${error.message}`));
|
|
243
|
+
this.failWaiters();
|
|
244
|
+
this.failExit();
|
|
245
|
+
});
|
|
246
|
+
child.on("exit", (code, signal) => {
|
|
247
|
+
const detail = `kernel bridge exited (code=${String(code)} signal=${String(signal)})`;
|
|
248
|
+
this.bridgeStderr += `${detail}\n`;
|
|
249
|
+
this.closed = true;
|
|
250
|
+
this.failReady(/* @__PURE__ */ new Error(`${detail}; stderr tail:\n${this.stderrTail()}`));
|
|
251
|
+
this.failWaiters();
|
|
252
|
+
this.failExit();
|
|
253
|
+
});
|
|
254
|
+
if (child.stdout) {
|
|
255
|
+
this.reader = createInterface({ input: child.stdout });
|
|
256
|
+
this.reader.on("line", (line) => {
|
|
257
|
+
this.accept(line);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
await ready;
|
|
262
|
+
} finally {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
this.startup = false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** Whether the bridge process is still live. */
|
|
268
|
+
get alive() {
|
|
269
|
+
return this.child !== void 0 && this.child.exitCode === null && !this.closed;
|
|
270
|
+
}
|
|
271
|
+
/** The bridge's stderr tail, for startup and crash diagnostics. */
|
|
272
|
+
stderrTail() {
|
|
273
|
+
return this.bridgeStderr.slice(-1024) || "(empty)";
|
|
274
|
+
}
|
|
275
|
+
/** Register a message handler; the returned function removes it. */
|
|
276
|
+
onMessage(handler) {
|
|
277
|
+
this.handlers.add(handler);
|
|
278
|
+
return () => {
|
|
279
|
+
this.handlers.delete(handler);
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Register a bridge-exit handler; the returned function removes it. A bridge
|
|
284
|
+
* that dies mid-cell must settle the active execution at once rather than at
|
|
285
|
+
* the cell budget.
|
|
286
|
+
*/
|
|
287
|
+
onExit(handler) {
|
|
288
|
+
this.exitHandlers.add(handler);
|
|
289
|
+
return () => {
|
|
290
|
+
this.exitHandlers.delete(handler);
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
exitHandlers = /* @__PURE__ */ new Set();
|
|
294
|
+
/** Notify exit handlers once per bridge process end. */
|
|
295
|
+
failExit() {
|
|
296
|
+
const handlers = [...this.exitHandlers];
|
|
297
|
+
this.exitHandlers.clear();
|
|
298
|
+
for (const handler of handlers) handler();
|
|
299
|
+
}
|
|
300
|
+
/** Send one message on the shell or control channel. */
|
|
301
|
+
send(channel, message) {
|
|
302
|
+
this.writeLine({
|
|
303
|
+
op: "send",
|
|
304
|
+
channel,
|
|
305
|
+
header: message.header,
|
|
306
|
+
content: message.content
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/** Ask the kernel to interrupt the running cell (control channel). */
|
|
310
|
+
interrupt() {
|
|
311
|
+
this.writeLine({ op: "interrupt" });
|
|
312
|
+
}
|
|
313
|
+
/** Write one protocol line, refusing instead of throwing when the pipe is gone. */
|
|
314
|
+
writeLine(payload) {
|
|
315
|
+
const stdin = this.child?.stdin;
|
|
316
|
+
if (stdin === null || stdin === void 0 || this.closed || !stdin.writable) throw new Error("kernel bridge is not connected");
|
|
317
|
+
stdin.write(`${JSON.stringify(payload)}\n`);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Wait for the next message on `channel` matching `match`.
|
|
321
|
+
* @returns The message, or null when the budget expires or the bridge dies.
|
|
322
|
+
*/
|
|
323
|
+
waitFor(channel, match, timeoutMs) {
|
|
324
|
+
if (this.closed) return Promise.resolve(null);
|
|
325
|
+
return new Promise((resolve$1) => {
|
|
326
|
+
const waiter = {
|
|
327
|
+
channel,
|
|
328
|
+
match,
|
|
329
|
+
settle: (message) => {
|
|
330
|
+
clearTimeout(waiter.timer);
|
|
331
|
+
this.waiters.delete(waiter);
|
|
332
|
+
resolve$1(message);
|
|
333
|
+
},
|
|
334
|
+
timer: setTimeout(() => {
|
|
335
|
+
this.waiters.delete(waiter);
|
|
336
|
+
resolve$1(null);
|
|
337
|
+
}, timeoutMs)
|
|
338
|
+
};
|
|
339
|
+
waiter.timer.unref?.();
|
|
340
|
+
this.waiters.add(waiter);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/** Stop the bridge (graceful shutdown note, then kill). */
|
|
344
|
+
async dispose() {
|
|
345
|
+
const child = this.child;
|
|
346
|
+
this.closed = true;
|
|
347
|
+
this.failWaiters();
|
|
348
|
+
this.reader?.close();
|
|
349
|
+
this.reader = void 0;
|
|
350
|
+
if (child === void 0) return;
|
|
351
|
+
try {
|
|
352
|
+
if (child.stdin?.writable === true) child.stdin.write(`${JSON.stringify({ op: "shutdown" })}\n`);
|
|
353
|
+
} catch {}
|
|
354
|
+
try {
|
|
355
|
+
child.stdin?.end();
|
|
356
|
+
} catch {}
|
|
357
|
+
if (child.exitCode !== null) return;
|
|
358
|
+
await new Promise((resolve$1) => {
|
|
359
|
+
const timer = setTimeout(() => {
|
|
360
|
+
try {
|
|
361
|
+
child.kill("SIGKILL");
|
|
362
|
+
} catch {}
|
|
363
|
+
resolve$1();
|
|
364
|
+
}, 500);
|
|
365
|
+
timer.unref?.();
|
|
366
|
+
child.once("exit", () => {
|
|
367
|
+
clearTimeout(timer);
|
|
368
|
+
resolve$1();
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
failReady(error) {
|
|
373
|
+
if (this.readySettled) return;
|
|
374
|
+
this.readySettled = true;
|
|
375
|
+
this.readyReject?.(error);
|
|
376
|
+
}
|
|
377
|
+
failWaiters() {
|
|
378
|
+
for (const waiter of [...this.waiters]) waiter.settle(null);
|
|
379
|
+
}
|
|
380
|
+
/** Route one decoded line; unknown events are ignored. */
|
|
381
|
+
accept(line) {
|
|
382
|
+
let event;
|
|
383
|
+
try {
|
|
384
|
+
event = JSON.parse(line);
|
|
385
|
+
} catch {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (event.ev === "ready") {
|
|
389
|
+
if (!this.readySettled) {
|
|
390
|
+
this.readySettled = true;
|
|
391
|
+
this.readyResolve?.();
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (event.ev === "msg") {
|
|
396
|
+
const channel = event.channel;
|
|
397
|
+
const raw = event.msg;
|
|
398
|
+
if (typeof channel !== "string" || raw === void 0 || typeof raw.header !== "object" || raw.header === null) return;
|
|
399
|
+
const message = {
|
|
400
|
+
header: raw.header,
|
|
401
|
+
parent_header: raw.parent_header ?? {},
|
|
402
|
+
content: raw.content ?? {}
|
|
403
|
+
};
|
|
404
|
+
for (const waiter of [...this.waiters]) if (waiter.channel === channel && waiter.match(message)) waiter.settle(message);
|
|
405
|
+
for (const handler of this.handlers) handler(channel, message);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (event.ev === "fatal") this.bridgeStderr += `fatal: ${String(event.message ?? "unknown")}\n`;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/bootstrap.ts
|
|
414
|
+
/**
|
|
415
|
+
* Python-side bootstrap executed once as an internal cell right after kernel
|
|
416
|
+
* startup. Seeds the user namespace with the host-request comm bridge
|
|
417
|
+
* (`dashr.host` target, the PA `host_request()` pattern), the binding-proxy
|
|
418
|
+
* factories, the typed rejection classes, and the lossless-JSON completion
|
|
419
|
+
* encoder. Names are `_dashr`/`__dashr` prefixed (any case) so they are
|
|
420
|
+
* excluded from namespace snapshots and never leak into user state.
|
|
421
|
+
* @module dashr/bootstrap
|
|
422
|
+
*/
|
|
423
|
+
/** Comm target the kernel-side shim opens for typed host requests. */
|
|
424
|
+
const HOST_COMM_TARGET = "dashr.host";
|
|
425
|
+
/**
|
|
426
|
+
* The bootstrap source. Kept dependency-free apart from `ast`, `asyncio`,
|
|
427
|
+
* `json`, and `comm` (present by construction — it IS the kernel).
|
|
428
|
+
*/
|
|
429
|
+
const KERNEL_BOOTSTRAP = `
|
|
430
|
+
import ast as _dashr_ast
|
|
431
|
+
import json as _dashr_json
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _dashr_install_control_comm_handlers():
|
|
435
|
+
# Host replies arrive as comm_msg on the CONTROL channel while the shell
|
|
436
|
+
# channel is busy executing our cell; teach the kernel to route them.
|
|
437
|
+
try:
|
|
438
|
+
from IPython import get_ipython
|
|
439
|
+
except Exception:
|
|
440
|
+
return
|
|
441
|
+
shell = get_ipython()
|
|
442
|
+
kernel = getattr(shell, 'kernel', None)
|
|
443
|
+
comm_manager = getattr(kernel, 'comm_manager', None)
|
|
444
|
+
control_handlers = getattr(kernel, 'control_handlers', None)
|
|
445
|
+
if comm_manager is None or not isinstance(control_handlers, dict):
|
|
446
|
+
return
|
|
447
|
+
control_handlers.setdefault('comm_msg', comm_manager.comm_msg)
|
|
448
|
+
control_handlers.setdefault('comm_close', comm_manager.comm_close)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _dashr_install_interrupt():
|
|
452
|
+
# ipykernel's async-cell SIGINT handling only schedules a callback on the
|
|
453
|
+
# kernel event loop, and a non-yielding cell ('while True: pass') blocks
|
|
454
|
+
# that very loop, so the control-channel interrupt can never break it.
|
|
455
|
+
# SIGALRM is untouched by ipykernel: raising KeyboardInterrupt from its
|
|
456
|
+
# handler breaks any cell, and the host sends it (after a confirm window,
|
|
457
|
+
# see the bridge) on timeout/abort. Best effort — where SIGALRM is
|
|
458
|
+
# unavailable the control-channel interrupt is all there is.
|
|
459
|
+
#
|
|
460
|
+
# The busy guard is load-bearing (M3-A, blueprint §5 "SIGALRM-at-idle 双重
|
|
461
|
+
# 窗口"): a KeyboardInterrupt raised OUTSIDE cell execution — while the
|
|
462
|
+
# kernel boots, idles between cells, or unwinds a finished one — escapes
|
|
463
|
+
# the shell's containment and terminates the process cleanly, which used
|
|
464
|
+
# to kill the kernel deterministically (10/10 same-tick aborts, 40/40
|
|
465
|
+
# during cold boot; dev/m2a-report.md §5.1). The handler therefore only
|
|
466
|
+
# raises while a dashr cell is actually executing, tracked by the
|
|
467
|
+
# module-level _dashr_state dict below and set/cleared by the run/snapshot
|
|
468
|
+
# cell scaffolds around their whole body. A stray SIGALRM outside that
|
|
469
|
+
# window is swallowed — the host's force-settle already resolves the run,
|
|
470
|
+
# and the kernel lives. The dict (not a bare flag) is captured by the
|
|
471
|
+
# handler closure, so user code rebinding the NAME cannot disarm the
|
|
472
|
+
# guard; mutating the dict itself stays possible and accepted — this shim
|
|
473
|
+
# is a capability seam, not a security boundary.
|
|
474
|
+
try:
|
|
475
|
+
import signal as _dashr_signal
|
|
476
|
+
|
|
477
|
+
def _dashr_on_sigalrm(_signum, _frame):
|
|
478
|
+
if not _dashr_state['executing']:
|
|
479
|
+
return
|
|
480
|
+
raise KeyboardInterrupt
|
|
481
|
+
|
|
482
|
+
_dashr_signal.signal(_dashr_signal.SIGALRM, _dashr_on_sigalrm)
|
|
483
|
+
except (ImportError, AttributeError, OSError, ValueError):
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# The dashr busy-guard flag (see _dashr_install_interrupt): True for the whole
|
|
488
|
+
# duration of a run or snapshot cell — the only window in which a SIGALRM may
|
|
489
|
+
# raise KeyboardInterrupt. Lives at module (user-namespace) level so the cell
|
|
490
|
+
# scaffolds can flip it; excluded from snapshots by the _dashr prefix rule.
|
|
491
|
+
_dashr_state = {'executing': False}
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
_dashr_install_interrupt()
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class _DashrRejected(Exception):
|
|
498
|
+
def __init__(self, message):
|
|
499
|
+
Exception.__init__(self, message)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _dashr_host_request(payload):
|
|
503
|
+
from comm import create_comm
|
|
504
|
+
import asyncio
|
|
505
|
+
_dashr_install_control_comm_handlers()
|
|
506
|
+
loop = asyncio.get_running_loop()
|
|
507
|
+
future = loop.create_future()
|
|
508
|
+
# primary=True publishes comm_open so the host learns this comm_id; the
|
|
509
|
+
# request itself travels as the first comm_msg.
|
|
510
|
+
comm = create_comm(target_name=${JSON.stringify(HOST_COMM_TARGET)})
|
|
511
|
+
|
|
512
|
+
def _resolve(action):
|
|
513
|
+
def _apply():
|
|
514
|
+
if future.done():
|
|
515
|
+
return
|
|
516
|
+
action()
|
|
517
|
+
comm.close()
|
|
518
|
+
# Replies arrive on the kernel's control channel, which may run off
|
|
519
|
+
# the event-loop thread; only call_soon_threadsafe may touch the
|
|
520
|
+
# future from there.
|
|
521
|
+
loop.call_soon_threadsafe(_apply)
|
|
522
|
+
|
|
523
|
+
def _on_msg(msg):
|
|
524
|
+
content = msg.get('content', {})
|
|
525
|
+
reply = content.get('data', {}) if isinstance(content, dict) else {}
|
|
526
|
+
if not isinstance(reply, dict) or future.done():
|
|
527
|
+
return
|
|
528
|
+
status = reply.get('status')
|
|
529
|
+
if status == 'ok':
|
|
530
|
+
_resolve(lambda: future.set_result(reply.get('result')))
|
|
531
|
+
elif status == 'error':
|
|
532
|
+
_resolve(lambda: future.set_exception(_DashrRejected(reply.get('error') or 'host request failed')))
|
|
533
|
+
|
|
534
|
+
comm.on_msg(_on_msg)
|
|
535
|
+
comm.send(dict(payload))
|
|
536
|
+
return future
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _dashr_make_error_class(name, member_property):
|
|
540
|
+
def _init(self, message, member=None):
|
|
541
|
+
Exception.__init__(self, message)
|
|
542
|
+
if member is not None:
|
|
543
|
+
setattr(self, member_property, member)
|
|
544
|
+
return type(name, (Exception,), {'__init__': _init})
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _dashr_make_proxy(global_name, function_name, error_class_name, member_property):
|
|
548
|
+
async def _dashr_proxy(*args, **kwargs):
|
|
549
|
+
if kwargs:
|
|
550
|
+
raise TypeError('dashr binding calls do not accept keyword arguments')
|
|
551
|
+
if len(args) == 1:
|
|
552
|
+
call_args = args[0]
|
|
553
|
+
elif len(args) == 0:
|
|
554
|
+
call_args = None
|
|
555
|
+
else:
|
|
556
|
+
call_args = list(args)
|
|
557
|
+
payload = {
|
|
558
|
+
'type': 'binding.call',
|
|
559
|
+
'global': global_name,
|
|
560
|
+
'name': function_name,
|
|
561
|
+
'args': call_args,
|
|
562
|
+
}
|
|
563
|
+
try:
|
|
564
|
+
return await _dashr_host_request(payload)
|
|
565
|
+
except _DashrRejected as rejected:
|
|
566
|
+
cls = globals().get(error_class_name) if error_class_name else None
|
|
567
|
+
if cls is None:
|
|
568
|
+
raise
|
|
569
|
+
raise cls(str(rejected), function_name) from None
|
|
570
|
+
return _dashr_proxy
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _dashr_make_holder(global_name, error_class_name, member_property, members):
|
|
574
|
+
# Attribute misses travel to the host instead of raising AttributeError
|
|
575
|
+
# here: the host owns the namespace, so IT rejects an unknown member as an
|
|
576
|
+
# unknown binding rather than the kernel inventing a local error class.
|
|
577
|
+
class _DashrBindingHolder:
|
|
578
|
+
def __dir__(self):
|
|
579
|
+
# F9 (v0.2.0-a): introspection tells the truth — dir(tool) lists
|
|
580
|
+
# exactly the bound names. A custom __dir__ fully replaces the
|
|
581
|
+
# default object.__dir__, so no dunder noise leaks in.
|
|
582
|
+
return list(members)
|
|
583
|
+
|
|
584
|
+
def __getattr__(self, function_name):
|
|
585
|
+
return _dashr_make_proxy(
|
|
586
|
+
global_name,
|
|
587
|
+
function_name,
|
|
588
|
+
error_class_name,
|
|
589
|
+
member_property,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
return _DashrBindingHolder()
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _dashr_make_callable(global_name, function_name, error_class_name, member_property):
|
|
596
|
+
# A BARE callable global (the flat per-tool bindings AND the bridge
|
|
597
|
+
# tools: 'callable: true' on the namespace) instead of an object
|
|
598
|
+
# holder. The one-object form is the single convention: every callable
|
|
599
|
+
# takes exactly one positional arguments object (e.g.
|
|
600
|
+
# agent_message({'receiver': 'child', ...})), and keyword / multi-positional forms are
|
|
601
|
+
# rejected HERE, at the Python call boundary, so the introspectable
|
|
602
|
+
# signature tells the truth and a TypeError names the binding global
|
|
603
|
+
# (read(...) got an unexpected keyword argument ...). The wire envelope
|
|
604
|
+
# keeps the historical {'args': [...], 'kwargs': {}} shape so the host's
|
|
605
|
+
# parseReplCall / flatToolArgs / flatBridgeToolArgs validation layers stay
|
|
606
|
+
# authoritative and unchanged. The single functions-entry name is
|
|
607
|
+
# transport-only: the program never sees it, and a typed rejection
|
|
608
|
+
# carries the GLOBAL name as its member (for a flat tool binding the
|
|
609
|
+
# global IS the tool name the program knows). __name__/__qualname__ are
|
|
610
|
+
# pinned to the global name so introspection shows the binding's name,
|
|
611
|
+
# not the factory-local _dashr_callable.
|
|
612
|
+
async def _dashr_callable(args=None, /):
|
|
613
|
+
payload = {
|
|
614
|
+
'type': 'binding.call',
|
|
615
|
+
'global': global_name,
|
|
616
|
+
'name': function_name,
|
|
617
|
+
'args': {'args': [] if args is None else [args], 'kwargs': {}},
|
|
618
|
+
}
|
|
619
|
+
try:
|
|
620
|
+
return await _dashr_host_request(payload)
|
|
621
|
+
except _DashrRejected as rejected:
|
|
622
|
+
cls = globals().get(error_class_name) if error_class_name else None
|
|
623
|
+
if cls is None:
|
|
624
|
+
raise
|
|
625
|
+
raise cls(str(rejected), global_name) from None
|
|
626
|
+
|
|
627
|
+
_dashr_callable.__name__ = global_name
|
|
628
|
+
_dashr_callable.__qualname__ = global_name
|
|
629
|
+
return _dashr_callable
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class _DashrNoValue:
|
|
633
|
+
# Marks 'no completion value': a cell whose last statement is not an
|
|
634
|
+
# expression, or whose last expression evaluates to None — the REPL
|
|
635
|
+
# displayhook suppresses None (IPython behavior), so no value crosses.
|
|
636
|
+
__slots__ = ()
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
_DASHR_NO_VALUE = _DashrNoValue()
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
class _DashrTopLevelReturnChecker(_dashr_ast.NodeVisitor):
|
|
643
|
+
# A cell runs at module scope, like any native IPython cell: a top-level
|
|
644
|
+
# 'return'/'yield' is a SyntaxError decided by the kernel itself, exactly
|
|
645
|
+
# as in a real REPL. Nested scopes (functions, lambdas, classes) keep
|
|
646
|
+
# their own return semantics.
|
|
647
|
+
def visit_FunctionDef(self, node):
|
|
648
|
+
return
|
|
649
|
+
|
|
650
|
+
def visit_AsyncFunctionDef(self, node):
|
|
651
|
+
return
|
|
652
|
+
|
|
653
|
+
def visit_ClassDef(self, node):
|
|
654
|
+
return
|
|
655
|
+
|
|
656
|
+
def visit_Lambda(self, node):
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
def visit_Return(self, node):
|
|
660
|
+
raise SyntaxError("'return' outside function")
|
|
661
|
+
|
|
662
|
+
def visit_Yield(self, node):
|
|
663
|
+
raise SyntaxError("'yield' outside function")
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
async def _dashr_run_program(source):
|
|
667
|
+
# Run one cell with the user namespace as BOTH globals and locals —
|
|
668
|
+
# module-scope REPL semantics: state persists across cells, top-level
|
|
669
|
+
# 'await' is legal (PyCF_ALLOW_TOP_LEVEL_AWAIT, the same mechanism IPython
|
|
670
|
+
# uses), a top-level 'return'/'yield' is a SyntaxError decided here by the
|
|
671
|
+
# kernel — exactly as in a native IPython cell — and the last expression's
|
|
672
|
+
# value is the cell's result (the REPL displayhook handoff).
|
|
673
|
+
import inspect as _dashr_inspect
|
|
674
|
+
user_ns = globals()
|
|
675
|
+
if not source.strip():
|
|
676
|
+
source = 'pass'
|
|
677
|
+
parsed = _dashr_ast.parse(source, '<dashr program>', 'exec')
|
|
678
|
+
_DashrTopLevelReturnChecker().visit(parsed)
|
|
679
|
+
body = parsed.body
|
|
680
|
+
if body and isinstance(body[-1], _dashr_ast.Expr):
|
|
681
|
+
# Capture the last expression the way a REPL's displayhook shows it:
|
|
682
|
+
# its value feeds the completion envelope instead of being discarded.
|
|
683
|
+
# The name is distinct from the scaffold's __dashr_completion__ (the
|
|
684
|
+
# envelope holder) so a statement-ending cell can never pop a stale
|
|
685
|
+
# envelope left by an earlier run.
|
|
686
|
+
last = body[-1]
|
|
687
|
+
body[-1] = _dashr_ast.Assign(
|
|
688
|
+
targets=[_dashr_ast.Name(id='__dashr_cell_value__', ctx=_dashr_ast.Store())],
|
|
689
|
+
value=last.value,
|
|
690
|
+
)
|
|
691
|
+
_dashr_ast.fix_missing_locations(body[-1])
|
|
692
|
+
code = compile(
|
|
693
|
+
parsed,
|
|
694
|
+
'<dashr program>',
|
|
695
|
+
'exec',
|
|
696
|
+
flags=_dashr_ast.PyCF_ALLOW_TOP_LEVEL_AWAIT,
|
|
697
|
+
)
|
|
698
|
+
# IPython's own run_code pattern: with PyCF_ALLOW_TOP_LEVEL_AWAIT, plain
|
|
699
|
+
# exec() DISCARDS the module coroutine silently; eval() under await
|
|
700
|
+
# returns it and runs the code (IPython InteractiveShell.run_code does
|
|
701
|
+
# exactly 'await eval(code_obj, user_global_ns, user_ns)').
|
|
702
|
+
result = eval(code, user_ns, user_ns)
|
|
703
|
+
if _dashr_inspect.iscoroutine(result):
|
|
704
|
+
await result
|
|
705
|
+
value = user_ns.pop('__dashr_cell_value__', _DASHR_NO_VALUE)
|
|
706
|
+
if value is None:
|
|
707
|
+
return _DASHR_NO_VALUE
|
|
708
|
+
return value
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _dashr_install_bindings(spec_json):
|
|
712
|
+
spec = _dashr_json.loads(spec_json)
|
|
713
|
+
user_ns = globals()
|
|
714
|
+
injected = user_ns.get('__dashr_injected__', {})
|
|
715
|
+
for old_name, old_spec in list(injected.items()):
|
|
716
|
+
if old_name not in spec:
|
|
717
|
+
user_ns.pop(old_name, None)
|
|
718
|
+
if old_spec.get('errorClass'):
|
|
719
|
+
user_ns.pop(old_spec['errorClass']['name'], None)
|
|
720
|
+
fresh = {}
|
|
721
|
+
# The flat per-tool shape declares the SAME error class on many
|
|
722
|
+
# namespaces: materialize each DISTINCT name once, so every proxy's
|
|
723
|
+
# raise-time globals() lookup finds ONE class and a single
|
|
724
|
+
# 'except ToolCallError' catches failures from every binding.
|
|
725
|
+
error_classes = {}
|
|
726
|
+
for global_name, namespace in spec.items():
|
|
727
|
+
error_class = namespace.get('errorClass')
|
|
728
|
+
if namespace.get('callable'):
|
|
729
|
+
# Exactly one function entry was validated by the host; its key is
|
|
730
|
+
# the transport name, the bare global itself is the callable.
|
|
731
|
+
function_name = next(iter(namespace['functions']))
|
|
732
|
+
user_ns[global_name] = _dashr_make_callable(
|
|
733
|
+
global_name,
|
|
734
|
+
function_name,
|
|
735
|
+
error_class['name'] if error_class else None,
|
|
736
|
+
error_class['memberNameProperty'] if error_class else None,
|
|
737
|
+
)
|
|
738
|
+
else:
|
|
739
|
+
user_ns[global_name] = _dashr_make_holder(
|
|
740
|
+
global_name,
|
|
741
|
+
error_class['name'] if error_class else None,
|
|
742
|
+
error_class['memberNameProperty'] if error_class else None,
|
|
743
|
+
sorted(namespace['functions']),
|
|
744
|
+
)
|
|
745
|
+
if error_class and error_class['name'] not in error_classes:
|
|
746
|
+
cls = _dashr_make_error_class(
|
|
747
|
+
error_class['name'],
|
|
748
|
+
error_class['memberNameProperty'],
|
|
749
|
+
)
|
|
750
|
+
error_classes[error_class['name']] = cls
|
|
751
|
+
user_ns[error_class['name']] = cls
|
|
752
|
+
fresh[global_name] = namespace
|
|
753
|
+
user_ns['__dashr_injected__'] = fresh
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _dashr_encode(value):
|
|
757
|
+
if value is _DASHR_NO_VALUE:
|
|
758
|
+
return {'ok': True}
|
|
759
|
+
try:
|
|
760
|
+
return {'ok': True, 'json': _dashr_json.dumps(value, allow_nan=False)}
|
|
761
|
+
except (TypeError, ValueError, OverflowError):
|
|
762
|
+
# Not lossless JSON: pass its repr TEXT through instead of failing
|
|
763
|
+
# the cell. The upstream renders the tool result as text either way,
|
|
764
|
+
# so a non-JSON value costs the model nothing; the host keeps a
|
|
765
|
+
# plain string it forwards untouched. The envelope itself never
|
|
766
|
+
# fails, so the completion never gate-rejects a return value.
|
|
767
|
+
return {'ok': True, 'json': _dashr_json.dumps(repr(value))}
|
|
768
|
+
`;
|
|
769
|
+
|
|
770
|
+
//#endregion
|
|
771
|
+
//#region src/python.ts
|
|
772
|
+
/**
|
|
773
|
+
* Build the single cell for one run.
|
|
774
|
+
* @param program - the model's cell program, executed at module scope.
|
|
775
|
+
* @param specJson - the JSON-encoded {@link NamespaceSpecs} map.
|
|
776
|
+
* @param sentinel - per-run nonce prefix; the envelope line starts with it.
|
|
777
|
+
* @returns the complete cell source.
|
|
778
|
+
*/
|
|
779
|
+
function buildRunCell(program, specJson, sentinel) {
|
|
780
|
+
return [
|
|
781
|
+
`_dashr_state['executing'] = True`,
|
|
782
|
+
`try:`,
|
|
783
|
+
` _dashr_install_bindings(${JSON.stringify(specJson)})`,
|
|
784
|
+
` __dashr_completion__ = _dashr_encode(await _dashr_run_program(${JSON.stringify(program)}))`,
|
|
785
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps(__dashr_completion__))`,
|
|
786
|
+
`finally:`,
|
|
787
|
+
` _dashr_state['executing'] = False`
|
|
788
|
+
].join("\n");
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Build the internal cell that dumps the user namespace to a dill snapshot and
|
|
792
|
+
* writes a JSON manifest next to it. Only USER state is captured: IPython's
|
|
793
|
+
* own session plumbing (`exit`, `quit`, `get_ipython`, … tracked in the
|
|
794
|
+
* shell's `user_ns_hidden` and still bound to the object IPython installed)
|
|
795
|
+
* reaches unpicklable kernel machinery — e.g. the history manager's
|
|
796
|
+
* sqlite3.Connection — and is never restored as program state anyway. Shim
|
|
797
|
+
* names (any `_dashr`/`__dashr` spelling) are excluded by prefix.
|
|
798
|
+
*
|
|
799
|
+
* Size-cap policy (M3-B, blueprint §8.3): dill cannot report a serialized
|
|
800
|
+
* size before dumping, and a GB-sized `DataFrame` dumped EVERY turn is the IO
|
|
801
|
+
* storm the cap exists to stop. The cell therefore gates in two tiers:
|
|
802
|
+
* first a bounded-depth namespace walk estimates the footprint (counting
|
|
803
|
+
* container lengths and reading `nbytes` / `memory_usage(deep=True)` for
|
|
804
|
+
* numpy/pandas objects — the stated pathological case — so the dump is
|
|
805
|
+
* skipped BEFORE any dill IO), then the actual dump goes to a sibling
|
|
806
|
+
* `.part` file whose true byte size is measured before it replaces the
|
|
807
|
+
* previous good snapshot. Skipped snapshots leave the previous
|
|
808
|
+
* `state.dill` + manifest untouched, so a revive restores the last good turn.
|
|
809
|
+
* @param payloadPath - absolute path for the dill payload.
|
|
810
|
+
* @param manifestPath - absolute path for the JSON manifest.
|
|
811
|
+
* @param spec - turn counter, skills list, and the size cap.
|
|
812
|
+
* @param sentinel - per-cell nonce prefix for the outcome envelope.
|
|
813
|
+
* @returns the complete cell source.
|
|
814
|
+
*/
|
|
815
|
+
function buildSnapshotCell(payloadPath, manifestPath, spec, sentinel) {
|
|
816
|
+
return [
|
|
817
|
+
`_dashr_state['executing'] = True`,
|
|
818
|
+
`try:`,
|
|
819
|
+
` import dill as _dashr_dill, json as _dashr_json, platform as _dashr_platform`,
|
|
820
|
+
` import os as _dashr_os, sys as _dashr_sys, datetime as _dashr_dt`,
|
|
821
|
+
` _dashr_shell = globals().get('get_ipython', lambda: None)()`,
|
|
822
|
+
` _dashr_hidden = getattr(_dashr_shell, 'user_ns_hidden', None) or {}`,
|
|
823
|
+
` _dashr_user_ns = {`,
|
|
824
|
+
` name: value`,
|
|
825
|
+
` for name, value in globals().items()`,
|
|
826
|
+
` if not name.lower().startswith(('_dashr', '__dashr'))`,
|
|
827
|
+
` and (name not in _dashr_hidden or _dashr_hidden[name] is not value)`,
|
|
828
|
+
` }`,
|
|
829
|
+
` _dashr_cap = ${JSON.stringify(spec.sizeCapBytes)}`,
|
|
830
|
+
` def _dashr_estimate(value, depth):`,
|
|
831
|
+
` if value is None or isinstance(value, (bool, int, float, complex)):`,
|
|
832
|
+
` return 32`,
|
|
833
|
+
` if isinstance(value, (str, bytes, bytearray)):`,
|
|
834
|
+
` return len(value) + 64`,
|
|
835
|
+
` if isinstance(value, (list, tuple, set, frozenset)):`,
|
|
836
|
+
` if depth <= 0:`,
|
|
837
|
+
` return 4096`,
|
|
838
|
+
` return 64 + sum(_dashr_estimate(item, depth - 1) for item in value)`,
|
|
839
|
+
` if isinstance(value, dict):`,
|
|
840
|
+
` if depth <= 0:`,
|
|
841
|
+
` return 4096`,
|
|
842
|
+
` total = 64`,
|
|
843
|
+
` for key, item in value.items():`,
|
|
844
|
+
` total += _dashr_estimate(key, depth - 1)`,
|
|
845
|
+
` total += _dashr_estimate(item, depth - 1)`,
|
|
846
|
+
` if total > _dashr_cap:`,
|
|
847
|
+
` return total`,
|
|
848
|
+
` return total`,
|
|
849
|
+
` _dashr_nbytes = getattr(value, 'nbytes', None)`,
|
|
850
|
+
` if isinstance(_dashr_nbytes, int) and _dashr_nbytes > 0:`,
|
|
851
|
+
` return _dashr_nbytes + 128`,
|
|
852
|
+
` _dashr_mem = getattr(value, 'memory_usage', None)`,
|
|
853
|
+
` if callable(_dashr_mem):`,
|
|
854
|
+
` try:`,
|
|
855
|
+
` _dashr_usage = _dashr_mem(deep=True)`,
|
|
856
|
+
` _dashr_sum = getattr(_dashr_usage, 'sum', None)`,
|
|
857
|
+
` if callable(_dashr_sum):`,
|
|
858
|
+
` # pandas returns a numpy scalar (np.int64), which is`,
|
|
859
|
+
` # not a Python int subclass; coerce, don't isinstance.`,
|
|
860
|
+
` _dashr_n = int(_dashr_sum())`,
|
|
861
|
+
` if _dashr_n > 0:`,
|
|
862
|
+
` return _dashr_n + 128`,
|
|
863
|
+
` except Exception:`,
|
|
864
|
+
` pass`,
|
|
865
|
+
` return 4096`,
|
|
866
|
+
` _dashr_estimate_bytes = sum(_dashr_estimate(value, 4) for value in _dashr_user_ns.values())`,
|
|
867
|
+
` if _dashr_estimate_bytes > _dashr_cap:`,
|
|
868
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({`,
|
|
869
|
+
` 'ok': False, 'skipped': True, 'reason': 'estimate',`,
|
|
870
|
+
` 'estimateBytes': _dashr_estimate_bytes, 'capBytes': _dashr_cap,`,
|
|
871
|
+
` }))`,
|
|
872
|
+
` else:`,
|
|
873
|
+
` _dashr_part = ${JSON.stringify(payloadPath)} + '.part'`,
|
|
874
|
+
` try:`,
|
|
875
|
+
` with open(_dashr_part, 'wb') as _dashr_f:`,
|
|
876
|
+
` _dashr_dill.dump(_dashr_user_ns, _dashr_f)`,
|
|
877
|
+
` _dashr_size = _dashr_os.path.getsize(_dashr_part)`,
|
|
878
|
+
` if _dashr_size > _dashr_cap:`,
|
|
879
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({`,
|
|
880
|
+
` 'ok': False, 'skipped': True, 'reason': 'actual',`,
|
|
881
|
+
` 'sizeBytes': _dashr_size, 'capBytes': _dashr_cap,`,
|
|
882
|
+
` }))`,
|
|
883
|
+
` else:`,
|
|
884
|
+
` _dashr_os.replace(_dashr_part, ${JSON.stringify(payloadPath)})`,
|
|
885
|
+
` _dashr_manifest = {`,
|
|
886
|
+
` 'snapshotFormat': 1,`,
|
|
887
|
+
` 'turn': ${JSON.stringify(spec.turn)},`,
|
|
888
|
+
` 'pythonVersion': _dashr_platform.python_version(),`,
|
|
889
|
+
` 'venvPath': _dashr_sys.executable,`,
|
|
890
|
+
` 'skills': ${JSON.stringify(spec.skills)},`,
|
|
891
|
+
` 'names': sorted(_dashr_user_ns),`,
|
|
892
|
+
` 'sizeBytes': _dashr_size,`,
|
|
893
|
+
` 'skipped': False,`,
|
|
894
|
+
` 'createdAt': _dashr_dt.datetime.now(_dashr_dt.timezone.utc).isoformat(),`,
|
|
895
|
+
` }`,
|
|
896
|
+
` with open(${JSON.stringify(manifestPath)}, 'w', encoding='utf-8') as _dashr_f:`,
|
|
897
|
+
` _dashr_json.dump(_dashr_manifest, _dashr_f)`,
|
|
898
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({`,
|
|
899
|
+
` 'ok': True, 'sizeBytes': _dashr_size, 'names': len(_dashr_user_ns),`,
|
|
900
|
+
` }))`,
|
|
901
|
+
` finally:`,
|
|
902
|
+
` if _dashr_os.path.exists(_dashr_part):`,
|
|
903
|
+
` try:`,
|
|
904
|
+
` _dashr_os.unlink(_dashr_part)`,
|
|
905
|
+
` except OSError:`,
|
|
906
|
+
` pass`,
|
|
907
|
+
`finally:`,
|
|
908
|
+
` _dashr_state['executing'] = False`
|
|
909
|
+
].join("\n");
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Build the internal cell that restores a dill snapshot into the user
|
|
913
|
+
* namespace. The kernel validates the manifest ITSELF (it is the only party
|
|
914
|
+
* that knows its own python version and `sys.executable`): a python-version
|
|
915
|
+
* mismatch, a different interpreter (`venvPath`), a non-empty skills list, or
|
|
916
|
+
* an unreadable/corrupt payload degrades to an empty namespace WITHOUT
|
|
917
|
+
* touching globals, and reports the reason through the envelope so the host
|
|
918
|
+
* can tell the model "snapshot not replayable, started from empty". Shim and
|
|
919
|
+
* IPython-hidden names are re-filtered on the way in — the same exclusion the
|
|
920
|
+
* snapshot applied on the way out, applied again defensively.
|
|
921
|
+
* @param payloadPath - absolute path of the dill payload.
|
|
922
|
+
* @param manifestPath - absolute path of the JSON manifest.
|
|
923
|
+
* @param sentinel - per-cell nonce prefix for the outcome envelope.
|
|
924
|
+
* @returns the complete cell source.
|
|
925
|
+
*/
|
|
926
|
+
function buildRestoreCell(payloadPath, manifestPath, sentinel) {
|
|
927
|
+
return [
|
|
928
|
+
`_dashr_state['executing'] = True`,
|
|
929
|
+
`try:`,
|
|
930
|
+
` import dill as _dashr_dill, json as _dashr_json, platform as _dashr_platform`,
|
|
931
|
+
` import os as _dashr_os, sys as _dashr_sys`,
|
|
932
|
+
` _dashr_manifest = None`,
|
|
933
|
+
` _dashr_reason = 'snapshot manifest could not be read'`,
|
|
934
|
+
` try:`,
|
|
935
|
+
` with open(${JSON.stringify(manifestPath)}, 'r', encoding='utf-8') as _dashr_f:`,
|
|
936
|
+
` _dashr_manifest = _dashr_json.load(_dashr_f)`,
|
|
937
|
+
` except Exception as _dashr_err:`,
|
|
938
|
+
` _dashr_reason = 'snapshot manifest could not be read: ' + repr(_dashr_err)`,
|
|
939
|
+
` if _dashr_manifest is None:`,
|
|
940
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': False, 'reason': _dashr_reason}))`,
|
|
941
|
+
` elif _dashr_manifest.get('snapshotFormat') != 1:`,
|
|
942
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': False, 'reason': 'snapshot format is not replayable'}))`,
|
|
943
|
+
` elif _dashr_manifest.get('skipped'):`,
|
|
944
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': False, 'reason': 'snapshot was skipped (namespace exceeded the size cap)'}))`,
|
|
945
|
+
` elif _dashr_manifest.get('pythonVersion') != _dashr_platform.python_version():`,
|
|
946
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': False, 'reason': 'python version mismatch: snapshot ' + str(_dashr_manifest.get('pythonVersion')) + ' vs kernel ' + _dashr_platform.python_version()}))`,
|
|
947
|
+
` elif _dashr_os.path.realpath(_dashr_manifest.get('venvPath') or '') != _dashr_os.path.realpath(_dashr_sys.executable):`,
|
|
948
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': False, 'reason': 'kernel interpreter differs from the snapshot venv'}))`,
|
|
949
|
+
` elif _dashr_manifest.get('skills'):`,
|
|
950
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': False, 'reason': 'snapshot skills are not replayable in this provider'}))`,
|
|
951
|
+
` else:`,
|
|
952
|
+
` try:`,
|
|
953
|
+
` with open(${JSON.stringify(payloadPath)}, 'rb') as _dashr_f:`,
|
|
954
|
+
` _dashr_restored = _dashr_dill.load(_dashr_f)`,
|
|
955
|
+
` except Exception as _dashr_err:`,
|
|
956
|
+
` _dashr_restored = None`,
|
|
957
|
+
` _dashr_reason = 'snapshot payload could not be loaded: ' + repr(_dashr_err)`,
|
|
958
|
+
` if not isinstance(_dashr_restored, dict):`,
|
|
959
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': False, 'reason': _dashr_reason if _dashr_restored is None else 'snapshot payload is not a namespace dict'}))`,
|
|
960
|
+
` else:`,
|
|
961
|
+
` _dashr_shell = globals().get('get_ipython', lambda: None)()`,
|
|
962
|
+
` _dashr_hidden = getattr(_dashr_shell, 'user_ns_hidden', None) or {}`,
|
|
963
|
+
` _dashr_user_ns = globals()`,
|
|
964
|
+
` _dashr_restored_count = 0`,
|
|
965
|
+
` for _dashr_name, _dashr_value in _dashr_restored.items():`,
|
|
966
|
+
` if not isinstance(_dashr_name, str):`,
|
|
967
|
+
` continue`,
|
|
968
|
+
` if _dashr_name.lower().startswith(('_dashr', '__dashr')):`,
|
|
969
|
+
` continue`,
|
|
970
|
+
` if _dashr_name in _dashr_hidden and _dashr_hidden[_dashr_name] is _dashr_value:`,
|
|
971
|
+
` continue`,
|
|
972
|
+
` _dashr_user_ns[_dashr_name] = _dashr_value`,
|
|
973
|
+
` _dashr_restored_count += 1`,
|
|
974
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': True, 'restored': _dashr_restored_count}))`,
|
|
975
|
+
`finally:`,
|
|
976
|
+
` _dashr_state['executing'] = False`
|
|
977
|
+
].join("\n");
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Build the internal cell that reads ONE user-namespace variable by name, or
|
|
981
|
+
* — when `name` is `null` — lists the namespace's user-variable names. The
|
|
982
|
+
* same exclusion the snapshot applies on the way out (any `_dashr`/`__dashr`
|
|
983
|
+
* spelling plus IPython's `user_ns_hidden`) defines what "user variable"
|
|
984
|
+
* means here, so `ctx://` sees exactly the namespace a revive would restore.
|
|
985
|
+
*
|
|
986
|
+
* Serialization boundary (design.md D4): a value that survives
|
|
987
|
+
* `json.dumps(…, allow_nan=False)` crosses as `kind: 'json'` text; anything
|
|
988
|
+
* else falls back to `repr` text (`kind: 'repr'`) so a non-JSON variable
|
|
989
|
+
* (the reason the snapshot uses dill at all) is still readable as text. The
|
|
990
|
+
* host keeps the `kind` so the presentation can annotate a repr as "this is
|
|
991
|
+
* repr text, not a JSON value".
|
|
992
|
+
* @param name - the exact variable name, or `null` to list namespace names.
|
|
993
|
+
* @param sentinel - per-cell nonce prefix for the outcome envelope.
|
|
994
|
+
* @returns the complete cell source.
|
|
995
|
+
*/
|
|
996
|
+
function buildQueryVarCell(name$1, sentinel) {
|
|
997
|
+
const lines = [
|
|
998
|
+
`_dashr_state['executing'] = True`,
|
|
999
|
+
`try:`,
|
|
1000
|
+
` import json as _dashr_json`,
|
|
1001
|
+
` _dashr_shell = globals().get('get_ipython', lambda: None)()`,
|
|
1002
|
+
` _dashr_hidden = getattr(_dashr_shell, 'user_ns_hidden', None) or {}`,
|
|
1003
|
+
` _dashr_user_ns = {`,
|
|
1004
|
+
` _name: _value`,
|
|
1005
|
+
` for _name, _value in globals().items()`,
|
|
1006
|
+
` if not _name.lower().startswith(('_dashr', '__dashr'))`,
|
|
1007
|
+
` and (_name not in _dashr_hidden or _dashr_hidden[_name] is not _value)`,
|
|
1008
|
+
` }`
|
|
1009
|
+
];
|
|
1010
|
+
if (name$1 === null) lines.push(` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': True, 'kind': 'names', 'names': sorted(_dashr_user_ns)}))`);
|
|
1011
|
+
else lines.push(` _dashr_name = ${JSON.stringify(name$1)}`, ` if _dashr_name not in _dashr_user_ns:`, ` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': True, 'kind': 'missing'}))`, ` else:`, ` _dashr_value = _dashr_user_ns[_dashr_name]`, ` try:`, ` _dashr_text = _dashr_json.dumps(_dashr_value, allow_nan=False)`, ` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': True, 'kind': 'json', 'text': _dashr_text}))`, ` except (TypeError, ValueError, OverflowError):`, ` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': True, 'kind': 'repr', 'text': repr(_dashr_value)}))`);
|
|
1012
|
+
lines.push(`finally:`, ` _dashr_state['executing'] = False`);
|
|
1013
|
+
return lines.join("\n");
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Build the internal cell that assigns one JSON value into the user
|
|
1017
|
+
* namespace under `name`. The value is carried as pre-serialized JSON text
|
|
1018
|
+
* (the host validated it as lossless JSON and the name as a usable
|
|
1019
|
+
* identifier), so the cell only decodes and binds — no arbitrary object can
|
|
1020
|
+
* cross the wire. Bracketed by the same SIGALRM busy guard as a run cell.
|
|
1021
|
+
* @param name - the validated identifier to assign under.
|
|
1022
|
+
* @param valueJson - the lossless-JSON text to decode and bind.
|
|
1023
|
+
* @param sentinel - per-cell nonce prefix for the outcome envelope.
|
|
1024
|
+
* @returns the complete cell source.
|
|
1025
|
+
*/
|
|
1026
|
+
function buildSetVarCell(name$1, valueJson, sentinel) {
|
|
1027
|
+
return [
|
|
1028
|
+
`_dashr_state['executing'] = True`,
|
|
1029
|
+
`try:`,
|
|
1030
|
+
` import json as _dashr_json`,
|
|
1031
|
+
` _dashr_value = _dashr_json.loads(${JSON.stringify(valueJson)})`,
|
|
1032
|
+
` globals()[${JSON.stringify(name$1)}] = _dashr_value`,
|
|
1033
|
+
` print(${JSON.stringify(sentinel)} + _dashr_json.dumps({'ok': True}))`,
|
|
1034
|
+
`finally:`,
|
|
1035
|
+
` _dashr_state['executing'] = False`
|
|
1036
|
+
].join("\n");
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region src/output.ts
|
|
1041
|
+
/** ANSI color escapes, which ipykernel embeds in published tracebacks. */
|
|
1042
|
+
const ANSI_ESCAPE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
1043
|
+
/** Smallest cap that can represent an empty logs array plus a quoted message. */
|
|
1044
|
+
const MIN_OUTPUT_BYTES = 4;
|
|
1045
|
+
/**
|
|
1046
|
+
* Split captured stream text into log lines and the completion envelope.
|
|
1047
|
+
* Tolerates a sentinel that landed mid-line after an emission without a
|
|
1048
|
+
* trailing newline (the prefix stays in the logs).
|
|
1049
|
+
* @param streamText - concatenated iopub stream text for one cell.
|
|
1050
|
+
* @param sentinel - per-run nonce prefix of the envelope line.
|
|
1051
|
+
*/
|
|
1052
|
+
function extractStream(streamText, sentinel) {
|
|
1053
|
+
const logs = [];
|
|
1054
|
+
let envelope;
|
|
1055
|
+
for (const line of streamText.split("\n")) {
|
|
1056
|
+
const at = line.indexOf(sentinel);
|
|
1057
|
+
if (at === -1) {
|
|
1058
|
+
logs.push(line);
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (at > 0) logs.push(line.slice(0, at));
|
|
1062
|
+
envelope = line.slice(at + sentinel.length);
|
|
1063
|
+
}
|
|
1064
|
+
if (logs.length > 0 && logs.at(-1) === "") logs.pop();
|
|
1065
|
+
return {
|
|
1066
|
+
logs,
|
|
1067
|
+
envelope: envelope === "" ? void 0 : envelope
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
/** Strip ANSI escapes from an ipykernel traceback for a model-facing message. */
|
|
1071
|
+
function plainTraceback(ename, evalue, traceback) {
|
|
1072
|
+
const body = traceback.map((line) => line.replace(ANSI_ESCAPE, "")).join("\n");
|
|
1073
|
+
return body.length > 0 ? `${ename}: ${evalue}\n${body}` : `${ename}: ${evalue}`;
|
|
1074
|
+
}
|
|
1075
|
+
function byteLength(text) {
|
|
1076
|
+
return Buffer.byteLength(text, "utf8");
|
|
1077
|
+
}
|
|
1078
|
+
/** Serialized size of the counted payloads: logs array, value, failure message. */
|
|
1079
|
+
function payloadBytes(logs, value, errorMessage) {
|
|
1080
|
+
let total = 2;
|
|
1081
|
+
for (const [i, text] of logs.entries()) total += (i > 0 ? 1 : 0) + byteLength(JSON.stringify(text));
|
|
1082
|
+
if (value !== void 0) total += byteLength(JSON.stringify(value));
|
|
1083
|
+
if (errorMessage !== void 0) total += byteLength(JSON.stringify(errorMessage));
|
|
1084
|
+
return total;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Apply the outer-output ledger: admit the result when the combined serialized
|
|
1088
|
+
* payloads fit, otherwise retain a fitting log prefix and report an explicit
|
|
1089
|
+
* `output-limit` failure. Conservative accounting — it may underuse the cap,
|
|
1090
|
+
* never exceed it.
|
|
1091
|
+
* @param logs - captured emission lines.
|
|
1092
|
+
* @param value - the completion value, when one crossed the JSON boundary.
|
|
1093
|
+
* @param error - the failure, when the run failed.
|
|
1094
|
+
* @param maxBytes - the configured cap for the combined counted payloads.
|
|
1095
|
+
*/
|
|
1096
|
+
function finalizeOutput(logs, value, error, maxBytes) {
|
|
1097
|
+
if (payloadBytes(logs, value, error?.message) <= maxBytes) return {
|
|
1098
|
+
logs,
|
|
1099
|
+
...value !== void 0 ? { value } : {},
|
|
1100
|
+
...error ? { error } : {}
|
|
1101
|
+
};
|
|
1102
|
+
const limitMessage = `outer output exceeded ${maxBytes} bytes`;
|
|
1103
|
+
const messageBytes = byteLength(JSON.stringify(limitMessage)) + 4;
|
|
1104
|
+
const budget = Math.max(0, maxBytes - 2 - messageBytes);
|
|
1105
|
+
const retained = [];
|
|
1106
|
+
let used = 0;
|
|
1107
|
+
for (const text of logs) {
|
|
1108
|
+
const serialized = byteLength(JSON.stringify(text));
|
|
1109
|
+
const separator = retained.length > 0 ? 1 : 0;
|
|
1110
|
+
if (used + separator + serialized > budget) break;
|
|
1111
|
+
retained.push(text);
|
|
1112
|
+
used += separator + serialized;
|
|
1113
|
+
}
|
|
1114
|
+
return {
|
|
1115
|
+
logs: retained,
|
|
1116
|
+
error: {
|
|
1117
|
+
kind: "output-limit",
|
|
1118
|
+
message: limitMessage
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
//#endregion
|
|
1124
|
+
//#region src/kernel.ts
|
|
1125
|
+
const PROTOCOL_VERSION = "5.3";
|
|
1126
|
+
/** Loopback PUB/SUB subscription propagation guard before the first execute. */
|
|
1127
|
+
const IOPUB_SUBSCRIBE_DELAY_MS = 50;
|
|
1128
|
+
const CONNECT_POLL_MS = 25;
|
|
1129
|
+
const SHUTDOWN_DRAIN_MS = 200;
|
|
1130
|
+
function isRecord(value) {
|
|
1131
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1132
|
+
}
|
|
1133
|
+
function buildMessage(msgType, content, session, username) {
|
|
1134
|
+
return {
|
|
1135
|
+
header: {
|
|
1136
|
+
msg_id: randomUUID(),
|
|
1137
|
+
session,
|
|
1138
|
+
username,
|
|
1139
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1140
|
+
msg_type: msgType,
|
|
1141
|
+
version: PROTOCOL_VERSION
|
|
1142
|
+
},
|
|
1143
|
+
parent_header: {},
|
|
1144
|
+
content
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function makeConnection() {
|
|
1148
|
+
const info = {
|
|
1149
|
+
ip: "127.0.0.1",
|
|
1150
|
+
transport: "tcp",
|
|
1151
|
+
shell_port: 0,
|
|
1152
|
+
iopub_port: 0,
|
|
1153
|
+
stdin_port: 0,
|
|
1154
|
+
control_port: 0,
|
|
1155
|
+
hb_port: 0,
|
|
1156
|
+
signature_scheme: "hmac-sha256",
|
|
1157
|
+
key: randomBytes(16).toString("hex"),
|
|
1158
|
+
kernel_name: "python3"
|
|
1159
|
+
};
|
|
1160
|
+
const tempDir = mkdtempSync(join(tmpdir(), "dashr-kernel-"));
|
|
1161
|
+
const path$1 = join(tempDir, "connection.json");
|
|
1162
|
+
writeFileSync(path$1, JSON.stringify(info, null, 2), { mode: 384 });
|
|
1163
|
+
return {
|
|
1164
|
+
path: path$1,
|
|
1165
|
+
tempDir
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function readResolvedConnection(path$1) {
|
|
1169
|
+
try {
|
|
1170
|
+
const parsed = JSON.parse(readFileSync(path$1, "utf8"));
|
|
1171
|
+
if (!isRecord(parsed) || parsed.ip !== "127.0.0.1" || parsed.transport !== "tcp" || parsed.signature_scheme !== "hmac-sha256" || typeof parsed.key !== "string") return null;
|
|
1172
|
+
for (const port of [
|
|
1173
|
+
"shell_port",
|
|
1174
|
+
"iopub_port",
|
|
1175
|
+
"stdin_port",
|
|
1176
|
+
"control_port",
|
|
1177
|
+
"hb_port"
|
|
1178
|
+
]) {
|
|
1179
|
+
const value = parsed[port];
|
|
1180
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return null;
|
|
1181
|
+
}
|
|
1182
|
+
return parsed;
|
|
1183
|
+
} catch {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* One persistent IPython kernel subprocess with its ZeroMQ channels. Cell
|
|
1189
|
+
* execution is serialized on an internal queue (the Jupyter shell channel is
|
|
1190
|
+
* request/reply).
|
|
1191
|
+
*/
|
|
1192
|
+
var IpyKernelBridge = class {
|
|
1193
|
+
session = randomUUID();
|
|
1194
|
+
state = "idle";
|
|
1195
|
+
startPromise;
|
|
1196
|
+
kernel;
|
|
1197
|
+
transport;
|
|
1198
|
+
connection;
|
|
1199
|
+
tempDir;
|
|
1200
|
+
kernelStderr = "";
|
|
1201
|
+
executionQueue = Promise.resolve();
|
|
1202
|
+
activeExec;
|
|
1203
|
+
commTargets = /* @__PURE__ */ new Map();
|
|
1204
|
+
servedComms = /* @__PURE__ */ new Set();
|
|
1205
|
+
constructor(options) {
|
|
1206
|
+
this.options = options;
|
|
1207
|
+
}
|
|
1208
|
+
/** The kernel subprocess pid, for lifecycle assertions; absent before spawn. */
|
|
1209
|
+
get pid() {
|
|
1210
|
+
return this.kernel?.pid;
|
|
1211
|
+
}
|
|
1212
|
+
get isRunning() {
|
|
1213
|
+
return this.state === "running";
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Whether the subprocess is gone (crashed or disposed). A dead bridge is
|
|
1217
|
+
* never resurrected — the owning provider replaces it with a fresh one
|
|
1218
|
+
* (respawn, M3-A), because kernel state is unrecoverable in-process.
|
|
1219
|
+
*/
|
|
1220
|
+
get isDead() {
|
|
1221
|
+
return this.state === "shutdown";
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Start the kernel (idempotent; concurrent callers share one startup).
|
|
1225
|
+
* @throws when the subprocess cannot spawn, ports never resolve, the
|
|
1226
|
+
* kernel_info probe times out, or the bootstrap cell fails.
|
|
1227
|
+
*/
|
|
1228
|
+
async start() {
|
|
1229
|
+
if (!this.startPromise) this.startPromise = this.doStart().catch((error) => {
|
|
1230
|
+
this.startPromise = void 0;
|
|
1231
|
+
throw error;
|
|
1232
|
+
});
|
|
1233
|
+
return this.startPromise;
|
|
1234
|
+
}
|
|
1235
|
+
stderrTail() {
|
|
1236
|
+
return this.kernelStderr.slice(-1024) || "(empty)";
|
|
1237
|
+
}
|
|
1238
|
+
async doStart() {
|
|
1239
|
+
if (this.state !== "idle") return;
|
|
1240
|
+
this.state = "starting";
|
|
1241
|
+
const { path: path$1, tempDir } = makeConnection();
|
|
1242
|
+
this.tempDir = tempDir;
|
|
1243
|
+
const kernel = spawn(this.options.python, [
|
|
1244
|
+
"-m",
|
|
1245
|
+
"ipykernel_launcher",
|
|
1246
|
+
"-f",
|
|
1247
|
+
path$1
|
|
1248
|
+
], {
|
|
1249
|
+
cwd: this.options.cwd,
|
|
1250
|
+
env: {
|
|
1251
|
+
PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
1252
|
+
LANG: "C.UTF-8",
|
|
1253
|
+
HOME: process.env.HOME ?? tmpdir()
|
|
1254
|
+
},
|
|
1255
|
+
stdio: [
|
|
1256
|
+
"ignore",
|
|
1257
|
+
"pipe",
|
|
1258
|
+
"pipe"
|
|
1259
|
+
]
|
|
1260
|
+
});
|
|
1261
|
+
this.kernel = kernel;
|
|
1262
|
+
kernel.stderr?.on("data", (buf) => {
|
|
1263
|
+
this.kernelStderr += buf.toString();
|
|
1264
|
+
});
|
|
1265
|
+
kernel.on("error", (error) => {
|
|
1266
|
+
this.kernelStderr += `spawn error: ${error.message}\n`;
|
|
1267
|
+
this.onKernelDeath();
|
|
1268
|
+
});
|
|
1269
|
+
kernel.on("exit", (code, signal) => {
|
|
1270
|
+
if (this.state !== "shutdown") this.kernelStderr += `unexpected exit code=${code} signal=${signal}\n`;
|
|
1271
|
+
this.onKernelDeath();
|
|
1272
|
+
});
|
|
1273
|
+
let conn;
|
|
1274
|
+
try {
|
|
1275
|
+
conn = await this.waitForConnection(path$1);
|
|
1276
|
+
this.connection = conn;
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
this.cleanupResources();
|
|
1279
|
+
this.state = "idle";
|
|
1280
|
+
throw error;
|
|
1281
|
+
}
|
|
1282
|
+
const transport = new KernelTransport({
|
|
1283
|
+
python: this.options.python,
|
|
1284
|
+
connectionFile: path$1,
|
|
1285
|
+
...this.options.cwd === void 0 ? {} : { cwd: this.options.cwd },
|
|
1286
|
+
readyTimeoutMs: this.options.startupTimeoutMs
|
|
1287
|
+
});
|
|
1288
|
+
this.transport = transport;
|
|
1289
|
+
transport.onMessage((channel, message) => {
|
|
1290
|
+
this.handleTransportMessage(channel, message);
|
|
1291
|
+
});
|
|
1292
|
+
transport.onExit(() => {
|
|
1293
|
+
if (this.state !== "shutdown") this.forceSettleActive({
|
|
1294
|
+
kind: "worker-exit",
|
|
1295
|
+
message: `kernel bridge died. stderr tail:\n${transport.stderrTail()}`
|
|
1296
|
+
});
|
|
1297
|
+
});
|
|
1298
|
+
try {
|
|
1299
|
+
await transport.start();
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
this.cleanupResources();
|
|
1302
|
+
this.state = "idle";
|
|
1303
|
+
throw error;
|
|
1304
|
+
}
|
|
1305
|
+
await setTimeout$1(IOPUB_SUBSCRIBE_DELAY_MS);
|
|
1306
|
+
try {
|
|
1307
|
+
await this.probeReady();
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
this.cleanupResources();
|
|
1310
|
+
this.state = "idle";
|
|
1311
|
+
throw error;
|
|
1312
|
+
}
|
|
1313
|
+
const bootstrap = await this.executeCell(KERNEL_BOOTSTRAP, {
|
|
1314
|
+
timeoutMs: this.options.startupTimeoutMs,
|
|
1315
|
+
interruptGraceMs: 1e3,
|
|
1316
|
+
interruptConfirmMs: Math.min(this.options.interruptConfirmMs, 500)
|
|
1317
|
+
});
|
|
1318
|
+
if (bootstrap.outcome !== "completed" || bootstrap.status !== "ok") {
|
|
1319
|
+
const detail = bootstrap.outcome === "completed" && bootstrap.cellError ? `${bootstrap.cellError.ename}: ${bootstrap.cellError.evalue}` : "bootstrap cell failed";
|
|
1320
|
+
this.cleanupResources();
|
|
1321
|
+
this.state = "idle";
|
|
1322
|
+
throw new Error(`dashr kernel bootstrap failed: ${detail}`);
|
|
1323
|
+
}
|
|
1324
|
+
this.state = "running";
|
|
1325
|
+
}
|
|
1326
|
+
async waitForConnection(path$1) {
|
|
1327
|
+
const deadline = Date.now() + this.options.startupTimeoutMs;
|
|
1328
|
+
while (Date.now() < deadline) {
|
|
1329
|
+
if (this.state === "shutdown") throw new Error(`kernel exited before resolving ports. stderr:\n${this.stderrTail()}`);
|
|
1330
|
+
const info = readResolvedConnection(path$1);
|
|
1331
|
+
if (info) return info;
|
|
1332
|
+
await setTimeout$1(CONNECT_POLL_MS);
|
|
1333
|
+
}
|
|
1334
|
+
throw new Error(`kernel did not resolve connection ports within ${this.options.startupTimeoutMs}ms. stderr tail:\n${this.stderrTail()}`);
|
|
1335
|
+
}
|
|
1336
|
+
async probeReady() {
|
|
1337
|
+
const transport = this.transport;
|
|
1338
|
+
if (!transport) throw new Error("kernel channels are not connected");
|
|
1339
|
+
const msg = buildMessage("kernel_info_request", {}, this.session, this.options.username);
|
|
1340
|
+
const requestMsgId = msg.header.msg_id;
|
|
1341
|
+
transport.send("shell", msg);
|
|
1342
|
+
if (await transport.waitFor("shell", (incoming) => incoming.header.msg_type === "kernel_info_reply" && incoming.parent_header.msg_id === requestMsgId, this.options.startupTimeoutMs)) return;
|
|
1343
|
+
const bridgeTail = transport.stderrTail();
|
|
1344
|
+
if (this.state === "shutdown") throw new Error(`kernel exited during startup. kernel stderr:\n${this.stderrTail()}\nbridge stderr:\n${bridgeTail}`);
|
|
1345
|
+
throw new Error(`kernel did not answer kernel_info_request within ${this.options.startupTimeoutMs}ms. kernel stderr:\n${this.stderrTail()}\nbridge stderr:\n${bridgeTail}`);
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Execute one cell on the persistent kernel, serialized against all other
|
|
1349
|
+
* executions. Resolves with the cell's outcome — never rejects for
|
|
1350
|
+
* cell-level failures (program errors, timeouts, aborts, kernel death).
|
|
1351
|
+
* @param code - complete cell source.
|
|
1352
|
+
* @param opts - budgets, abort signal, and the host-request dispatcher.
|
|
1353
|
+
*/
|
|
1354
|
+
async executeCell(code, opts) {
|
|
1355
|
+
const prev = this.executionQueue;
|
|
1356
|
+
let releaseQueue;
|
|
1357
|
+
this.executionQueue = new Promise((resolve$1) => {
|
|
1358
|
+
releaseQueue = resolve$1;
|
|
1359
|
+
});
|
|
1360
|
+
await prev;
|
|
1361
|
+
try {
|
|
1362
|
+
if (this.state === "shutdown" || this.state === "idle") return {
|
|
1363
|
+
outcome: "forced",
|
|
1364
|
+
failure: {
|
|
1365
|
+
kind: "worker-exit",
|
|
1366
|
+
message: "kernel has been shut down"
|
|
1367
|
+
},
|
|
1368
|
+
streamText: ""
|
|
1369
|
+
};
|
|
1370
|
+
return await this.executeInner(code, opts);
|
|
1371
|
+
} finally {
|
|
1372
|
+
releaseQueue();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
executeInner(code, opts) {
|
|
1376
|
+
const transport = this.transport;
|
|
1377
|
+
if (!transport) return Promise.resolve({
|
|
1378
|
+
outcome: "forced",
|
|
1379
|
+
failure: {
|
|
1380
|
+
kind: "worker-exit",
|
|
1381
|
+
message: "kernel channels are not connected"
|
|
1382
|
+
},
|
|
1383
|
+
streamText: ""
|
|
1384
|
+
});
|
|
1385
|
+
const msg = buildMessage("execute_request", {
|
|
1386
|
+
code,
|
|
1387
|
+
silent: false,
|
|
1388
|
+
store_history: false,
|
|
1389
|
+
user_expressions: {},
|
|
1390
|
+
allow_stdin: false,
|
|
1391
|
+
stop_on_error: true
|
|
1392
|
+
}, this.session, this.options.username);
|
|
1393
|
+
return new Promise((resolve$1) => {
|
|
1394
|
+
const execution = {
|
|
1395
|
+
requestMsgId: msg.header.msg_id,
|
|
1396
|
+
opts,
|
|
1397
|
+
streamText: "",
|
|
1398
|
+
status: "ok",
|
|
1399
|
+
settled: false,
|
|
1400
|
+
resolve: resolve$1
|
|
1401
|
+
};
|
|
1402
|
+
this.activeExec = execution;
|
|
1403
|
+
const settle = (outcome) => {
|
|
1404
|
+
if (execution.settled) return;
|
|
1405
|
+
execution.settled = true;
|
|
1406
|
+
if (execution.forceTimer) clearTimeout(execution.forceTimer);
|
|
1407
|
+
if (execution.timeoutTimer) clearTimeout(execution.timeoutTimer);
|
|
1408
|
+
if (execution.alarmTimer) clearTimeout(execution.alarmTimer);
|
|
1409
|
+
execution.detachSignal?.();
|
|
1410
|
+
if (this.activeExec === execution) this.activeExec = void 0;
|
|
1411
|
+
resolve$1(outcome);
|
|
1412
|
+
};
|
|
1413
|
+
const interruptAndForce = (failure) => {
|
|
1414
|
+
if (this.activeExec !== execution) return;
|
|
1415
|
+
if (execution.pendingForce === void 0) execution.pendingForce = failure;
|
|
1416
|
+
this.sendInterrupt(execution);
|
|
1417
|
+
execution.forceTimer = setTimeout(() => {
|
|
1418
|
+
settle({
|
|
1419
|
+
outcome: "forced",
|
|
1420
|
+
failure,
|
|
1421
|
+
streamText: execution.streamText
|
|
1422
|
+
});
|
|
1423
|
+
}, opts.interruptGraceMs);
|
|
1424
|
+
execution.forceTimer.unref?.();
|
|
1425
|
+
};
|
|
1426
|
+
const onAbort = () => {
|
|
1427
|
+
interruptAndForce({
|
|
1428
|
+
kind: "abort",
|
|
1429
|
+
message: String(opts.signal?.reason)
|
|
1430
|
+
});
|
|
1431
|
+
};
|
|
1432
|
+
execution.timeoutTimer = setTimeout(() => {
|
|
1433
|
+
interruptAndForce({
|
|
1434
|
+
kind: "timeout",
|
|
1435
|
+
message: `cell exceeded ${opts.timeoutMs}ms wall budget`
|
|
1436
|
+
});
|
|
1437
|
+
}, opts.timeoutMs);
|
|
1438
|
+
execution.timeoutTimer.unref?.();
|
|
1439
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1440
|
+
execution.detachSignal = () => opts.signal?.removeEventListener("abort", onAbort);
|
|
1441
|
+
if (opts.signal?.aborted) onAbort();
|
|
1442
|
+
try {
|
|
1443
|
+
transport.send("shell", msg);
|
|
1444
|
+
} catch {
|
|
1445
|
+
settle({
|
|
1446
|
+
outcome: "forced",
|
|
1447
|
+
failure: {
|
|
1448
|
+
kind: "worker-exit",
|
|
1449
|
+
message: "shell channel send failed"
|
|
1450
|
+
},
|
|
1451
|
+
streamText: execution.streamText
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Route one relayed message. Only iopub drives executions and comms; shell
|
|
1458
|
+
* replies serve the startup probe (via `waitFor`), and control is inbound
|
|
1459
|
+
* for host replies.
|
|
1460
|
+
*/
|
|
1461
|
+
handleTransportMessage(channel, incoming) {
|
|
1462
|
+
if (channel !== "iopub") return;
|
|
1463
|
+
const msgType = incoming.header.msg_type;
|
|
1464
|
+
if (msgType === "comm_open" || msgType === "comm_msg" || msgType === "comm_close") {
|
|
1465
|
+
this.handleCommMessage(incoming);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
this.handleExecutionMessage(incoming);
|
|
1469
|
+
}
|
|
1470
|
+
forceSettleActive(failure) {
|
|
1471
|
+
const execution = this.activeExec;
|
|
1472
|
+
if (!execution || execution.settled) return;
|
|
1473
|
+
execution.settled = true;
|
|
1474
|
+
if (execution.forceTimer) clearTimeout(execution.forceTimer);
|
|
1475
|
+
if (execution.timeoutTimer) clearTimeout(execution.timeoutTimer);
|
|
1476
|
+
if (execution.alarmTimer) clearTimeout(execution.alarmTimer);
|
|
1477
|
+
execution.detachSignal?.();
|
|
1478
|
+
this.activeExec = void 0;
|
|
1479
|
+
execution.resolve({
|
|
1480
|
+
outcome: "forced",
|
|
1481
|
+
failure,
|
|
1482
|
+
streamText: execution.streamText
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
handleExecutionMessage(incoming) {
|
|
1486
|
+
const execution = this.activeExec;
|
|
1487
|
+
if (!execution || execution.settled) return;
|
|
1488
|
+
if (incoming.parent_header.msg_id !== execution.requestMsgId) return;
|
|
1489
|
+
const msgType = incoming.header.msg_type;
|
|
1490
|
+
if (msgType === "stream") {
|
|
1491
|
+
const content = incoming.content;
|
|
1492
|
+
if (typeof content.text === "string") execution.streamText += content.text;
|
|
1493
|
+
} else if (msgType === "error") {
|
|
1494
|
+
const content = incoming.content;
|
|
1495
|
+
execution.cellError = {
|
|
1496
|
+
ename: typeof content.ename === "string" ? content.ename : "Error",
|
|
1497
|
+
evalue: typeof content.evalue === "string" ? content.evalue : "",
|
|
1498
|
+
traceback: Array.isArray(content.traceback) ? content.traceback : []
|
|
1499
|
+
};
|
|
1500
|
+
execution.status = "error";
|
|
1501
|
+
} else if (msgType === "status") {
|
|
1502
|
+
if (incoming.content.execution_state === "idle") this.settleCompleted(execution);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
settleCompleted(execution) {
|
|
1506
|
+
if (execution.settled) return;
|
|
1507
|
+
execution.settled = true;
|
|
1508
|
+
if (execution.forceTimer) clearTimeout(execution.forceTimer);
|
|
1509
|
+
if (execution.timeoutTimer) clearTimeout(execution.timeoutTimer);
|
|
1510
|
+
if (execution.alarmTimer) clearTimeout(execution.alarmTimer);
|
|
1511
|
+
execution.detachSignal?.();
|
|
1512
|
+
if (this.activeExec === execution) this.activeExec = void 0;
|
|
1513
|
+
if (execution.pendingForce !== void 0) {
|
|
1514
|
+
execution.resolve({
|
|
1515
|
+
outcome: "forced",
|
|
1516
|
+
failure: execution.pendingForce,
|
|
1517
|
+
streamText: execution.streamText
|
|
1518
|
+
});
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
execution.resolve({
|
|
1522
|
+
outcome: "completed",
|
|
1523
|
+
status: execution.status,
|
|
1524
|
+
streamText: execution.streamText,
|
|
1525
|
+
...execution.cellError ? { cellError: execution.cellError } : {}
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
handleCommMessage(incoming) {
|
|
1529
|
+
const content = incoming.content;
|
|
1530
|
+
const commId = content.comm_id;
|
|
1531
|
+
if (typeof commId !== "string") return;
|
|
1532
|
+
const msgType = incoming.header.msg_type;
|
|
1533
|
+
if (msgType === "comm_close") {
|
|
1534
|
+
this.commTargets.delete(commId);
|
|
1535
|
+
this.servedComms.delete(commId);
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
if (msgType === "comm_open") {
|
|
1539
|
+
const targetName = content.target_name;
|
|
1540
|
+
if (typeof targetName === "string") this.commTargets.set(commId, targetName);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (msgType === "comm_msg" && this.commTargets.get(commId) === HOST_COMM_TARGET) this.serveHostRequest(commId, content.data);
|
|
1544
|
+
}
|
|
1545
|
+
serveHostRequest(commId, data) {
|
|
1546
|
+
if (this.servedComms.has(commId)) return;
|
|
1547
|
+
this.servedComms.add(commId);
|
|
1548
|
+
const handler = this.activeExec?.opts.hostRequestHandler;
|
|
1549
|
+
if (!handler) {
|
|
1550
|
+
this.replyComm(commId, {
|
|
1551
|
+
status: "error",
|
|
1552
|
+
error: "no host request handler is active"
|
|
1553
|
+
}).catch(() => void 0);
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
if (!isRecord(data)) {
|
|
1557
|
+
this.replyComm(commId, {
|
|
1558
|
+
status: "error",
|
|
1559
|
+
error: "host request payload must be an object"
|
|
1560
|
+
}).catch(() => void 0);
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
(async () => {
|
|
1564
|
+
try {
|
|
1565
|
+
const outcome = await handler(data);
|
|
1566
|
+
if (outcome.ok) await this.replyComm(commId, {
|
|
1567
|
+
status: "ok",
|
|
1568
|
+
result: outcome.result
|
|
1569
|
+
});
|
|
1570
|
+
else await this.replyComm(commId, {
|
|
1571
|
+
status: "error",
|
|
1572
|
+
error: outcome.message
|
|
1573
|
+
});
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1576
|
+
await this.replyComm(commId, {
|
|
1577
|
+
status: "error",
|
|
1578
|
+
error: message
|
|
1579
|
+
}).catch(() => void 0);
|
|
1580
|
+
}
|
|
1581
|
+
})().catch(() => void 0);
|
|
1582
|
+
}
|
|
1583
|
+
async replyComm(commId, data) {
|
|
1584
|
+
const transport = this.transport;
|
|
1585
|
+
if (!transport) throw new Error("kernel channels are not connected");
|
|
1586
|
+
transport.send("control", buildMessage("comm_msg", {
|
|
1587
|
+
comm_id: commId,
|
|
1588
|
+
data
|
|
1589
|
+
}, this.session, this.options.username));
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Phase one NOW, phase two on a timer: send the control-channel interrupt,
|
|
1593
|
+
* then — after the confirm window, and only if the execution has still not
|
|
1594
|
+
* settled — escalate to SIGALRM. The zmq interrupt only lands when the cell
|
|
1595
|
+
* yields to the kernel event loop; a busy sync cell ('while True: pass')
|
|
1596
|
+
* never does, which is why the escalation exists at all. Sending both
|
|
1597
|
+
* channels in the same tick (the M1 design) made SIGALRM the FIRST signal
|
|
1598
|
+
* an idle-or-booting kernel saw on timeout/abort: its handler raised
|
|
1599
|
+
* KeyboardInterrupt at the top level and the process exited cleanly — a
|
|
1600
|
+
* deterministic kill (10/10 same-tick aborts, 8/10 at +1-2ms, 40/40 during
|
|
1601
|
+
* cold boot; dev/m2a-report.md §5.1). Two-phase escalation plus the
|
|
1602
|
+
* kernel-side busy guard closes that window while keeping the hard-abort
|
|
1603
|
+
* contract: a busy loop still breaks, one confirm window later.
|
|
1604
|
+
*/
|
|
1605
|
+
async sendInterrupt(execution) {
|
|
1606
|
+
const transport = this.transport;
|
|
1607
|
+
if (!transport) return;
|
|
1608
|
+
try {
|
|
1609
|
+
transport.interrupt();
|
|
1610
|
+
} catch {}
|
|
1611
|
+
execution.alarmTimer = setTimeout(() => {
|
|
1612
|
+
if (execution.settled) return;
|
|
1613
|
+
if (this.kernel?.exitCode === null) try {
|
|
1614
|
+
this.kernel.kill("SIGALRM");
|
|
1615
|
+
} catch {}
|
|
1616
|
+
}, execution.opts.interruptConfirmMs);
|
|
1617
|
+
execution.alarmTimer.unref?.();
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Snapshot the user namespace via an internal dill cell. Requires `dill` in
|
|
1621
|
+
* the kernel environment. The cell reports its outcome through a nonce
|
|
1622
|
+
* envelope (skipped-for-size vs written); a failed/skipped dump NEVER
|
|
1623
|
+
* replaces a previous good snapshot on disk.
|
|
1624
|
+
* @param payloadPath - dill payload destination.
|
|
1625
|
+
* @param manifestPath - JSON manifest destination (turn, names, python
|
|
1626
|
+
* version, venv path, skills, size).
|
|
1627
|
+
* @param spec - turn counter, skills list, and the size cap.
|
|
1628
|
+
*/
|
|
1629
|
+
async snapshotState(payloadPath, manifestPath, spec) {
|
|
1630
|
+
if (!this.isRunning) return {
|
|
1631
|
+
kind: "failed",
|
|
1632
|
+
reason: "kernel is not running"
|
|
1633
|
+
};
|
|
1634
|
+
const sentinel = `__dashr_snapshot_${randomBytes(9).toString("hex")}__`;
|
|
1635
|
+
const outcome = await this.executeCell(buildSnapshotCell(payloadPath, manifestPath, spec, sentinel), {
|
|
1636
|
+
timeoutMs: this.options.snapshotTimeoutMs,
|
|
1637
|
+
interruptGraceMs: 1e3,
|
|
1638
|
+
interruptConfirmMs: Math.min(this.options.interruptConfirmMs, 500)
|
|
1639
|
+
});
|
|
1640
|
+
if (outcome.outcome !== "completed" || outcome.status !== "ok") return {
|
|
1641
|
+
kind: "failed",
|
|
1642
|
+
reason: "snapshot cell failed"
|
|
1643
|
+
};
|
|
1644
|
+
return this.parseSnapshotEnvelope(outcome.streamText, sentinel);
|
|
1645
|
+
}
|
|
1646
|
+
/** Parse one snapshot cell's nonce envelope into its outcome. */
|
|
1647
|
+
parseSnapshotEnvelope(streamText, sentinel) {
|
|
1648
|
+
const capture = extractStream(streamText, sentinel);
|
|
1649
|
+
if (capture.envelope === void 0) return {
|
|
1650
|
+
kind: "failed",
|
|
1651
|
+
reason: "snapshot cell produced no envelope"
|
|
1652
|
+
};
|
|
1653
|
+
let envelope;
|
|
1654
|
+
try {
|
|
1655
|
+
envelope = JSON.parse(capture.envelope);
|
|
1656
|
+
} catch {
|
|
1657
|
+
return {
|
|
1658
|
+
kind: "failed",
|
|
1659
|
+
reason: "snapshot envelope was not valid JSON"
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
if (typeof envelope !== "object" || envelope === null) return {
|
|
1663
|
+
kind: "failed",
|
|
1664
|
+
reason: "snapshot envelope was not an object"
|
|
1665
|
+
};
|
|
1666
|
+
const record = envelope;
|
|
1667
|
+
if (record.ok === true) return {
|
|
1668
|
+
kind: "ok",
|
|
1669
|
+
sizeBytes: typeof record.sizeBytes === "number" ? record.sizeBytes : 0,
|
|
1670
|
+
names: typeof record.names === "number" ? record.names : 0
|
|
1671
|
+
};
|
|
1672
|
+
if (record.skipped === true) return {
|
|
1673
|
+
kind: "skipped",
|
|
1674
|
+
reason: typeof record.reason === "string" ? record.reason : "size cap",
|
|
1675
|
+
...typeof record.estimateBytes === "number" ? { estimateBytes: record.estimateBytes } : {},
|
|
1676
|
+
...typeof record.sizeBytes === "number" ? { sizeBytes: record.sizeBytes } : {},
|
|
1677
|
+
capBytes: typeof record.capBytes === "number" ? record.capBytes : 0
|
|
1678
|
+
};
|
|
1679
|
+
return {
|
|
1680
|
+
kind: "failed",
|
|
1681
|
+
reason: typeof record.reason === "string" ? record.reason : "snapshot failed"
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Restore a dill snapshot into the user namespace via an internal cell. The
|
|
1686
|
+
* kernel validates the manifest (python version, interpreter identity,
|
|
1687
|
+
* skills) and reports restored vs degraded through a nonce envelope; a
|
|
1688
|
+
* degraded restore leaves the namespace empty.
|
|
1689
|
+
* @param payloadPath - dill payload source.
|
|
1690
|
+
* @param manifestPath - JSON manifest source.
|
|
1691
|
+
*/
|
|
1692
|
+
async restoreState(payloadPath, manifestPath) {
|
|
1693
|
+
if (!this.isRunning) return {
|
|
1694
|
+
kind: "degraded",
|
|
1695
|
+
reason: "kernel is not running"
|
|
1696
|
+
};
|
|
1697
|
+
const sentinel = `__dashr_restore_${randomBytes(9).toString("hex")}__`;
|
|
1698
|
+
const outcome = await this.executeCell(buildRestoreCell(payloadPath, manifestPath, sentinel), {
|
|
1699
|
+
timeoutMs: this.options.snapshotTimeoutMs,
|
|
1700
|
+
interruptGraceMs: 1e3,
|
|
1701
|
+
interruptConfirmMs: Math.min(this.options.interruptConfirmMs, 500)
|
|
1702
|
+
});
|
|
1703
|
+
if (outcome.outcome !== "completed" || outcome.status !== "ok") return {
|
|
1704
|
+
kind: "degraded",
|
|
1705
|
+
reason: "restore cell failed"
|
|
1706
|
+
};
|
|
1707
|
+
const capture = extractStream(outcome.streamText, sentinel);
|
|
1708
|
+
if (capture.envelope === void 0) return {
|
|
1709
|
+
kind: "degraded",
|
|
1710
|
+
reason: "restore cell produced no envelope"
|
|
1711
|
+
};
|
|
1712
|
+
let envelope;
|
|
1713
|
+
try {
|
|
1714
|
+
envelope = JSON.parse(capture.envelope);
|
|
1715
|
+
} catch {
|
|
1716
|
+
return {
|
|
1717
|
+
kind: "degraded",
|
|
1718
|
+
reason: "restore envelope was not valid JSON"
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
if (typeof envelope !== "object" || envelope === null) return {
|
|
1722
|
+
kind: "degraded",
|
|
1723
|
+
reason: "restore envelope was not an object"
|
|
1724
|
+
};
|
|
1725
|
+
const record = envelope;
|
|
1726
|
+
if (record.ok === true) return {
|
|
1727
|
+
kind: "restored",
|
|
1728
|
+
count: typeof record.restored === "number" ? record.restored : 0
|
|
1729
|
+
};
|
|
1730
|
+
return {
|
|
1731
|
+
kind: "degraded",
|
|
1732
|
+
reason: typeof record.reason === "string" ? record.reason : "snapshot not replayable"
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Read one user-namespace variable by name (or list the namespace's user
|
|
1737
|
+
* names when `name` is `null`) via an internal cell. JSON-serializable
|
|
1738
|
+
* values cross as `kind: 'json'` text; anything else falls back to `repr`
|
|
1739
|
+
* text (`kind: 'repr'`) so a non-JSON variable is still readable. A missing
|
|
1740
|
+
* name reports `kind: 'missing'` rather than failing the cell.
|
|
1741
|
+
* @param name - the exact variable name, or `null` to list namespace names.
|
|
1742
|
+
*/
|
|
1743
|
+
async queryVar(name$1) {
|
|
1744
|
+
if (!this.isRunning) return {
|
|
1745
|
+
kind: "failed",
|
|
1746
|
+
reason: "kernel is not running"
|
|
1747
|
+
};
|
|
1748
|
+
const sentinel = `__dashr_query_${randomBytes(9).toString("hex")}__`;
|
|
1749
|
+
const outcome = await this.executeCell(buildQueryVarCell(name$1, sentinel), {
|
|
1750
|
+
timeoutMs: this.options.snapshotTimeoutMs,
|
|
1751
|
+
interruptGraceMs: 1e3,
|
|
1752
|
+
interruptConfirmMs: Math.min(this.options.interruptConfirmMs, 500)
|
|
1753
|
+
});
|
|
1754
|
+
if (outcome.outcome !== "completed" || outcome.status !== "ok") return {
|
|
1755
|
+
kind: "failed",
|
|
1756
|
+
reason: "query cell failed"
|
|
1757
|
+
};
|
|
1758
|
+
const capture = extractStream(outcome.streamText, sentinel);
|
|
1759
|
+
if (capture.envelope === void 0) return {
|
|
1760
|
+
kind: "failed",
|
|
1761
|
+
reason: "query cell produced no envelope"
|
|
1762
|
+
};
|
|
1763
|
+
let envelope;
|
|
1764
|
+
try {
|
|
1765
|
+
envelope = JSON.parse(capture.envelope);
|
|
1766
|
+
} catch {
|
|
1767
|
+
return {
|
|
1768
|
+
kind: "failed",
|
|
1769
|
+
reason: "query envelope was not valid JSON"
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
if (!isRecord(envelope)) return {
|
|
1773
|
+
kind: "failed",
|
|
1774
|
+
reason: "query envelope was not an object"
|
|
1775
|
+
};
|
|
1776
|
+
if (envelope.ok !== true) return {
|
|
1777
|
+
kind: "failed",
|
|
1778
|
+
reason: typeof envelope.reason === "string" ? envelope.reason : "query failed"
|
|
1779
|
+
};
|
|
1780
|
+
const kind = envelope.kind;
|
|
1781
|
+
if (kind === "names") return {
|
|
1782
|
+
kind: "names",
|
|
1783
|
+
names: Array.isArray(envelope.names) ? envelope.names.filter((name$2) => typeof name$2 === "string") : []
|
|
1784
|
+
};
|
|
1785
|
+
if (kind === "missing") return { kind: "missing" };
|
|
1786
|
+
if (kind === "json" || kind === "repr") return typeof envelope.text === "string" ? {
|
|
1787
|
+
kind,
|
|
1788
|
+
text: envelope.text
|
|
1789
|
+
} : {
|
|
1790
|
+
kind: "failed",
|
|
1791
|
+
reason: "query envelope carried no text"
|
|
1792
|
+
};
|
|
1793
|
+
return {
|
|
1794
|
+
kind: "failed",
|
|
1795
|
+
reason: `unknown query kind ${JSON.stringify(kind)}`
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Assign one pre-serialized JSON value into the user namespace under
|
|
1800
|
+
* `name` via an internal cell. The host already validated the name and the
|
|
1801
|
+
* value; the cell only decodes and binds. Uses the same busy guard and
|
|
1802
|
+
* internal-cell budget as the snapshot cells.
|
|
1803
|
+
* @param name - the validated identifier to assign under.
|
|
1804
|
+
* @param valueJson - the lossless-JSON text to decode and bind.
|
|
1805
|
+
*/
|
|
1806
|
+
async setVar(name$1, valueJson) {
|
|
1807
|
+
if (!this.isRunning) return {
|
|
1808
|
+
kind: "failed",
|
|
1809
|
+
reason: "kernel is not running"
|
|
1810
|
+
};
|
|
1811
|
+
const sentinel = `__dashr_set_${randomBytes(9).toString("hex")}__`;
|
|
1812
|
+
const outcome = await this.executeCell(buildSetVarCell(name$1, valueJson, sentinel), {
|
|
1813
|
+
timeoutMs: this.options.snapshotTimeoutMs,
|
|
1814
|
+
interruptGraceMs: 1e3,
|
|
1815
|
+
interruptConfirmMs: Math.min(this.options.interruptConfirmMs, 500)
|
|
1816
|
+
});
|
|
1817
|
+
if (outcome.outcome !== "completed" || outcome.status !== "ok") return {
|
|
1818
|
+
kind: "failed",
|
|
1819
|
+
reason: "set cell failed"
|
|
1820
|
+
};
|
|
1821
|
+
const capture = extractStream(outcome.streamText, sentinel);
|
|
1822
|
+
if (capture.envelope === void 0) return {
|
|
1823
|
+
kind: "failed",
|
|
1824
|
+
reason: "set cell produced no envelope"
|
|
1825
|
+
};
|
|
1826
|
+
let envelope;
|
|
1827
|
+
try {
|
|
1828
|
+
envelope = JSON.parse(capture.envelope);
|
|
1829
|
+
} catch {
|
|
1830
|
+
return {
|
|
1831
|
+
kind: "failed",
|
|
1832
|
+
reason: "set envelope was not valid JSON"
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
if (!isRecord(envelope) || envelope.ok !== true) return {
|
|
1836
|
+
kind: "failed",
|
|
1837
|
+
reason: isRecord(envelope) && typeof envelope.reason === "string" ? envelope.reason : "set failed"
|
|
1838
|
+
};
|
|
1839
|
+
return { kind: "ok" };
|
|
1840
|
+
}
|
|
1841
|
+
onKernelDeath() {
|
|
1842
|
+
this.state = "shutdown";
|
|
1843
|
+
this.forceSettleActive({
|
|
1844
|
+
kind: "worker-exit",
|
|
1845
|
+
message: `kernel process died. stderr tail:\n${this.stderrTail()}`
|
|
1846
|
+
});
|
|
1847
|
+
this.cleanupResources();
|
|
1848
|
+
}
|
|
1849
|
+
cleanupResources() {
|
|
1850
|
+
this.transport?.dispose();
|
|
1851
|
+
this.transport = void 0;
|
|
1852
|
+
this.connection = void 0;
|
|
1853
|
+
try {
|
|
1854
|
+
this.kernel?.kill("SIGKILL");
|
|
1855
|
+
} catch {}
|
|
1856
|
+
this.kernel = void 0;
|
|
1857
|
+
if (this.tempDir) try {
|
|
1858
|
+
rmSync(this.tempDir, {
|
|
1859
|
+
recursive: true,
|
|
1860
|
+
force: true
|
|
1861
|
+
});
|
|
1862
|
+
} catch {}
|
|
1863
|
+
this.tempDir = void 0;
|
|
1864
|
+
}
|
|
1865
|
+
/** Graceful teardown: shutdown_request, then SIGKILL after the dispose budget. */
|
|
1866
|
+
async dispose() {
|
|
1867
|
+
if (this.state === "shutdown") {
|
|
1868
|
+
this.cleanupResources();
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
this.state = "shutdown";
|
|
1872
|
+
try {
|
|
1873
|
+
if (this.transport) {
|
|
1874
|
+
this.transport.send("control", buildMessage("shutdown_request", { restart: false }, this.session, this.options.username));
|
|
1875
|
+
await setTimeout$1(SHUTDOWN_DRAIN_MS);
|
|
1876
|
+
}
|
|
1877
|
+
} catch {}
|
|
1878
|
+
const kernel = this.kernel;
|
|
1879
|
+
this.cleanupResources();
|
|
1880
|
+
if (kernel && kernel.exitCode === null && kernel.signalCode === null) await new Promise((resolve$1) => {
|
|
1881
|
+
const timer = setTimeout(() => {
|
|
1882
|
+
kernel.removeListener("exit", onExit);
|
|
1883
|
+
try {
|
|
1884
|
+
kernel.kill("SIGKILL");
|
|
1885
|
+
} catch {}
|
|
1886
|
+
resolve$1();
|
|
1887
|
+
}, this.options.disposeTimeoutMs);
|
|
1888
|
+
timer.unref?.();
|
|
1889
|
+
const onExit = () => {
|
|
1890
|
+
clearTimeout(timer);
|
|
1891
|
+
resolve$1();
|
|
1892
|
+
};
|
|
1893
|
+
kernel.once("exit", onExit);
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1898
|
+
//#endregion
|
|
1899
|
+
//#region src/runtime.ts
|
|
1900
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1901
|
+
/**
|
|
1902
|
+
* Names the kernel-side shim owns (`_dashr`/`__dashr` prefixed, any case —
|
|
1903
|
+
* bootstrap helpers plus the per-run `__dashr_completion__` and
|
|
1904
|
+
* `__dashr_injected__`), refused as binding globals on top of the seam's
|
|
1905
|
+
* shared set.
|
|
1906
|
+
*/
|
|
1907
|
+
const KERNEL_OWNED_NAME = /^_+dashr/i;
|
|
1908
|
+
/** Manifest format version; a snapshot written under a different version is not replayable. */
|
|
1909
|
+
const SNAPSHOT_FORMAT = 1;
|
|
1910
|
+
function messageOf(error) {
|
|
1911
|
+
return error instanceof Error ? error.message : String(error);
|
|
1912
|
+
}
|
|
1913
|
+
/** Key under which the per-session kernel map stores agentless runs. */
|
|
1914
|
+
const AGENTLESS_KEY = "(agentless)";
|
|
1915
|
+
/**
|
|
1916
|
+
* Encode one principal as a single path segment for its per-key snapshot
|
|
1917
|
+
* subdirectory: `encodeURIComponent` neutralizes separators and traversal,
|
|
1918
|
+
* and a leading run of dots (which would encode to itself, addressing `.` or
|
|
1919
|
+
* `..`) is re-escaped so every key lands strictly inside `snapshotDir`.
|
|
1920
|
+
*/
|
|
1921
|
+
function keyDirectoryName(principal) {
|
|
1922
|
+
return encodeURIComponent(principal).replace(/^\.+/, (dots) => dots.replace(/\./g, "%2E"));
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* The {@link ReplRuntime} backend this package registers (`ctx.replRuntime`)
|
|
1926
|
+
* — the standing-mount layer of the v0.1.5 architecture, the de facto daemon
|
|
1927
|
+
* while the profile-level DashrDaemon stays an empty shell. One service
|
|
1928
|
+
* instance per mount holds one lazily-spawned kernel per session principal;
|
|
1929
|
+
* each kernel's lifecycle — snapshot and shutdown on session end, snapshot
|
|
1930
|
+
* and shutdown of every key on plugin disposal — is effect-owned.
|
|
1931
|
+
*/
|
|
1932
|
+
var DashrRuntime = class extends ReplRuntime {
|
|
1933
|
+
static Config = Schema.object({
|
|
1934
|
+
python: Schema.string().default("python3"),
|
|
1935
|
+
startupTimeoutMs: Schema.number().default(3e4),
|
|
1936
|
+
runTimeoutMs: Schema.number().default(12e4),
|
|
1937
|
+
interruptGraceMs: Schema.number().default(2e3),
|
|
1938
|
+
interruptConfirmMs: Schema.number().default(250),
|
|
1939
|
+
disposeTimeoutMs: Schema.number().default(5e3),
|
|
1940
|
+
snapshotTimeoutMs: Schema.number().default(3e4),
|
|
1941
|
+
maxOutputBytes: Schema.number().default(67108864),
|
|
1942
|
+
snapshotDir: Schema.string(),
|
|
1943
|
+
snapshotSizeCapBytes: Schema.number().default(268435456),
|
|
1944
|
+
kernelEnvDir: Schema.string(),
|
|
1945
|
+
kernelPythonVersion: Schema.string(),
|
|
1946
|
+
kernelAutoInstall: Schema.boolean().default(true),
|
|
1947
|
+
username: Schema.string().default("dashr")
|
|
1948
|
+
});
|
|
1949
|
+
language = "python";
|
|
1950
|
+
isolation = "process";
|
|
1951
|
+
config;
|
|
1952
|
+
logger;
|
|
1953
|
+
/** One entry per session principal that has run code (lazy — never pre-seeded). */
|
|
1954
|
+
kernels = /* @__PURE__ */ new Map();
|
|
1955
|
+
disposed = false;
|
|
1956
|
+
/** Lazily-resolved (and, when managed, provisioned) kernel interpreter. */
|
|
1957
|
+
kernelEnvPromise;
|
|
1958
|
+
constructor(ctx, config) {
|
|
1959
|
+
super(ctx);
|
|
1960
|
+
this.config = config;
|
|
1961
|
+
for (const key of [
|
|
1962
|
+
"startupTimeoutMs",
|
|
1963
|
+
"runTimeoutMs",
|
|
1964
|
+
"interruptGraceMs",
|
|
1965
|
+
"disposeTimeoutMs",
|
|
1966
|
+
"snapshotTimeoutMs"
|
|
1967
|
+
]) {
|
|
1968
|
+
const value = this.config[key];
|
|
1969
|
+
if (!(Number.isFinite(value) && value > 0)) throw new Error(`dashr-repl: config.${key} must be a positive number, got ${String(value)}`);
|
|
1970
|
+
}
|
|
1971
|
+
if (!(this.config.interruptConfirmMs > 0 && this.config.interruptConfirmMs < this.config.interruptGraceMs)) throw new Error(`dashr-repl: config.interruptConfirmMs must be positive and below interruptGraceMs (${this.config.interruptGraceMs}), got ${String(this.config.interruptConfirmMs)}`);
|
|
1972
|
+
if (!Number.isSafeInteger(this.config.maxOutputBytes) || this.config.maxOutputBytes < MIN_OUTPUT_BYTES) throw new Error(`dashr-repl: config.maxOutputBytes must be a safe integer of at least ${MIN_OUTPUT_BYTES}, got ${String(this.config.maxOutputBytes)}`);
|
|
1973
|
+
if (!Number.isSafeInteger(this.config.snapshotSizeCapBytes) || this.config.snapshotSizeCapBytes < 1) throw new Error(`dashr-repl: config.snapshotSizeCapBytes must be a positive safe integer, got ${String(this.config.snapshotSizeCapBytes)}`);
|
|
1974
|
+
this.logger = ctx.logger("dashr-repl");
|
|
1975
|
+
ctx.effect(() => () => this.teardown(), "eval code-runtime teardown");
|
|
1976
|
+
ctx.events.on("agent/disposed", (payload) => {
|
|
1977
|
+
const principal = payload?.agent?.id;
|
|
1978
|
+
if (typeof principal === "string" && principal.length > 0) this.destroyKey(principal).catch(() => void 0);
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* The agentless-default kernel's pid, for lifecycle diagnostics; absent
|
|
1983
|
+
* before the first agentless run. Per-session pids: {@link kernelPids}.
|
|
1984
|
+
*/
|
|
1985
|
+
get kernelPid() {
|
|
1986
|
+
return this.kernels.get(AGENTLESS_KEY)?.bridge.pid;
|
|
1987
|
+
}
|
|
1988
|
+
/** Every live kernel subprocess pid, one per session that has run code. */
|
|
1989
|
+
get kernelPids() {
|
|
1990
|
+
return [...this.kernels.values()].map((entry) => entry.bridge.pid).filter((pid) => pid !== void 0);
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Execute one program as one cell on the persistent kernel. Program
|
|
1994
|
+
* outcomes — including kernel startup failure — resolve with
|
|
1995
|
+
* `result.error`; the method rejects only for Service Definition contract
|
|
1996
|
+
* misuse (a disposed runtime, an invalid binding namespace).
|
|
1997
|
+
* @param request - the program, its bindings, and the abort signal.
|
|
1998
|
+
* @returns the run's outcome per the seam contract.
|
|
1999
|
+
*/
|
|
2000
|
+
async run(request) {
|
|
2001
|
+
if (this.disposed) throw new Error("dashr-repl: run() after disposal");
|
|
2002
|
+
const bindings = this.validateBindings(request);
|
|
2003
|
+
if (request.signal?.aborted) return {
|
|
2004
|
+
logs: [],
|
|
2005
|
+
error: {
|
|
2006
|
+
kind: "abort",
|
|
2007
|
+
message: String(request.signal.reason)
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2010
|
+
const key = request.principal && request.principal.length > 0 ? request.principal : AGENTLESS_KEY;
|
|
2011
|
+
if (request.reset) await this.resetKey(key);
|
|
2012
|
+
const dead = this.kernels.get(key);
|
|
2013
|
+
if (dead?.bridge.isDead) {
|
|
2014
|
+
this.kernels.delete(key);
|
|
2015
|
+
if (dead.ranCells) return await this.reviveAfterDeath(key, dead);
|
|
2016
|
+
}
|
|
2017
|
+
let entry;
|
|
2018
|
+
try {
|
|
2019
|
+
entry = await this.ensureKernel(key, request.cwd);
|
|
2020
|
+
} catch (error) {
|
|
2021
|
+
return finalizeOutput([], void 0, {
|
|
2022
|
+
kind: "worker-exit",
|
|
2023
|
+
message: `kernel failed to start: ${messageOf(error)}`
|
|
2024
|
+
}, this.config.maxOutputBytes);
|
|
2025
|
+
}
|
|
2026
|
+
if (!entry.restoreAttempted) {
|
|
2027
|
+
entry.restoreAttempted = true;
|
|
2028
|
+
const restore = await this.restoreEntry(entry);
|
|
2029
|
+
if (restore.kind === "restored") entry.pendingNotice = `namespace restored from the turn-${restore.turn} snapshot`;
|
|
2030
|
+
else if (restore.kind === "degraded") entry.pendingNotice = `snapshot not replayable (${restore.reason}); started from an EMPTY namespace — re-create any state you need`;
|
|
2031
|
+
}
|
|
2032
|
+
const kernel = entry.bridge;
|
|
2033
|
+
const specs = Object.fromEntries([...bindings.entries()].map(([global, namespace]) => [global, {
|
|
2034
|
+
functions: Object.keys(namespace.functions),
|
|
2035
|
+
...namespace.callable ? { callable: true } : {},
|
|
2036
|
+
...namespace.errorClass ? { errorClass: namespace.errorClass } : {}
|
|
2037
|
+
}]));
|
|
2038
|
+
const sentinel = `__dashr_${randomBytes(9).toString("hex")}__`;
|
|
2039
|
+
const cell = buildRunCell(request.program, JSON.stringify(specs), sentinel);
|
|
2040
|
+
const outcome = await kernel.executeCell(cell, {
|
|
2041
|
+
timeoutMs: request.timeoutMs ?? this.config.runTimeoutMs,
|
|
2042
|
+
interruptGraceMs: this.config.interruptGraceMs,
|
|
2043
|
+
interruptConfirmMs: this.config.interruptConfirmMs,
|
|
2044
|
+
...request.signal ? { signal: request.signal } : {},
|
|
2045
|
+
hostRequestHandler: (data) => this.dispatchHostRequest(data, bindings)
|
|
2046
|
+
});
|
|
2047
|
+
if (outcome.outcome === "completed") {
|
|
2048
|
+
entry.ranCells = true;
|
|
2049
|
+
entry.turn += 1;
|
|
2050
|
+
} else if (outcome.outcome === "forced" && outcome.failure.kind === "worker-exit" && kernel.isDead && !this.disposed) {
|
|
2051
|
+
if (this.kernels.get(key) === entry) this.kernels.delete(key);
|
|
2052
|
+
if (entry.ranCells) return await this.reviveAfterDeath(key, entry);
|
|
2053
|
+
}
|
|
2054
|
+
let result = this.assembleResult(outcome, sentinel);
|
|
2055
|
+
if (outcome.outcome === "completed") {
|
|
2056
|
+
const snapshot = await this.snapshotTurnEnd(entry);
|
|
2057
|
+
const notices = [];
|
|
2058
|
+
if (snapshot.skipped && !entry.warnedSnapshotSkip) {
|
|
2059
|
+
entry.warnedSnapshotSkip = true;
|
|
2060
|
+
const detail = snapshot.estimateBytes !== void 0 ? `estimated ${snapshot.estimateBytes} bytes` : `${snapshot.sizeBytes ?? "unknown"} bytes serialized`;
|
|
2061
|
+
notices.push(`[dashr] namespace snapshot skipped: ${detail} exceeds snapshotSizeCapBytes (${this.config.snapshotSizeCapBytes}) — later turn-end snapshots are skipped while the namespace stays this large`);
|
|
2062
|
+
}
|
|
2063
|
+
if (entry.pendingNotice) {
|
|
2064
|
+
notices.push(`[dashr] ${entry.pendingNotice}`);
|
|
2065
|
+
entry.pendingNotice = void 0;
|
|
2066
|
+
}
|
|
2067
|
+
if (notices.length > 0) result = finalizeOutput([...result.logs, ...notices], result.value, result.error, this.config.maxOutputBytes);
|
|
2068
|
+
}
|
|
2069
|
+
return result;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Read one user-namespace variable by name on the session's kernel. A
|
|
2073
|
+
* bare/empty name lists the namespace's user-variable names; a
|
|
2074
|
+
* JSON-serializable value resolves to its JSON text, any other value to its
|
|
2075
|
+
* `repr` text, and a missing name to `{ kind: 'missing' }`. When no live
|
|
2076
|
+
* kernel holds state for the key the namespace is empty (no spawn — this
|
|
2077
|
+
* channel never creates a kernel). Pure additive: the run/execute/snapshot
|
|
2078
|
+
* lifecycle is untouched.
|
|
2079
|
+
* @param name - the variable name, or empty to list namespace names.
|
|
2080
|
+
* @param principal - the session key (absent → the shared default).
|
|
2081
|
+
*/
|
|
2082
|
+
async queryVar(name$1, principal) {
|
|
2083
|
+
if (this.disposed) throw new Error("dashr-repl: queryVar() after disposal");
|
|
2084
|
+
const key = principal && principal.length > 0 ? principal : AGENTLESS_KEY;
|
|
2085
|
+
const bridge = this.kernels.get(key)?.bridge;
|
|
2086
|
+
if (!bridge || !bridge.isRunning) return name$1.trim().length === 0 ? {
|
|
2087
|
+
kind: "names",
|
|
2088
|
+
names: []
|
|
2089
|
+
} : { kind: "missing" };
|
|
2090
|
+
const outcome = await bridge.queryVar(name$1.trim().length === 0 ? null : name$1);
|
|
2091
|
+
switch (outcome.kind) {
|
|
2092
|
+
case "json": return {
|
|
2093
|
+
kind: "json",
|
|
2094
|
+
text: outcome.text
|
|
2095
|
+
};
|
|
2096
|
+
case "repr": return {
|
|
2097
|
+
kind: "repr",
|
|
2098
|
+
text: outcome.text
|
|
2099
|
+
};
|
|
2100
|
+
case "names": return {
|
|
2101
|
+
kind: "names",
|
|
2102
|
+
names: outcome.names
|
|
2103
|
+
};
|
|
2104
|
+
case "missing": return { kind: "missing" };
|
|
2105
|
+
case "failed": throw new Error(`dashr-repl: queryVar(${JSON.stringify(name$1)}) failed: ${outcome.reason}`);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Assign one lossless-JSON value into the user namespace under `name` on
|
|
2110
|
+
* the session's kernel. `name` must be a usable identifier (and not a
|
|
2111
|
+
* kernel-shim name); `value` must be lossless JSON. Requires a live kernel
|
|
2112
|
+
* for the key — the channel never spawns one, so a session that has not yet
|
|
2113
|
+
* run a cell must `run` first. Pure additive to the existing lifecycle.
|
|
2114
|
+
* @param name - the identifier to assign under.
|
|
2115
|
+
* @param value - the lossless-JSON value to bind.
|
|
2116
|
+
* @param principal - the session key (absent → the shared default).
|
|
2117
|
+
*/
|
|
2118
|
+
async setVar(name$1, value, principal) {
|
|
2119
|
+
if (this.disposed) throw new Error("dashr-repl: setVar() after disposal");
|
|
2120
|
+
const trimmed = name$1.trim();
|
|
2121
|
+
if (trimmed.length === 0) throw new Error("dashr-repl: setVar() requires a non-empty variable name");
|
|
2122
|
+
if (!IDENTIFIER.test(trimmed)) throw new Error(`dashr-repl: setVar() name ${JSON.stringify(trimmed)} is not a usable identifier`);
|
|
2123
|
+
if (KERNEL_OWNED_NAME.test(trimmed)) throw new Error(`dashr-repl: setVar() name ${JSON.stringify(trimmed)} is reserved by the kernel`);
|
|
2124
|
+
const jsonValue = snapshotJsonValue(value);
|
|
2125
|
+
if (jsonValue === void 0) throw new Error("dashr-repl: setVar() value must be lossless JSON");
|
|
2126
|
+
const key = principal && principal.length > 0 ? principal : AGENTLESS_KEY;
|
|
2127
|
+
const bridge = this.kernels.get(key)?.bridge;
|
|
2128
|
+
if (!bridge || !bridge.isRunning) throw new Error("dashr-repl: setVar() requires a live kernel for this session — run a cell first");
|
|
2129
|
+
const outcome = await bridge.setVar(trimmed, JSON.stringify(jsonValue));
|
|
2130
|
+
if (outcome.kind === "failed") throw new Error(`dashr-repl: setVar(${JSON.stringify(trimmed)}) failed: ${outcome.reason}`);
|
|
2131
|
+
}
|
|
2132
|
+
/** Map one cell outcome onto the seam's result shape under the output ledger. */
|
|
2133
|
+
assembleResult(outcome, sentinel) {
|
|
2134
|
+
const capture = extractStream(outcome.streamText, sentinel);
|
|
2135
|
+
const maxBytes = this.config.maxOutputBytes;
|
|
2136
|
+
if (outcome.outcome === "forced") return finalizeOutput(capture.logs, void 0, outcome.failure, maxBytes);
|
|
2137
|
+
if (outcome.status === "error") {
|
|
2138
|
+
const failure = outcome.cellError ? {
|
|
2139
|
+
kind: "exception",
|
|
2140
|
+
message: plainTraceback(outcome.cellError.ename, outcome.cellError.evalue, outcome.cellError.traceback)
|
|
2141
|
+
} : {
|
|
2142
|
+
kind: "exception",
|
|
2143
|
+
message: "the program failed without a traceback"
|
|
2144
|
+
};
|
|
2145
|
+
return finalizeOutput(capture.logs, void 0, failure, maxBytes);
|
|
2146
|
+
}
|
|
2147
|
+
if (capture.envelope === void 0) return finalizeOutput(capture.logs, void 0, void 0, maxBytes);
|
|
2148
|
+
let envelope;
|
|
2149
|
+
try {
|
|
2150
|
+
envelope = JSON.parse(capture.envelope);
|
|
2151
|
+
} catch {
|
|
2152
|
+
return finalizeOutput(capture.logs, void 0, {
|
|
2153
|
+
kind: "invalid-output",
|
|
2154
|
+
message: "completion envelope was not valid JSON"
|
|
2155
|
+
}, maxBytes);
|
|
2156
|
+
}
|
|
2157
|
+
if (typeof envelope !== "object" || envelope === null || envelope.ok !== true) return finalizeOutput(capture.logs, void 0, {
|
|
2158
|
+
kind: "invalid-output",
|
|
2159
|
+
message: "program completion must be lossless JSON"
|
|
2160
|
+
}, maxBytes);
|
|
2161
|
+
const json = envelope.json;
|
|
2162
|
+
if (typeof json !== "string") return finalizeOutput(capture.logs, void 0, void 0, maxBytes);
|
|
2163
|
+
return finalizeOutput(capture.logs, JSON.parse(json), void 0, maxBytes);
|
|
2164
|
+
}
|
|
2165
|
+
/** Reject malformed binding globals or typed-error declarations as contract misuse. */
|
|
2166
|
+
validateBindings(request) {
|
|
2167
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
2168
|
+
for (const namespace of request.bindings) {
|
|
2169
|
+
if (!IDENTIFIER.test(namespace.global) || PORTABLE_RESERVED_WORDS.has(namespace.global)) throw new Error(`dashr-repl: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`);
|
|
2170
|
+
if (RESERVED_BINDING_GLOBALS.has(namespace.global) || KERNEL_OWNED_NAME.test(namespace.global)) throw new Error(`dashr-repl: reserved binding global ${JSON.stringify(namespace.global)}`);
|
|
2171
|
+
if (bindings.has(namespace.global)) throw new Error(`dashr-repl: duplicate binding global ${JSON.stringify(namespace.global)}`);
|
|
2172
|
+
if (namespace.callable === true && Object.keys(namespace.functions).length !== 1) throw new Error(`dashr-repl: callable binding global ${JSON.stringify(namespace.global)} must declare exactly one function`);
|
|
2173
|
+
bindings.set(namespace.global, namespace);
|
|
2174
|
+
}
|
|
2175
|
+
const errorClasses = /* @__PURE__ */ new Map();
|
|
2176
|
+
for (const namespace of request.bindings) {
|
|
2177
|
+
const descriptor = namespace.errorClass;
|
|
2178
|
+
if (!descriptor) continue;
|
|
2179
|
+
if (!IDENTIFIER.test(descriptor.name) || PORTABLE_RESERVED_WORDS.has(descriptor.name)) throw new Error(`dashr-repl: binding error class ${JSON.stringify(descriptor.name)} is not a usable identifier`);
|
|
2180
|
+
if (RESERVED_BINDING_GLOBALS.has(descriptor.name) || KERNEL_OWNED_NAME.test(descriptor.name)) throw new Error(`dashr-repl: reserved binding global ${JSON.stringify(descriptor.name)}`);
|
|
2181
|
+
if (bindings.has(descriptor.name)) throw new Error(`dashr-repl: duplicate injected global ${JSON.stringify(descriptor.name)}`);
|
|
2182
|
+
const member = descriptor.memberNameProperty;
|
|
2183
|
+
if (member.length === 0 || RESERVED_ERROR_MEMBERS.has(member) || DUNDER_MEMBER.test(member)) throw new Error(`dashr-repl: binding error member property ${JSON.stringify(descriptor.memberNameProperty)} is not usable`);
|
|
2184
|
+
const declared = errorClasses.get(descriptor.name);
|
|
2185
|
+
if (declared !== void 0 && declared !== member) throw new Error(`dashr-repl: error class ${JSON.stringify(descriptor.name)} declared with conflicting member properties ${JSON.stringify(declared)} and ${JSON.stringify(member)}`);
|
|
2186
|
+
errorClasses.set(descriptor.name, member);
|
|
2187
|
+
}
|
|
2188
|
+
return bindings;
|
|
2189
|
+
}
|
|
2190
|
+
/** Dispatch one kernel-side host request; unknown types and bad payloads become error replies. */
|
|
2191
|
+
async dispatchHostRequest(data, bindings) {
|
|
2192
|
+
if (data.type !== "binding.call" || typeof data.global !== "string" || typeof data.name !== "string") return {
|
|
2193
|
+
ok: false,
|
|
2194
|
+
message: "host request must be a binding.call with global and name"
|
|
2195
|
+
};
|
|
2196
|
+
const record = bindings.get(data.global)?.functions;
|
|
2197
|
+
const fn = record && Object.hasOwn(record, data.name) ? record[data.name] : void 0;
|
|
2198
|
+
if (typeof fn !== "function") return {
|
|
2199
|
+
ok: false,
|
|
2200
|
+
message: `unknown binding ${JSON.stringify(`${data.global}.${data.name}`)}`
|
|
2201
|
+
};
|
|
2202
|
+
const args = snapshotJsonValue(data.args);
|
|
2203
|
+
if (args === void 0) return {
|
|
2204
|
+
ok: false,
|
|
2205
|
+
message: "binding arguments must be lossless JSON"
|
|
2206
|
+
};
|
|
2207
|
+
let resolved;
|
|
2208
|
+
try {
|
|
2209
|
+
resolved = await fn(args);
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
return {
|
|
2212
|
+
ok: false,
|
|
2213
|
+
message: messageOf(error)
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
const value = snapshotJsonValue(resolved);
|
|
2217
|
+
if (value === void 0) return {
|
|
2218
|
+
ok: false,
|
|
2219
|
+
message: "binding resolution must be lossless JSON"
|
|
2220
|
+
};
|
|
2221
|
+
return {
|
|
2222
|
+
ok: true,
|
|
2223
|
+
result: value
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
/** Resolve (and provision, when managed) the kernel interpreter once per runtime. */
|
|
2227
|
+
resolveKernelPython() {
|
|
2228
|
+
this.kernelEnvPromise ??= resolveKernelEnv({
|
|
2229
|
+
python: this.config.python,
|
|
2230
|
+
venvDir: this.config.kernelEnvDir,
|
|
2231
|
+
pythonVersion: this.config.kernelPythonVersion,
|
|
2232
|
+
autoInstall: this.config.kernelAutoInstall,
|
|
2233
|
+
log: (level, message) => {
|
|
2234
|
+
if (level === "warn") this.logger.warn(message);
|
|
2235
|
+
else this.logger.info(message);
|
|
2236
|
+
}
|
|
2237
|
+
}).catch((error) => {
|
|
2238
|
+
this.kernelEnvPromise = void 0;
|
|
2239
|
+
throw error;
|
|
2240
|
+
});
|
|
2241
|
+
return this.kernelEnvPromise;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Spawn-or-reuse the kernel for one key. The lazy map guarantees one
|
|
2245
|
+
* entry is created here and nowhere else, so a key that never runs never
|
|
2246
|
+
* holds a subprocess — the subagent fan-out guarantee (blueprint §6:
|
|
2247
|
+
* subagent ×N spawns nothing until a child actually executes code).
|
|
2248
|
+
* @param key - the run principal (or the agentless default).
|
|
2249
|
+
*/
|
|
2250
|
+
async ensureKernel(key, cwd) {
|
|
2251
|
+
let entry = this.kernels.get(key);
|
|
2252
|
+
if (!entry) {
|
|
2253
|
+
const kernelCwd = cwd && existsSync(cwd) ? cwd : void 0;
|
|
2254
|
+
entry = {
|
|
2255
|
+
key,
|
|
2256
|
+
cwd: kernelCwd,
|
|
2257
|
+
bridge: new IpyKernelBridge({
|
|
2258
|
+
python: (await this.resolveKernelPython()).python,
|
|
2259
|
+
...kernelCwd ? { cwd: kernelCwd } : {},
|
|
2260
|
+
startupTimeoutMs: this.config.startupTimeoutMs,
|
|
2261
|
+
disposeTimeoutMs: this.config.disposeTimeoutMs,
|
|
2262
|
+
interruptConfirmMs: this.config.interruptConfirmMs,
|
|
2263
|
+
snapshotTimeoutMs: this.config.snapshotTimeoutMs,
|
|
2264
|
+
username: this.config.username
|
|
2265
|
+
}),
|
|
2266
|
+
ranCells: false,
|
|
2267
|
+
turn: 0
|
|
2268
|
+
};
|
|
2269
|
+
this.kernels.set(key, entry);
|
|
2270
|
+
}
|
|
2271
|
+
await entry.bridge.start();
|
|
2272
|
+
return entry;
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Restore a key's on-disk snapshot into a freshly booted kernel, when one
|
|
2276
|
+
* exists and its manifest is plausible. Called once per entry boot. The
|
|
2277
|
+
* kernel-side restore cell performs the authoritative environment checks
|
|
2278
|
+
* (python version, interpreter identity, skills); this method only gates on
|
|
2279
|
+
* the manifest the host can read cheaply, then records the turn so the
|
|
2280
|
+
* first-run notice and the revive chain can name it.
|
|
2281
|
+
*/
|
|
2282
|
+
async restoreEntry(entry) {
|
|
2283
|
+
if (!this.config.snapshotDir) return { kind: "none" };
|
|
2284
|
+
const dir = join(this.config.snapshotDir, keyDirectoryName(entry.key));
|
|
2285
|
+
const payloadPath = join(dir, "state.dill");
|
|
2286
|
+
const manifestPath = join(dir, "manifest.json");
|
|
2287
|
+
const manifest = this.readSnapshotManifest(entry.key);
|
|
2288
|
+
if (manifest === void 0) return { kind: "none" };
|
|
2289
|
+
if (manifest.snapshotFormat !== SNAPSHOT_FORMAT) return {
|
|
2290
|
+
kind: "degraded",
|
|
2291
|
+
reason: `snapshot format ${String(manifest.snapshotFormat)} is not replayable`
|
|
2292
|
+
};
|
|
2293
|
+
if (manifest.skipped === true) return { kind: "none" };
|
|
2294
|
+
if (!existsSync(payloadPath)) return {
|
|
2295
|
+
kind: "degraded",
|
|
2296
|
+
reason: "snapshot payload is missing"
|
|
2297
|
+
};
|
|
2298
|
+
const restored = await entry.bridge.restoreState(payloadPath, manifestPath);
|
|
2299
|
+
if (restored.kind === "restored") {
|
|
2300
|
+
entry.turn = typeof manifest.turn === "number" && Number.isSafeInteger(manifest.turn) && manifest.turn >= 0 ? manifest.turn : 0;
|
|
2301
|
+
entry.lastSnapshotTurn = entry.turn;
|
|
2302
|
+
entry.ranCells = true;
|
|
2303
|
+
return {
|
|
2304
|
+
kind: "restored",
|
|
2305
|
+
turn: entry.turn
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
return {
|
|
2309
|
+
kind: "degraded",
|
|
2310
|
+
reason: restored.reason
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
/** Read the per-key manifest's host-relevant fields; absent when unreadable/missing. */
|
|
2314
|
+
readSnapshotManifest(key) {
|
|
2315
|
+
if (!this.config.snapshotDir) return void 0;
|
|
2316
|
+
try {
|
|
2317
|
+
return JSON.parse(readFileSync(join(this.config.snapshotDir, keyDirectoryName(key), "manifest.json"), "utf8"));
|
|
2318
|
+
} catch {
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Revive one session's kernel after death: respawn a fresh kernel, restore
|
|
2324
|
+
* the nearest replayable snapshot onto it (the normal restore path), and
|
|
2325
|
+
* return the death-observing run's explicit error naming the turn. When no
|
|
2326
|
+
* replayable snapshot exists the message falls back to M3-A's fresh-empty
|
|
2327
|
+
* contract. The run itself does NOT execute — an error that lies about a
|
|
2328
|
+
* NameError costs the model more than one dropped cell (blueprint §8.3).
|
|
2329
|
+
* @param key - the run principal.
|
|
2330
|
+
* @param dead - the entry that died (its `turn` is the last completed turn).
|
|
2331
|
+
*/
|
|
2332
|
+
async reviveAfterDeath(key, dead) {
|
|
2333
|
+
let entry;
|
|
2334
|
+
try {
|
|
2335
|
+
entry = await this.ensureKernel(key, dead.cwd);
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
return finalizeOutput([], void 0, {
|
|
2338
|
+
kind: "worker-exit",
|
|
2339
|
+
message: `kernel died and respawn failed (${messageOf(error)}); the next run will retry a fresh kernel`
|
|
2340
|
+
}, this.config.maxOutputBytes);
|
|
2341
|
+
}
|
|
2342
|
+
entry.restoreAttempted = true;
|
|
2343
|
+
const restore = await this.restoreEntry(entry);
|
|
2344
|
+
const observingTurn = dead.turn + 1;
|
|
2345
|
+
if (restore.kind === "restored") {
|
|
2346
|
+
const lost = observingTurn - restore.turn;
|
|
2347
|
+
const rounds = lost === 1 ? "round" : "rounds";
|
|
2348
|
+
return finalizeOutput([], void 0, {
|
|
2349
|
+
kind: "worker-exit",
|
|
2350
|
+
message: `the kernel for this session died; its namespace was restored from the turn-${restore.turn} snapshot — the last ${lost} ${rounds} of variable operations must be replayed. This run did not execute.`
|
|
2351
|
+
}, this.config.maxOutputBytes);
|
|
2352
|
+
}
|
|
2353
|
+
return finalizeOutput([], void 0, {
|
|
2354
|
+
kind: "worker-exit",
|
|
2355
|
+
message: `the kernel for this session died and no replayable snapshot was available${restore.kind === "degraded" ? ` (${restore.reason})` : ""}; the next run starts a fresh kernel with an EMPTY namespace — re-create any state you still need. This run did not execute.`
|
|
2356
|
+
}, this.config.maxOutputBytes);
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Write one key's turn-end snapshot, when a snapshot directory is
|
|
2360
|
+
* configured. A size-cap skip is reported (so the run can warn the model
|
|
2361
|
+
* once); other failures log without disturbing the run.
|
|
2362
|
+
*/
|
|
2363
|
+
async snapshotTurnEnd(entry) {
|
|
2364
|
+
if (!this.config.snapshotDir) return { skipped: false };
|
|
2365
|
+
const kernel = entry.bridge;
|
|
2366
|
+
if (!kernel.isRunning) return { skipped: false };
|
|
2367
|
+
const dir = join(this.config.snapshotDir, keyDirectoryName(entry.key));
|
|
2368
|
+
mkdirSync(dir, { recursive: true });
|
|
2369
|
+
const spec = {
|
|
2370
|
+
turn: entry.turn,
|
|
2371
|
+
skills: [],
|
|
2372
|
+
sizeCapBytes: this.config.snapshotSizeCapBytes
|
|
2373
|
+
};
|
|
2374
|
+
const outcome = await kernel.snapshotState(join(dir, "state.dill"), join(dir, "manifest.json"), spec);
|
|
2375
|
+
if (outcome.kind === "ok") {
|
|
2376
|
+
entry.lastSnapshotTurn = entry.turn;
|
|
2377
|
+
return { skipped: false };
|
|
2378
|
+
}
|
|
2379
|
+
if (outcome.kind === "skipped") return {
|
|
2380
|
+
skipped: true,
|
|
2381
|
+
...outcome.estimateBytes !== void 0 ? { estimateBytes: outcome.estimateBytes } : {},
|
|
2382
|
+
...outcome.sizeBytes !== void 0 ? { sizeBytes: outcome.sizeBytes } : {}
|
|
2383
|
+
};
|
|
2384
|
+
this.logger.warn(`turn-end snapshot for ${JSON.stringify(entry.key)} failed: ${outcome.reason}`);
|
|
2385
|
+
return { skipped: false };
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Dispose to quiescence: snapshot every live key's namespace when a
|
|
2389
|
+
* snapshot directory is configured (each under its own per-key
|
|
2390
|
+
* subdirectory), then shut the subprocesses down. Registered as the
|
|
2391
|
+
* plugin's `ctx.effect` disposer.
|
|
2392
|
+
*/
|
|
2393
|
+
async teardown() {
|
|
2394
|
+
this.disposed = true;
|
|
2395
|
+
const entries = [...this.kernels.values()];
|
|
2396
|
+
this.kernels.clear();
|
|
2397
|
+
await Promise.all(entries.map((entry) => this.teardownKernel(entry)));
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Tear one session's kernel down (session end via the `agent/disposed`
|
|
2401
|
+
* listener). A no-op for keys that never ran code through this instance —
|
|
2402
|
+
* which is what makes hearing every agent's disposal safe.
|
|
2403
|
+
*/
|
|
2404
|
+
async destroyKey(key) {
|
|
2405
|
+
const entry = this.kernels.get(key);
|
|
2406
|
+
if (!entry) return;
|
|
2407
|
+
this.kernels.delete(key);
|
|
2408
|
+
await this.teardownKernel(entry);
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Reset one session's kernel to a fresh, empty namespace: dispose the live
|
|
2412
|
+
* subprocess WITHOUT a turn-end snapshot and clear its on-disk snapshot, so
|
|
2413
|
+
* the next ensureKernel spawns empty (restore finds nothing to replay).
|
|
2414
|
+
*/
|
|
2415
|
+
async resetKey(key) {
|
|
2416
|
+
const entry = this.kernels.get(key);
|
|
2417
|
+
if (entry) {
|
|
2418
|
+
this.kernels.delete(key);
|
|
2419
|
+
await entry.bridge.dispose();
|
|
2420
|
+
}
|
|
2421
|
+
if (this.config.snapshotDir) rmSync(join(this.config.snapshotDir, keyDirectoryName(key)), {
|
|
2422
|
+
recursive: true,
|
|
2423
|
+
force: true
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
/** Snapshot (when configured) then dispose one key's kernel; failures log, never throw into a listener. */
|
|
2427
|
+
async teardownKernel(entry) {
|
|
2428
|
+
const kernel = entry.bridge;
|
|
2429
|
+
if (this.config.snapshotDir && kernel.isRunning) try {
|
|
2430
|
+
const dir = join(this.config.snapshotDir, keyDirectoryName(entry.key));
|
|
2431
|
+
mkdirSync(dir, { recursive: true });
|
|
2432
|
+
const spec = {
|
|
2433
|
+
turn: entry.turn,
|
|
2434
|
+
skills: [],
|
|
2435
|
+
sizeCapBytes: this.config.snapshotSizeCapBytes
|
|
2436
|
+
};
|
|
2437
|
+
const saved = await kernel.snapshotState(join(dir, "state.dill"), join(dir, "manifest.json"), spec);
|
|
2438
|
+
if (saved.kind !== "ok" && saved.kind !== "skipped") this.logger.warn(`kernel namespace snapshot for ${JSON.stringify(entry.key)} failed: ${saved.reason}`);
|
|
2439
|
+
} catch (error) {
|
|
2440
|
+
this.logger.warn(`kernel namespace snapshot for ${JSON.stringify(entry.key)} failed: ${messageOf(error)}`);
|
|
2441
|
+
}
|
|
2442
|
+
await kernel.dispose();
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
|
|
2446
|
+
//#endregion
|
|
2447
|
+
//#region src/tool-call-id.ts
|
|
2448
|
+
/** Brand a string as the host's tool-call id (identity at runtime). */
|
|
2449
|
+
const toolCallId = dshLlm.ToolCallId;
|
|
2450
|
+
|
|
2451
|
+
//#endregion
|
|
2452
|
+
//#region src/bridges/index.ts
|
|
2453
|
+
/**
|
|
2454
|
+
* Unwrap one bridge-tool {@link parseReplCall} packaging into its single
|
|
2455
|
+
* arguments object. Same one-object contract as a registry tool, but bridge
|
|
2456
|
+
* tools return a structured `{ error }` value instead of throwing, and a call
|
|
2457
|
+
* with no arguments reads as an empty object. Keyword and multi-positional
|
|
2458
|
+
* forms are rejected.
|
|
2459
|
+
*/
|
|
2460
|
+
function flatBridgeToolArgs(rawArgs) {
|
|
2461
|
+
if (rawArgs === void 0 || rawArgs === null) return {
|
|
2462
|
+
ok: true,
|
|
2463
|
+
args: {}
|
|
2464
|
+
};
|
|
2465
|
+
if (Array.isArray(rawArgs)) return {
|
|
2466
|
+
ok: false,
|
|
2467
|
+
error: "tools take exactly one positional arguments object — call e.g. tool.name({\"field\": value})"
|
|
2468
|
+
};
|
|
2469
|
+
if (typeof rawArgs !== "object") return {
|
|
2470
|
+
ok: false,
|
|
2471
|
+
error: "tools take one positional arguments object, not a bare value — call e.g. tool.name({\"field\": value})"
|
|
2472
|
+
};
|
|
2473
|
+
return {
|
|
2474
|
+
ok: true,
|
|
2475
|
+
args: rawArgs
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
/**
|
|
2479
|
+
* The wire layer delivers `type:'json'` parameters as JSON STRINGS (the cell
|
|
2480
|
+
* path delivers parsed objects); accept either form. An unparseable string
|
|
2481
|
+
* falls through untouched — the caller's object check turns it into the
|
|
2482
|
+
* structured error.
|
|
2483
|
+
*/
|
|
2484
|
+
function coerceJsonField(value) {
|
|
2485
|
+
if (typeof value !== "string") return value;
|
|
2486
|
+
try {
|
|
2487
|
+
return JSON.parse(value);
|
|
2488
|
+
} catch {
|
|
2489
|
+
return value;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
/** Reject unexpected keys on one bridge call's arguments object. */
|
|
2493
|
+
function rejectUnknownKeys(tool, args, allowed) {
|
|
2494
|
+
const unknownKeys = Object.keys(args).filter((key) => !allowed.includes(key));
|
|
2495
|
+
if (unknownKeys.length > 0) return { error: `${tool}() got unexpected key(s): ${unknownKeys.join(", ")}` };
|
|
2496
|
+
}
|
|
2497
|
+
/** Human-readable reason for one non-`completed` subagent stop (native `stopReasonError`). */
|
|
2498
|
+
function stopReasonError(reason) {
|
|
2499
|
+
switch (reason) {
|
|
2500
|
+
case "completed": return "subagent run completed";
|
|
2501
|
+
case "aborted": return "subagent run was cancelled";
|
|
2502
|
+
case "error": return "subagent run failed";
|
|
2503
|
+
case "max-tokens": return "subagent run hit its token limit before finishing";
|
|
2504
|
+
case "refusal": return "subagent declined the task";
|
|
2505
|
+
default: return `subagent run ended abnormally (${reason})`;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
/** The native `dsh-tool-subagent` default delegation-depth cap (Config default `3`). */
|
|
2509
|
+
const NATIVE_MAX_DEPTH = 3;
|
|
2510
|
+
/** The fixed Ralph orchestration (deployment-owned, copied verbatim from `@deepseek-ai/dsh-tool-ralph`). */
|
|
2511
|
+
const RALPH_META = {
|
|
2512
|
+
name: "ralph-loop",
|
|
2513
|
+
description: "Iterate toward one objective with a fresh child and bounded structured handoff per round.",
|
|
2514
|
+
phases: [{
|
|
2515
|
+
title: "Fresh-agent rounds",
|
|
2516
|
+
detail: "One clean child context per Ralph round."
|
|
2517
|
+
}]
|
|
2518
|
+
};
|
|
2519
|
+
/** Native Ralph defaults (Config defaults). */
|
|
2520
|
+
const RALPH_SUBAGENT_PROVIDER = "spawn";
|
|
2521
|
+
const RALPH_MAX_ROUNDS = 256;
|
|
2522
|
+
const RALPH_MAX_HANDOFF_CHARS = 16384;
|
|
2523
|
+
/** The fixed, deployment-owned Ralph script, copied verbatim from `@deepseek-ai/dsh-tool-ralph`. */
|
|
2524
|
+
const RALPH_SCRIPT = String.raw`
|
|
2525
|
+
const reportSchema = {
|
|
2526
|
+
type: 'object',
|
|
2527
|
+
properties: {
|
|
2528
|
+
status: { type: 'string', enum: ['continue', 'complete', 'blocked'] },
|
|
2529
|
+
summary: { type: 'string' },
|
|
2530
|
+
evidence: { type: 'array', items: { type: 'string' } },
|
|
2531
|
+
nextSteps: { type: 'array', items: { type: 'string' } },
|
|
2532
|
+
blocker: { type: 'string' },
|
|
2533
|
+
},
|
|
2534
|
+
required: ['status', 'summary', 'evidence', 'nextSteps', 'blocker'],
|
|
2535
|
+
additionalProperties: false,
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
function normalizedText(value) {
|
|
2539
|
+
return typeof value === 'string' && value.length > 0 && value === value.trim()
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
function normalizedList(value) {
|
|
2543
|
+
return Array.isArray(value) && value.every(normalizedText)
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
function validateReport(report) {
|
|
2547
|
+
if (report === null || typeof report !== 'object' || Array.isArray(report)) {
|
|
2548
|
+
throw new Error('Ralph child returned no structured round report')
|
|
2549
|
+
}
|
|
2550
|
+
if (!normalizedText(report.summary)) {
|
|
2551
|
+
throw new Error('Ralph round report summary must be non-empty and normalized')
|
|
2552
|
+
}
|
|
2553
|
+
if (!normalizedList(report.evidence) || !normalizedList(report.nextSteps)) {
|
|
2554
|
+
throw new Error('Ralph round report evidence and nextSteps must contain only non-empty normalized strings')
|
|
2555
|
+
}
|
|
2556
|
+
if (typeof report.blocker !== 'string' || report.blocker !== report.blocker.trim()) {
|
|
2557
|
+
throw new Error('Ralph round report blocker must be a normalized string')
|
|
2558
|
+
}
|
|
2559
|
+
switch (report.status) {
|
|
2560
|
+
case 'continue':
|
|
2561
|
+
if (report.nextSteps.length === 0 || report.blocker !== '') {
|
|
2562
|
+
throw new Error('a continuing Ralph report needs nextSteps and an empty blocker')
|
|
2563
|
+
}
|
|
2564
|
+
break
|
|
2565
|
+
case 'complete':
|
|
2566
|
+
if (report.evidence.length === 0 || report.nextSteps.length !== 0 || report.blocker !== '') {
|
|
2567
|
+
throw new Error('a complete Ralph report needs evidence, no nextSteps, and an empty blocker')
|
|
2568
|
+
}
|
|
2569
|
+
break
|
|
2570
|
+
case 'blocked':
|
|
2571
|
+
if (!normalizedText(report.blocker)) {
|
|
2572
|
+
throw new Error('a blocked Ralph report needs a concrete blocker')
|
|
2573
|
+
}
|
|
2574
|
+
break
|
|
2575
|
+
default:
|
|
2576
|
+
throw new Error('Ralph round report status is invalid')
|
|
2577
|
+
}
|
|
2578
|
+
const serialized = JSON.stringify(report)
|
|
2579
|
+
if (serialized.length > args.maxHandoffChars) {
|
|
2580
|
+
throw new Error('Ralph round report exceeds maxHandoffChars (' + serialized.length + ' > ' + args.maxHandoffChars + ')')
|
|
2581
|
+
}
|
|
2582
|
+
return report
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
let previous
|
|
2586
|
+
phase('Fresh-agent rounds')
|
|
2587
|
+
for (let round = 1; round <= args.maxRounds; round += 1) {
|
|
2588
|
+
const prior = previous === undefined ? '(none — this is the first round)' : JSON.stringify(previous)
|
|
2589
|
+
const prompt = [
|
|
2590
|
+
'You are one fresh worker in a foreground Ralph loop. You receive no parent conversation and no prior child session. Do not call the ralph tool: this round already is its worker.',
|
|
2591
|
+
'Immutable objective:\n' + args.objective,
|
|
2592
|
+
'Ralph round: ' + round + ' of ' + args.maxRounds + '.',
|
|
2593
|
+
'The shared workspace and its current working tree are the long-term memory and source of truth. Inspect them before acting, preserve existing work, perform concrete in-scope work, and verify what you change. Treat the previous report only as a bounded handoff; confirm it against the workspace.',
|
|
2594
|
+
'Previous structured handoff:\n' + prior,
|
|
2595
|
+
'Return one report with exact normalized strings. Use status continue with at least one nextSteps entry while useful work remains; complete only with concrete evidence and no nextSteps; blocked only when no meaningful progress is possible without human input or an external-state change. blocker must be empty unless blocked.',
|
|
2596
|
+
].join('\n\n')
|
|
2597
|
+
const rawReport = await agent(prompt, {
|
|
2598
|
+
label: 'Ralph round ' + round,
|
|
2599
|
+
phase: 'Fresh-agent rounds',
|
|
2600
|
+
schema: reportSchema,
|
|
2601
|
+
})
|
|
2602
|
+
if (rawReport === null) {
|
|
2603
|
+
return { status: 'round-failed', roundsStarted: round, lastReport: previous ?? null }
|
|
2604
|
+
}
|
|
2605
|
+
const report = validateReport(rawReport)
|
|
2606
|
+
if (report.status === 'complete') return { status: 'complete', roundsStarted: round, report }
|
|
2607
|
+
if (report.status === 'blocked') return { status: 'blocked', roundsStarted: round, report }
|
|
2608
|
+
previous = report
|
|
2609
|
+
}
|
|
2610
|
+
return { status: 'budget-limited', roundsStarted: args.maxRounds, report: previous }
|
|
2611
|
+
`;
|
|
2612
|
+
const agentExecutor = async (rawArgs, exec, deps) => {
|
|
2613
|
+
const parsed = flatBridgeToolArgs(rawArgs);
|
|
2614
|
+
if (!parsed.ok) return { error: parsed.error };
|
|
2615
|
+
const a = parsed.args;
|
|
2616
|
+
const unknown = rejectUnknownKeys("agent", a, [
|
|
2617
|
+
"description",
|
|
2618
|
+
"prompt",
|
|
2619
|
+
"run_in_background",
|
|
2620
|
+
"mode"
|
|
2621
|
+
]);
|
|
2622
|
+
if (unknown !== void 0) return unknown;
|
|
2623
|
+
const description = a["description"];
|
|
2624
|
+
const prompt = a["prompt"];
|
|
2625
|
+
if (typeof description !== "string" || description.length === 0) return { error: "agent() requires {\"description\": \"...\"} — a short (3-5 word) display label" };
|
|
2626
|
+
if (typeof prompt !== "string" || prompt.length === 0) return { error: "agent() requires {\"prompt\": \"...\"} — the task for the child agent" };
|
|
2627
|
+
const mode = a["mode"] ?? "delegate";
|
|
2628
|
+
if (mode !== "delegate" && mode !== "fork") return { error: `agent() unknown mode ${JSON.stringify(mode)}: expected 'delegate' or 'fork'` };
|
|
2629
|
+
const provider = mode === "fork" ? "fork" : "spawn";
|
|
2630
|
+
if (!exec.agent) return { error: "agent() requires a calling agent (this run has no agent to delegate from)" };
|
|
2631
|
+
const subagents = deps.requireSubagents?.();
|
|
2632
|
+
if (!subagents) return { error: "agent() is unavailable: no ctx.subagents service is mounted in this composition" };
|
|
2633
|
+
const delegation = {
|
|
2634
|
+
label: description,
|
|
2635
|
+
prompt: [{
|
|
2636
|
+
type: "text",
|
|
2637
|
+
text: prompt
|
|
2638
|
+
}],
|
|
2639
|
+
parent: exec.agent,
|
|
2640
|
+
maxDepth: NATIVE_MAX_DEPTH
|
|
2641
|
+
};
|
|
2642
|
+
const runInBackground = a["run_in_background"] ?? true;
|
|
2643
|
+
try {
|
|
2644
|
+
if (runInBackground) return {
|
|
2645
|
+
kind: "continuable",
|
|
2646
|
+
subagentId: (await subagents.startContinuable({
|
|
2647
|
+
provider,
|
|
2648
|
+
label: description,
|
|
2649
|
+
request: delegation,
|
|
2650
|
+
signal: exec.signal
|
|
2651
|
+
})).childId
|
|
2652
|
+
};
|
|
2653
|
+
const run = await subagents.start(provider, {
|
|
2654
|
+
...delegation,
|
|
2655
|
+
signal: exec.signal
|
|
2656
|
+
});
|
|
2657
|
+
try {
|
|
2658
|
+
const result = await run.result;
|
|
2659
|
+
if (result.stopReason !== "completed") return { error: `agent() ${stopReasonError(result.stopReason)}` };
|
|
2660
|
+
return {
|
|
2661
|
+
kind: "foreground",
|
|
2662
|
+
runId: run.id,
|
|
2663
|
+
output: result.output
|
|
2664
|
+
};
|
|
2665
|
+
} finally {
|
|
2666
|
+
await run.dispose();
|
|
2667
|
+
}
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
return { error: `agent() failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
const agentMessageExecutor = async (rawArgs, exec, deps) => {
|
|
2673
|
+
const parsed = flatBridgeToolArgs(rawArgs);
|
|
2674
|
+
if (!parsed.ok) return { error: parsed.error };
|
|
2675
|
+
const a = parsed.args;
|
|
2676
|
+
const unknown = rejectUnknownKeys("agent_message", a, [
|
|
2677
|
+
"receiver",
|
|
2678
|
+
"message",
|
|
2679
|
+
"subagent_id",
|
|
2680
|
+
"target_session_id"
|
|
2681
|
+
]);
|
|
2682
|
+
if (unknown !== void 0) return unknown;
|
|
2683
|
+
const receiver = a["receiver"];
|
|
2684
|
+
if (typeof receiver !== "string") return { error: "agent_message() requires {\"receiver\": \"child\" | \"parent\" | \"interrupt\"}" };
|
|
2685
|
+
const message = a["message"];
|
|
2686
|
+
if (receiver === "child") {
|
|
2687
|
+
if (typeof message !== "string") return { error: "agent_message() receiver \"child\" requires {\"message\": \"...\"}" };
|
|
2688
|
+
const subagentId = a["subagent_id"];
|
|
2689
|
+
if (typeof subagentId !== "string" || subagentId.length === 0) return { error: "agent_message() receiver \"child\" requires {\"subagent_id\": \"...\"} — the durable subagent id a spawn returned" };
|
|
2690
|
+
if (!exec.agent) return { error: "agent_message(receiver='child') requires an agent session (this run has no agent to deliver from)" };
|
|
2691
|
+
const subagents = deps.requireSubagents?.();
|
|
2692
|
+
if (!subagents) return { error: "agent_message(receiver='child') is unavailable: no ctx.subagents service is mounted in this composition" };
|
|
2693
|
+
try {
|
|
2694
|
+
return { messageId: await subagents.sendMessage(exec.agent, subagentId, [{
|
|
2695
|
+
type: "text",
|
|
2696
|
+
text: message
|
|
2697
|
+
}], { signal: exec.signal }) };
|
|
2698
|
+
} catch (error) {
|
|
2699
|
+
return { error: `agent_message(receiver='child') failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (receiver === "parent") {
|
|
2703
|
+
if (typeof message !== "string") return { error: "agent_message() receiver \"parent\" requires {\"message\": \"...\"}" };
|
|
2704
|
+
if (!exec.agent) return { error: "agent_message(receiver='parent') requires an agent session (this run has no agent to report from)" };
|
|
2705
|
+
const subagents = deps.requireSubagents?.();
|
|
2706
|
+
if (!subagents) return { error: "agent_message(receiver='parent') is unavailable: no ctx.subagents service is mounted in this composition" };
|
|
2707
|
+
try {
|
|
2708
|
+
const parentId = exec.agent.session.header.parentSession;
|
|
2709
|
+
if (parentId === void 0) return { error: "agent_message(receiver='parent') rejected: only a live continuable child agent can report to its parent (a root agent has none)" };
|
|
2710
|
+
return {
|
|
2711
|
+
delivered: true,
|
|
2712
|
+
message_id: await subagents.sendMessage(exec.agent, parentId, [{
|
|
2713
|
+
type: "text",
|
|
2714
|
+
text: message
|
|
2715
|
+
}], { signal: exec.signal })
|
|
2716
|
+
};
|
|
2717
|
+
} catch (error) {
|
|
2718
|
+
if (error.code === "UNAUTHORIZED") return { error: `agent_message(receiver='parent') rejected: only a live continuable child agent can report to its parent (a root agent has none) — ${error instanceof Error ? error.message : String(error)}` };
|
|
2719
|
+
return { error: `agent_message(receiver='parent') failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
if (receiver === "interrupt") {
|
|
2723
|
+
const targetSessionId = a["target_session_id"];
|
|
2724
|
+
if (typeof targetSessionId !== "string" || targetSessionId.length === 0) return { error: "agent_message() receiver 'interrupt' requires {\"target_session_id\": \"...\"} — the target agent's session id" };
|
|
2725
|
+
if (!exec.agent) return { error: "agent_message(receiver='interrupt') requires a calling agent (this run has no agent to authorize the interrupt)" };
|
|
2726
|
+
const subagents = deps.requireSubagents?.();
|
|
2727
|
+
if (!subagents) return { error: "agent_message(receiver='interrupt') is unavailable: no ctx.subagents service is mounted in this composition" };
|
|
2728
|
+
try {
|
|
2729
|
+
subagents.interrupt(targetSessionId, {
|
|
2730
|
+
kind: "ancestor",
|
|
2731
|
+
agent: exec.agent
|
|
2732
|
+
});
|
|
2733
|
+
return { accepted: true };
|
|
2734
|
+
} catch (error) {
|
|
2735
|
+
return { error: `agent_message(receiver='interrupt') failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
return { error: `agent_message() unknown receiver ${JSON.stringify(receiver)}: expected 'child', 'parent', or 'interrupt'` };
|
|
2739
|
+
};
|
|
2740
|
+
const agentWorkflowExecutor = async (rawArgs, exec, deps) => {
|
|
2741
|
+
const parsed = flatBridgeToolArgs(rawArgs);
|
|
2742
|
+
if (!parsed.ok) return { error: parsed.error };
|
|
2743
|
+
const a = parsed.args;
|
|
2744
|
+
const unknown = rejectUnknownKeys("agent_workflow", a, [
|
|
2745
|
+
"mode",
|
|
2746
|
+
"script",
|
|
2747
|
+
"meta",
|
|
2748
|
+
"args",
|
|
2749
|
+
"objective",
|
|
2750
|
+
"maxRounds"
|
|
2751
|
+
]);
|
|
2752
|
+
if (unknown !== void 0) return unknown;
|
|
2753
|
+
const mode = a["mode"] ?? "script";
|
|
2754
|
+
if (mode !== "script" && mode !== "rfc") return { error: `agent_workflow() unknown mode ${JSON.stringify(mode)}: expected 'script' or 'rfc'` };
|
|
2755
|
+
if (!exec.agent) return { error: "agent_workflow() requires a calling agent (this run has no agent to attribute the workflow to)" };
|
|
2756
|
+
const meta = coerceJsonField(a["meta"]);
|
|
2757
|
+
const argsField = coerceJsonField(a["args"]);
|
|
2758
|
+
if (mode === "script") {
|
|
2759
|
+
if (typeof a["script"] !== "string" || a["script"].length === 0) return { error: "agent_workflow() mode \"script\" requires {\"script\": \"...\"} — the plain-JS workflow script body" };
|
|
2760
|
+
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return { error: "agent_workflow() mode \"script\" requires {\"meta\": {...}} — the workflow identity block" };
|
|
2761
|
+
} else if (typeof a["objective"] !== "string" || a["objective"].trim().length === 0) return { error: "agent_workflow() mode \"rfc\" requires {\"objective\": \"...\"} — the immutable completion objective" };
|
|
2762
|
+
const engine = deps.requireWorkflowEngine?.(exec.agent);
|
|
2763
|
+
if (!engine) return { error: "agent_workflow() is unavailable: no workflowEngine service is mounted for this agent's preset" };
|
|
2764
|
+
const settle = async (run) => {
|
|
2765
|
+
const onAbort = () => {
|
|
2766
|
+
run.cancel("parent step aborted");
|
|
2767
|
+
};
|
|
2768
|
+
exec.signal.addEventListener("abort", onAbort, { once: true });
|
|
2769
|
+
try {
|
|
2770
|
+
const result = await run.result;
|
|
2771
|
+
if (result.stopReason !== "completed") return { error: `agent_workflow() did not finish cleanly: ${result.error ?? result.stopReason}` };
|
|
2772
|
+
return {
|
|
2773
|
+
runId: run.id,
|
|
2774
|
+
agentsStarted: result.agentsStarted,
|
|
2775
|
+
result: result.value
|
|
2776
|
+
};
|
|
2777
|
+
} catch (error) {
|
|
2778
|
+
return { error: `agent_workflow() failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
2779
|
+
} finally {
|
|
2780
|
+
exec.signal.removeEventListener("abort", onAbort);
|
|
2781
|
+
await run.dispose();
|
|
2782
|
+
}
|
|
2783
|
+
};
|
|
2784
|
+
try {
|
|
2785
|
+
if (mode === "script") return await settle(engine.start({
|
|
2786
|
+
script: a["script"],
|
|
2787
|
+
meta,
|
|
2788
|
+
...argsField !== void 0 ? { args: argsField } : {},
|
|
2789
|
+
parent: exec.agent,
|
|
2790
|
+
signal: exec.signal
|
|
2791
|
+
}));
|
|
2792
|
+
const maxRounds = a["maxRounds"] === void 0 ? RALPH_MAX_ROUNDS : a["maxRounds"];
|
|
2793
|
+
if (typeof maxRounds !== "number" || !Number.isSafeInteger(maxRounds) || maxRounds < 1) return { error: "agent_workflow() mode \"rfc\" maxRounds must be a positive safe integer" };
|
|
2794
|
+
if (maxRounds > RALPH_MAX_ROUNDS) return { error: `agent_workflow() mode "rfc" maxRounds ${maxRounds} exceeds the deployment ceiling ${RALPH_MAX_ROUNDS}` };
|
|
2795
|
+
return await settle(engine.start({
|
|
2796
|
+
script: RALPH_SCRIPT,
|
|
2797
|
+
meta: RALPH_META,
|
|
2798
|
+
args: {
|
|
2799
|
+
objective: a["objective"].trim(),
|
|
2800
|
+
maxRounds,
|
|
2801
|
+
maxHandoffChars: RALPH_MAX_HANDOFF_CHARS
|
|
2802
|
+
},
|
|
2803
|
+
subagentProvider: RALPH_SUBAGENT_PROVIDER,
|
|
2804
|
+
maxTotalAgents: maxRounds,
|
|
2805
|
+
parent: exec.agent,
|
|
2806
|
+
signal: exec.signal
|
|
2807
|
+
}));
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
return { error: `agent_workflow() failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
2810
|
+
}
|
|
2811
|
+
};
|
|
2812
|
+
/**
|
|
2813
|
+
* Build the three delegation bridge TOOLS: real registry registrations whose
|
|
2814
|
+
* execute runs the pure executors with the host-level service resolvers.
|
|
2815
|
+
* Register once at the same host layer as `eval`; the registry projection is
|
|
2816
|
+
* then the single source for the wire tools array, the tool catalog, and the
|
|
2817
|
+
* REPL `tool.*` bindings (via the mechanical auto-bridge). Runtime argument
|
|
2818
|
+
* validation stays inside the executors; the schemas below are the declared
|
|
2819
|
+
* model-facing contract, and a structured `{ error }` value — not a thrown
|
|
2820
|
+
* exception — is the failure form.
|
|
2821
|
+
*/
|
|
2822
|
+
function createAgentBridgeTools(deps) {
|
|
2823
|
+
const jsonRender = (_args, value) => [{
|
|
2824
|
+
type: "text",
|
|
2825
|
+
text: JSON.stringify(value, null, 2)
|
|
2826
|
+
}];
|
|
2827
|
+
const errorVariant = {
|
|
2828
|
+
type: "object",
|
|
2829
|
+
properties: { error: { type: "string" } },
|
|
2830
|
+
additionalProperties: false
|
|
2831
|
+
};
|
|
2832
|
+
return [
|
|
2833
|
+
defineTool({
|
|
2834
|
+
name: "agent",
|
|
2835
|
+
description: "Start a child agent. Runs in the background by default: returns a durable subagent id immediately and keeps the child conversation open for agent_message follow-ups. Set run_in_background false to run a ONE-SHOT child instead — the call waits for the result and the child ends with the run. mode \"delegate\" (default) starts a fresh child; mode \"fork\" seeds the child with this agent's completed conversation turns.",
|
|
2836
|
+
parameters: {
|
|
2837
|
+
description: {
|
|
2838
|
+
type: "string",
|
|
2839
|
+
description: "A short (3-5 word) description of the delegated task, for display."
|
|
2840
|
+
},
|
|
2841
|
+
prompt: {
|
|
2842
|
+
type: "string",
|
|
2843
|
+
description: "The task prompt for the child agent."
|
|
2844
|
+
},
|
|
2845
|
+
run_in_background: {
|
|
2846
|
+
type: "boolean",
|
|
2847
|
+
description: "Default true: return a durable subagent id immediately (continuable, agent_message can follow up). Explicit false: one-shot — wait for the child's result here."
|
|
2848
|
+
},
|
|
2849
|
+
mode: {
|
|
2850
|
+
type: "string",
|
|
2851
|
+
description: "'delegate' (default: a fresh child) or 'fork' (a child seeded with this agent's completed conversation turns)."
|
|
2852
|
+
}
|
|
2853
|
+
},
|
|
2854
|
+
output: {
|
|
2855
|
+
schema: { oneOf: [
|
|
2856
|
+
{
|
|
2857
|
+
type: "object",
|
|
2858
|
+
properties: {
|
|
2859
|
+
kind: {
|
|
2860
|
+
type: "string",
|
|
2861
|
+
enum: ["continuable"]
|
|
2862
|
+
},
|
|
2863
|
+
subagentId: { type: "string" }
|
|
2864
|
+
},
|
|
2865
|
+
additionalProperties: false
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
type: "object",
|
|
2869
|
+
properties: {
|
|
2870
|
+
kind: {
|
|
2871
|
+
type: "string",
|
|
2872
|
+
enum: ["foreground"]
|
|
2873
|
+
},
|
|
2874
|
+
runId: { type: "string" },
|
|
2875
|
+
output: { type: "json" }
|
|
2876
|
+
},
|
|
2877
|
+
additionalProperties: false
|
|
2878
|
+
},
|
|
2879
|
+
errorVariant
|
|
2880
|
+
] },
|
|
2881
|
+
render: jsonRender
|
|
2882
|
+
},
|
|
2883
|
+
execute: (args, exec) => agentExecutor(args, exec, deps)
|
|
2884
|
+
}),
|
|
2885
|
+
defineTool({
|
|
2886
|
+
name: "agent_message",
|
|
2887
|
+
description: "The single agent-to-agent channel, three ways: receiver \"child\" delivers a follow-up message down to a child (subagent_id required); receiver \"parent\" reports up to this agent's parent (live continuable children only; a root agent gets a structured UNAUTHORIZED instead); receiver \"interrupt\" stops a child's current turn (target_session_id required).",
|
|
2888
|
+
parameters: {
|
|
2889
|
+
receiver: {
|
|
2890
|
+
type: "string",
|
|
2891
|
+
description: "'child' (deliver down), 'parent' (report up), or 'interrupt' (stop a child's current turn)."
|
|
2892
|
+
},
|
|
2893
|
+
message: {
|
|
2894
|
+
type: "string",
|
|
2895
|
+
description: "The message text (required for receiver 'child' and 'parent')."
|
|
2896
|
+
},
|
|
2897
|
+
subagent_id: {
|
|
2898
|
+
type: "string",
|
|
2899
|
+
description: "The child's durable id (required when receiver is 'child')."
|
|
2900
|
+
},
|
|
2901
|
+
target_session_id: {
|
|
2902
|
+
type: "string",
|
|
2903
|
+
description: "The target agent's session id to interrupt (required when receiver is 'interrupt')."
|
|
2904
|
+
}
|
|
2905
|
+
},
|
|
2906
|
+
output: {
|
|
2907
|
+
schema: { oneOf: [
|
|
2908
|
+
{
|
|
2909
|
+
type: "object",
|
|
2910
|
+
properties: { messageId: { type: "string" } },
|
|
2911
|
+
additionalProperties: false
|
|
2912
|
+
},
|
|
2913
|
+
{
|
|
2914
|
+
type: "object",
|
|
2915
|
+
properties: {
|
|
2916
|
+
delivered: {
|
|
2917
|
+
type: "boolean",
|
|
2918
|
+
enum: [true]
|
|
2919
|
+
},
|
|
2920
|
+
message_id: { type: "string" }
|
|
2921
|
+
},
|
|
2922
|
+
additionalProperties: false
|
|
2923
|
+
},
|
|
2924
|
+
{
|
|
2925
|
+
type: "object",
|
|
2926
|
+
properties: { accepted: {
|
|
2927
|
+
type: "boolean",
|
|
2928
|
+
enum: [true]
|
|
2929
|
+
} },
|
|
2930
|
+
additionalProperties: false
|
|
2931
|
+
},
|
|
2932
|
+
errorVariant
|
|
2933
|
+
] },
|
|
2934
|
+
render: jsonRender
|
|
2935
|
+
},
|
|
2936
|
+
execute: (args, exec) => agentMessageExecutor(args, exec, deps)
|
|
2937
|
+
}),
|
|
2938
|
+
defineTool({
|
|
2939
|
+
name: "agent_workflow",
|
|
2940
|
+
description: "Run a multi-agent orchestration: mode \"script\" runs a plain-JS orchestration script (script/meta required); mode \"rfc\" runs the fixed fresh-agent Ralph loop toward one objective (objective required, maxRounds optional).",
|
|
2941
|
+
parameters: {
|
|
2942
|
+
mode: {
|
|
2943
|
+
type: "string",
|
|
2944
|
+
description: "'script' (default: an orchestration script) or 'rfc' (a fixed fresh-agent Ralph loop)."
|
|
2945
|
+
},
|
|
2946
|
+
script: {
|
|
2947
|
+
type: "string",
|
|
2948
|
+
description: "The plain-JS workflow script body (required when mode is \"script\")."
|
|
2949
|
+
},
|
|
2950
|
+
meta: {
|
|
2951
|
+
type: "json",
|
|
2952
|
+
description: "The workflow identity block (required when mode is \"script\"); must carry non-empty name and description (host invariant)."
|
|
2953
|
+
},
|
|
2954
|
+
args: {
|
|
2955
|
+
type: "json",
|
|
2956
|
+
description: "Optional JSON input exposed to the script as the args global."
|
|
2957
|
+
},
|
|
2958
|
+
objective: {
|
|
2959
|
+
type: "string",
|
|
2960
|
+
description: "The immutable completion objective for the Ralph loop (required when mode is \"rfc\")."
|
|
2961
|
+
},
|
|
2962
|
+
maxRounds: {
|
|
2963
|
+
type: "number",
|
|
2964
|
+
description: "Optional round cap for the Ralph loop, bounded by the deployment ceiling."
|
|
2965
|
+
}
|
|
2966
|
+
},
|
|
2967
|
+
output: {
|
|
2968
|
+
schema: { oneOf: [{
|
|
2969
|
+
type: "object",
|
|
2970
|
+
properties: {
|
|
2971
|
+
runId: { type: "string" },
|
|
2972
|
+
agentsStarted: { type: "number" },
|
|
2973
|
+
result: { type: "json" }
|
|
2974
|
+
},
|
|
2975
|
+
additionalProperties: false
|
|
2976
|
+
}, errorVariant] },
|
|
2977
|
+
render: jsonRender
|
|
2978
|
+
},
|
|
2979
|
+
execute: (args, exec) => agentWorkflowExecutor(args, exec, deps)
|
|
2980
|
+
})
|
|
2981
|
+
];
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
//#endregion
|
|
2985
|
+
//#region src/llm-completion.ts
|
|
2986
|
+
/** Default output-token ceiling for one completion (cost guard; the caller may lower it, never raise past this). */
|
|
2987
|
+
const MAX_COMPLETION_TOKENS = 4096;
|
|
2988
|
+
/** Structured failure text for one non-`stop` finish (mirrors the session-title generator's finishError). */
|
|
2989
|
+
function finishError(finish) {
|
|
2990
|
+
switch (finish.kind) {
|
|
2991
|
+
case "stop": return "";
|
|
2992
|
+
case "error":
|
|
2993
|
+
case "aborted": return `llm_completion() call ${finish.kind}: ${String(finish.failure?.message ?? finish.kind)}`;
|
|
2994
|
+
case "max-tokens": return "llm_completion() output reached its maxTokens ceiling";
|
|
2995
|
+
case "tool-calls": return "llm_completion() model unexpectedly requested a tool (a bare call offers none)";
|
|
2996
|
+
default: return `llm_completion() unsupported finish reason ${JSON.stringify(finish.kind)}`;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
3000
|
+
* Build the `llm_completion` tool. Input: `{ prompt, system?, maxTokens? }`;
|
|
3001
|
+
* output: the model's text (a bare string root), or a structured `{ error }`
|
|
3002
|
+
* object — never a thrown exception for bad input or a degraded finish.
|
|
3003
|
+
*/
|
|
3004
|
+
/** Narrow a tool result to the success variant (for the model-facing render). */
|
|
3005
|
+
function isTextResult(value) {
|
|
3006
|
+
return typeof value === "object" && value !== null && value.ok === true && typeof value.text === "string";
|
|
3007
|
+
}
|
|
3008
|
+
function createLlmCompletionTool(deps) {
|
|
3009
|
+
return defineTool({
|
|
3010
|
+
name: "llm_completion",
|
|
3011
|
+
description: "One-shot stateless LLM call — no tools, no history, no agent. Give {prompt} (and optional {system}, {maxTokens}); get a discriminated result back: {ok:true,text} on success or {ok:false,error} on failure, so a programmatic caller can branch without shape guessing (a direct tool call renders just the text on success). For judge steps, extraction, and handoff compression inside one cell, without spawning a subagent.",
|
|
3012
|
+
parameters: {
|
|
3013
|
+
prompt: {
|
|
3014
|
+
type: "string",
|
|
3015
|
+
description: "The prompt for the one-shot call."
|
|
3016
|
+
},
|
|
3017
|
+
system: {
|
|
3018
|
+
type: "string",
|
|
3019
|
+
description: "Optional system prompt for the call."
|
|
3020
|
+
},
|
|
3021
|
+
maxTokens: {
|
|
3022
|
+
type: "integer",
|
|
3023
|
+
description: `Optional output-token ceiling for this call (default ${MAX_COMPLETION_TOKENS}, which is also the cap).`
|
|
3024
|
+
}
|
|
3025
|
+
},
|
|
3026
|
+
output: {
|
|
3027
|
+
schema: { oneOf: [{
|
|
3028
|
+
type: "object",
|
|
3029
|
+
properties: {
|
|
3030
|
+
ok: {
|
|
3031
|
+
type: "boolean",
|
|
3032
|
+
required: true
|
|
3033
|
+
},
|
|
3034
|
+
text: {
|
|
3035
|
+
type: "string",
|
|
3036
|
+
required: true
|
|
3037
|
+
}
|
|
3038
|
+
},
|
|
3039
|
+
additionalProperties: false
|
|
3040
|
+
}, {
|
|
3041
|
+
type: "object",
|
|
3042
|
+
properties: {
|
|
3043
|
+
ok: {
|
|
3044
|
+
type: "boolean",
|
|
3045
|
+
required: true
|
|
3046
|
+
},
|
|
3047
|
+
error: {
|
|
3048
|
+
type: "string",
|
|
3049
|
+
required: true
|
|
3050
|
+
}
|
|
3051
|
+
},
|
|
3052
|
+
additionalProperties: false
|
|
3053
|
+
}] },
|
|
3054
|
+
render: (_args, value) => [{
|
|
3055
|
+
type: "text",
|
|
3056
|
+
text: isTextResult(value) ? value.text : JSON.stringify(value, null, 2)
|
|
3057
|
+
}]
|
|
3058
|
+
},
|
|
3059
|
+
execute: async (args, exec) => {
|
|
3060
|
+
const a = args;
|
|
3061
|
+
const prompt = a["prompt"];
|
|
3062
|
+
if (typeof prompt !== "string" || prompt.length === 0) return {
|
|
3063
|
+
ok: false,
|
|
3064
|
+
error: "llm_completion() requires {\"prompt\": \"...\"} — the one-shot prompt"
|
|
3065
|
+
};
|
|
3066
|
+
const system = a["system"];
|
|
3067
|
+
if (system !== void 0 && typeof system !== "string") return {
|
|
3068
|
+
ok: false,
|
|
3069
|
+
error: "llm_completion() system must be a string"
|
|
3070
|
+
};
|
|
3071
|
+
const maxTokens = a["maxTokens"] === void 0 ? MAX_COMPLETION_TOKENS : a["maxTokens"];
|
|
3072
|
+
if (typeof maxTokens !== "number" || !Number.isSafeInteger(maxTokens) || maxTokens < 1 || maxTokens > MAX_COMPLETION_TOKENS) return {
|
|
3073
|
+
ok: false,
|
|
3074
|
+
error: `llm_completion() maxTokens must be a positive safe integer no greater than ${MAX_COMPLETION_TOKENS}`
|
|
3075
|
+
};
|
|
3076
|
+
const llm = deps.requireLlm();
|
|
3077
|
+
if (llm === void 0) return {
|
|
3078
|
+
ok: false,
|
|
3079
|
+
error: "llm_completion() is unavailable: no ctx.llm service is mounted in this composition"
|
|
3080
|
+
};
|
|
3081
|
+
const agent = exec.agent;
|
|
3082
|
+
const route = agent?.options;
|
|
3083
|
+
const provider = route?.provider;
|
|
3084
|
+
const model = route?.model;
|
|
3085
|
+
if (typeof provider !== "string" || provider.length === 0 || typeof model !== "string" || model.length === 0) return {
|
|
3086
|
+
ok: false,
|
|
3087
|
+
error: "llm_completion() requires a model route: the calling agent has no provider/model selected (a judge call runs on its caller's tier)"
|
|
3088
|
+
};
|
|
3089
|
+
const options = {
|
|
3090
|
+
provider,
|
|
3091
|
+
model,
|
|
3092
|
+
messages: [createUserMessage({
|
|
3093
|
+
content: [{
|
|
3094
|
+
type: "text",
|
|
3095
|
+
text: prompt
|
|
3096
|
+
}],
|
|
3097
|
+
source: { kind: "plugin:better-dsh" }
|
|
3098
|
+
})],
|
|
3099
|
+
maxTokens,
|
|
3100
|
+
...system !== void 0 ? { system } : {},
|
|
3101
|
+
...agent ? { sessionId: agent.session.id } : {},
|
|
3102
|
+
signal: exec.signal
|
|
3103
|
+
};
|
|
3104
|
+
const assembler = new BlockAssembler();
|
|
3105
|
+
try {
|
|
3106
|
+
for await (const chunk of llm.stream(options)) {
|
|
3107
|
+
exec.signal.throwIfAborted();
|
|
3108
|
+
assembler.push(chunk);
|
|
3109
|
+
}
|
|
3110
|
+
} catch (error) {
|
|
3111
|
+
return {
|
|
3112
|
+
ok: false,
|
|
3113
|
+
error: `llm_completion() failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
const failure = finishError(assembler.finish);
|
|
3117
|
+
if (failure !== "") return {
|
|
3118
|
+
ok: false,
|
|
3119
|
+
error: failure
|
|
3120
|
+
};
|
|
3121
|
+
const blocks = assembler.blocks();
|
|
3122
|
+
if (blocks.some((block) => block.type === "tool-call")) return {
|
|
3123
|
+
ok: false,
|
|
3124
|
+
error: "llm_completion() output must contain text only"
|
|
3125
|
+
};
|
|
3126
|
+
const text = blocks.filter((block) => block.type === "text").map((block) => block.text).join(" ");
|
|
3127
|
+
if (text.trim().length === 0) return {
|
|
3128
|
+
ok: false,
|
|
3129
|
+
error: "llm_completion() produced no text"
|
|
3130
|
+
};
|
|
3131
|
+
return {
|
|
3132
|
+
ok: true,
|
|
3133
|
+
text
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
//#endregion
|
|
3140
|
+
//#region src/index.ts
|
|
3141
|
+
/** Cordis plugin name. */
|
|
3142
|
+
const name = "dashr-repl";
|
|
3143
|
+
/**
|
|
3144
|
+
* Required services. `replRuntime` is NOT listed: see the module doc — the
|
|
3145
|
+
* mode-dependent wait is declared inside {@link apply} instead, and the
|
|
3146
|
+
* execution path re-reads the service at use time with an actionable error.
|
|
3147
|
+
*/
|
|
3148
|
+
const inject = ["tools"];
|
|
3149
|
+
/** Runtime schema. */
|
|
3150
|
+
const Config = Schema.intersect([DashrRuntime.Config, Schema.object({ maxParallelSubCalls: Schema.natural().min(1).default(10) })]);
|
|
3151
|
+
/** The model-facing name of the DASHR cell transport. */
|
|
3152
|
+
const EVAL_NAME = "eval";
|
|
3153
|
+
/**
|
|
3154
|
+
* The wire-mask deny list (design D1): the upstream delegation and
|
|
3155
|
+
* guidance tools DASHR displaces from the model's surface. The list feeds
|
|
3156
|
+
* ONE registry-level mechanism — `tools.restrict({deny})` on the agent's
|
|
3157
|
+
* own scope layer at session-start — which removes every name from ALL
|
|
3158
|
+
* registry projections at once (wire schemas, catalog, SDK, REPL bindings,
|
|
3159
|
+
* by-name dispatch): the restricted-away names are gone for the model on
|
|
3160
|
+
* every surface, not hidden per-presentation. Two exemptions are registry
|
|
3161
|
+
* facts, not list policy: a name the host never registered is skipped (a
|
|
3162
|
+
* restriction may only name inherited tools), and a name registered on the
|
|
3163
|
+
* agent's OWN layer (the child-scoped native `report`, this composition's
|
|
3164
|
+
* bridges below, never through the masked name. `subagent` is deliberately
|
|
3165
|
+
* NOT in this list (v0.2.1b): its standard-preset registration
|
|
3166
|
+
* (`modelSelectionSettings: true`) lands on the agent's OWN layer, outside
|
|
3167
|
+
* registry restriction reach, so it stays visible on every surface and the
|
|
3168
|
+
* control prompt annotates it as an alias of the `agent` delegation tool.
|
|
3169
|
+
* `skill` was REMOVED from this list (2026-09-12, user ruling): the native
|
|
3170
|
+
* skill tool stays visible and self-surfaces its session catalog
|
|
3171
|
+
* (`<available_skills>` is gated on the tool's registry visibility — masking
|
|
3172
|
+
* the tool silenced the catalog). dashr's dual pipeline keeps `read
|
|
3173
|
+
* skill://<name>` as the addressing form alongside native invocation.
|
|
3174
|
+
* own URL wrappers) is exempt from restrictions by construction — the
|
|
3175
|
+
* capability it carries stays reachable through the captured-definition
|
|
3176
|
+
* bridges below, never through the masked name.
|
|
3177
|
+
*/
|
|
3178
|
+
const MASKED_TOOL_NAMES = new Set([
|
|
3179
|
+
"send_message",
|
|
3180
|
+
"report",
|
|
3181
|
+
"list_agents",
|
|
3182
|
+
"subagent_fork",
|
|
3183
|
+
"interrupt_agent",
|
|
3184
|
+
"workflow",
|
|
3185
|
+
"ralph"
|
|
3186
|
+
]);
|
|
3187
|
+
/**
|
|
3188
|
+
* The effective deny list the session-start wiring restricts. An alias of
|
|
3189
|
+
* {@link MASKED_TOOL_NAMES} today; a config override (a deployment naming
|
|
3190
|
+
* its own mask) slots in HERE and nowhere else — the constant is the single
|
|
3191
|
+
* source, so the mask never drifts between capture, restrict, and the
|
|
3192
|
+
* bridges that read captured definitions.
|
|
3193
|
+
*/
|
|
3194
|
+
const WIRE_MASKED_NAMES = MASKED_TOOL_NAMES;
|
|
3195
|
+
/**
|
|
3196
|
+
* The typed-rejection contract declared on EVERY flat tool namespace: a
|
|
3197
|
+
* failed tool call raises `ToolCallError` with `.toolName` set to the
|
|
3198
|
+
* binding global (= the tool name the model knows). Identical descriptors
|
|
3199
|
+
* may repeat across namespaces; the runtime materializes the class once.
|
|
3200
|
+
*/
|
|
3201
|
+
const TOOL_CALL_ERROR_CLASS = {
|
|
3202
|
+
name: "ToolCallError",
|
|
3203
|
+
memberNameProperty: "toolName"
|
|
3204
|
+
};
|
|
3205
|
+
/**
|
|
3206
|
+
* The `dashr:escalation-guidance` CONTEXT order: sits inside the runtime-context snapshot
|
|
3207
|
+
* band between upstream `approval:policy` (115) and `subagent:delegation` (120) — the
|
|
3208
|
+
* sandbox policy statement is order 110, the approval policy 115. Re-check if upstream
|
|
3209
|
+
* adds context entries in this band (dsh-system-prompt CONTEXT_ORDERS).
|
|
3210
|
+
*/
|
|
3211
|
+
const ESCALATION_GUIDANCE_ORDER = 116;
|
|
3212
|
+
/**
|
|
3213
|
+
* The `eval` tool's whole model-facing description, loaded at module time
|
|
3214
|
+
* from the sibling markdown file (editable without touching TS). It carries
|
|
3215
|
+
* ALL REPL guidance — cell semantics, the direct-vs-cell decision rule, the
|
|
3216
|
+
* delegation alias note, and the one-form bridge at the end.
|
|
3217
|
+
*/
|
|
3218
|
+
const EVAL_DESCRIPTION = readFileSync(new URL("../eval-description.md", import.meta.url), "utf8");
|
|
3219
|
+
/** The `cell` parameter's model-facing description. */
|
|
3220
|
+
const EVAL_CELL_PARAM_DESCRIPTION = "The cell: one Python program body for the session-persistent scripting pad (top-level `await` works; top-level `return` is a SyntaxError — the cell runs in module scope).";
|
|
3221
|
+
/**
|
|
3222
|
+
* The `description` parameter's model-facing description: the UI label
|
|
3223
|
+
* contract, ported verbatim in shape from upstream `run_code` (the label
|
|
3224
|
+
* surfaces on the generic card as the call's always-visible title).
|
|
3225
|
+
*/
|
|
3226
|
+
const EVAL_DESCRIPTION_PARAM_DESCRIPTION = "Clear, concise description of what this cell does in active voice, 5-10 words (shown in the UI). Examples: \"Load dataframe and summarize columns\"; \"Run test file and capture failures\"; \"Patch config key across cordis.yml files\".";
|
|
3227
|
+
/**
|
|
3228
|
+
* Thrown by `eval` when the cell itself failed — a program exception, a
|
|
3229
|
+
* budget expiry, an abort, or kernel death. Extends {@link HarnessError} with
|
|
3230
|
+
* the same `code: 'CODE_RUN_FAILED'` as upstream `CodeRunFailedError`, so
|
|
3231
|
+
* registry-side error taxonomy and session-log consumers see the shape they
|
|
3232
|
+
* already know; the registry's execution pipeline converts it into a
|
|
3233
|
+
* structured `isError` result whose text carries the failure kind plus the
|
|
3234
|
+
* captured logs, so the model can self-correct.
|
|
3235
|
+
*/
|
|
3236
|
+
var DASHRRunFailedError = class extends HarnessError {
|
|
3237
|
+
constructor(message) {
|
|
3238
|
+
super(message, "CODE_RUN_FAILED");
|
|
3239
|
+
this.name = "DASHRRunFailedError";
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
/**
|
|
3243
|
+
* Snapshot one binding call's argument as lossless JSON, then snapshot that
|
|
3244
|
+
* detached value again so dispatch and logging stay independent without
|
|
3245
|
+
* reintroducing structured-clone's platform-specific nesting limit. Ported
|
|
3246
|
+
* verbatim from upstream `code-mode.ts` (`jsonNormalizeArgs`).
|
|
3247
|
+
*/
|
|
3248
|
+
function jsonNormalizeArgs(value) {
|
|
3249
|
+
let snapshot;
|
|
3250
|
+
try {
|
|
3251
|
+
snapshot = snapshotJsonValue(value);
|
|
3252
|
+
} catch (error) {
|
|
3253
|
+
throw new Error(`tool arguments must be lossless JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
3254
|
+
}
|
|
3255
|
+
if (snapshot === void 0) throw new Error("tool arguments must be lossless JSON (call the tool with an arguments object, e.g. `{}`)");
|
|
3256
|
+
const logged = snapshotJsonValue(snapshot);
|
|
3257
|
+
if (logged === void 0) throw new Error("tool arguments could not be detached for durable logging");
|
|
3258
|
+
return {
|
|
3259
|
+
dispatched: snapshot,
|
|
3260
|
+
logged
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
/** Resolve the eval overlap cap at the config boundary (schemastery already validated the range; direct construction in tests bypasses it). */
|
|
3264
|
+
function resolveMaxParallelSubCalls(value) {
|
|
3265
|
+
const maxParallelSubCalls = value ?? 10;
|
|
3266
|
+
if (!Number.isInteger(maxParallelSubCalls) || maxParallelSubCalls < 1) throw new Error("dashr-repl: maxParallelSubCalls must be a positive integer");
|
|
3267
|
+
return maxParallelSubCalls;
|
|
3268
|
+
}
|
|
3269
|
+
/** Two-space JSON presentation, matching the shallow `eval` text contract (ported). */
|
|
3270
|
+
const JSON_INDENT = " ";
|
|
3271
|
+
/** ECMAScript caps `JSON.stringify`'s `space` string at ten characters; total indentation is capped there so formatted output stays linear (ported). */
|
|
3272
|
+
const MAX_JSON_INDENT_CHARS = 10;
|
|
3273
|
+
/** Render one non-string JSON root without recursive traversal or unbounded indentation growth (ported from upstream code-mode.ts). */
|
|
3274
|
+
function renderJsonValue(value) {
|
|
3275
|
+
const chunks = [];
|
|
3276
|
+
const tasks = [{
|
|
3277
|
+
kind: "value",
|
|
3278
|
+
value,
|
|
3279
|
+
depth: 0,
|
|
3280
|
+
compact: false
|
|
3281
|
+
}];
|
|
3282
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
3283
|
+
if (task.kind === "text") {
|
|
3284
|
+
chunks.push(task.text);
|
|
3285
|
+
continue;
|
|
3286
|
+
}
|
|
3287
|
+
const current = task.value;
|
|
3288
|
+
if (current === null || typeof current === "boolean" || typeof current === "number") {
|
|
3289
|
+
chunks.push(String(current));
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
if (typeof current === "string") {
|
|
3293
|
+
chunks.push(JSON.stringify(current));
|
|
3294
|
+
continue;
|
|
3295
|
+
}
|
|
3296
|
+
const compact = task.compact || (task.depth + 1) * 2 > MAX_JSON_INDENT_CHARS;
|
|
3297
|
+
const childDepth = task.depth + 1;
|
|
3298
|
+
if (Array.isArray(current)) {
|
|
3299
|
+
chunks.push("[");
|
|
3300
|
+
if (current.length === 0) {
|
|
3301
|
+
chunks.push("]");
|
|
3302
|
+
continue;
|
|
3303
|
+
}
|
|
3304
|
+
tasks.push({
|
|
3305
|
+
kind: "text",
|
|
3306
|
+
text: compact ? "]" : `\n${JSON_INDENT.repeat(task.depth)}]`
|
|
3307
|
+
});
|
|
3308
|
+
for (let index = current.length - 1; index >= 0; index--) {
|
|
3309
|
+
const item = current[index];
|
|
3310
|
+
if (item === void 0) throw new Error("dashr-repl: cannot render a sparse JSON array");
|
|
3311
|
+
tasks.push({
|
|
3312
|
+
kind: "value",
|
|
3313
|
+
value: item,
|
|
3314
|
+
depth: childDepth,
|
|
3315
|
+
compact
|
|
3316
|
+
});
|
|
3317
|
+
tasks.push({
|
|
3318
|
+
kind: "text",
|
|
3319
|
+
text: compact ? index === 0 ? "" : "," : `${index === 0 ? "\n" : ",\n"}${JSON_INDENT.repeat(childDepth)}`
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3322
|
+
continue;
|
|
3323
|
+
}
|
|
3324
|
+
const keys = Object.keys(current);
|
|
3325
|
+
chunks.push("{");
|
|
3326
|
+
if (keys.length === 0) {
|
|
3327
|
+
chunks.push("}");
|
|
3328
|
+
continue;
|
|
3329
|
+
}
|
|
3330
|
+
tasks.push({
|
|
3331
|
+
kind: "text",
|
|
3332
|
+
text: compact ? "}" : `\n${JSON_INDENT.repeat(task.depth)}}`
|
|
3333
|
+
});
|
|
3334
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
3335
|
+
const key = keys[index];
|
|
3336
|
+
if (key === void 0) throw new Error("dashr-repl: cannot render a missing JSON object key");
|
|
3337
|
+
const item = current[key];
|
|
3338
|
+
if (item === void 0) throw new Error("dashr-repl: cannot render an undefined JSON object property");
|
|
3339
|
+
tasks.push({
|
|
3340
|
+
kind: "value",
|
|
3341
|
+
value: item,
|
|
3342
|
+
depth: childDepth,
|
|
3343
|
+
compact
|
|
3344
|
+
});
|
|
3345
|
+
tasks.push({
|
|
3346
|
+
kind: "text",
|
|
3347
|
+
text: compact ? `${index === 0 ? "" : ","}${JSON.stringify(key)}:` : `${index === 0 ? "\n" : ",\n"}${JSON_INDENT.repeat(childDepth)}${JSON.stringify(key)}: `
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
return chunks.join("");
|
|
3352
|
+
}
|
|
3353
|
+
/** Render one present cell completion value for the model-facing result text (ported). */
|
|
3354
|
+
function renderValue(value) {
|
|
3355
|
+
return typeof value === "string" ? value : renderJsonValue(value);
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Build the `eval` {@link ToolDefinition}: required `cell` and
|
|
3359
|
+
* `description` parameters, executed through the dispatch bridge described
|
|
3360
|
+
* in the module doc. Sub-calls ride the registry's exported staged scheduler
|
|
3361
|
+
* (`prepare`/`dispatch`/`finalize`/`finish`) under the native concurrency
|
|
3362
|
+
* contract; each sub-dispatch is logged for reconstruction
|
|
3363
|
+
* (`tool/ptc-dispatch-start` / `tool/ptc-dispatch`) while only the outer
|
|
3364
|
+
* curated result enters model history.
|
|
3365
|
+
* @param registry - the host tool registry (sub-calls go through its staged
|
|
3366
|
+
* scheduler, bindings cover its registered tools).
|
|
3367
|
+
* @param options - the bridge capabilities described above.
|
|
3368
|
+
* @returns the registry-ready definition.
|
|
3369
|
+
*/
|
|
3370
|
+
function createRunCellTool(registry, options) {
|
|
3371
|
+
const { requireRuntime, maxParallel, shapeDispatchLog, warn } = options;
|
|
3372
|
+
return defineTool({
|
|
3373
|
+
name: EVAL_NAME,
|
|
3374
|
+
description: EVAL_DESCRIPTION,
|
|
3375
|
+
parameters: {
|
|
3376
|
+
cell: {
|
|
3377
|
+
type: "string",
|
|
3378
|
+
required: true,
|
|
3379
|
+
description: EVAL_CELL_PARAM_DESCRIPTION
|
|
3380
|
+
},
|
|
3381
|
+
description: {
|
|
3382
|
+
type: "string",
|
|
3383
|
+
required: true,
|
|
3384
|
+
description: EVAL_DESCRIPTION_PARAM_DESCRIPTION
|
|
3385
|
+
},
|
|
3386
|
+
timeout: {
|
|
3387
|
+
type: "number",
|
|
3388
|
+
description: "Optional wall-clock budget for this cell, in seconds; the cell is interrupted (then force-stopped) if it exceeds the budget. Omit to use the runtime default."
|
|
3389
|
+
},
|
|
3390
|
+
reset: {
|
|
3391
|
+
type: "boolean",
|
|
3392
|
+
description: "Optional: reset the persistent pad namespace to EMPTY before running — variables, imports, and definitions from earlier cells are discarded and the pad restarts fresh. Omit to keep the namespace."
|
|
3393
|
+
}
|
|
3394
|
+
},
|
|
3395
|
+
output: {
|
|
3396
|
+
schema: {
|
|
3397
|
+
type: "object",
|
|
3398
|
+
additionalProperties: false,
|
|
3399
|
+
properties: {
|
|
3400
|
+
logs: {
|
|
3401
|
+
type: "array",
|
|
3402
|
+
required: true,
|
|
3403
|
+
items: { type: "string" }
|
|
3404
|
+
},
|
|
3405
|
+
result: { type: "json" }
|
|
3406
|
+
}
|
|
3407
|
+
},
|
|
3408
|
+
render: (_args, value) => {
|
|
3409
|
+
const rendered = value.result === void 0 ? "" : renderValue(value.result);
|
|
3410
|
+
const parts = [value.logs.join("\n"), rendered].filter((part) => part.length > 0);
|
|
3411
|
+
return [{
|
|
3412
|
+
type: "text",
|
|
3413
|
+
text: parts.length > 0 ? parts.join("\n") : `(${EVAL_NAME} completed with no output)`
|
|
3414
|
+
}];
|
|
3415
|
+
}
|
|
3416
|
+
},
|
|
3417
|
+
async execute(args, exec) {
|
|
3418
|
+
if (args.description.trim().length === 0) throw new Error("dashr-repl: invalid description: expected a non-empty string");
|
|
3419
|
+
const runtime = requireRuntime();
|
|
3420
|
+
const runController = new AbortController();
|
|
3421
|
+
const onOuterAbort = () => {
|
|
3422
|
+
runController.abort(exec.signal.reason);
|
|
3423
|
+
};
|
|
3424
|
+
exec.signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
3425
|
+
let dispatches = 0;
|
|
3426
|
+
const pendingQueue = [];
|
|
3427
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
3428
|
+
/** Tracked settle-event side work (log-content listener + append), drained at run settlement. */
|
|
3429
|
+
const logWork = /* @__PURE__ */ new Set();
|
|
3430
|
+
const commitQueue = [];
|
|
3431
|
+
let exclusiveActive = false;
|
|
3432
|
+
let driving = false;
|
|
3433
|
+
let driverRun = Promise.resolve();
|
|
3434
|
+
let wake;
|
|
3435
|
+
const wakeup = () => {
|
|
3436
|
+
const release = wake;
|
|
3437
|
+
wake = void 0;
|
|
3438
|
+
release?.();
|
|
3439
|
+
};
|
|
3440
|
+
/** The single ordered lane (ported; see the block comment above). */
|
|
3441
|
+
const drive = () => {
|
|
3442
|
+
if (driving) return driverRun;
|
|
3443
|
+
driving = true;
|
|
3444
|
+
driverRun = (async () => {
|
|
3445
|
+
try {
|
|
3446
|
+
for (;;) {
|
|
3447
|
+
const signal = new Promise((resolve$1) => {
|
|
3448
|
+
wake = resolve$1;
|
|
3449
|
+
});
|
|
3450
|
+
const commitHead = commitQueue[0];
|
|
3451
|
+
if (commitHead !== void 0 && commitHead.settled) {
|
|
3452
|
+
commitQueue.shift();
|
|
3453
|
+
await commitHead.commit();
|
|
3454
|
+
if (commitHead.mode === "exclusive") exclusiveActive = false;
|
|
3455
|
+
continue;
|
|
3456
|
+
}
|
|
3457
|
+
const head = pendingQueue[0];
|
|
3458
|
+
if (head !== void 0) {
|
|
3459
|
+
if (runController.signal.aborted) {
|
|
3460
|
+
pendingQueue.shift();
|
|
3461
|
+
head.abandon();
|
|
3462
|
+
continue;
|
|
3463
|
+
}
|
|
3464
|
+
const mode = head.classify();
|
|
3465
|
+
if (!exclusiveActive && (mode === "exclusive" ? inFlight.size === 0 : inFlight.size < maxParallel)) {
|
|
3466
|
+
if (mode === "exclusive") exclusiveActive = true;
|
|
3467
|
+
head.mode = mode;
|
|
3468
|
+
pendingQueue.shift();
|
|
3469
|
+
commitQueue.push(head);
|
|
3470
|
+
await head.start();
|
|
3471
|
+
const flight = head.flight.finally(() => {
|
|
3472
|
+
inFlight.delete(flight);
|
|
3473
|
+
wakeup();
|
|
3474
|
+
});
|
|
3475
|
+
inFlight.add(flight);
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
if (pendingQueue.length === 0 && commitQueue.length === 0 && inFlight.size === 0) return;
|
|
3480
|
+
await signal;
|
|
3481
|
+
}
|
|
3482
|
+
} catch (error) {
|
|
3483
|
+
warn(`dashr-repl: dispatch lane failed (${error instanceof Error ? error.message : String(error)}); settling queued dispatches as errors`);
|
|
3484
|
+
for (const entry of [...pendingQueue, ...commitQueue]) if (!entry.settled) entry.fail(error);
|
|
3485
|
+
pendingQueue.length = 0;
|
|
3486
|
+
commitQueue.length = 0;
|
|
3487
|
+
exclusiveActive = false;
|
|
3488
|
+
} finally {
|
|
3489
|
+
driving = false;
|
|
3490
|
+
wake = void 0;
|
|
3491
|
+
}
|
|
3492
|
+
})();
|
|
3493
|
+
return driverRun;
|
|
3494
|
+
};
|
|
3495
|
+
/** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */
|
|
3496
|
+
const drainDispatches = async () => {
|
|
3497
|
+
await drive();
|
|
3498
|
+
while (logWork.size > 0) await Promise.allSettled([...logWork]);
|
|
3499
|
+
};
|
|
3500
|
+
const runOver = () => runController.signal.aborted;
|
|
3501
|
+
const binding = (name$1) => async (rawArgs) => {
|
|
3502
|
+
if (runOver()) throw new Error(`${EVAL_NAME} run is over (${String(runController.signal.reason)}); ${name$1} not dispatched`);
|
|
3503
|
+
const normalized = jsonNormalizeArgs(rawArgs);
|
|
3504
|
+
const n = ++dispatches;
|
|
3505
|
+
const subCallId = toolCallId(`${String(exec.callId)}:code:${n}`);
|
|
3506
|
+
const input = {
|
|
3507
|
+
callId: subCallId,
|
|
3508
|
+
rootCallId: exec.rootCallId,
|
|
3509
|
+
name: name$1,
|
|
3510
|
+
arguments: normalized.dispatched,
|
|
3511
|
+
...exec.agent ? { agent: exec.agent } : {},
|
|
3512
|
+
parent: exec.token,
|
|
3513
|
+
signal: runController.signal
|
|
3514
|
+
};
|
|
3515
|
+
const scheduler = registry[TOOL_RUNTIME_SCHEDULER];
|
|
3516
|
+
if (scheduler === void 0) {
|
|
3517
|
+
let resolvedFrom;
|
|
3518
|
+
try {
|
|
3519
|
+
resolvedFrom = createRequire(import.meta.url).resolve("@deepseek-ai/dsh-tools");
|
|
3520
|
+
} catch {
|
|
3521
|
+
resolvedFrom = "(unresolvable from the plugin position)";
|
|
3522
|
+
}
|
|
3523
|
+
throw new Error(`${EVAL_NAME}: the tools service mounted by this composition lacks the TOOL_RUNTIME_SCHEDULER symbol as this plugin sees it — REPL sub-dispatch cannot run. Most likely a dual-copy dsh-tools: the symbol keying the mounted ToolRuntime instance is not the symbol this plugin imported (named methods like executionMode keep working across copies; only the symbol-keyed field goes missing). This plugin resolves @deepseek-ai/dsh-tools from: ${resolvedFrom}. Check the deployed plugin's node_modules for stray @deepseek-ai entries (only schemastery and cosmokit belong there)`);
|
|
3524
|
+
}
|
|
3525
|
+
const outcome = await new Promise((resolve$1, reject) => {
|
|
3526
|
+
let parked;
|
|
3527
|
+
const settle = (result) => {
|
|
3528
|
+
resolve$1(result.isError ? {
|
|
3529
|
+
isError: true,
|
|
3530
|
+
message: result.error.message
|
|
3531
|
+
} : {
|
|
3532
|
+
isError: false,
|
|
3533
|
+
value: result.value
|
|
3534
|
+
});
|
|
3535
|
+
const agent = exec.agent;
|
|
3536
|
+
if (agent === void 0) return;
|
|
3537
|
+
const task = (async () => {
|
|
3538
|
+
const logged = await shapeDispatchLog({
|
|
3539
|
+
exec,
|
|
3540
|
+
agent,
|
|
3541
|
+
subCallId,
|
|
3542
|
+
name: name$1,
|
|
3543
|
+
isError: result.isError,
|
|
3544
|
+
content: result.content
|
|
3545
|
+
});
|
|
3546
|
+
agent.session.append("tool/ptc-dispatch", {
|
|
3547
|
+
rootCallId: exec.rootCallId,
|
|
3548
|
+
parentCallId: exec.callId,
|
|
3549
|
+
subCallId,
|
|
3550
|
+
name: name$1,
|
|
3551
|
+
arguments: normalized.logged,
|
|
3552
|
+
isError: result.isError,
|
|
3553
|
+
content: logged
|
|
3554
|
+
});
|
|
3555
|
+
})().finally(() => {
|
|
3556
|
+
logWork.delete(task);
|
|
3557
|
+
});
|
|
3558
|
+
logWork.add(task);
|
|
3559
|
+
};
|
|
3560
|
+
const settleError = (error) => {
|
|
3561
|
+
settle({
|
|
3562
|
+
isError: true,
|
|
3563
|
+
error: { message: error instanceof Error ? error.message : String(error) },
|
|
3564
|
+
content: []
|
|
3565
|
+
});
|
|
3566
|
+
};
|
|
3567
|
+
pendingQueue.push({
|
|
3568
|
+
flight: Promise.resolve(),
|
|
3569
|
+
settled: false,
|
|
3570
|
+
classify: () => registry.executionMode(input).kind,
|
|
3571
|
+
abandon: () => {
|
|
3572
|
+
reject(/* @__PURE__ */ new Error(`${EVAL_NAME} run is over (${String(runController.signal.reason)}); ${name$1} tool call abandoned`));
|
|
3573
|
+
},
|
|
3574
|
+
fail(error) {
|
|
3575
|
+
if (this.settled) return;
|
|
3576
|
+
this.settled = true;
|
|
3577
|
+
settleError(error);
|
|
3578
|
+
},
|
|
3579
|
+
async start() {
|
|
3580
|
+
exec.agent?.session.append("tool/ptc-dispatch-start", {
|
|
3581
|
+
rootCallId: exec.rootCallId,
|
|
3582
|
+
parentCallId: exec.callId,
|
|
3583
|
+
subCallId,
|
|
3584
|
+
name: name$1,
|
|
3585
|
+
arguments: normalized.logged
|
|
3586
|
+
});
|
|
3587
|
+
try {
|
|
3588
|
+
const prepared = await scheduler.prepare(input);
|
|
3589
|
+
if (prepared.kind === "dispatch") {
|
|
3590
|
+
this.flight = scheduler.dispatch(prepared.exec).then((dispatchOutcome) => {
|
|
3591
|
+
parked = {
|
|
3592
|
+
kind: dispatchOutcome.kind,
|
|
3593
|
+
exec: prepared.exec,
|
|
3594
|
+
result: dispatchOutcome.result
|
|
3595
|
+
};
|
|
3596
|
+
this.settled = true;
|
|
3597
|
+
}, (error) => {
|
|
3598
|
+
this.settled = true;
|
|
3599
|
+
settleError(error);
|
|
3600
|
+
});
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
parked = {
|
|
3604
|
+
kind: prepared.kind,
|
|
3605
|
+
exec: prepared.exec,
|
|
3606
|
+
result: prepared.result
|
|
3607
|
+
};
|
|
3608
|
+
this.settled = true;
|
|
3609
|
+
} catch (error) {
|
|
3610
|
+
this.settled = true;
|
|
3611
|
+
settleError(error);
|
|
3612
|
+
}
|
|
3613
|
+
},
|
|
3614
|
+
async commit() {
|
|
3615
|
+
if (parked === void 0) return;
|
|
3616
|
+
try {
|
|
3617
|
+
const result = parked.kind === "post-result" ? await scheduler.finalize(parked.exec, parked.result) : scheduler.finish(parked.exec, parked.result);
|
|
3618
|
+
for (const context of result.additionalContexts ?? []) exec.deferContext(context);
|
|
3619
|
+
if (result.concludesTurn) exec.concludeTurn();
|
|
3620
|
+
settle(result);
|
|
3621
|
+
while (logWork.size > maxParallel) await Promise.race(logWork);
|
|
3622
|
+
} catch (error) {
|
|
3623
|
+
settleError(error);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
});
|
|
3627
|
+
wakeup();
|
|
3628
|
+
drive();
|
|
3629
|
+
});
|
|
3630
|
+
if (runOver()) throw new Error(`${EVAL_NAME} run is over (${String(runController.signal.reason)}); ${name$1} result discarded`);
|
|
3631
|
+
if (outcome.isError) throw new Error(outcome.message);
|
|
3632
|
+
return outcome.value;
|
|
3633
|
+
};
|
|
3634
|
+
const toolFunctions = {};
|
|
3635
|
+
for (const schema of registry.schemas(exec.agent)) {
|
|
3636
|
+
if (schema.name === EVAL_NAME) continue;
|
|
3637
|
+
if (!isFlatBindableName(schema.name)) continue;
|
|
3638
|
+
toolFunctions[schema.name] = async (args$1) => binding(schema.name)(args$1);
|
|
3639
|
+
}
|
|
3640
|
+
try {
|
|
3641
|
+
let result;
|
|
3642
|
+
try {
|
|
3643
|
+
result = await runtime.run({
|
|
3644
|
+
program: args.cell,
|
|
3645
|
+
bindings: [{
|
|
3646
|
+
global: "tool",
|
|
3647
|
+
functions: { ...toolFunctions },
|
|
3648
|
+
errorClass: TOOL_CALL_ERROR_CLASS
|
|
3649
|
+
}],
|
|
3650
|
+
signal: runController.signal,
|
|
3651
|
+
...exec.agent ? { principal: exec.agent.id } : {},
|
|
3652
|
+
...exec.agent ? { cwd: exec.agent.session.header.cwd } : {},
|
|
3653
|
+
...typeof args.timeout === "number" && Number.isFinite(args.timeout) && args.timeout > 0 ? { timeoutMs: Math.max(1, Math.floor(args.timeout * 1e3)) } : {},
|
|
3654
|
+
...args.reset === true ? { reset: true } : {}
|
|
3655
|
+
});
|
|
3656
|
+
} finally {
|
|
3657
|
+
runController.abort(`${EVAL_NAME} settled`);
|
|
3658
|
+
await drainDispatches();
|
|
3659
|
+
}
|
|
3660
|
+
if (result.error) {
|
|
3661
|
+
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
|
|
3662
|
+
throw new DASHRRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`);
|
|
3663
|
+
}
|
|
3664
|
+
return {
|
|
3665
|
+
logs: result.logs,
|
|
3666
|
+
...result.value !== void 0 ? { result: result.value } : {}
|
|
3667
|
+
};
|
|
3668
|
+
} finally {
|
|
3669
|
+
exec.signal.removeEventListener("abort", onOuterAbort);
|
|
3670
|
+
}
|
|
3671
|
+
},
|
|
3672
|
+
presentCall: (args) => ({
|
|
3673
|
+
card: "generic",
|
|
3674
|
+
title: args.description,
|
|
3675
|
+
kind: "execute",
|
|
3676
|
+
rawInput: args.cell
|
|
3677
|
+
})
|
|
3678
|
+
});
|
|
3679
|
+
}
|
|
3680
|
+
/**
|
|
3681
|
+
* Declare the DASHR cell presentation for every agent this composition
|
|
3682
|
+
* covers: the `eval` transport tool, the model-direct collapse guard, and the assembly filter that
|
|
3683
|
+
* leaves `eval` the only contributed tool schema.
|
|
3684
|
+
*
|
|
3685
|
+
* Mount through a preset's standing scope (`agent.cordis.yml` include row);
|
|
3686
|
+
* mounting unscoped is legal for a whole-process DASHR deployment and gives
|
|
3687
|
+
* the same shape at the global layer.
|
|
3688
|
+
* @param ctx - the mounting composition's context (a preset's standing scope).
|
|
3689
|
+
* @param config - the plugin config.
|
|
3690
|
+
*/
|
|
3691
|
+
/** The runtime slice's config keys, mirrored from `DashrRuntime.Config` in `./runtime.ts`. */
|
|
3692
|
+
const RUNTIME_CONFIG_KEYS = [
|
|
3693
|
+
"python",
|
|
3694
|
+
"cwd",
|
|
3695
|
+
"startupTimeoutMs",
|
|
3696
|
+
"runTimeoutMs",
|
|
3697
|
+
"interruptGraceMs",
|
|
3698
|
+
"interruptConfirmMs",
|
|
3699
|
+
"disposeTimeoutMs",
|
|
3700
|
+
"snapshotTimeoutMs",
|
|
3701
|
+
"maxOutputBytes",
|
|
3702
|
+
"snapshotDir",
|
|
3703
|
+
"snapshotSizeCapBytes",
|
|
3704
|
+
"username",
|
|
3705
|
+
"kernelEnvDir",
|
|
3706
|
+
"kernelPythonVersion",
|
|
3707
|
+
"kernelAutoInstall"
|
|
3708
|
+
];
|
|
3709
|
+
/** The runtime slice of the merged config — the keys `DashrRuntime` owns. */
|
|
3710
|
+
function pickRuntimeConfig(config) {
|
|
3711
|
+
const out = {};
|
|
3712
|
+
for (const key of RUNTIME_CONFIG_KEYS) out[key] = config[key];
|
|
3713
|
+
return out;
|
|
3714
|
+
}
|
|
3715
|
+
function apply(ctx, config) {
|
|
3716
|
+
ctx.plugin(DashrRuntime, pickRuntimeConfig(config));
|
|
3717
|
+
const logger = ctx.logger("dashr-repl");
|
|
3718
|
+
const maxParallel = resolveMaxParallelSubCalls(config.maxParallelSubCalls);
|
|
3719
|
+
{
|
|
3720
|
+
const kernelConfig = pickRuntimeConfig(config);
|
|
3721
|
+
resolveKernelEnv({
|
|
3722
|
+
python: kernelConfig.python,
|
|
3723
|
+
venvDir: kernelConfig.kernelEnvDir,
|
|
3724
|
+
pythonVersion: kernelConfig.kernelPythonVersion,
|
|
3725
|
+
autoInstall: kernelConfig.kernelAutoInstall ?? true,
|
|
3726
|
+
log: (level, message) => {
|
|
3727
|
+
logger[level](message);
|
|
3728
|
+
}
|
|
3729
|
+
}).catch((error) => {
|
|
3730
|
+
logger.error(`dashr-repl: spin-up kernel check failed (non-blocking): ${error instanceof Error ? error.message : String(error)}`);
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
ctx.inject(["replRuntime"], (runtimeCtx) => {
|
|
3734
|
+
const requireRuntime = () => {
|
|
3735
|
+
const runtime = runtimeCtx.get("replRuntime");
|
|
3736
|
+
if (!runtime) throw new Error("dashr-repl: eval requires an replRuntime service — load a ctx.replRuntime implementation in this composition (dashr-repl mounts one at apply)");
|
|
3737
|
+
if (runtime.language !== "python") throw new Error(`dashr-repl: no cell SDK for runtime language ${JSON.stringify(runtime.language)} (dashr-repl presents Python only; got a ${JSON.stringify(runtime.language)} runtime under ctx.replRuntime)`);
|
|
3738
|
+
return runtime;
|
|
3739
|
+
};
|
|
3740
|
+
const registry = runtimeCtx.tools;
|
|
3741
|
+
const systemPrompt = runtimeCtx.get("systemPrompt");
|
|
3742
|
+
if (!systemPrompt) throw new Error("dashr-repl: ctx.systemPrompt is required beside ctx.tools (the tools service itself depends on it) — this composition mounted tools without a system prompt registry");
|
|
3743
|
+
const shapeDispatchLog = async (dispatch) => {
|
|
3744
|
+
try {
|
|
3745
|
+
const own = await runtimeCtx.waterfall(scopeTarget(registry, dispatch.agent), "dashr/repl-dispatch-log", dispatch, () => Promise.resolve(dispatch.content));
|
|
3746
|
+
return await runtimeCtx.waterfall(scopeTarget(registry, dispatch.agent), "tools/ptc-dispatch-log", dispatch, () => Promise.resolve(own));
|
|
3747
|
+
} catch (error) {
|
|
3748
|
+
logger.warn(`dashr-repl: dispatch-log listener failed for ${dispatch.name}: ${error instanceof Error ? error.message : String(error)}; logging the original settled content`);
|
|
3749
|
+
return dispatch.content;
|
|
3750
|
+
}
|
|
3751
|
+
};
|
|
3752
|
+
const requireSubagents = () => runtimeCtx.get("subagents");
|
|
3753
|
+
let serviceForAgent;
|
|
3754
|
+
import("@deepseek-ai/dsh-agent-presets").then((mod) => {
|
|
3755
|
+
serviceForAgent = mod.serviceForAgent;
|
|
3756
|
+
}, () => {});
|
|
3757
|
+
const requireWorkflowEngine = (agent) => {
|
|
3758
|
+
if (agent !== void 0 && serviceForAgent !== void 0) {
|
|
3759
|
+
const preset = serviceForAgent(runtimeCtx, agent, "workflowEngine");
|
|
3760
|
+
if (preset !== void 0) return preset;
|
|
3761
|
+
}
|
|
3762
|
+
return runtimeCtx.get("workflowEngine");
|
|
3763
|
+
};
|
|
3764
|
+
runtimeCtx.tools.register(createRunCellTool(registry, {
|
|
3765
|
+
requireRuntime,
|
|
3766
|
+
maxParallel,
|
|
3767
|
+
shapeDispatchLog,
|
|
3768
|
+
warn: (message) => logger.warn(message)
|
|
3769
|
+
}));
|
|
3770
|
+
for (const bridgeTool of createAgentBridgeTools({
|
|
3771
|
+
requireSubagents,
|
|
3772
|
+
requireWorkflowEngine
|
|
3773
|
+
})) runtimeCtx.tools.register(bridgeTool);
|
|
3774
|
+
runtimeCtx.tools.register(createLlmCompletionTool({ requireLlm: () => runtimeCtx.get("llm") }));
|
|
3775
|
+
const wireMasked = /* @__PURE__ */ new WeakSet();
|
|
3776
|
+
runtimeCtx.on("agent/created", ({ agent }) => {
|
|
3777
|
+
if (wireMasked.has(agent)) return void 0;
|
|
3778
|
+
wireMasked.add(agent);
|
|
3779
|
+
try {
|
|
3780
|
+
captureAllTools(runtimeCtx, agent);
|
|
3781
|
+
const visible = new Set(agent.ctx.tools.schemas(agent).map((schema) => schema.name));
|
|
3782
|
+
const deny = [...WIRE_MASKED_NAMES].filter((name$1) => visible.has(name$1));
|
|
3783
|
+
if (deny.length === 0) return void 0;
|
|
3784
|
+
try {
|
|
3785
|
+
agent.ctx.tools.restrict({ deny });
|
|
3786
|
+
return;
|
|
3787
|
+
} catch {}
|
|
3788
|
+
for (const name$1 of deny) try {
|
|
3789
|
+
agent.ctx.tools.restrict({ deny: [name$1] });
|
|
3790
|
+
} catch {}
|
|
3791
|
+
} catch (error) {
|
|
3792
|
+
logger.warn(`dashr-repl: wire mask failed for agent ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
3795
|
+
systemPrompt.context({
|
|
3796
|
+
name: "dashr:escalation-guidance",
|
|
3797
|
+
order: ESCALATION_GUIDANCE_ORDER,
|
|
3798
|
+
text: (context) => {
|
|
3799
|
+
const session = context.agent?.session;
|
|
3800
|
+
if (session === void 0) return "";
|
|
3801
|
+
const sandboxPolicy = runtimeCtx.get("sandboxPolicy");
|
|
3802
|
+
if (!sandboxPolicy) return "";
|
|
3803
|
+
return sandboxPolicy.resolve({ session }).mode === "workspace-write" ? "A sandbox-deniable/denied call may be escalated with `sandbox_permissions=\"danger-full-access\"` and a one-line `justification`; the runtime will prompt for user approval. Escalation and its approval/denial are per-call." : "";
|
|
3804
|
+
}
|
|
3805
|
+
});
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3808
|
+
var src_default = {
|
|
3809
|
+
name,
|
|
3810
|
+
inject,
|
|
3811
|
+
Config,
|
|
3812
|
+
apply
|
|
3813
|
+
};
|
|
3814
|
+
|
|
3815
|
+
//#endregion
|
|
3816
|
+
export { Config, DASHRRunFailedError, DashrRuntime, ESCALATION_GUIDANCE_ORDER, EVAL_NAME, MASKED_TOOL_NAMES, ReplRuntime, WIRE_MASKED_NAMES, apply, createRunCellTool, src_default as default, inject, name, resolveMaxParallelSubCalls };
|