ashlr-plugin 1.36.4
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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +39 -0
- package/.claude-plugin/plugin.json +36 -0
- package/.codex/agents/README.md +9 -0
- package/.codex/agents/ashlr-explorer.md +36 -0
- package/.codex/agents/ashlr-worker.md +38 -0
- package/.codex-plugin/plugin.json +45 -0
- package/.mcp.json +14 -0
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/agents/ashlr-code.md +69 -0
- package/agents/ashlr-explore.md +44 -0
- package/agents/ashlr-plan.md +54 -0
- package/bench/fixtures/notebook-large.ipynb +660 -0
- package/bench/fixtures/notebook-small.ipynb +39 -0
- package/bench/fixtures/tasklist-closed.json +62 -0
- package/bench/fixtures/tasklist-large-mixed.json +27 -0
- package/bench/fixtures/tasklist-sprint-open.json +79 -0
- package/bench/fixtures/websearch-bun-runtime.json +32 -0
- package/bench/fixtures/websearch-react-hooks-patterns.json +32 -0
- package/bench/fixtures/websearch-typescript-generics.json +38 -0
- package/bench/refs/node-sdk/.refrev +1 -0
- package/bench/refs/node-sdk/README.md +17 -0
- package/bench/refs/node-sdk/src/agent/agent.ts +163 -0
- package/bench/refs/node-sdk/src/agent/create-agent-ui-stream.ts +83 -0
- package/bench/refs/node-sdk/src/agent/tool-loop-agent.ts +238 -0
- package/bench/refs/node-sdk/src/embed/embed-many.ts +409 -0
- package/bench/refs/node-sdk/src/embed/embed-result.ts +50 -0
- package/bench/refs/node-sdk/src/embed/embed.ts +257 -0
- package/bench/refs/node-sdk/src/error/invalid-argument-error.ts +34 -0
- package/bench/refs/node-sdk/src/error/invalid-tool-input-error.ts +33 -0
- package/bench/refs/node-sdk/src/error/no-object-generated-error.ts +70 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image-result.ts +42 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image.ts +362 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object-result.ts +67 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object.ts +501 -0
- package/bench/refs/node-sdk/src/generate-object/inject-json-instruction.ts +30 -0
- package/bench/refs/node-sdk/src/generate-object/output-strategy.ts +415 -0
- package/bench/refs/node-sdk/src/generate-object/repair-text.ts +12 -0
- package/bench/refs/node-sdk/src/generate-object/stream-object.ts +960 -0
- package/bench/refs/node-sdk/src/generate-speech/generate-speech.ts +191 -0
- package/bench/refs/node-sdk/src/generate-text/generate-text.ts +1484 -0
- package/bench/refs/node-sdk/src/generate-text/stream-text.ts +2708 -0
- package/bench/refs/node-sdk/src/generate-text/tool-call.ts +59 -0
- package/bench/refs/node-sdk/src/generate-text/tool-result.ts +36 -0
- package/bench/refs/node-sdk/src/telemetry/create-telemetry-dispatcher.ts +146 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry-registry.ts +15 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry.ts +223 -0
- package/bench/refs/node-sdk/src/util/async-iterable-stream.ts +94 -0
- package/bench/refs/node-sdk/src/util/consume-stream.ts +31 -0
- package/bench/refs/node-sdk/src/util/create-stitchable-stream.ts +112 -0
- package/bench/refs/node-sdk/src/util/download/download.ts +74 -0
- package/bench/refs/node-sdk/src/util/error-handler.ts +1 -0
- package/bench/refs/node-sdk/src/util/fix-json.ts +401 -0
- package/bench/refs/node-sdk/src/util/merge-objects.ts +79 -0
- package/bench/refs/python-lib/.refrev +1 -0
- package/bench/refs/python-lib/README.md +18 -0
- package/bench/refs/python-lib/pandas/core/_numba/executor.py +249 -0
- package/bench/refs/python-lib/pandas/core/_numba/extensions.py +586 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/mean_.py +198 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/min_max_.py +178 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/sum_.py +255 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/var_.py +251 -0
- package/bench/refs/python-lib/pandas/core/accessor.py +520 -0
- package/bench/refs/python-lib/pandas/core/api.py +138 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_accumulations.py +97 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_reductions.py +252 -0
- package/bench/refs/python-lib/pandas/core/array_algos/putmask.py +150 -0
- package/bench/refs/python-lib/pandas/core/array_algos/quantile.py +232 -0
- package/bench/refs/python-lib/pandas/core/array_algos/replace.py +159 -0
- package/bench/refs/python-lib/pandas/core/array_algos/take.py +609 -0
- package/bench/refs/python-lib/pandas/core/arraylike.py +534 -0
- package/bench/refs/python-lib/pandas/core/arrays/_arrow_string_mixins.py +434 -0
- package/bench/refs/python-lib/pandas/core/arrays/_mixins.py +630 -0
- package/bench/refs/python-lib/pandas/core/arrays/_ranges.py +276 -0
- package/bench/refs/python-lib/pandas/core/arrays/_utils.py +77 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/accessors.py +521 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/extension_types.py +174 -0
- package/bench/refs/python-lib/pandas/core/arrays/boolean.py +454 -0
- package/bench/refs/python-lib/pandas/core/arrays/floating.py +192 -0
- package/bench/refs/python-lib/pandas/core/arrays/integer.py +296 -0
- package/bench/refs/python-lib/pandas/core/arrays/masked.py +2140 -0
- package/bench/refs/python-lib/pandas/core/arrays/numeric.py +305 -0
- package/bench/refs/python-lib/pandas/core/arrays/numpy_.py +701 -0
- package/bench/refs/python-lib/pandas/core/arrays/period.py +1586 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/accessor.py +505 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/scipy_sparse.py +208 -0
- package/bench/refs/rust-project/.refrev +1 -0
- package/bench/refs/rust-project/README.md +17 -0
- package/bench/refs/rust-project/tokio/src/doc/os.rs +68 -0
- package/bench/refs/rust-project/tokio/src/fs/dir_builder.rs +129 -0
- package/bench/refs/rust-project/tokio/src/fs/file.rs +1113 -0
- package/bench/refs/rust-project/tokio/src/fs/mocks.rs +176 -0
- package/bench/refs/rust-project/tokio/src/fs/mod.rs +324 -0
- package/bench/refs/rust-project/tokio/src/fs/open_options.rs +856 -0
- package/bench/refs/rust-project/tokio/src/fs/read.rs +80 -0
- package/bench/refs/rust-project/tokio/src/fs/read_dir.rs +357 -0
- package/bench/refs/rust-project/tokio/src/fs/write.rs +99 -0
- package/bench/refs/rust-project/tokio/src/future/maybe_done.rs +123 -0
- package/bench/refs/rust-project/tokio/src/future/try_join.rs +82 -0
- package/bench/refs/rust-project/tokio/src/io/async_buf_read.rs +117 -0
- package/bench/refs/rust-project/tokio/src/io/async_read.rs +133 -0
- package/bench/refs/rust-project/tokio/src/io/async_seek.rs +96 -0
- package/bench/refs/rust-project/tokio/src/io/async_write.rs +402 -0
- package/bench/refs/rust-project/tokio/src/io/blocking.rs +339 -0
- package/bench/refs/rust-project/tokio/src/io/interest.rs +345 -0
- package/bench/refs/rust-project/tokio/src/io/join.rs +130 -0
- package/bench/refs/rust-project/tokio/src/io/mod.rs +308 -0
- package/bench/refs/rust-project/tokio/src/io/poll_evented.rs +319 -0
- package/bench/refs/rust-project/tokio/src/io/read_buf.rs +344 -0
- package/bench/refs/rust-project/tokio/src/io/ready.rs +322 -0
- package/bench/refs/rust-project/tokio/src/io/split.rs +166 -0
- package/bench/refs/rust-project/tokio/src/io/stderr.rs +148 -0
- package/bench/refs/rust-project/tokio/src/io/stdin.rs +98 -0
- package/bench/refs/rust-project/tokio/src/io/stdio_common.rs +222 -0
- package/bench/refs/rust-project/tokio/src/io/stdout.rs +197 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_reader.rs +311 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_stream.rs +207 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_writer.rs +310 -0
- package/bench/refs/rust-project/tokio/src/io/util/chain.rs +144 -0
- package/commands/_spawn-patterns.json +32 -0
- package/commands/ashlr-allow.md +21 -0
- package/commands/ashlr-benchmark.md +53 -0
- package/commands/ashlr-brief.md +97 -0
- package/commands/ashlr-budget.md +76 -0
- package/commands/ashlr-compact.md +54 -0
- package/commands/ashlr-dashboard.md +37 -0
- package/commands/ashlr-doctor.md +56 -0
- package/commands/ashlr-eco-mode.md +92 -0
- package/commands/ashlr-genome-init.md +24 -0
- package/commands/ashlr-genome-keygen.md +36 -0
- package/commands/ashlr-genome-loop.md +118 -0
- package/commands/ashlr-genome-push.md +36 -0
- package/commands/ashlr-genome-rewrap.md +48 -0
- package/commands/ashlr-genome-team-init.md +67 -0
- package/commands/ashlr-handoff.md +23 -0
- package/commands/ashlr-help.md +86 -0
- package/commands/ashlr-hook-timings.md +30 -0
- package/commands/ashlr-leaderboard.md +50 -0
- package/commands/ashlr-ollama-setup.md +39 -0
- package/commands/ashlr-orchestrate-status.md +72 -0
- package/commands/ashlr-orchestrate.md +83 -0
- package/commands/ashlr-parallelize.md +63 -0
- package/commands/ashlr-report-crash.md +33 -0
- package/commands/ashlr-resume.md +72 -0
- package/commands/ashlr-savings.md +24 -0
- package/commands/ashlr-settings.md +50 -0
- package/commands/ashlr-spawn.md +61 -0
- package/commands/ashlr-start.md +60 -0
- package/commands/ashlr-status.md +64 -0
- package/commands/ashlr-team-invite.md +47 -0
- package/commands/ashlr-tier.md +122 -0
- package/commands/ashlr-tour.md +60 -0
- package/commands/ashlr-update.md +155 -0
- package/commands/ashlr-upgrade.md +96 -0
- package/commands/goal.md +58 -0
- package/commands/loop.md +40 -0
- package/docs/CNAME +1 -0
- package/docs/PRO_TIER.md +104 -0
- package/docs/README.md +21 -0
- package/docs/architecture.md +415 -0
- package/docs/ashlr-pipe.md +161 -0
- package/docs/assets/logo.svg +19 -0
- package/docs/assets/og.png +0 -0
- package/docs/assets/og.svg +41 -0
- package/docs/benchmarks-v2.json +355 -0
- package/docs/benchmarks.json +102 -0
- package/docs/benchmarks.md +211 -0
- package/docs/billing.md +185 -0
- package/docs/cloud-genome.md +155 -0
- package/docs/competitive-wozcode.md +92 -0
- package/docs/deploy.md +325 -0
- package/docs/email-templates.md +113 -0
- package/docs/embed-tuning.md +103 -0
- package/docs/genome.md +120 -0
- package/docs/github-oauth-onboarding.md +127 -0
- package/docs/hero-video-script.md +129 -0
- package/docs/index.html +2262 -0
- package/docs/install-prompt.md +78 -0
- package/docs/install-windows.md +132 -0
- package/docs/install.ps1 +171 -0
- package/docs/install.sh +146 -0
- package/docs/launch/README.md +114 -0
- package/docs/launch/hn.md +49 -0
- package/docs/launch/reddit.md +45 -0
- package/docs/launch/x.md +65 -0
- package/docs/launch-post.md +112 -0
- package/docs/legal.md +78 -0
- package/docs/llm-providers.md +99 -0
- package/docs/multi-host-mcp.md +217 -0
- package/docs/multi-turn-architecture.md +108 -0
- package/docs/operations/deployment.md +108 -0
- package/docs/operations/error-monitoring.md +70 -0
- package/docs/operations/weekly-digest.md +70 -0
- package/docs/operations.md +494 -0
- package/docs/platform-support.md +133 -0
- package/docs/policy-packs.md +188 -0
- package/docs/press-kit/fact-sheet.md +78 -0
- package/docs/press-kit/founder-bio.md +3 -0
- package/docs/press-kit/logo.svg +33 -0
- package/docs/press-kit/screenshots.md +146 -0
- package/docs/pricing.md +251 -0
- package/docs/pro-backend-architecture.md +523 -0
- package/docs/producthunt.md +52 -0
- package/docs/router-migration.md +110 -0
- package/docs/session-log-schema.md +68 -0
- package/docs/social.md +95 -0
- package/docs/team-genome.md +528 -0
- package/docs/telemetry.md +194 -0
- package/docs/test-isolation.md +100 -0
- package/docs/upgrade.md +159 -0
- package/hooks/_hook-errors.ts +87 -0
- package/hooks/_hook-perf-emit.ts +113 -0
- package/hooks/_hook-timing-ledger.ts +1133 -0
- package/hooks/_nudge-throttle.ts +127 -0
- package/hooks/_recent-blocks.ts +127 -0
- package/hooks/audit-upload.ts +86 -0
- package/hooks/codex-hooks.json +171 -0
- package/hooks/commit-attribution.ts +179 -0
- package/hooks/edit-batching-nudge.ts +154 -0
- package/hooks/genome-scribe-hook.ts +219 -0
- package/hooks/hooks.json +294 -0
- package/hooks/orient-nudge-hook.ts +242 -0
- package/hooks/policy-enforce.ts +214 -0
- package/hooks/post-tool-use-embedding.ts +93 -0
- package/hooks/post-tool-use-genome.sh +37 -0
- package/hooks/post-tool-use-genome.ts +69 -0
- package/hooks/posttooluse-correlate.ts +122 -0
- package/hooks/posttooluse-dedupe-output.ts +241 -0
- package/hooks/posttooluse-genome-refresh.ts +223 -0
- package/hooks/posttooluse-native-nudge.ts +205 -0
- package/hooks/posttooluse-stale-result.ts +277 -0
- package/hooks/pretooluse-bash.ts +63 -0
- package/hooks/pretooluse-budget-guard.ts +318 -0
- package/hooks/pretooluse-common.ts +985 -0
- package/hooks/pretooluse-eco-router.ts +196 -0
- package/hooks/pretooluse-edit.sh +93 -0
- package/hooks/pretooluse-edit.ts +197 -0
- package/hooks/pretooluse-glob.ts +70 -0
- package/hooks/pretooluse-grep.sh +85 -0
- package/hooks/pretooluse-grep.ts +96 -0
- package/hooks/pretooluse-notebookedit.ts +121 -0
- package/hooks/pretooluse-read.sh +118 -0
- package/hooks/pretooluse-read.ts +118 -0
- package/hooks/pretooluse-task.ts +130 -0
- package/hooks/pretooluse-webfetch.ts +66 -0
- package/hooks/pretooluse-websearch.ts +85 -0
- package/hooks/pretooluse-write.ts +128 -0
- package/hooks/pulse-emit.ts +197 -0
- package/hooks/session-end-consolidate.sh +35 -0
- package/hooks/session-end-consolidate.ts +188 -0
- package/hooks/session-end-stats-gc.ts +80 -0
- package/hooks/session-log-append.sh +94 -0
- package/hooks/session-log-append.ts +116 -0
- package/hooks/session-start.ts +993 -0
- package/hooks/sessionend-hook-health-nudge.ts +451 -0
- package/hooks/sessionend-savings-nudge.ts +318 -0
- package/hooks/sessionend-status-line-nudge.ts +296 -0
- package/hooks/sessionstart-brief.ts +140 -0
- package/hooks/sessionstart-cost-refactor.ts +95 -0
- package/hooks/sessionstart-efficient.ts +110 -0
- package/hooks/sessionstart-genome-author.ts +93 -0
- package/hooks/sessionstart-lean-tools.ts +98 -0
- package/hooks/sessionstart-search.ts +97 -0
- package/hooks/stop-accounting.ts +152 -0
- package/hooks/subagent-stop-rollup.ts +162 -0
- package/hooks/userpromptsubmit-brief-trigger.ts +156 -0
- package/package.json +56 -0
- package/ports/README.md +108 -0
- package/ports/cursor/mcp.json +11 -0
- package/ports/goose/recipe.yaml +26 -0
- package/scripts/ashlr-mcp.ts +40 -0
- package/scripts/auto-update.ts +177 -0
- package/scripts/baseline-scan.ts +831 -0
- package/scripts/bench-orchestrate.ts +377 -0
- package/scripts/benchmark-refs.ts +292 -0
- package/scripts/bootstrap.mjs +100 -0
- package/scripts/brief-eval.ts +234 -0
- package/scripts/bump-version.ts +82 -0
- package/scripts/bun-resolve.d.mts +11 -0
- package/scripts/bun-resolve.mjs +59 -0
- package/scripts/calibrate-grep.ts +467 -0
- package/scripts/capture-grep-workload.ts +260 -0
- package/scripts/check-tier.ts +61 -0
- package/scripts/check-version-sync.ts +97 -0
- package/scripts/cli-orchestrate-status.ts +386 -0
- package/scripts/cli-orchestrate.ts +303 -0
- package/scripts/cli.ts +478 -0
- package/scripts/cloud-smoke-test.ts +313 -0
- package/scripts/coach-report.ts +373 -0
- package/scripts/context-db-migrate.ts +137 -0
- package/scripts/context-status.ts +43 -0
- package/scripts/demo-run.ts +255 -0
- package/scripts/deploy-smoke.sh +140 -0
- package/scripts/doctor.ts +992 -0
- package/scripts/embed-file-worker.ts +104 -0
- package/scripts/embed-tune.ts +308 -0
- package/scripts/errors-report.ts +428 -0
- package/scripts/find-test-leak.ts +69 -0
- package/scripts/generate-badge.ts +357 -0
- package/scripts/genome-auto-consolidate.ts +533 -0
- package/scripts/genome-auto-propose.ts +578 -0
- package/scripts/genome-cloud-pull.ts +363 -0
- package/scripts/genome-cloud-push.ts +382 -0
- package/scripts/genome-cloud-sync.ts +587 -0
- package/scripts/genome-commit-watcher.ts +295 -0
- package/scripts/genome-init.ts +986 -0
- package/scripts/genome-key.ts +233 -0
- package/scripts/genome-keygen.ts +168 -0
- package/scripts/genome-link.ts +59 -0
- package/scripts/genome-refresh-worker.ts +364 -0
- package/scripts/genome-rewrap.ts +90 -0
- package/scripts/genome-synthesizer.ts +679 -0
- package/scripts/genome-team-init.ts +312 -0
- package/scripts/handoff-pack.ts +438 -0
- package/scripts/hook-bootstrap.mjs +75 -0
- package/scripts/hook-timing-reader.ts +371 -0
- package/scripts/hook-timings-report.ts +554 -0
- package/scripts/install-genome-hooks.ts +196 -0
- package/scripts/install-onnx-model.ts +154 -0
- package/scripts/install-permissions.ts +276 -0
- package/scripts/install-status-line.ts +150 -0
- package/scripts/mcp-entrypoint.sh +78 -0
- package/scripts/mcp-entrypoint.ts +130 -0
- package/scripts/mdx-angle-bracket-lint.ts +152 -0
- package/scripts/measure-cold-start.ts +126 -0
- package/scripts/migrate-stats-to-sqlite.ts +86 -0
- package/scripts/ollama-setup.ts +498 -0
- package/scripts/onboarding-wizard.ts +1691 -0
- package/scripts/orchestrate-emit-telemetry.ts +177 -0
- package/scripts/orchestrate-expand.ts +517 -0
- package/scripts/orchestrate-run.ts +744 -0
- package/scripts/print-legend.ts +50 -0
- package/scripts/product-metadata.ts +38 -0
- package/scripts/prune-old-memories.ts +208 -0
- package/scripts/publish.sh +88 -0
- package/scripts/read-calibration.ts +122 -0
- package/scripts/report-crash.ts +226 -0
- package/scripts/run-benchmark.ts +750 -0
- package/scripts/run-root-tests.ts +28 -0
- package/scripts/savings-dashboard.ts +1258 -0
- package/scripts/savings-report-extras.ts +447 -0
- package/scripts/savings-status-line.ts +873 -0
- package/scripts/session-event-emit.ts +266 -0
- package/scripts/session-greet.ts +686 -0
- package/scripts/session-log-report.ts +551 -0
- package/scripts/session-resume.ts +614 -0
- package/scripts/set-hook-mode.ts +83 -0
- package/scripts/smoke-cross-platform.ts +117 -0
- package/scripts/smoke-realtime.ts +257 -0
- package/scripts/smoke-tools.ts +21 -0
- package/scripts/stats-cloud-pull.ts +213 -0
- package/scripts/stats-cloud-sync.ts +261 -0
- package/scripts/telemetry-flush.ts +160 -0
- package/scripts/telemetry-status.ts +266 -0
- package/scripts/timing-budget.ts +35 -0
- package/scripts/ui-animation.ts +120 -0
- package/scripts/ui.ts +431 -0
- package/scripts/upgrade-flow.ts +775 -0
- package/scripts/weekly-digest.ts +394 -0
- package/servers/_accounting.ts +78 -0
- package/servers/_ask-router.ts +84 -0
- package/servers/_ast-chunker.ts +434 -0
- package/servers/_ast-helpers.ts +183 -0
- package/servers/_ast-languages.ts +153 -0
- package/servers/_ast-refactor/_shared.ts +221 -0
- package/servers/_ast-refactor/cross-file-rename.ts +560 -0
- package/servers/_ast-refactor/extract-function.ts +357 -0
- package/servers/_ast-refactor/file-local-rename.ts +172 -0
- package/servers/_ast-refactor.ts +41 -0
- package/servers/_ast-skeleton.ts +107 -0
- package/servers/_bash-summarizers-registry.ts +1096 -0
- package/servers/_crash-dump.ts +210 -0
- package/servers/_cwd-clamp.ts +342 -0
- package/servers/_edit-log.ts +28 -0
- package/servers/_edit-match.ts +236 -0
- package/servers/_edit-validate.ts +196 -0
- package/servers/_embed-calibration.ts +123 -0
- package/servers/_embedding-cache.ts +401 -0
- package/servers/_embedding-model.ts +329 -0
- package/servers/_events.ts +113 -0
- package/servers/_genome-cache.ts +154 -0
- package/servers/_genome-commits.ts +172 -0
- package/servers/_genome-crypto-v2.ts +242 -0
- package/servers/_genome-crypto.ts +259 -0
- package/servers/_genome-discoveries.ts +140 -0
- package/servers/_genome-embed-populator.ts +216 -0
- package/servers/_genome-freshness.ts +134 -0
- package/servers/_genome-issues.ts +171 -0
- package/servers/_genome-live.ts +263 -0
- package/servers/_genome-prs.ts +188 -0
- package/servers/_genome-search.ts +479 -0
- package/servers/_genome-sync.ts +297 -0
- package/servers/_history-tracker.ts +279 -0
- package/servers/_http-helpers.ts +151 -0
- package/servers/_identity-hash.ts +151 -0
- package/servers/_llm-providers/anthropic.ts +132 -0
- package/servers/_llm-providers/cloud.ts +195 -0
- package/servers/_llm-providers/index.ts +350 -0
- package/servers/_llm-providers/local.ts +127 -0
- package/servers/_llm-providers/onnx.ts +393 -0
- package/servers/_llm-providers/types.ts +27 -0
- package/servers/_manifest-v2.ts +447 -0
- package/servers/_mcp-host.ts +93 -0
- package/servers/_nudge-events.ts +451 -0
- package/servers/_orchestrate-executor.ts +316 -0
- package/servers/_prefetch.ts +435 -0
- package/servers/_pricing.ts +256 -0
- package/servers/_pro.ts +381 -0
- package/servers/_read-cache.ts +44 -0
- package/servers/_router-handlers.ts +46 -0
- package/servers/_router.ts +55 -0
- package/servers/_run-with-timeout.ts +173 -0
- package/servers/_savings-render.ts +214 -0
- package/servers/_stats-sqlite.ts +640 -0
- package/servers/_stats.ts +1183 -0
- package/servers/_status-line-cells.ts +469 -0
- package/servers/_streaks.ts +162 -0
- package/servers/_summarize.ts +208 -0
- package/servers/_task-graph-render.ts +217 -0
- package/servers/_task-graph.ts +545 -0
- package/servers/_telemetry.ts +719 -0
- package/servers/_test-parsers.ts +530 -0
- package/servers/_test-watch.ts +626 -0
- package/servers/_text-helpers.ts +37 -0
- package/servers/_token-measure.ts +160 -0
- package/servers/_tool-base.ts +248 -0
- package/servers/_weekly-digest.ts +391 -0
- package/servers/ask-server-handlers.ts +49 -0
- package/servers/ask-server.ts +174 -0
- package/servers/bash-server-handlers.ts +138 -0
- package/servers/bash-server.ts +1136 -0
- package/servers/bench.ts +186 -0
- package/servers/diff-semantic-server-handlers.ts +39 -0
- package/servers/diff-semantic-server.ts +566 -0
- package/servers/diff-server-handlers.ts +317 -0
- package/servers/diff-server.ts +20 -0
- package/servers/edit-server-handlers.ts +31 -0
- package/servers/edit-server.ts +209 -0
- package/servers/edit-structural-server-handlers.ts +322 -0
- package/servers/efficiency-server-handlers.ts +161 -0
- package/servers/efficiency-server.ts +1135 -0
- package/servers/flush-server-handlers.ts +21 -0
- package/servers/flush-server.ts +19 -0
- package/servers/genome-server-handlers.ts +118 -0
- package/servers/genome-server.ts +397 -0
- package/servers/github-server-handlers.ts +187 -0
- package/servers/github-server.ts +856 -0
- package/servers/glob-server-handlers.ts +233 -0
- package/servers/glob-server.ts +20 -0
- package/servers/grep-server-handlers.ts +61 -0
- package/servers/grep-server.ts +564 -0
- package/servers/http-server-handlers.ts +36 -0
- package/servers/http-server.ts +149 -0
- package/servers/logs-server-handlers.ts +42 -0
- package/servers/logs-server.ts +359 -0
- package/servers/ls-server-handlers.ts +166 -0
- package/servers/ls-server.ts +20 -0
- package/servers/multi-edit-server-handlers.ts +51 -0
- package/servers/multi-edit-server.ts +422 -0
- package/servers/notebook-edit-server-handlers.ts +34 -0
- package/servers/notebook-edit-server.ts +181 -0
- package/servers/orient-server-handlers.ts +52 -0
- package/servers/orient-server.ts +436 -0
- package/servers/pipe-server-handlers.ts +68 -0
- package/servers/pipe-server.ts +300 -0
- package/servers/read-server-handlers.ts +42 -0
- package/servers/read-server.ts +206 -0
- package/servers/rename-file-server-handlers.ts +67 -0
- package/servers/rename-file-server.ts +699 -0
- package/servers/savings-server-handlers.ts +27 -0
- package/servers/savings-server.ts +54 -0
- package/servers/search-replace-regex-server-handlers.ts +96 -0
- package/servers/search-replace-regex-server.ts +751 -0
- package/servers/sql-server-handlers.ts +58 -0
- package/servers/sql-server.ts +505 -0
- package/servers/task-server-handlers.ts +358 -0
- package/servers/task-server.ts +24 -0
- package/servers/test-server-handlers.ts +351 -0
- package/servers/test-server.ts +20 -0
- package/servers/tree-server-handlers.ts +503 -0
- package/servers/tree-server.ts +20 -0
- package/servers/webfetch-server-handlers.ts +166 -0
- package/servers/webfetch-server.ts +20 -0
- package/servers/websearch-server-handlers.ts +215 -0
- package/servers/websearch-server.ts +19 -0
- package/servers/write-server-handlers.ts +27 -0
- package/servers/write-server.ts +97 -0
- package/skills/ashlr-brief/SKILL.md +80 -0
- package/skills/ashlr-codex-doctor/SKILL.md +21 -0
- package/skills/ashlr-codex-efficient-tools/SKILL.md +17 -0
- package/skills/ashlr-codex-genome-refresh/SKILL.md +20 -0
- package/skills/ashlr-codex-parallel-explore/SKILL.md +20 -0
- package/skills/ashlr-codex-savings/SKILL.md +17 -0
- package/skills/ashlr-cost-refactor/SKILL.md +50 -0
- package/skills/ashlr-efficient/SKILL.md +64 -0
- package/skills/ashlr-genome-author/SKILL.md +43 -0
- package/skills/ashlr-lean-tools/SKILL.md +43 -0
- package/skills/ashlr-search/SKILL.md +46 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* orchestrate-run.ts — Track B runner for the orchestration DAG.
|
|
3
|
+
*
|
|
4
|
+
* Walks the TaskGraph in dependency order, spawning a per-node Bun subprocess
|
|
5
|
+
* (stub for the MVP — real Claude orchestration ships in wk 7+), propagating
|
|
6
|
+
* handoff payloads downstream, and emitting per-node telemetry when consent
|
|
7
|
+
* is on.
|
|
8
|
+
*
|
|
9
|
+
* v1 (sequential): nodes ran one-at-a-time in topological order.
|
|
10
|
+
* v2 (Q1'27 wk 4-6, this file): PARALLEL-where-deps-allow scheduling.
|
|
11
|
+
* - Pro tier: maxConcurrency=3 (matches the 3-agent cap).
|
|
12
|
+
* - Team tier: maxConcurrency=10 (matches the 10-agent cap).
|
|
13
|
+
* - Override via RunOptions.maxConcurrency for tests.
|
|
14
|
+
* - maxConcurrency=1 produces sequential behavior identical to v1 (used by
|
|
15
|
+
* the existing __tests__/orchestrate-run.test.ts).
|
|
16
|
+
*
|
|
17
|
+
* Tier gates (top of run, BEFORE any subprocess):
|
|
18
|
+
* - free → ok=false error="free-tier"
|
|
19
|
+
* - pro, nodes.length > 3 → ok=false error="pro-tier-3-agent-cap"
|
|
20
|
+
* - team, nodes.length > 10 → ok=false error="team-tier-10-agent-cap"
|
|
21
|
+
*
|
|
22
|
+
* Tier resolution honors ASHLR_TEST_TIER ("free"|"pro"|"team") for tests; in
|
|
23
|
+
* production it falls back to isProSync. DI seams (_setIsProSyncForTests +
|
|
24
|
+
* _setIsTelemetryEnabledForTests) follow the v1.30 _setDailyHeartbeatFetch
|
|
25
|
+
* pattern in servers/_telemetry.ts.
|
|
26
|
+
*
|
|
27
|
+
* Shape compatibility: the existing Track C CLI (scripts/cli-orchestrate.ts)
|
|
28
|
+
* consumes RunResult fields `ok`, `totalDurationMs`, `totalTokens`, `nodes`,
|
|
29
|
+
* where each node carries `id/ok/durationMs/tokens/error`. We KEEP those for
|
|
30
|
+
* back-compat AND add the spec-required aliases (graphId, startedAt,
|
|
31
|
+
* finishedAt, nodeResults, totalTokensUsed, nodeId) so external callers can
|
|
32
|
+
* use either name. v2 adds `maxConcurrentWindows` — a timeline of running-set
|
|
33
|
+
* transitions for observability tools.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import * as fs from "fs";
|
|
37
|
+
import * as os from "os";
|
|
38
|
+
import * as path from "path";
|
|
39
|
+
import { executeNodeLlm } from "../servers/_orchestrate-executor.ts";
|
|
40
|
+
import { isProSync } from "../servers/_pro.ts";
|
|
41
|
+
import { isTelemetryEnabled, recordTelemetryEvent } from "../servers/_telemetry.ts";
|
|
42
|
+
import type { TaskGraph, TaskNode } from "../servers/_task-graph.ts";
|
|
43
|
+
import { emitOrchestrationRunTelemetry } from "./orchestrate-emit-telemetry.ts";
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Public types
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export interface NodeResult {
|
|
50
|
+
/** Legacy field name kept for Track C cli-orchestrate compatibility. */
|
|
51
|
+
id: string;
|
|
52
|
+
/** Spec-required alias of `id`. */
|
|
53
|
+
nodeId: string;
|
|
54
|
+
ok: boolean;
|
|
55
|
+
durationMs: number;
|
|
56
|
+
/** Legacy field name. */
|
|
57
|
+
tokens: number;
|
|
58
|
+
/** Spec-required alias of `tokens`. */
|
|
59
|
+
tokensUsed: number;
|
|
60
|
+
output?: string;
|
|
61
|
+
error?: string;
|
|
62
|
+
/** Q1'27 wk 10-12 — retries after the initial attempt (0..NODE_MAX_ATTEMPTS-1). */
|
|
63
|
+
retryCount?: number;
|
|
64
|
+
/** Q1'27 wk 10-12 — total attempts run (1..NODE_MAX_ATTEMPTS). */
|
|
65
|
+
finalAttempt?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Snapshot of which nodes were running at a given instant. Recorded only on
|
|
70
|
+
* transitions (a node starts or completes); lets observability tools
|
|
71
|
+
* reconstruct the parallel-execution timeline without polling.
|
|
72
|
+
*/
|
|
73
|
+
export interface ConcurrencyWindow {
|
|
74
|
+
/** ISO timestamp of the transition. */
|
|
75
|
+
time: string;
|
|
76
|
+
/** Node IDs currently running at that instant. */
|
|
77
|
+
runningIds: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface RunResult {
|
|
81
|
+
ok: boolean;
|
|
82
|
+
/** Stable ID of the source graph. */
|
|
83
|
+
graphId: string;
|
|
84
|
+
startedAt: string;
|
|
85
|
+
finishedAt: string;
|
|
86
|
+
/** Legacy total duration field kept for cli-orchestrate. */
|
|
87
|
+
totalDurationMs: number;
|
|
88
|
+
/** Legacy total token field kept for cli-orchestrate. */
|
|
89
|
+
totalTokens: number;
|
|
90
|
+
/** Spec-required alias of totalTokens. */
|
|
91
|
+
totalTokensUsed: number;
|
|
92
|
+
/** Legacy per-node list — cli-orchestrate iterates this. */
|
|
93
|
+
nodes: NodeResult[];
|
|
94
|
+
/** Spec-required alias of nodes. */
|
|
95
|
+
nodeResults: NodeResult[];
|
|
96
|
+
/**
|
|
97
|
+
* Timeline of running-set transitions. Each entry is captured when the
|
|
98
|
+
* running set changes (a node starts OR completes). Empty when no nodes
|
|
99
|
+
* ever ran (e.g., tier-rejected runs).
|
|
100
|
+
*/
|
|
101
|
+
maxConcurrentWindows: ConcurrencyWindow[];
|
|
102
|
+
/** Optional reason when the run was rejected at the tier gate. */
|
|
103
|
+
error?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Q1'27 wk 10-12 — count of handoff payloads that exceeded
|
|
106
|
+
* HANDOFF_BUDGET_BYTES (8KB) and were truncated before being passed to a
|
|
107
|
+
* successor node. Always present (0 when nothing truncated).
|
|
108
|
+
*/
|
|
109
|
+
handoffsTruncated: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface RunOptions {
|
|
113
|
+
graph: TaskGraph;
|
|
114
|
+
dryRun?: boolean;
|
|
115
|
+
onNodeStart?: (node: TaskNode) => void;
|
|
116
|
+
onNodeComplete?: (node: TaskNode, result: NodeResult) => void;
|
|
117
|
+
cwd?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Override the per-run concurrency cap. Defaults to the tier cap
|
|
120
|
+
* (Pro=3, Team=10). Tests use this to assert wave-by-wave timing.
|
|
121
|
+
* Must be >= 1.
|
|
122
|
+
*/
|
|
123
|
+
maxConcurrency?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Constants
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
const PRO_NODE_CAP = 3;
|
|
131
|
+
const TEAM_NODE_CAP = 10;
|
|
132
|
+
const NODE_TIMEOUT_MS = 30_000;
|
|
133
|
+
|
|
134
|
+
// Q1'27 wk 10-12 — retry-with-backoff. A fully-failed node may take up to
|
|
135
|
+
// ~3 * NODE_TIMEOUT_MS + backoff sum (250 + 500 = 750ms) ≈ 90.75s wallclock.
|
|
136
|
+
const NODE_MAX_ATTEMPTS = 3;
|
|
137
|
+
const NODE_BACKOFF_BASE_MS = 250;
|
|
138
|
+
|
|
139
|
+
// Q1'27 wk 10-12 — handoff context budget. Predecessor output concatenated
|
|
140
|
+
// past this cap is truncated with a "[handoff truncated: was N bytes,
|
|
141
|
+
// capped at 8192]" marker so the successor knows the input was clipped.
|
|
142
|
+
const HANDOFF_BUDGET_BYTES = 8192;
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// DI seams — tests swap these via _setIsProSyncForTests / _setIsTelemetry...
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
type IsProFn = () => boolean;
|
|
149
|
+
type IsTelemetryFn = () => boolean;
|
|
150
|
+
type SpawnFn = typeof Bun.spawn;
|
|
151
|
+
|
|
152
|
+
let _isProImpl: IsProFn | null = null;
|
|
153
|
+
let _isTelemetryImpl: IsTelemetryFn | null = null;
|
|
154
|
+
let _spawnImpl: SpawnFn | null = null;
|
|
155
|
+
|
|
156
|
+
/** Test seam: override the pro-tier check. Pass null to restore default. */
|
|
157
|
+
export function _setIsProSyncForTests(fn: IsProFn | null): void {
|
|
158
|
+
_isProImpl = fn;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Test seam: override the telemetry-enabled check. Pass null to restore. */
|
|
162
|
+
export function _setIsTelemetryEnabledForTests(fn: IsTelemetryFn | null): void {
|
|
163
|
+
_isTelemetryImpl = fn;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Test seam: override Bun.spawn. Pass null to restore. */
|
|
167
|
+
export function _setSpawnForTests(fn: SpawnFn | null): void {
|
|
168
|
+
_spawnImpl = fn;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isProGuarded(): boolean {
|
|
172
|
+
return _isProImpl ? _isProImpl() : isProSync();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isTelemetryGuarded(): boolean {
|
|
176
|
+
return _isTelemetryImpl ? _isTelemetryImpl() : isTelemetryEnabled();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function spawnGuarded(...args: Parameters<SpawnFn>): ReturnType<SpawnFn> {
|
|
180
|
+
return (_spawnImpl ?? Bun.spawn)(...args);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Tier resolution
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
export type Tier = "free" | "pro" | "team";
|
|
188
|
+
|
|
189
|
+
function resolveTierForRun(env: NodeJS.ProcessEnv = process.env): Tier {
|
|
190
|
+
const override = env["ASHLR_TEST_TIER"];
|
|
191
|
+
if (override === "free" || override === "pro" || override === "team") return override;
|
|
192
|
+
return isProGuarded() ? "pro" : "free";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function tierDefaultConcurrency(tier: Tier): number {
|
|
196
|
+
if (tier === "team") return TEAM_NODE_CAP;
|
|
197
|
+
if (tier === "pro") return PRO_NODE_CAP;
|
|
198
|
+
return 1; // free tier never reaches the scheduler, but be defensive.
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Topological sort — used as a TIEBREAKER for ready nodes so the runner has
|
|
203
|
+
// stable, deterministic ordering between otherwise-parallel-eligible peers.
|
|
204
|
+
// Duplicated here so the runner doesn't import the renderer (separation of
|
|
205
|
+
// concerns).
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
function topoSort(nodes: readonly TaskNode[]): TaskNode[] {
|
|
209
|
+
const byId = new Map<string, TaskNode>();
|
|
210
|
+
const indegree = new Map<string, number>();
|
|
211
|
+
const inputOrder = new Map<string, number>();
|
|
212
|
+
nodes.forEach((n, i) => {
|
|
213
|
+
byId.set(n.id, n);
|
|
214
|
+
inputOrder.set(n.id, i);
|
|
215
|
+
});
|
|
216
|
+
const successors = new Map<string, string[]>();
|
|
217
|
+
for (const n of nodes) {
|
|
218
|
+
const deps = (Array.isArray(n.deps) ? n.deps : []).filter((d) => byId.has(d));
|
|
219
|
+
indegree.set(n.id, deps.length);
|
|
220
|
+
for (const d of deps) {
|
|
221
|
+
const list = successors.get(d) ?? [];
|
|
222
|
+
list.push(n.id);
|
|
223
|
+
successors.set(d, list);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const ready: string[] = [];
|
|
227
|
+
for (const [id, deg] of indegree) if (deg === 0) ready.push(id);
|
|
228
|
+
ready.sort((a, b) => (inputOrder.get(a) ?? 0) - (inputOrder.get(b) ?? 0));
|
|
229
|
+
|
|
230
|
+
const out: TaskNode[] = [];
|
|
231
|
+
const visited = new Set<string>();
|
|
232
|
+
while (ready.length > 0) {
|
|
233
|
+
const id = ready.shift()!;
|
|
234
|
+
if (visited.has(id)) continue;
|
|
235
|
+
visited.add(id);
|
|
236
|
+
const n = byId.get(id);
|
|
237
|
+
if (n) out.push(n);
|
|
238
|
+
for (const s of successors.get(id) ?? []) {
|
|
239
|
+
const next = (indegree.get(s) ?? 0) - 1;
|
|
240
|
+
indegree.set(s, next);
|
|
241
|
+
if (next === 0 && !visited.has(s)) ready.push(s);
|
|
242
|
+
}
|
|
243
|
+
ready.sort((a, b) => (inputOrder.get(a) ?? 0) - (inputOrder.get(b) ?? 0));
|
|
244
|
+
}
|
|
245
|
+
if (out.length < nodes.length) {
|
|
246
|
+
for (const n of nodes) if (!visited.has(n.id)) out.push(n);
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// Result helpers
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
function tierRejectResult(graphId: string, reason: string): RunResult {
|
|
256
|
+
const now = new Date().toISOString();
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
graphId,
|
|
260
|
+
startedAt: now,
|
|
261
|
+
finishedAt: now,
|
|
262
|
+
totalDurationMs: 0,
|
|
263
|
+
totalTokens: 0,
|
|
264
|
+
totalTokensUsed: 0,
|
|
265
|
+
nodes: [],
|
|
266
|
+
nodeResults: [],
|
|
267
|
+
maxConcurrentWindows: [],
|
|
268
|
+
error: reason,
|
|
269
|
+
handoffsTruncated: 0,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function makeNodeResult(
|
|
274
|
+
node: TaskNode,
|
|
275
|
+
partial: { ok: boolean; durationMs: number; tokens: number; output?: string; error?: string; retryCount?: number; finalAttempt?: number },
|
|
276
|
+
): NodeResult {
|
|
277
|
+
return {
|
|
278
|
+
id: node.id,
|
|
279
|
+
nodeId: node.id,
|
|
280
|
+
ok: partial.ok,
|
|
281
|
+
durationMs: partial.durationMs,
|
|
282
|
+
tokens: partial.tokens,
|
|
283
|
+
tokensUsed: partial.tokens,
|
|
284
|
+
output: partial.output,
|
|
285
|
+
error: partial.error,
|
|
286
|
+
retryCount: partial.retryCount,
|
|
287
|
+
finalAttempt: partial.finalAttempt,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Subprocess execution
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
interface SpawnEnv {
|
|
296
|
+
NODE_ID: string;
|
|
297
|
+
NODE_GOAL: string;
|
|
298
|
+
NODE_SCOPE: string;
|
|
299
|
+
HANDOFF_PAYLOAD: string;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Execute a single node as a Bun subprocess. The MVP stub just echoes a
|
|
304
|
+
* canned message via `bun --eval`; the real implementation in wk 7+ will
|
|
305
|
+
* spawn a Claude Code subagent.
|
|
306
|
+
*
|
|
307
|
+
* Returns the node result. Per-node failures are recorded but never thrown —
|
|
308
|
+
* the caller continues running subsequent nodes.
|
|
309
|
+
*/
|
|
310
|
+
async function executeNode(
|
|
311
|
+
node: TaskNode,
|
|
312
|
+
env: SpawnEnv,
|
|
313
|
+
cwd: string | undefined,
|
|
314
|
+
): Promise<{ ok: boolean; output: string; error?: string; durationMs: number; tokensIn?: number; tokensOut?: number }> {
|
|
315
|
+
// Q1'27 wk 7+ — when ASHLR_ORCHESTRATE_REAL_LLM=1, dispatch to the real
|
|
316
|
+
// Claude executor in servers/_orchestrate-executor.ts (PR #93). The flag
|
|
317
|
+
// is a global kill-switch — the default (unset) path is the byte-identical
|
|
318
|
+
// Bun-subprocess stub below, so existing tests keep passing.
|
|
319
|
+
if (process.env["ASHLR_ORCHESTRATE_REAL_LLM"] === "1") {
|
|
320
|
+
const llm = await executeNodeLlm({
|
|
321
|
+
node,
|
|
322
|
+
handoffPayload: env.HANDOFF_PAYLOAD ?? "",
|
|
323
|
+
cwd: cwd ?? process.cwd(),
|
|
324
|
+
timeoutMs: NODE_TIMEOUT_MS,
|
|
325
|
+
});
|
|
326
|
+
return {
|
|
327
|
+
ok: llm.ok,
|
|
328
|
+
output: llm.output,
|
|
329
|
+
error: llm.error,
|
|
330
|
+
durationMs: llm.durationMs,
|
|
331
|
+
tokensIn: llm.tokensIn,
|
|
332
|
+
tokensOut: llm.tokensOut,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const t0 = Date.now();
|
|
337
|
+
try {
|
|
338
|
+
const proc = spawnGuarded(
|
|
339
|
+
[
|
|
340
|
+
"bun",
|
|
341
|
+
"--eval",
|
|
342
|
+
"console.log('STUB-NODE: ' + (process.env.NODE_ID || 'unknown'));",
|
|
343
|
+
],
|
|
344
|
+
{
|
|
345
|
+
env: { ...process.env, ...env },
|
|
346
|
+
cwd,
|
|
347
|
+
stdout: "pipe",
|
|
348
|
+
stderr: "pipe",
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// 30s wallclock per-node timeout.
|
|
353
|
+
let timedOut = false;
|
|
354
|
+
const timer = setTimeout(() => {
|
|
355
|
+
timedOut = true;
|
|
356
|
+
try {
|
|
357
|
+
proc.kill();
|
|
358
|
+
} catch {
|
|
359
|
+
/* best-effort */
|
|
360
|
+
}
|
|
361
|
+
}, NODE_TIMEOUT_MS);
|
|
362
|
+
|
|
363
|
+
let stdout = "";
|
|
364
|
+
let stderr = "";
|
|
365
|
+
try {
|
|
366
|
+
const stdoutPromise = proc.stdout instanceof ReadableStream
|
|
367
|
+
? new Response(proc.stdout).text()
|
|
368
|
+
: Promise.resolve("");
|
|
369
|
+
const stderrPromise = proc.stderr instanceof ReadableStream
|
|
370
|
+
? new Response(proc.stderr).text()
|
|
371
|
+
: Promise.resolve("");
|
|
372
|
+
stdout = await stdoutPromise;
|
|
373
|
+
stderr = await stderrPromise;
|
|
374
|
+
await proc.exited;
|
|
375
|
+
} finally {
|
|
376
|
+
clearTimeout(timer);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const durationMs = Date.now() - t0;
|
|
380
|
+
|
|
381
|
+
if (timedOut) {
|
|
382
|
+
return {
|
|
383
|
+
ok: false,
|
|
384
|
+
output: stdout,
|
|
385
|
+
error: `timeout after ${NODE_TIMEOUT_MS}ms`,
|
|
386
|
+
durationMs,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const exitCode = typeof proc.exitCode === "number" ? proc.exitCode : 0;
|
|
391
|
+
if (exitCode !== 0) {
|
|
392
|
+
return {
|
|
393
|
+
ok: false,
|
|
394
|
+
output: stdout,
|
|
395
|
+
error: stderr.trim() || `exit ${exitCode}`,
|
|
396
|
+
durationMs,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return { ok: true, output: stdout, durationMs };
|
|
401
|
+
} catch (err) {
|
|
402
|
+
return {
|
|
403
|
+
ok: false,
|
|
404
|
+
output: "",
|
|
405
|
+
error: err instanceof Error ? err.message : String(err),
|
|
406
|
+
durationMs: Date.now() - t0,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// Run result persistence — best-effort. Writes ~/.ashlr/orchestrations/<id>/
|
|
413
|
+
// result.json so /ashlr-orchestrate-status can inspect past runs offline.
|
|
414
|
+
// Failures are swallowed: the runner must never throw or block the return on
|
|
415
|
+
// filesystem I/O. Also prunes to the 50 most-recent run dirs (sort by mtime
|
|
416
|
+
// DESC, remove the rest) to bound disk usage.
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
const ORCH_PRUNE_KEEP = 50;
|
|
420
|
+
|
|
421
|
+
/** DI seam for tests so they can redirect away from the real ~/.ashlr/. */
|
|
422
|
+
let _homedirImpl: (() => string) | null = null;
|
|
423
|
+
|
|
424
|
+
/** Test seam: override the HOME directory. Pass null to restore. */
|
|
425
|
+
export function _setHomedirForTests(fn: (() => string) | null): void {
|
|
426
|
+
_homedirImpl = fn;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function resolveOrchestrationsDir(): string {
|
|
430
|
+
const home = _homedirImpl ? _homedirImpl() : (os.homedir?.() ?? process.env["HOME"] ?? "~");
|
|
431
|
+
return path.join(home, ".ashlr", "orchestrations");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Best-effort: write the run + graph to ~/.ashlr/orchestrations/<id>/result.json
|
|
436
|
+
* and prune older run dirs beyond the 50-most-recent. Never throws.
|
|
437
|
+
*/
|
|
438
|
+
function persistRunResult(graph: TaskGraph, finalResult: RunResult): void {
|
|
439
|
+
try {
|
|
440
|
+
const base = resolveOrchestrationsDir();
|
|
441
|
+
const dir = path.join(base, graph.id);
|
|
442
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
443
|
+
fs.writeFileSync(
|
|
444
|
+
path.join(dir, "result.json"),
|
|
445
|
+
JSON.stringify({ graph, result: finalResult }, null, 2),
|
|
446
|
+
);
|
|
447
|
+
} catch {
|
|
448
|
+
/* best-effort */
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const base = resolveOrchestrationsDir();
|
|
453
|
+
if (!fs.existsSync(base)) return;
|
|
454
|
+
const entries = fs.readdirSync(base, { withFileTypes: true })
|
|
455
|
+
.filter((d) => d.isDirectory())
|
|
456
|
+
.map((d) => {
|
|
457
|
+
const full = path.join(base, d.name);
|
|
458
|
+
let mtime = 0;
|
|
459
|
+
try {
|
|
460
|
+
mtime = fs.statSync(full).mtimeMs;
|
|
461
|
+
} catch {
|
|
462
|
+
/* skip */
|
|
463
|
+
}
|
|
464
|
+
return { name: d.name, full, mtime };
|
|
465
|
+
});
|
|
466
|
+
if (entries.length <= ORCH_PRUNE_KEEP) return;
|
|
467
|
+
entries.sort((a, b) => b.mtime - a.mtime);
|
|
468
|
+
const toRemove = entries.slice(ORCH_PRUNE_KEEP);
|
|
469
|
+
for (const e of toRemove) {
|
|
470
|
+
try {
|
|
471
|
+
fs.rmSync(e.full, { recursive: true, force: true });
|
|
472
|
+
} catch {
|
|
473
|
+
/* best-effort */
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
/* best-effort */
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
// Public entry — runTaskGraph
|
|
483
|
+
// ---------------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
export async function runTaskGraph(opts: RunOptions): Promise<RunResult> {
|
|
486
|
+
const g = opts.graph;
|
|
487
|
+
const startedAt = new Date().toISOString();
|
|
488
|
+
const t0 = Date.now();
|
|
489
|
+
|
|
490
|
+
// -- Tier gate ------------------------------------------------------------
|
|
491
|
+
const tier = resolveTierForRun();
|
|
492
|
+
if (tier === "free") {
|
|
493
|
+
return tierRejectResult(g.id, "free-tier");
|
|
494
|
+
}
|
|
495
|
+
if (tier === "pro" && g.nodes.length > PRO_NODE_CAP) {
|
|
496
|
+
return tierRejectResult(g.id, "pro-tier-3-agent-cap");
|
|
497
|
+
}
|
|
498
|
+
if (tier === "team" && g.nodes.length > TEAM_NODE_CAP) {
|
|
499
|
+
return tierRejectResult(g.id, "team-tier-10-agent-cap");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// -- Concurrency selection -------------------------------------------------
|
|
503
|
+
// The override clamps to >=1. Default mirrors the tier's agent cap so the
|
|
504
|
+
// scheduler can saturate every slot the tier paid for.
|
|
505
|
+
const maxConcurrency = Math.max(
|
|
506
|
+
1,
|
|
507
|
+
Math.floor(opts.maxConcurrency ?? tierDefaultConcurrency(tier)),
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
// -- Scheduler state -------------------------------------------------------
|
|
511
|
+
// `topoOrder` is the tiebreaker for picking among multiple ready nodes —
|
|
512
|
+
// gives stable, deterministic output between parallel-eligible peers.
|
|
513
|
+
const topoOrder = topoSort(g.nodes);
|
|
514
|
+
const topoIndex = new Map<string, number>();
|
|
515
|
+
topoOrder.forEach((n, i) => topoIndex.set(n.id, i));
|
|
516
|
+
const byId = new Map(g.nodes.map((n) => [n.id, n] as const));
|
|
517
|
+
|
|
518
|
+
const results: NodeResult[] = [];
|
|
519
|
+
const resultById = new Map<string, NodeResult>();
|
|
520
|
+
const completed = new Set<string>();
|
|
521
|
+
const running = new Map<string, Promise<{ id: string; node: TaskNode; result: NodeResult }>>();
|
|
522
|
+
const remaining = new Set<string>(g.nodes.map((n) => n.id));
|
|
523
|
+
const windows: ConcurrencyWindow[] = [];
|
|
524
|
+
// Q1'27 wk 10-12 — counts handoff payloads truncated to HANDOFF_BUDGET_BYTES.
|
|
525
|
+
let runHandoffsTruncated = 0;
|
|
526
|
+
|
|
527
|
+
const snapshotWindow = (): void => {
|
|
528
|
+
windows.push({
|
|
529
|
+
time: new Date().toISOString(),
|
|
530
|
+
runningIds: Array.from(running.keys()),
|
|
531
|
+
});
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const launch = (node: TaskNode): void => {
|
|
535
|
+
opts.onNodeStart?.(node);
|
|
536
|
+
emitTelemetry("orchestrate_node_start", {
|
|
537
|
+
node_id_hash: hashShort(node.id),
|
|
538
|
+
agent_kind: node.agentKind,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Handoff payload: concatenated stdout of all completed predecessors,
|
|
542
|
+
// separated by node-id headers so receivers can disambiguate. Because we
|
|
543
|
+
// only launch nodes whose deps are ALL in `completed`, this is guaranteed
|
|
544
|
+
// to include every predecessor's terminal output (success OR failure).
|
|
545
|
+
const rawHandoff = (node.deps ?? [])
|
|
546
|
+
.map((dep) => resultById.get(dep))
|
|
547
|
+
.filter((r): r is NodeResult => !!r)
|
|
548
|
+
.map((r) => `[from ${r.id}]\n${r.output ?? ""}`)
|
|
549
|
+
.join("\n---\n");
|
|
550
|
+
|
|
551
|
+
// Q1'27 wk 10-12 — enforce HANDOFF_BUDGET_BYTES. Slice + append a marker
|
|
552
|
+
// so the successor knows its context was clipped; bump runHandoffsTruncated.
|
|
553
|
+
let handoff = rawHandoff;
|
|
554
|
+
const origBytes = Buffer.byteLength(rawHandoff, "utf8");
|
|
555
|
+
if (origBytes > HANDOFF_BUDGET_BYTES) {
|
|
556
|
+
handoff =
|
|
557
|
+
rawHandoff.slice(0, HANDOFF_BUDGET_BYTES) +
|
|
558
|
+
`\n\n[handoff truncated: was ${origBytes} bytes, capped at ${HANDOFF_BUDGET_BYTES}]\n`;
|
|
559
|
+
runHandoffsTruncated++;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const promise = (async () => {
|
|
563
|
+
let nodeResult: NodeResult;
|
|
564
|
+
if (opts.dryRun) {
|
|
565
|
+
nodeResult = makeNodeResult(node, {
|
|
566
|
+
ok: true,
|
|
567
|
+
durationMs: 0,
|
|
568
|
+
tokens: node.estimatedTokens ?? 0,
|
|
569
|
+
output: "[dry-run]",
|
|
570
|
+
retryCount: 0,
|
|
571
|
+
finalAttempt: 1,
|
|
572
|
+
});
|
|
573
|
+
} else {
|
|
574
|
+
// Q1'27 wk 10-12 — retry-with-backoff. Up to NODE_MAX_ATTEMPTS attempts
|
|
575
|
+
// with exponential backoff (250ms, 500ms). Per-attempt 30s wallclock
|
|
576
|
+
// cap means a fully-failed node can take up to ~90s + 750ms backoff.
|
|
577
|
+
let attempt = 0;
|
|
578
|
+
let exec: Awaited<ReturnType<typeof executeNode>>;
|
|
579
|
+
// eslint-disable-next-line no-constant-condition
|
|
580
|
+
while (true) {
|
|
581
|
+
exec = await executeNode(
|
|
582
|
+
node,
|
|
583
|
+
{
|
|
584
|
+
NODE_ID: node.id,
|
|
585
|
+
NODE_GOAL: node.goal,
|
|
586
|
+
NODE_SCOPE: (node.scope ?? []).join(":"),
|
|
587
|
+
HANDOFF_PAYLOAD: handoff,
|
|
588
|
+
},
|
|
589
|
+
opts.cwd,
|
|
590
|
+
);
|
|
591
|
+
if (exec.ok) break;
|
|
592
|
+
attempt++;
|
|
593
|
+
if (attempt >= NODE_MAX_ATTEMPTS) break;
|
|
594
|
+
await new Promise((r) => setTimeout(r, NODE_BACKOFF_BASE_MS * 2 ** (attempt - 1)));
|
|
595
|
+
}
|
|
596
|
+
// retryCount = retries that ran after the initial attempt.
|
|
597
|
+
// finalAttempt = total attempts run.
|
|
598
|
+
// - Success on initial: attempt=0 → retryCount=0, finalAttempt=1
|
|
599
|
+
// - Success after 2 retries: attempt=2 → retryCount=2, finalAttempt=3
|
|
600
|
+
// - 3 consecutive failures: attempt=3 → retryCount=2, finalAttempt=3
|
|
601
|
+
// (the third attempt was a retry, not a "retry of the retry").
|
|
602
|
+
const finalAttempt = exec.ok ? attempt + 1 : NODE_MAX_ATTEMPTS;
|
|
603
|
+
const retryCount = finalAttempt - 1;
|
|
604
|
+
nodeResult = makeNodeResult(node, {
|
|
605
|
+
ok: exec.ok,
|
|
606
|
+
durationMs: exec.durationMs,
|
|
607
|
+
tokens: exec.ok ? (node.estimatedTokens ?? 0) : 0,
|
|
608
|
+
output: exec.output,
|
|
609
|
+
error: exec.error,
|
|
610
|
+
retryCount,
|
|
611
|
+
finalAttempt,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
return { id: node.id, node, result: nodeResult };
|
|
615
|
+
})();
|
|
616
|
+
|
|
617
|
+
running.set(node.id, promise);
|
|
618
|
+
snapshotWindow(); // transition: a node just started.
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// -- Main scheduler loop --------------------------------------------------
|
|
622
|
+
// Invariants:
|
|
623
|
+
// - A node is in exactly one of {remaining, running, completed}.
|
|
624
|
+
// - We only `launch` a node whose every dep is in `completed`.
|
|
625
|
+
// - Each iteration starts as many ready nodes as `maxConcurrency` allows,
|
|
626
|
+
// then awaits the first one to finish (Promise.race). On finish, we
|
|
627
|
+
// record + drain, then loop.
|
|
628
|
+
while (remaining.size > 0 || running.size > 0) {
|
|
629
|
+
// Fill capacity with every ready-and-not-running node, preferring topo
|
|
630
|
+
// order for stable output among equivalents.
|
|
631
|
+
while (running.size < maxConcurrency) {
|
|
632
|
+
const ready: TaskNode[] = [];
|
|
633
|
+
for (const id of remaining) {
|
|
634
|
+
const n = byId.get(id);
|
|
635
|
+
if (!n) continue;
|
|
636
|
+
const depsDone = (n.deps ?? []).every((d) => completed.has(d) || !byId.has(d));
|
|
637
|
+
if (depsDone) ready.push(n);
|
|
638
|
+
}
|
|
639
|
+
if (ready.length === 0) break;
|
|
640
|
+
ready.sort((a, b) => (topoIndex.get(a.id) ?? 0) - (topoIndex.get(b.id) ?? 0));
|
|
641
|
+
const next = ready[0]!;
|
|
642
|
+
remaining.delete(next.id);
|
|
643
|
+
launch(next);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (running.size === 0) {
|
|
647
|
+
// Defensive: nothing ready, nothing running — would deadlock. Break to
|
|
648
|
+
// exit cleanly; remaining nodes will be left unrun and `ok` will be
|
|
649
|
+
// false because any unmet deps mean the graph was malformed.
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Wait for the FIRST running node to finish, then record + emit hooks.
|
|
654
|
+
const done = await Promise.race(running.values());
|
|
655
|
+
running.delete(done.id);
|
|
656
|
+
completed.add(done.id);
|
|
657
|
+
results.push(done.result);
|
|
658
|
+
resultById.set(done.id, done.result);
|
|
659
|
+
|
|
660
|
+
opts.onNodeComplete?.(done.node, done.result);
|
|
661
|
+
emitTelemetry("orchestrate_node_complete", {
|
|
662
|
+
node_id_hash: hashShort(done.id),
|
|
663
|
+
ok: done.result.ok,
|
|
664
|
+
duration_ms: done.result.durationMs,
|
|
665
|
+
retry_count: done.result.retryCount ?? 0,
|
|
666
|
+
final_attempt: done.result.finalAttempt ?? 1,
|
|
667
|
+
});
|
|
668
|
+
snapshotWindow(); // transition: a node just completed.
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// -- Stable result ordering ----------------------------------------------
|
|
672
|
+
// `results` is in COMPLETION order (which depends on subprocess timing).
|
|
673
|
+
// Re-sort by topological order so external consumers see deterministic
|
|
674
|
+
// output regardless of scheduling jitter. cli-orchestrate iterates this.
|
|
675
|
+
results.sort((a, b) => (topoIndex.get(a.id) ?? 0) - (topoIndex.get(b.id) ?? 0));
|
|
676
|
+
|
|
677
|
+
const finishedAt = new Date().toISOString();
|
|
678
|
+
const totalDurationMs = Date.now() - t0;
|
|
679
|
+
const totalTokens = results.reduce((s, r) => s + (r.tokens ?? 0), 0);
|
|
680
|
+
const ok = results.length === g.nodes.length && results.every((r) => r.ok);
|
|
681
|
+
|
|
682
|
+
const finalResult: RunResult = {
|
|
683
|
+
ok,
|
|
684
|
+
graphId: g.id,
|
|
685
|
+
startedAt,
|
|
686
|
+
finishedAt,
|
|
687
|
+
totalDurationMs,
|
|
688
|
+
totalTokens,
|
|
689
|
+
totalTokensUsed: totalTokens,
|
|
690
|
+
nodes: results,
|
|
691
|
+
nodeResults: results,
|
|
692
|
+
maxConcurrentWindows: windows,
|
|
693
|
+
handoffsTruncated: runHandoffsTruncated,
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// Q1'27 orchestration telemetry — best-effort, fire-and-forget. Gated on
|
|
697
|
+
// consent inside the emit module. Never blocks the return: the fetch is
|
|
698
|
+
// dispatched in the background with a 5s timeout and errors are swallowed.
|
|
699
|
+
// The mode flag reflects which executor path actually ran: 'real-llm' when
|
|
700
|
+
// ASHLR_ORCHESTRATE_REAL_LLM=1 dispatched to executeNodeLlm above, 'stub'
|
|
701
|
+
// for the byte-identical Bun-subprocess default.
|
|
702
|
+
const telemetryMode: "stub" | "real-llm" =
|
|
703
|
+
process.env["ASHLR_ORCHESTRATE_REAL_LLM"] === "1" ? "real-llm" : "stub";
|
|
704
|
+
try {
|
|
705
|
+
await emitOrchestrationRunTelemetry(finalResult, g, telemetryMode);
|
|
706
|
+
} catch {
|
|
707
|
+
/* never propagate */
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Persist the run for /ashlr-orchestrate-status — best-effort, never blocks.
|
|
711
|
+
persistRunResult(g, finalResult);
|
|
712
|
+
|
|
713
|
+
return finalResult;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ---------------------------------------------------------------------------
|
|
717
|
+
// Telemetry — only emits when consent is on. We piggy-back on the generic
|
|
718
|
+
// recordTelemetryEvent so the buffer + privacy guards are inherited.
|
|
719
|
+
// ---------------------------------------------------------------------------
|
|
720
|
+
|
|
721
|
+
function emitTelemetry(kind: string, fields: Record<string, unknown>): void {
|
|
722
|
+
try {
|
|
723
|
+
if (!isTelemetryGuarded()) return;
|
|
724
|
+
// We use the `wizard_step` typed kind only to satisfy the type-checker —
|
|
725
|
+
// the underlying recordTelemetryEvent only cares about `kind` + spread.
|
|
726
|
+
// The server tolerates unknown kinds (rolls them up under "other").
|
|
727
|
+
recordTelemetryEvent({
|
|
728
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
729
|
+
kind: kind as any,
|
|
730
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
731
|
+
...(fields as any),
|
|
732
|
+
} as never);
|
|
733
|
+
} catch {
|
|
734
|
+
/* never propagate */
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function hashShort(s: string): string {
|
|
739
|
+
let h = 5381;
|
|
740
|
+
for (let i = 0; i < s.length; i++) {
|
|
741
|
+
h = ((h << 5) + h) ^ s.charCodeAt(i);
|
|
742
|
+
}
|
|
743
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
744
|
+
}
|