@bjoernrochel/sevro 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +34 -4
- package/README.md +7 -6
- package/dist/examples/basic/graded.json +13 -0
- package/dist/examples/basic/host.js +9 -0
- package/dist/examples/basic/host.js.map +1 -0
- package/dist/examples/basic/prompt-only.json +7 -0
- package/dist/examples/recipes/candidate.js +23 -0
- package/dist/examples/recipes/candidate.js.map +1 -0
- package/dist/examples/recipes/cases/coding-change.json +21 -0
- package/dist/examples/recipes/cases/host-tools.json +21 -0
- package/dist/examples/recipes/cases/mise.json +21 -0
- package/dist/examples/recipes/cases/node.json +21 -0
- package/dist/examples/recipes/cases/python-uv.json +21 -0
- package/dist/examples/recipes/cases/semantic.json +17 -0
- package/dist/examples/recipes/coding-change/check.js +16 -0
- package/dist/examples/recipes/coding-change/check.js.map +1 -0
- package/dist/examples/recipes/executable.js +20 -0
- package/dist/examples/recipes/executable.js.map +1 -0
- package/dist/examples/recipes/extension-artifacts.js +56 -0
- package/dist/examples/recipes/extension-artifacts.js.map +1 -0
- package/dist/examples/recipes/extension-methods.js +167 -0
- package/dist/examples/recipes/extension-methods.js.map +1 -0
- package/dist/examples/recipes/extension.js +28 -0
- package/dist/examples/recipes/extension.js.map +1 -0
- package/dist/examples/recipes/modules.js +9 -0
- package/dist/examples/recipes/modules.js.map +1 -0
- package/dist/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/dist/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/dist/examples/recipes/repository-skill/README.md +3 -0
- package/dist/examples/recipes/run-inputs.js +144 -0
- package/dist/examples/recipes/run-inputs.js.map +1 -0
- package/dist/examples/recipes/run-options.js +103 -0
- package/dist/examples/recipes/run-options.js.map +1 -0
- package/dist/examples/recipes/run.js +62 -0
- package/dist/examples/recipes/run.js.map +1 -0
- package/dist/examples/recipes/runtime/empty.json +1 -0
- package/dist/examples/recipes/runtime/goals.json +1 -0
- package/dist/examples/recipes/runtime/mise.json +6 -0
- package/dist/examples/recipes/runtime/path.json +1 -0
- package/dist/examples/recipes/runtime/plugins.json +4 -0
- package/dist/examples/recipes/runtime/python-uv.json +10 -0
- package/dist/examples/recipes/semantic-judge.js +27 -0
- package/dist/examples/recipes/semantic-judge.js.map +1 -0
- package/dist/examples/templates/coding/adapter.js +14 -0
- package/dist/examples/templates/coding/adapter.js.map +1 -0
- package/dist/examples/templates/coding/case.json +15 -0
- package/dist/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/dist/examples/templates/repository-skill/extension-data.js +61 -0
- package/dist/examples/templates/repository-skill/extension-data.js.map +1 -0
- package/dist/examples/templates/repository-skill/extension.js +29 -0
- package/dist/examples/templates/repository-skill/extension.js.map +1 -0
- package/dist/examples/templates/response/adapter.js +13 -0
- package/dist/examples/templates/response/adapter.js.map +1 -0
- package/dist/examples/templates/response/case.json +13 -0
- package/dist/src/advisory.js +86 -0
- package/dist/src/advisory.js.map +1 -0
- package/dist/src/candidate-artifacts.js +139 -0
- package/dist/src/candidate-artifacts.js.map +1 -0
- package/dist/src/candidate-codex-state.js +110 -0
- package/dist/src/candidate-codex-state.js.map +1 -0
- package/dist/src/case-validation.js +92 -0
- package/dist/src/case-validation.js.map +1 -0
- package/dist/src/cli/cancellation.js +21 -0
- package/dist/src/cli/cancellation.js.map +1 -0
- package/dist/src/cli/command.js +60 -0
- package/dist/src/cli/command.js.map +1 -0
- package/dist/src/cli/completion.js +14 -0
- package/dist/src/cli/completion.js.map +1 -0
- package/dist/src/cli/demo.js +45 -0
- package/dist/src/cli/demo.js.map +1 -0
- package/dist/src/cli/evaluation.js +40 -0
- package/dist/src/cli/evaluation.js.map +1 -0
- package/dist/src/cli/extension.js +54 -0
- package/dist/src/cli/extension.js.map +1 -0
- package/dist/src/cli/help.js +190 -0
- package/dist/src/cli/help.js.map +1 -0
- package/dist/src/cli/hosts.js +33 -0
- package/dist/src/cli/hosts.js.map +1 -0
- package/dist/src/cli/init-settings.js +29 -0
- package/dist/src/cli/init-settings.js.map +1 -0
- package/dist/src/cli/init.js +82 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/invocation.js +431 -0
- package/dist/src/cli/invocation.js.map +1 -0
- package/dist/src/cli/loading.js +110 -0
- package/dist/src/cli/loading.js.map +1 -0
- package/dist/src/cli/preparation.js +88 -0
- package/dist/src/cli/preparation.js.map +1 -0
- package/dist/src/cli/presentation.js +61 -0
- package/dist/src/cli/presentation.js.map +1 -0
- package/dist/src/cli/progress.js +5 -0
- package/dist/src/cli/progress.js.map +1 -0
- package/dist/src/cli/run-manifest.js +108 -0
- package/dist/src/cli/run-manifest.js.map +1 -0
- package/dist/src/cli/run-options.js +367 -0
- package/dist/src/cli/run-options.js.map +1 -0
- package/dist/src/cli/run-overrides.js +51 -0
- package/dist/src/cli/run-overrides.js.map +1 -0
- package/dist/src/cli/settings.js +127 -0
- package/dist/src/cli/settings.js.map +1 -0
- package/dist/src/cli/summary.js +28 -0
- package/dist/src/cli/summary.js.map +1 -0
- package/dist/src/cli.js +20 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/contract-types.js +2 -0
- package/dist/src/contract-types.js.map +1 -0
- package/dist/src/engine.js +4 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/evaluation/advisory.js +85 -0
- package/dist/src/evaluation/advisory.js.map +1 -0
- package/dist/src/evaluation/check-plan.js +292 -0
- package/dist/src/evaluation/check-plan.js.map +1 -0
- package/dist/src/evaluation/collection.js +61 -0
- package/dist/src/evaluation/collection.js.map +1 -0
- package/dist/src/evaluation/context.js +2 -0
- package/dist/src/evaluation/context.js.map +1 -0
- package/dist/src/evaluation/grading-host.js +28 -0
- package/dist/src/evaluation/grading-host.js.map +1 -0
- package/dist/src/evaluation/grading.js +154 -0
- package/dist/src/evaluation/grading.js.map +1 -0
- package/dist/src/evaluation/invocation-plan.js +312 -0
- package/dist/src/evaluation/invocation-plan.js.map +1 -0
- package/dist/src/evaluation/plan.js +376 -0
- package/dist/src/evaluation/plan.js.map +1 -0
- package/dist/src/evaluation/progress.js +14 -0
- package/dist/src/evaluation/progress.js.map +1 -0
- package/dist/src/evaluation/resources.js +159 -0
- package/dist/src/evaluation/resources.js.map +1 -0
- package/dist/src/evaluation/run.js +148 -0
- package/dist/src/evaluation/run.js.map +1 -0
- package/dist/src/evaluation/scheduler.js +27 -0
- package/dist/src/evaluation/scheduler.js.map +1 -0
- package/dist/src/evaluation/semantic.js +183 -0
- package/dist/src/evaluation/semantic.js.map +1 -0
- package/dist/src/evaluation/trial.js +593 -0
- package/dist/src/evaluation/trial.js.map +1 -0
- package/dist/src/evaluation/types.js +20 -0
- package/dist/src/evaluation/types.js.map +1 -0
- package/dist/src/evidence/host-results.js +67 -0
- package/dist/src/evidence/host-results.js.map +1 -0
- package/dist/src/evidence/retained-artifacts.js +17 -0
- package/dist/src/evidence/retained-artifacts.js.map +1 -0
- package/dist/src/evidence/trial-writer.js +131 -0
- package/dist/src/evidence/trial-writer.js.map +1 -0
- package/dist/src/evidence/usage.js +14 -0
- package/dist/src/evidence/usage.js.map +1 -0
- package/dist/src/executable.js +20 -0
- package/dist/src/executable.js.map +1 -0
- package/dist/src/extension-client.js +212 -0
- package/dist/src/extension-client.js.map +1 -0
- package/dist/src/extension-session.js +236 -0
- package/dist/src/extension-session.js.map +1 -0
- package/dist/src/fixtures/advisory.js +206 -0
- package/dist/src/fixtures/advisory.js.map +1 -0
- package/dist/src/fixtures/bin.js +41 -0
- package/dist/src/fixtures/bin.js.map +1 -0
- package/dist/src/fixtures/cleanup.js +43 -0
- package/dist/src/fixtures/cleanup.js.map +1 -0
- package/dist/src/fixtures/create.js +94 -0
- package/dist/src/fixtures/create.js.map +1 -0
- package/dist/src/fixtures/generated.js +181 -0
- package/dist/src/fixtures/generated.js.map +1 -0
- package/dist/src/fixtures/git-hooks.js +16 -0
- package/dist/src/fixtures/git-hooks.js.map +1 -0
- package/dist/src/fixtures/preparation.js +201 -0
- package/dist/src/fixtures/preparation.js.map +1 -0
- package/dist/src/fixtures/repository.js +255 -0
- package/dist/src/fixtures/repository.js.map +1 -0
- package/dist/src/fixtures/setup.js +175 -0
- package/dist/src/fixtures/setup.js.map +1 -0
- package/dist/src/fixtures/tools.js +57 -0
- package/dist/src/fixtures/tools.js.map +1 -0
- package/dist/src/fixtures/workspace-state.js +155 -0
- package/dist/src/fixtures/workspace-state.js.map +1 -0
- package/dist/src/generated/cli-result.cjs +2 -0
- package/dist/src/generated/extension.cjs +2 -0
- package/dist/src/generated/report.cjs +2 -0
- package/dist/src/generated/run-evidence.cjs +2 -0
- package/dist/src/generated/runtime.cjs +2 -0
- package/dist/src/git.js +72 -0
- package/dist/src/git.js.map +1 -0
- package/dist/src/graders/artifact.js +68 -0
- package/dist/src/graders/artifact.js.map +1 -0
- package/dist/src/graders/git-head.js +112 -0
- package/dist/src/graders/git-head.js.map +1 -0
- package/dist/src/graders/output.js +345 -0
- package/dist/src/graders/output.js.map +1 -0
- package/dist/src/graders/semantic.js +132 -0
- package/dist/src/graders/semantic.js.map +1 -0
- package/dist/src/graders/shell.js +321 -0
- package/dist/src/graders/shell.js.map +1 -0
- package/dist/src/host-diagnostic-redaction.js +52 -0
- package/dist/src/host-diagnostic-redaction.js.map +1 -0
- package/dist/src/host-diagnostics.js +248 -0
- package/dist/src/host-diagnostics.js.map +1 -0
- package/dist/src/hosts/claude-continuation.js +89 -0
- package/dist/src/hosts/claude-continuation.js.map +1 -0
- package/dist/src/hosts/claude-credential.js +121 -0
- package/dist/src/hosts/claude-credential.js.map +1 -0
- package/dist/src/hosts/claude-events.js +136 -0
- package/dist/src/hosts/claude-events.js.map +1 -0
- package/dist/src/hosts/claude-keychain.js +100 -0
- package/dist/src/hosts/claude-keychain.js.map +1 -0
- package/dist/src/hosts/claude-login-recovery.js +67 -0
- package/dist/src/hosts/claude-login-recovery.js.map +1 -0
- package/dist/src/hosts/claude-login-refresh.js +146 -0
- package/dist/src/hosts/claude-login-refresh.js.map +1 -0
- package/dist/src/hosts/claude-login-store.js +124 -0
- package/dist/src/hosts/claude-login-store.js.map +1 -0
- package/dist/src/hosts/claude-login.js +74 -0
- package/dist/src/hosts/claude-login.js.map +1 -0
- package/dist/src/hosts/claude-native-goal.js +138 -0
- package/dist/src/hosts/claude-native-goal.js.map +1 -0
- package/dist/src/hosts/claude-process.js +10 -0
- package/dist/src/hosts/claude-process.js.map +1 -0
- package/dist/src/hosts/claude-refresh-lock.js +76 -0
- package/dist/src/hosts/claude-refresh-lock.js.map +1 -0
- package/dist/src/hosts/claude-repository-invocation.js +229 -0
- package/dist/src/hosts/claude-repository-invocation.js.map +1 -0
- package/dist/src/hosts/claude-settings.js +156 -0
- package/dist/src/hosts/claude-settings.js.map +1 -0
- package/dist/src/hosts/claude-skill-activations.js +131 -0
- package/dist/src/hosts/claude-skill-activations.js.map +1 -0
- package/dist/src/hosts/claude-state.js +237 -0
- package/dist/src/hosts/claude-state.js.map +1 -0
- package/dist/src/hosts/claude-tool-calls.js +150 -0
- package/dist/src/hosts/claude-tool-calls.js.map +1 -0
- package/dist/src/hosts/claude.js +373 -0
- package/dist/src/hosts/claude.js.map +1 -0
- package/dist/src/hosts/codex-app-server.js +764 -0
- package/dist/src/hosts/codex-app-server.js.map +1 -0
- package/dist/src/hosts/codex-configuration.js +91 -0
- package/dist/src/hosts/codex-configuration.js.map +1 -0
- package/dist/src/hosts/codex-events.js +104 -0
- package/dist/src/hosts/codex-events.js.map +1 -0
- package/dist/src/hosts/codex-exec-controls.js +434 -0
- package/dist/src/hosts/codex-exec-controls.js.map +1 -0
- package/dist/src/hosts/codex-invocation.js +54 -0
- package/dist/src/hosts/codex-invocation.js.map +1 -0
- package/dist/src/hosts/codex-marketplace.js +185 -0
- package/dist/src/hosts/codex-marketplace.js.map +1 -0
- package/dist/src/hosts/codex-native-calls.js +516 -0
- package/dist/src/hosts/codex-native-calls.js.map +1 -0
- package/dist/src/hosts/codex-process.js +10 -0
- package/dist/src/hosts/codex-process.js.map +1 -0
- package/dist/src/hosts/codex-profile.js +206 -0
- package/dist/src/hosts/codex-profile.js.map +1 -0
- package/dist/src/hosts/codex-session-files.js +89 -0
- package/dist/src/hosts/codex-session-files.js.map +1 -0
- package/dist/src/hosts/codex-state.js +310 -0
- package/dist/src/hosts/codex-state.js.map +1 -0
- package/dist/src/hosts/codex.js +527 -0
- package/dist/src/hosts/codex.js.map +1 -0
- package/dist/src/hosts/isolation-common.js +43 -0
- package/dist/src/hosts/isolation-common.js.map +1 -0
- package/dist/src/hosts/isolation-roots.js +64 -0
- package/dist/src/hosts/isolation-roots.js.map +1 -0
- package/dist/src/hosts/isolation.js +12 -0
- package/dist/src/hosts/isolation.js.map +1 -0
- package/dist/src/hosts/linux-sandbox.js +127 -0
- package/dist/src/hosts/linux-sandbox.js.map +1 -0
- package/dist/src/hosts/mac-sandbox.js +137 -0
- package/dist/src/hosts/mac-sandbox.js.map +1 -0
- package/dist/src/hosts/native-controls.js +151 -0
- package/dist/src/hosts/native-controls.js.map +1 -0
- package/dist/src/hosts/native-request.js +33 -0
- package/dist/src/hosts/native-request.js.map +1 -0
- package/dist/src/hosts/process-lifecycle.js +167 -0
- package/dist/src/hosts/process-lifecycle.js.map +1 -0
- package/dist/src/hosts/protected-worktrees.js +81 -0
- package/dist/src/hosts/protected-worktrees.js.map +1 -0
- package/dist/src/hosts/skill-hook-catalog.js +100 -0
- package/dist/src/hosts/skill-hook-catalog.js.map +1 -0
- package/dist/src/hosts/skill-hook-handler.js +228 -0
- package/dist/src/hosts/skill-hook-handler.js.map +1 -0
- package/dist/src/hosts/skill-hooks.js +423 -0
- package/dist/src/hosts/skill-hooks.js.map +1 -0
- package/dist/src/hosts/workspace-fingerprint.js +72 -0
- package/dist/src/hosts/workspace-fingerprint.js.map +1 -0
- package/dist/src/identity.js +167 -0
- package/dist/src/identity.js.map +1 -0
- package/dist/src/instrumentation.js +83 -0
- package/dist/src/instrumentation.js.map +1 -0
- package/dist/src/native-transcript-state.js +85 -0
- package/dist/src/native-transcript-state.js.map +1 -0
- package/dist/src/native-transcript-view.js +65 -0
- package/dist/src/native-transcript-view.js.map +1 -0
- package/dist/src/native-transcripts.js +208 -0
- package/dist/src/native-transcripts.js.map +1 -0
- package/dist/src/paths.js +13 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/process-lifecycle.js +101 -0
- package/dist/src/process-lifecycle.js.map +1 -0
- package/dist/src/provenance.js +263 -0
- package/dist/src/provenance.js.map +1 -0
- package/dist/src/prune.js +228 -0
- package/dist/src/prune.js.map +1 -0
- package/dist/src/report.js +276 -0
- package/dist/src/report.js.map +1 -0
- package/dist/src/resolved-case.js +29 -0
- package/dist/src/resolved-case.js.map +1 -0
- package/dist/src/results-lock.js +40 -0
- package/dist/src/results-lock.js.map +1 -0
- package/dist/src/results.js +127 -0
- package/dist/src/results.js.map +1 -0
- package/dist/src/run-owner.js +283 -0
- package/dist/src/run-owner.js.map +1 -0
- package/dist/src/runner-paths.js +11 -0
- package/dist/src/runner-paths.js.map +1 -0
- package/dist/src/runtime/config.js +323 -0
- package/dist/src/runtime/config.js.map +1 -0
- package/dist/src/runtime/discovery-apple.js +65 -0
- package/dist/src/runtime/discovery-apple.js.map +1 -0
- package/dist/src/runtime/discovery-providers.js +37 -0
- package/dist/src/runtime/discovery-providers.js.map +1 -0
- package/dist/src/runtime/discovery.js +40 -0
- package/dist/src/runtime/discovery.js.map +1 -0
- package/dist/src/runtime/hook-processes.js +81 -0
- package/dist/src/runtime/hook-processes.js.map +1 -0
- package/dist/src/runtime/hooks.js +304 -0
- package/dist/src/runtime/hooks.js.map +1 -0
- package/dist/src/runtime/paths.js +244 -0
- package/dist/src/runtime/paths.js.map +1 -0
- package/dist/src/runtime/query.js +76 -0
- package/dist/src/runtime/query.js.map +1 -0
- package/dist/src/runtime/seeds.js +98 -0
- package/dist/src/runtime/seeds.js.map +1 -0
- package/dist/src/runtime/shared-cache.js +81 -0
- package/dist/src/runtime/shared-cache.js.map +1 -0
- package/dist/src/runtime/state.js +106 -0
- package/dist/src/runtime/state.js.map +1 -0
- package/dist/src/runtime-process.js +148 -0
- package/dist/src/runtime-process.js.map +1 -0
- package/dist/src/schema-types.js +2 -0
- package/dist/src/schema-types.js.map +1 -0
- package/dist/src/schema.js +16 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/shell.js +5 -0
- package/dist/src/shell.js.map +1 -0
- package/dist/src/sqlite-lock.js +18 -0
- package/dist/src/sqlite-lock.js.map +1 -0
- package/dist/src/storage.js +21 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/transcript-retention.js +109 -0
- package/dist/src/transcript-retention.js.map +1 -0
- package/dist/src/value-guards.js +26 -0
- package/dist/src/value-guards.js.map +1 -0
- package/docs/architecture.md +5 -2
- package/docs/creating-cases.md +2 -3
- package/docs/development-cli.md +12 -7
- package/docs/documentation-quality.md +4 -3
- package/docs/evaluation-workflow.md +8 -8
- package/docs/getting-started.md +7 -6
- package/docs/guide-evaluation.md +1 -1
- package/docs/identity-v1.md +4 -1
- package/docs/installing.md +31 -13
- package/docs/licensing.md +2 -2
- package/docs/native-hosts.md +1 -1
- package/docs/recipes.md +41 -40
- package/docs/releases.md +20 -4
- package/docs/run-settings-v1.md +3 -3
- package/docs/specs/repository-guide.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/typescript-quality.md +18 -1
- package/examples/recipes/executable.ts +19 -0
- package/examples/recipes/extension-artifacts.ts +1 -1
- package/examples/recipes/extension.ts +2 -1
- package/examples/recipes/modules.ts +9 -0
- package/examples/recipes/run-inputs.ts +14 -19
- package/examples/recipes/run-options.ts +9 -8
- package/examples/recipes/run.ts +21 -7
- package/examples/templates/repository-skill/extension-data.ts +1 -1
- package/examples/templates/repository-skill/extension.ts +2 -1
- package/package.json +14 -6
- package/src/cli/demo.ts +3 -2
- package/src/cli/help.ts +4 -3
- package/src/cli/init-settings.ts +5 -4
- package/src/cli/init.ts +9 -8
- package/src/cli/invocation.ts +2 -1
- package/src/cli/preparation.ts +2 -2
- package/src/cli.ts +1 -1
- package/src/executable.ts +19 -0
- package/src/extension-client.ts +6 -12
- package/src/fixtures/setup.ts +3 -2
- package/src/git.ts +2 -1
- package/src/graders/git-head.ts +3 -2
- package/src/graders/shell.ts +4 -3
- package/src/hosts/claude-keychain.ts +3 -2
- package/src/hosts/claude-login-refresh.ts +3 -2
- package/src/hosts/claude-refresh-lock.ts +2 -1
- package/src/hosts/isolation-roots.ts +3 -2
- package/src/hosts/process-lifecycle.ts +4 -6
- package/src/hosts/protected-worktrees.ts +2 -1
- package/src/hosts/skill-hook-handler.ts +1 -1
- package/src/hosts/skill-hooks.ts +10 -2
- package/src/provenance.ts +7 -6
- package/src/results-lock.ts +4 -4
- package/src/run-owner.ts +15 -9
- package/src/runner-paths.ts +21 -0
- package/src/runtime/hook-processes.ts +2 -1
- package/src/runtime/hooks.ts +9 -7
- package/src/runtime-process.ts +194 -0
- package/src/sqlite-lock.ts +25 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-processes.js","sourceRoot":"","sources":["hook-processes.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"../runtime-process\";\nimport { readFile, readdir, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { isRecord } from \"../value-guards\";\n\nexport async function hookExecutions(\n receipts: string,\n): Promise<Record<string, unknown>[]> {\n return Promise.all(\n (await readdir(receipts))\n .filter((name) => name.startsWith(\"call.\"))\n .map(async (name) => {\n try {\n const value: unknown = JSON.parse(\n await readFile(join(receipts, name), \"utf8\"),\n );\n return isRecord(value) ? value : { status: \"unknown\" };\n } catch {\n return { status: \"unknown\" };\n }\n }),\n );\n}\n\nasync function processOutput(argv: string[]): Promise<string> {\n const child = spawn(argv, { stdout: \"pipe\", stderr: \"ignore\" });\n const output = await new Response(child.stdout).text();\n await child.exited;\n if (Buffer.byteLength(output) > 1024 * 1024)\n throw new Error(\"hook process ownership exceeds size limit\");\n return output;\n}\n\nfunction ownedWrapper(\n execution: Record<string, unknown>,\n receipts: string,\n): execution is Record<string, unknown> & { pid: number; wrapper: string } {\n return (\n validProcessId(execution.pid) &&\n typeof execution.wrapper === \"string\" &&\n dirname(execution.wrapper) === dirname(receipts) &&\n /^hook-[0-9]+\\.sh$/.test(basename(execution.wrapper))\n );\n}\n\nfunction validProcessId(pid: unknown): pid is number {\n return typeof pid === \"number\" && Number.isSafeInteger(pid) && pid > 1;\n}\n\nfunction processChildren(table: string, root: number): number[] {\n const pairs = table\n .trim()\n .split(\"\\n\")\n .map((line) => line.trim().split(/\\s+/).map(Number));\n const pending = [root],\n children: number[] = [];\n while (pending.length) {\n const parent = pending.pop();\n const found = pairs\n .filter((pair) => pair[1] === parent)\n .map((pair) => pair[0])\n .filter((pid): pid is number => pid !== undefined);\n pending.push(...found);\n children.push(...found);\n }\n return children;\n}\n\nfunction stopProcess(pid: number): void {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n /* The owned process may already have exited. */\n }\n}\n\n/** Stop only processes still bound to a runner-owned private hook wrapper. */\nexport async function releaseHookProcesses(receipts: string): Promise<void> {\n await writeFile(join(receipts, \"stopped\"), \"stopped\\n\", { mode: 0o600 });\n for (const execution of await hookExecutions(receipts)) {\n if (!ownedWrapper(execution, receipts)) continue;\n const command = await processOutput([\n \"/bin/ps\",\n \"-p\",\n String(execution.pid),\n \"-o\",\n \"command=\",\n ]);\n if (!command.includes(execution.wrapper)) continue;\n const children = processChildren(\n await processOutput([\"/bin/ps\", \"-axo\", \"pid=,ppid=\"]),\n execution.pid,\n );\n children.reverse().forEach(stopProcess);\n await new Promise((resolve) => setTimeout(resolve, 50));\n stopProcess(execution.pid);\n }\n}\n"]}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { cp, mkdir, readFile, readdir, realpath, writeFile, } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { shellQuote as quote } from "../shell.js";
|
|
6
|
+
import { insideRuntimeRoot, requireRuntimeReadRoots } from "./paths.js";
|
|
7
|
+
import { runtimeSeedDigest } from "./seeds.js";
|
|
8
|
+
import { prepareRuntimeState } from "./state.js";
|
|
9
|
+
import { sharedCacheDenyRoots, sharedCacheRoots } from "./shared-cache.js";
|
|
10
|
+
import { macSandboxProfile } from "../hosts/mac-sandbox.js";
|
|
11
|
+
import { prepareLinuxSandboxCommand } from "../hosts/linux-sandbox.js";
|
|
12
|
+
import { isRecord, isUnknownArray } from "../value-guards.js";
|
|
13
|
+
import { hookExecutions, releaseHookProcesses } from "./hook-processes.js";
|
|
14
|
+
import { nativeStateParent, requireNativeHookRoots, } from "../native-transcript-state.js";
|
|
15
|
+
export function runtimeHooksEnabled(policy) {
|
|
16
|
+
const hooks = policy?.hooks;
|
|
17
|
+
if (!hooks)
|
|
18
|
+
return false;
|
|
19
|
+
const plugins = hooks.plugins ?? [];
|
|
20
|
+
return hooks.nativeGoal === true || plugins.length > 0;
|
|
21
|
+
}
|
|
22
|
+
function object(value) {
|
|
23
|
+
if (!isRecord(value))
|
|
24
|
+
throw new Error("invalid runtime hook declaration");
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/** Materialize immutable mounts; only declared plugin hooks reach the host. */
|
|
28
|
+
export async function prepareHookMounts(options) {
|
|
29
|
+
let mountRoot = join(options.stateRoot, "runtime-plugins");
|
|
30
|
+
const receipts = join(options.privateRoot, "hook-receipts");
|
|
31
|
+
await Promise.all([mountRoot, receipts].map((path) => mkdir(path, { recursive: true, mode: 0o700 })));
|
|
32
|
+
mountRoot = await realpath(mountRoot);
|
|
33
|
+
let runtime;
|
|
34
|
+
const prepareRuntime = () => (runtime ??= prepareRuntimeState(options.workspace, options.policy, "hooks", join(options.privateRoot, "hook-runtime")));
|
|
35
|
+
const selected = selectedPlugins(options.policy);
|
|
36
|
+
const plugins = [], isolation = [], directories = [];
|
|
37
|
+
for (const [index, source] of options.pluginRoots.entries()) {
|
|
38
|
+
const root = join(mountRoot, `plugin-${index}`);
|
|
39
|
+
const sourceDigest = await runtimeSeedDigest(source);
|
|
40
|
+
await cp(source, root, {
|
|
41
|
+
recursive: true,
|
|
42
|
+
dereference: true,
|
|
43
|
+
force: false,
|
|
44
|
+
errorOnExist: true,
|
|
45
|
+
});
|
|
46
|
+
const manifest = object(JSON.parse(await readFile(join(root, ".claude-plugin/plugin.json"), "utf8")));
|
|
47
|
+
const name = uniquePluginName(manifest, plugins);
|
|
48
|
+
const allowed = selected.delete(name);
|
|
49
|
+
const readRoots = await hookReadRoots(options, root);
|
|
50
|
+
const rewrite = {
|
|
51
|
+
root,
|
|
52
|
+
allowed,
|
|
53
|
+
privateRoot: options.privateRoot,
|
|
54
|
+
ordinal: index * 10000,
|
|
55
|
+
files: new Set(),
|
|
56
|
+
execution: lazyHookExecution(options, index, root, readRoots, prepareRuntime, isolation),
|
|
57
|
+
};
|
|
58
|
+
await rewritePlugin(manifest, rewrite);
|
|
59
|
+
plugins.push({
|
|
60
|
+
name,
|
|
61
|
+
allowed,
|
|
62
|
+
sourceDigest,
|
|
63
|
+
effectiveDigest: await runtimeSeedDigest(root),
|
|
64
|
+
});
|
|
65
|
+
directories.push(root);
|
|
66
|
+
}
|
|
67
|
+
if (selected.size)
|
|
68
|
+
throw new Error("runtime hook policy selects an unavailable plugin");
|
|
69
|
+
return { directories, receipts, plugins, isolation };
|
|
70
|
+
}
|
|
71
|
+
function lazyHookExecution(options, index, root, readRoots, prepareRuntime, isolation) {
|
|
72
|
+
let execution;
|
|
73
|
+
return () => (execution ??= (async () => {
|
|
74
|
+
const runtime = await prepareRuntime();
|
|
75
|
+
const boundary = await prepareHookIsolation(options, index, readRoots, runtime);
|
|
76
|
+
if (boundary.isolation)
|
|
77
|
+
isolation.push(boundary.isolation);
|
|
78
|
+
return {
|
|
79
|
+
...boundary,
|
|
80
|
+
environment: {
|
|
81
|
+
...runtime.environment,
|
|
82
|
+
PATH: hookPath(runtime.environment),
|
|
83
|
+
CLAUDE_PLUGIN_ROOT: root,
|
|
84
|
+
PLUGIN_ROOT: root,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
})());
|
|
88
|
+
}
|
|
89
|
+
async function prepareHookIsolation(options, index, roots, runtime) {
|
|
90
|
+
const cacheRoots = await sharedCacheRoots(options.policy, "hooks", options.protectedRoots);
|
|
91
|
+
const protectedRoots = [
|
|
92
|
+
...options.protectedRoots,
|
|
93
|
+
...sharedCacheDenyRoots(options.policy, "hooks"),
|
|
94
|
+
];
|
|
95
|
+
if (process.platform === "linux")
|
|
96
|
+
return {
|
|
97
|
+
isolation: await prepareLinuxSandboxCommand({
|
|
98
|
+
argv: ["/bin/true"],
|
|
99
|
+
workspace: options.workspace,
|
|
100
|
+
protectedRoots,
|
|
101
|
+
privateStateRoot: options.privateRoot,
|
|
102
|
+
denyNetwork: true,
|
|
103
|
+
readOnlyRoots: roots.ordinary,
|
|
104
|
+
hookReadRoots: roots.curated,
|
|
105
|
+
writableRuntimeRoot: runtime.root,
|
|
106
|
+
writableCacheRoots: cacheRoots,
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
const profile = join(options.privateRoot, `hooks-${index}.sb`);
|
|
110
|
+
await writeFile(profile, macSandboxProfile(protectedRoots, true, roots.ordinary, runtime.root, roots.curated, options.workspace, undefined, cacheRoots) + "\n(deny process-info* (require-not (target self)))\n", { mode: 0o600 });
|
|
111
|
+
return { profile };
|
|
112
|
+
}
|
|
113
|
+
async function hookReadRoots(options, root) {
|
|
114
|
+
const trusted = insideRuntimeRoot(await nativeStateParent(), root);
|
|
115
|
+
const ordinary = [
|
|
116
|
+
...options.policy.readOnlyRoots,
|
|
117
|
+
...(trusted ? [] : [root]),
|
|
118
|
+
];
|
|
119
|
+
const curated = trusted ? [root] : [];
|
|
120
|
+
requireRuntimeReadRoots(ordinary, options.protectedRoots);
|
|
121
|
+
await requireNativeHookRoots(curated, options.privateRoot);
|
|
122
|
+
return { ordinary, curated };
|
|
123
|
+
}
|
|
124
|
+
function hookPath(environment) {
|
|
125
|
+
return environment.PATH ?? "/usr/bin:/bin:/usr/sbin:/sbin";
|
|
126
|
+
}
|
|
127
|
+
function selectedPlugins(policy) {
|
|
128
|
+
return new Set(policy.hooks?.plugins ?? []);
|
|
129
|
+
}
|
|
130
|
+
function uniquePluginName(manifest, plugins) {
|
|
131
|
+
if (typeof manifest.name !== "string")
|
|
132
|
+
throw new Error("runtime hook plugin has no name");
|
|
133
|
+
const name = manifest.name;
|
|
134
|
+
if (plugins.some((plugin) => plugin.name === name))
|
|
135
|
+
throw new Error("runtime hook plugin names overlap");
|
|
136
|
+
return name;
|
|
137
|
+
}
|
|
138
|
+
async function rewritePlugin(manifest, state) {
|
|
139
|
+
requireHookOnlyPlugin(state.root);
|
|
140
|
+
await rewriteManifest(manifest, state, ".claude-plugin/plugin.json");
|
|
141
|
+
const native = join(state.root, ".codex-plugin/plugin.json");
|
|
142
|
+
if (existsSync(native))
|
|
143
|
+
await rewriteManifest(object(JSON.parse(await readFile(native, "utf8"))), state, ".codex-plugin/plugin.json");
|
|
144
|
+
const standard = join(state.root, "hooks/hooks.json");
|
|
145
|
+
if (existsSync(standard))
|
|
146
|
+
await hookFile(standard, state);
|
|
147
|
+
await rewriteFrontmatter(state.root, state);
|
|
148
|
+
}
|
|
149
|
+
function requireHookOnlyPlugin(root) {
|
|
150
|
+
for (const name of [".mcp.json", ".lsp.json"])
|
|
151
|
+
if (existsSync(join(root, name)))
|
|
152
|
+
throw new Error("runtime auxiliary plugin execution is unsupported");
|
|
153
|
+
}
|
|
154
|
+
async function rewriteManifest(manifest, state, path) {
|
|
155
|
+
if (manifest.mods !== undefined)
|
|
156
|
+
throw new Error("runtime plugin mods are unsupported");
|
|
157
|
+
delete manifest.mcpServers;
|
|
158
|
+
delete manifest.lspServers;
|
|
159
|
+
if (manifest.hooks !== undefined)
|
|
160
|
+
manifest.hooks = await hookSource(manifest.hooks, state);
|
|
161
|
+
await writeFile(join(state.root, path), JSON.stringify(manifest));
|
|
162
|
+
}
|
|
163
|
+
async function hookSource(value, state) {
|
|
164
|
+
if (typeof value === "string") {
|
|
165
|
+
const path = await realpath(join(state.root, value));
|
|
166
|
+
if (!insideRuntimeRoot(state.root, path))
|
|
167
|
+
throw new Error("runtime hook file escapes its plugin");
|
|
168
|
+
await hookFile(path, state);
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
if (isUnknownArray(value))
|
|
172
|
+
return Promise.all(value.map((source) => hookSource(source, state)));
|
|
173
|
+
const configuration = object(value);
|
|
174
|
+
if (configuration.hooks !== undefined)
|
|
175
|
+
return {
|
|
176
|
+
...configuration,
|
|
177
|
+
hooks: await hookMap(configuration.hooks, state),
|
|
178
|
+
};
|
|
179
|
+
return hookMap(configuration, state);
|
|
180
|
+
}
|
|
181
|
+
async function hookFile(path, state) {
|
|
182
|
+
if (state.files.has(path))
|
|
183
|
+
return;
|
|
184
|
+
state.files.add(path);
|
|
185
|
+
const value = object(JSON.parse(await readFile(path, "utf8")));
|
|
186
|
+
value.hooks = await hookMap(value.hooks, state);
|
|
187
|
+
await writeFile(path, JSON.stringify(value));
|
|
188
|
+
}
|
|
189
|
+
async function hookMap(value, state) {
|
|
190
|
+
if (!state.allowed)
|
|
191
|
+
return {};
|
|
192
|
+
const entries = [];
|
|
193
|
+
for (const [event, groups] of Object.entries(object(value))) {
|
|
194
|
+
if (!isUnknownArray(groups))
|
|
195
|
+
throw new Error("runtime hook groups must be arrays");
|
|
196
|
+
entries.push([
|
|
197
|
+
event,
|
|
198
|
+
await Promise.all(groups.map(async (group) => {
|
|
199
|
+
const definition = object(group);
|
|
200
|
+
if (!isUnknownArray(definition.hooks))
|
|
201
|
+
throw new Error("runtime hook handlers must be arrays");
|
|
202
|
+
return {
|
|
203
|
+
...definition,
|
|
204
|
+
hooks: await Promise.all(definition.hooks.map((handler) => commandHook(handler, state))),
|
|
205
|
+
};
|
|
206
|
+
})),
|
|
207
|
+
]);
|
|
208
|
+
}
|
|
209
|
+
return Object.fromEntries(entries);
|
|
210
|
+
}
|
|
211
|
+
async function commandHook(value, state) {
|
|
212
|
+
const handler = commandHandler(value);
|
|
213
|
+
if (handler.command.includes("\0") || handler.command.length > 65536)
|
|
214
|
+
throw new Error("runtime hook command is invalid or oversized");
|
|
215
|
+
const wrapper = join(state.privateRoot, `hook-${state.ordinal++}.sh`);
|
|
216
|
+
const execution = await state.execution();
|
|
217
|
+
const environment = Object.entries(execution.environment).map(([name, content]) => `${name}=${content}`);
|
|
218
|
+
const receipts = join(state.privateRoot, "hook-receipts");
|
|
219
|
+
const command = isolatedHookCommand(execution, environment, handler.command);
|
|
220
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
221
|
+
set -u
|
|
222
|
+
receipt=$(/usr/bin/mktemp ${quote(join(receipts, "call.XXXXXX"))}) || exit 125
|
|
223
|
+
test ! -f ${quote(join(receipts, "stopped"))} || exit 125
|
|
224
|
+
printf '{"status":"running","pid":%s,"wrapper":%s}\\n' "$$" ${quote(JSON.stringify(wrapper))} > "$receipt"
|
|
225
|
+
${command}
|
|
226
|
+
code=$?
|
|
227
|
+
printf '{"status":"completed","pid":%s,"exitCode":%s,"wrapper":%s}\\n' "$$" "$code" ${quote(JSON.stringify(wrapper))} > "$receipt"
|
|
228
|
+
exit "$code"
|
|
229
|
+
`, { mode: 0o700 });
|
|
230
|
+
return { ...handler, command: `/bin/sh ${quote(wrapper)}` };
|
|
231
|
+
}
|
|
232
|
+
function isolatedHookCommand(state, environment, command) {
|
|
233
|
+
const prefix = state.isolation
|
|
234
|
+
? state.isolation.argv.slice(0, -1)
|
|
235
|
+
: ["/usr/bin/sandbox-exec", "-f", state.profile ?? ""];
|
|
236
|
+
return [
|
|
237
|
+
...prefix,
|
|
238
|
+
"/usr/bin/env",
|
|
239
|
+
"-i",
|
|
240
|
+
...environment,
|
|
241
|
+
"/bin/sh",
|
|
242
|
+
"-c",
|
|
243
|
+
command,
|
|
244
|
+
]
|
|
245
|
+
.map(quote)
|
|
246
|
+
.join(" ");
|
|
247
|
+
}
|
|
248
|
+
function commandHandler(value) {
|
|
249
|
+
const handler = object(value);
|
|
250
|
+
if (handler.type !== "command" || typeof handler.command !== "string")
|
|
251
|
+
throw new Error("runtime policy supports command plugin hooks only");
|
|
252
|
+
if (handler.args !== undefined)
|
|
253
|
+
throw new Error("runtime exec-form hook arguments are unsupported");
|
|
254
|
+
return { ...handler, command: handler.command };
|
|
255
|
+
}
|
|
256
|
+
async function rewriteFrontmatter(directory, state) {
|
|
257
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
258
|
+
const path = join(directory, entry.name);
|
|
259
|
+
if (entry.isDirectory())
|
|
260
|
+
await rewriteFrontmatter(path, state);
|
|
261
|
+
else if (entry.name.endsWith(".md"))
|
|
262
|
+
await rewriteMarkdown(path, state);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function rewriteMarkdown(path, state) {
|
|
266
|
+
const body = await readFile(path, "utf8");
|
|
267
|
+
const header = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(body);
|
|
268
|
+
const metadata = hookFrontmatter(header?.[1]);
|
|
269
|
+
if (!metadata || !header)
|
|
270
|
+
return;
|
|
271
|
+
if (state.allowed)
|
|
272
|
+
metadata.hooks = await hookMap(metadata.hooks, state);
|
|
273
|
+
else
|
|
274
|
+
delete metadata.hooks;
|
|
275
|
+
await writeFile(path, `---\n${stringifyYaml(metadata)}\n---\n${body.slice(header[0].length)}`);
|
|
276
|
+
}
|
|
277
|
+
function hookFrontmatter(header) {
|
|
278
|
+
if (!header?.includes("hooks"))
|
|
279
|
+
return undefined;
|
|
280
|
+
const metadata = object(parseYaml(header));
|
|
281
|
+
return metadata.hooks === undefined ? undefined : metadata;
|
|
282
|
+
}
|
|
283
|
+
export async function runtimeHookObservation(mounts) {
|
|
284
|
+
await releaseHookProcesses(mounts.receipts);
|
|
285
|
+
const executions = (await hookExecutions(mounts.receipts)).map((execution) => ({
|
|
286
|
+
status: execution.status,
|
|
287
|
+
pid: execution.pid,
|
|
288
|
+
exitCode: execution.exitCode,
|
|
289
|
+
}));
|
|
290
|
+
return {
|
|
291
|
+
id: "sevro.host.hooks",
|
|
292
|
+
completeness: executions.every((execution) => execution.status === "completed")
|
|
293
|
+
? "complete"
|
|
294
|
+
: "partial",
|
|
295
|
+
data: { plugins: mounts.plugins, executions },
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
export async function releaseRuntimeHooks(mounts) {
|
|
299
|
+
if (!mounts)
|
|
300
|
+
return;
|
|
301
|
+
await releaseHookProcesses(mounts.receipts);
|
|
302
|
+
await Promise.all(mounts.isolation.map((item) => item.release()));
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { existsSync } from \"node:fs\";\nimport {\n cp,\n mkdir,\n readFile,\n readdir,\n realpath,\n writeFile,\n} from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { shellQuote as quote } from \"../shell\";\nimport type { RuntimePolicy } from \"./config\";\nimport { insideRuntimeRoot, requireRuntimeReadRoots } from \"./paths\";\nimport { runtimeSeedDigest } from \"./seeds\";\nimport { prepareRuntimeState } from \"./state\";\nimport { sharedCacheDenyRoots, sharedCacheRoots } from \"./shared-cache\";\nimport { macSandboxProfile } from \"../hosts/mac-sandbox\";\nimport type { IsolatedCommand } from \"../hosts/mac-sandbox\";\nimport { prepareLinuxSandboxCommand } from \"../hosts/linux-sandbox\";\nimport { isRecord, isUnknownArray } from \"../value-guards\";\nimport { hookExecutions, releaseHookProcesses } from \"./hook-processes\";\nimport {\n nativeStateParent,\n requireNativeHookRoots,\n} from \"../native-transcript-state\";\n\nexport interface HookMounts {\n directories: string[];\n receipts: string;\n isolation: IsolatedCommand[];\n plugins: {\n name: string;\n allowed: boolean;\n sourceDigest: string;\n effectiveDigest: string;\n }[];\n}\ninterface HookRewrite {\n allowed: boolean;\n root: string;\n privateRoot: string;\n execution: () => Promise<HookExecution>;\n ordinal: number;\n files: Set<string>;\n}\n\ninterface HookExecution {\n environment: Record<string, string>;\n profile?: string;\n isolation?: IsolatedCommand;\n}\n\nexport function runtimeHooksEnabled(\n policy: RuntimePolicy | undefined,\n): boolean {\n const hooks = policy?.hooks;\n if (!hooks) return false;\n const plugins = hooks.plugins ?? [];\n return hooks.nativeGoal === true || plugins.length > 0;\n}\n\nfunction object(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new Error(\"invalid runtime hook declaration\");\n return value;\n}\n\n/** Materialize immutable mounts; only declared plugin hooks reach the host. */\nexport async function prepareHookMounts(options: {\n workspace: string;\n stateRoot: string;\n privateRoot: string;\n pluginRoots: string[];\n protectedRoots: string[];\n policy: RuntimePolicy;\n}): Promise<HookMounts> {\n let mountRoot = join(options.stateRoot, \"runtime-plugins\");\n const receipts = join(options.privateRoot, \"hook-receipts\");\n await Promise.all(\n [mountRoot, receipts].map((path) =>\n mkdir(path, { recursive: true, mode: 0o700 }),\n ),\n );\n mountRoot = await realpath(mountRoot);\n let runtime: ReturnType<typeof prepareRuntimeState> | undefined;\n const prepareRuntime = () =>\n (runtime ??= prepareRuntimeState(\n options.workspace,\n options.policy,\n \"hooks\",\n join(options.privateRoot, \"hook-runtime\"),\n ));\n const selected = selectedPlugins(options.policy);\n const plugins: HookMounts[\"plugins\"] = [],\n isolation: IsolatedCommand[] = [],\n directories: string[] = [];\n for (const [index, source] of options.pluginRoots.entries()) {\n const root = join(mountRoot, `plugin-${index}`);\n const sourceDigest = await runtimeSeedDigest(source);\n await cp(source, root, {\n recursive: true,\n dereference: true,\n force: false,\n errorOnExist: true,\n });\n const manifest = object(\n JSON.parse(\n await readFile(join(root, \".claude-plugin/plugin.json\"), \"utf8\"),\n ),\n );\n const name = uniquePluginName(manifest, plugins);\n const allowed = selected.delete(name);\n const readRoots = await hookReadRoots(options, root);\n const rewrite: HookRewrite = {\n root,\n allowed,\n privateRoot: options.privateRoot,\n ordinal: index * 10000,\n files: new Set(),\n execution: lazyHookExecution(\n options,\n index,\n root,\n readRoots,\n prepareRuntime,\n isolation,\n ),\n };\n await rewritePlugin(manifest, rewrite);\n plugins.push({\n name,\n allowed,\n sourceDigest,\n effectiveDigest: await runtimeSeedDigest(root),\n });\n directories.push(root);\n }\n if (selected.size)\n throw new Error(\"runtime hook policy selects an unavailable plugin\");\n return { directories, receipts, plugins, isolation };\n}\n\nfunction lazyHookExecution(\n options: Parameters<typeof prepareHookMounts>[0],\n index: number,\n root: string,\n readRoots: { ordinary: string[]; curated: string[] },\n prepareRuntime: () => ReturnType<typeof prepareRuntimeState>,\n isolation: IsolatedCommand[],\n): () => Promise<HookExecution> {\n let execution: Promise<HookExecution> | undefined;\n return () =>\n (execution ??= (async () => {\n const runtime = await prepareRuntime();\n const boundary = await prepareHookIsolation(\n options,\n index,\n readRoots,\n runtime,\n );\n if (boundary.isolation) isolation.push(boundary.isolation);\n return {\n ...boundary,\n environment: {\n ...runtime.environment,\n PATH: hookPath(runtime.environment),\n CLAUDE_PLUGIN_ROOT: root,\n PLUGIN_ROOT: root,\n },\n };\n })());\n}\n\nasync function prepareHookIsolation(\n options: Parameters<typeof prepareHookMounts>[0],\n index: number,\n roots: { ordinary: string[]; curated: string[] },\n runtime: Awaited<ReturnType<typeof prepareRuntimeState>>,\n): Promise<{ profile?: string; isolation?: IsolatedCommand }> {\n const cacheRoots = await sharedCacheRoots(\n options.policy,\n \"hooks\",\n options.protectedRoots,\n );\n const protectedRoots = [\n ...options.protectedRoots,\n ...sharedCacheDenyRoots(options.policy, \"hooks\"),\n ];\n if (process.platform === \"linux\")\n return {\n isolation: await prepareLinuxSandboxCommand({\n argv: [\"/bin/true\"],\n workspace: options.workspace,\n protectedRoots,\n privateStateRoot: options.privateRoot,\n denyNetwork: true,\n readOnlyRoots: roots.ordinary,\n hookReadRoots: roots.curated,\n writableRuntimeRoot: runtime.root,\n writableCacheRoots: cacheRoots,\n }),\n };\n const profile = join(options.privateRoot, `hooks-${index}.sb`);\n await writeFile(\n profile,\n macSandboxProfile(\n protectedRoots,\n true,\n roots.ordinary,\n runtime.root,\n roots.curated,\n options.workspace,\n undefined,\n cacheRoots,\n ) + \"\\n(deny process-info* (require-not (target self)))\\n\",\n { mode: 0o600 },\n );\n return { profile };\n}\n\nasync function hookReadRoots(\n options: Parameters<typeof prepareHookMounts>[0],\n root: string,\n) {\n const trusted = insideRuntimeRoot(await nativeStateParent(), root);\n const ordinary = [\n ...options.policy.readOnlyRoots,\n ...(trusted ? [] : [root]),\n ];\n const curated = trusted ? [root] : [];\n requireRuntimeReadRoots(ordinary, options.protectedRoots);\n await requireNativeHookRoots(curated, options.privateRoot);\n return { ordinary, curated };\n}\n\nfunction hookPath(environment: Record<string, string>): string {\n return environment.PATH ?? \"/usr/bin:/bin:/usr/sbin:/sbin\";\n}\nfunction selectedPlugins(policy: RuntimePolicy): Set<string> {\n return new Set(policy.hooks?.plugins ?? []);\n}\nfunction uniquePluginName(\n manifest: Record<string, unknown>,\n plugins: HookMounts[\"plugins\"],\n): string {\n if (typeof manifest.name !== \"string\")\n throw new Error(\"runtime hook plugin has no name\");\n const name = manifest.name;\n if (plugins.some((plugin) => plugin.name === name))\n throw new Error(\"runtime hook plugin names overlap\");\n return name;\n}\n\nasync function rewritePlugin(\n manifest: Record<string, unknown>,\n state: HookRewrite,\n): Promise<void> {\n requireHookOnlyPlugin(state.root);\n await rewriteManifest(manifest, state, \".claude-plugin/plugin.json\");\n const native = join(state.root, \".codex-plugin/plugin.json\");\n if (existsSync(native))\n await rewriteManifest(\n object(JSON.parse(await readFile(native, \"utf8\"))),\n state,\n \".codex-plugin/plugin.json\",\n );\n const standard = join(state.root, \"hooks/hooks.json\");\n if (existsSync(standard)) await hookFile(standard, state);\n await rewriteFrontmatter(state.root, state);\n}\n\nfunction requireHookOnlyPlugin(root: string): void {\n for (const name of [\".mcp.json\", \".lsp.json\"])\n if (existsSync(join(root, name)))\n throw new Error(\"runtime auxiliary plugin execution is unsupported\");\n}\n\nasync function rewriteManifest(\n manifest: Record<string, unknown>,\n state: HookRewrite,\n path: string,\n): Promise<void> {\n if (manifest.mods !== undefined)\n throw new Error(\"runtime plugin mods are unsupported\");\n delete manifest.mcpServers;\n delete manifest.lspServers;\n if (manifest.hooks !== undefined)\n manifest.hooks = await hookSource(manifest.hooks, state);\n await writeFile(join(state.root, path), JSON.stringify(manifest));\n}\n\nasync function hookSource(\n value: unknown,\n state: HookRewrite,\n): Promise<unknown> {\n if (typeof value === \"string\") {\n const path = await realpath(join(state.root, value));\n if (!insideRuntimeRoot(state.root, path))\n throw new Error(\"runtime hook file escapes its plugin\");\n await hookFile(path, state);\n return value;\n }\n if (isUnknownArray(value))\n return Promise.all(value.map((source) => hookSource(source, state)));\n const configuration = object(value);\n if (configuration.hooks !== undefined)\n return {\n ...configuration,\n hooks: await hookMap(configuration.hooks, state),\n };\n return hookMap(configuration, state);\n}\n\nasync function hookFile(path: string, state: HookRewrite): Promise<void> {\n if (state.files.has(path)) return;\n state.files.add(path);\n const value = object(JSON.parse(await readFile(path, \"utf8\")));\n value.hooks = await hookMap(value.hooks, state);\n await writeFile(path, JSON.stringify(value));\n}\n\nasync function hookMap(\n value: unknown,\n state: HookRewrite,\n): Promise<Record<string, unknown>> {\n if (!state.allowed) return {};\n const entries: [string, unknown][] = [];\n for (const [event, groups] of Object.entries(object(value))) {\n if (!isUnknownArray(groups))\n throw new Error(\"runtime hook groups must be arrays\");\n entries.push([\n event,\n await Promise.all(\n groups.map(async (group) => {\n const definition = object(group);\n if (!isUnknownArray(definition.hooks))\n throw new Error(\"runtime hook handlers must be arrays\");\n return {\n ...definition,\n hooks: await Promise.all(\n definition.hooks.map((handler) => commandHook(handler, state)),\n ),\n };\n }),\n ),\n ]);\n }\n return Object.fromEntries(entries);\n}\n\nasync function commandHook(\n value: unknown,\n state: HookRewrite,\n): Promise<Record<string, unknown>> {\n const handler = commandHandler(value);\n if (handler.command.includes(\"\\0\") || handler.command.length > 65536)\n throw new Error(\"runtime hook command is invalid or oversized\");\n const wrapper = join(state.privateRoot, `hook-${state.ordinal++}.sh`);\n const execution = await state.execution();\n const environment = Object.entries(execution.environment).map(\n ([name, content]) => `${name}=${content}`,\n );\n const receipts = join(state.privateRoot, \"hook-receipts\");\n const command = isolatedHookCommand(execution, environment, handler.command);\n await writeFile(\n wrapper,\n `#!/bin/sh\nset -u\nreceipt=$(/usr/bin/mktemp ${quote(join(receipts, \"call.XXXXXX\"))}) || exit 125\ntest ! -f ${quote(join(receipts, \"stopped\"))} || exit 125\nprintf '{\"status\":\"running\",\"pid\":%s,\"wrapper\":%s}\\\\n' \"$$\" ${quote(JSON.stringify(wrapper))} > \"$receipt\"\n${command}\ncode=$?\nprintf '{\"status\":\"completed\",\"pid\":%s,\"exitCode\":%s,\"wrapper\":%s}\\\\n' \"$$\" \"$code\" ${quote(JSON.stringify(wrapper))} > \"$receipt\"\nexit \"$code\"\n`,\n { mode: 0o700 },\n );\n return { ...handler, command: `/bin/sh ${quote(wrapper)}` };\n}\n\nfunction isolatedHookCommand(\n state: HookExecution,\n environment: string[],\n command: string,\n): string {\n const prefix = state.isolation\n ? state.isolation.argv.slice(0, -1)\n : [\"/usr/bin/sandbox-exec\", \"-f\", state.profile ?? \"\"];\n return [\n ...prefix,\n \"/usr/bin/env\",\n \"-i\",\n ...environment,\n \"/bin/sh\",\n \"-c\",\n command,\n ]\n .map(quote)\n .join(\" \");\n}\n\nfunction commandHandler(\n value: unknown,\n): Record<string, unknown> & { command: string } {\n const handler = object(value);\n if (handler.type !== \"command\" || typeof handler.command !== \"string\")\n throw new Error(\"runtime policy supports command plugin hooks only\");\n if (handler.args !== undefined)\n throw new Error(\"runtime exec-form hook arguments are unsupported\");\n return { ...handler, command: handler.command };\n}\n\nasync function rewriteFrontmatter(\n directory: string,\n state: HookRewrite,\n): Promise<void> {\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n const path = join(directory, entry.name);\n if (entry.isDirectory()) await rewriteFrontmatter(path, state);\n else if (entry.name.endsWith(\".md\")) await rewriteMarkdown(path, state);\n }\n}\n\nasync function rewriteMarkdown(\n path: string,\n state: HookRewrite,\n): Promise<void> {\n const body = await readFile(path, \"utf8\");\n const header = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/.exec(body);\n const metadata = hookFrontmatter(header?.[1]);\n if (!metadata || !header) return;\n if (state.allowed) metadata.hooks = await hookMap(metadata.hooks, state);\n else delete metadata.hooks;\n await writeFile(\n path,\n `---\\n${stringifyYaml(metadata)}\\n---\\n${body.slice(header[0].length)}`,\n );\n}\n\nfunction hookFrontmatter(\n header: string | undefined,\n): Record<string, unknown> | undefined {\n if (!header?.includes(\"hooks\")) return undefined;\n const metadata = object(parseYaml(header));\n return metadata.hooks === undefined ? undefined : metadata;\n}\n\nexport async function runtimeHookObservation(mounts: HookMounts) {\n await releaseHookProcesses(mounts.receipts);\n const executions = (await hookExecutions(mounts.receipts)).map(\n (execution) => ({\n status: execution.status,\n pid: execution.pid,\n exitCode: execution.exitCode,\n }),\n );\n return {\n id: \"sevro.host.hooks\",\n completeness: executions.every(\n (execution) => execution.status === \"completed\",\n )\n ? (\"complete\" as const)\n : (\"partial\" as const),\n data: { plugins: mounts.plugins, executions },\n };\n}\n\nexport async function releaseRuntimeHooks(\n mounts: HookMounts | undefined,\n): Promise<void> {\n if (!mounts) return;\n await releaseHookProcesses(mounts.receipts);\n await Promise.all(mounts.isolation.map((item) => item.release()));\n}\n"]}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { access, lstat, readdir, readlink, realpath, stat, } from "node:fs/promises";
|
|
2
|
+
import { constants, lstatSync, readlinkSync, realpathSync } from "node:fs";
|
|
3
|
+
import { homedir, userInfo } from "node:os";
|
|
4
|
+
import { nativeStateProtectedRoots } from "../native-transcript-state.js";
|
|
5
|
+
import { workspaceStatePath } from "../fixtures/workspace-state.js";
|
|
6
|
+
import { insideRoot as insideRuntimeRoot } from "../paths.js";
|
|
7
|
+
import { containsControl } from "../value-guards.js";
|
|
8
|
+
export { insideRoot as insideRuntimeRoot } from "../paths.js";
|
|
9
|
+
import { basename, delimiter, dirname, isAbsolute, join, resolve, } from "node:path";
|
|
10
|
+
export function isRuntimeHomeRoot(root) {
|
|
11
|
+
return [homedir(), userInfo().homedir]
|
|
12
|
+
.map((home) => resolve(home))
|
|
13
|
+
.includes(root);
|
|
14
|
+
}
|
|
15
|
+
export function requireRuntimeReadRoots(readRoots, protectedRoots) {
|
|
16
|
+
const hardRoots = [
|
|
17
|
+
...protectedRoots.filter((root) => !isRuntimeHomeRoot(root)),
|
|
18
|
+
...runtimeCredentialRoots(),
|
|
19
|
+
...nativeStateProtectedRoots(),
|
|
20
|
+
workspaceStatePath(),
|
|
21
|
+
];
|
|
22
|
+
for (const root of readRoots) {
|
|
23
|
+
if (root === "/" || isRuntimeHomeRoot(root))
|
|
24
|
+
throw new Error("runtime read root is too broad");
|
|
25
|
+
if (overlapsRuntimeRoots([root], hardRoots))
|
|
26
|
+
throw new Error("runtime read root overlaps protected data");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function runtimeCredentialRoots() {
|
|
30
|
+
return credentialRoots().flatMap((root) => [
|
|
31
|
+
root,
|
|
32
|
+
canonicalCredentialRoot(root),
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
function overlapsRuntimeRoots(roots, deniedRoots) {
|
|
36
|
+
return roots.some((root) => deniedRoots.some((denied) => insideRuntimeRoot(denied, root) || insideRuntimeRoot(root, denied)));
|
|
37
|
+
}
|
|
38
|
+
function canonicalCredentialRoot(path) {
|
|
39
|
+
try {
|
|
40
|
+
return realpathSync(path);
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
if (cause.code !== "ENOENT")
|
|
44
|
+
throw cause;
|
|
45
|
+
const target = missingCredentialLink(path);
|
|
46
|
+
if (target)
|
|
47
|
+
return canonicalCredentialRoot(target);
|
|
48
|
+
return join(canonicalCredentialRoot(dirname(path)), basename(path));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function missingCredentialLink(path) {
|
|
52
|
+
try {
|
|
53
|
+
return lstatSync(path).isSymbolicLink()
|
|
54
|
+
? resolve(dirname(path), readlinkSync(path))
|
|
55
|
+
: null;
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
if (cause.code === "ENOENT")
|
|
59
|
+
return null;
|
|
60
|
+
throw cause;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function runtimeExecutableProtection(readRoots, protectedRoots, allowHomeRuntime) {
|
|
64
|
+
if (!allowHomeRuntime)
|
|
65
|
+
return protectedRoots;
|
|
66
|
+
requireRuntimeReadRoots(readRoots, []);
|
|
67
|
+
return protectedRoots.filter((root) => !isRuntimeHomeRoot(root));
|
|
68
|
+
}
|
|
69
|
+
function credentialRoots() {
|
|
70
|
+
return [homedir(), userInfo().homedir].flatMap((home) => [
|
|
71
|
+
".ssh",
|
|
72
|
+
".aws",
|
|
73
|
+
".codex",
|
|
74
|
+
".claude",
|
|
75
|
+
".config/gcloud",
|
|
76
|
+
".local/share/keyrings",
|
|
77
|
+
].map((path) => join(home, path)));
|
|
78
|
+
}
|
|
79
|
+
export async function canonicalRuntimeRoot(path) {
|
|
80
|
+
try {
|
|
81
|
+
return await realpath(path);
|
|
82
|
+
}
|
|
83
|
+
catch (cause) {
|
|
84
|
+
if (cause.code !== "ENOENT")
|
|
85
|
+
throw cause;
|
|
86
|
+
const target = await missingRuntimeLink(path);
|
|
87
|
+
if (target)
|
|
88
|
+
return canonicalRuntimeRoot(target);
|
|
89
|
+
return join(await canonicalRuntimeRoot(dirname(path)), basename(path));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function missingRuntimeLink(path) {
|
|
93
|
+
try {
|
|
94
|
+
return (await lstat(path)).isSymbolicLink()
|
|
95
|
+
? resolve(dirname(path), await readlink(path))
|
|
96
|
+
: null;
|
|
97
|
+
}
|
|
98
|
+
catch (cause) {
|
|
99
|
+
if (cause.code === "ENOENT")
|
|
100
|
+
return null;
|
|
101
|
+
throw cause;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function installationRoot(directory) {
|
|
105
|
+
if (basename(directory) !== "bin")
|
|
106
|
+
return null;
|
|
107
|
+
const root = dirname(directory);
|
|
108
|
+
const broad = [
|
|
109
|
+
"/",
|
|
110
|
+
"/usr",
|
|
111
|
+
"/usr/local",
|
|
112
|
+
"/opt/homebrew",
|
|
113
|
+
...[homedir(), userInfo().homedir].flatMap((home) => [
|
|
114
|
+
home,
|
|
115
|
+
join(home, ".local"),
|
|
116
|
+
]),
|
|
117
|
+
];
|
|
118
|
+
return broad.includes(root) ? null : root;
|
|
119
|
+
}
|
|
120
|
+
async function linkedToolRoots(directory) {
|
|
121
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
122
|
+
if (entries.length > 4096)
|
|
123
|
+
throw new Error("runtime PATH directory exceeds entry limit");
|
|
124
|
+
const roots = [];
|
|
125
|
+
for (const entry of entries.filter((entry) => entry.isSymbolicLink())) {
|
|
126
|
+
const target = await linkedTarget(join(directory, entry.name));
|
|
127
|
+
if (!target)
|
|
128
|
+
continue;
|
|
129
|
+
const bin = dirname(target);
|
|
130
|
+
roots.push(bin);
|
|
131
|
+
const installation = installationRoot(bin);
|
|
132
|
+
if (installation)
|
|
133
|
+
roots.push(installation);
|
|
134
|
+
}
|
|
135
|
+
return roots;
|
|
136
|
+
}
|
|
137
|
+
async function linkedTarget(path) {
|
|
138
|
+
try {
|
|
139
|
+
const target = await realpath(path);
|
|
140
|
+
return (await stat(target)).isFile() ? target : null;
|
|
141
|
+
}
|
|
142
|
+
catch (cause) {
|
|
143
|
+
if (["EACCES", "EPERM", "ENOENT"].includes(cause.code ?? ""))
|
|
144
|
+
return null;
|
|
145
|
+
throw cause;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** Missing PATH entries are inert; relative entries cannot grant host access. */
|
|
149
|
+
export async function runtimePathDirectories(path) {
|
|
150
|
+
if (path === undefined)
|
|
151
|
+
return [];
|
|
152
|
+
const roots = [];
|
|
153
|
+
for (const entry of path.split(delimiter)) {
|
|
154
|
+
roots.push(...(await pathEntryRoots(entry)));
|
|
155
|
+
}
|
|
156
|
+
return [...new Set(roots)].sort();
|
|
157
|
+
}
|
|
158
|
+
function requirePathEntry(entry) {
|
|
159
|
+
if (!isAbsolute(entry) || containsControl(entry))
|
|
160
|
+
throw new Error("runtime PATH entries must be absolute and nonempty");
|
|
161
|
+
}
|
|
162
|
+
/** Ambient tool discovery must not grant access to the operator's credentials. */
|
|
163
|
+
export async function runtimeInheritedPath(path) {
|
|
164
|
+
const deniedRoots = runtimeCredentialRoots();
|
|
165
|
+
const selected = [];
|
|
166
|
+
for (const entry of path.split(delimiter)) {
|
|
167
|
+
requirePathEntry(entry);
|
|
168
|
+
const paths = [resolve(entry), await canonicalRuntimeRoot(entry)];
|
|
169
|
+
if (overlapsRuntimeRoots(paths, deniedRoots))
|
|
170
|
+
continue;
|
|
171
|
+
if (overlapsRuntimeRoots(await pathEntryRoots(entry), deniedRoots))
|
|
172
|
+
continue;
|
|
173
|
+
selected.push(entry);
|
|
174
|
+
}
|
|
175
|
+
if (selected.length === 0)
|
|
176
|
+
throw new Error("runtime inherited PATH has no unprotected entries");
|
|
177
|
+
return selected.join(delimiter);
|
|
178
|
+
}
|
|
179
|
+
async function pathEntryRoots(entry) {
|
|
180
|
+
requirePathEntry(entry);
|
|
181
|
+
const directory = await existingDirectory(entry);
|
|
182
|
+
if (!directory)
|
|
183
|
+
return [];
|
|
184
|
+
const installation = installationRoot(directory);
|
|
185
|
+
return [
|
|
186
|
+
resolve(entry),
|
|
187
|
+
directory,
|
|
188
|
+
...(installation ? [installation] : []),
|
|
189
|
+
...(await linkedToolRoots(directory)),
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
async function existingDirectory(path) {
|
|
193
|
+
try {
|
|
194
|
+
const canonical = await realpath(path);
|
|
195
|
+
if (!(await stat(canonical)).isDirectory())
|
|
196
|
+
throw new Error("runtime PATH entry is not a directory");
|
|
197
|
+
await access(canonical, constants.R_OK | constants.X_OK);
|
|
198
|
+
return canonical;
|
|
199
|
+
}
|
|
200
|
+
catch (cause) {
|
|
201
|
+
if (cause.code === "ENOENT")
|
|
202
|
+
return null;
|
|
203
|
+
throw cause;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export function runtimeDeclaredPath(path, environment, base) {
|
|
207
|
+
const expanded = path.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name) => {
|
|
208
|
+
const value = environment[name];
|
|
209
|
+
if (value === undefined)
|
|
210
|
+
throw new Error(`runtime path variable is undeclared: ${name}`);
|
|
211
|
+
return value;
|
|
212
|
+
});
|
|
213
|
+
if (containsControl(expanded) || expanded.includes("${"))
|
|
214
|
+
throw new Error("runtime path contains invalid characters");
|
|
215
|
+
return expanded.startsWith("~/")
|
|
216
|
+
? join(homedir(), expanded.slice(2))
|
|
217
|
+
: resolve(base, expanded);
|
|
218
|
+
}
|
|
219
|
+
export async function declaredRuntimeRoots(paths, environment, base) {
|
|
220
|
+
const roots = await Promise.all(paths.map(async (path) => {
|
|
221
|
+
const directory = await existingDirectory(runtimeDeclaredPath(path, environment, base));
|
|
222
|
+
if (!directory)
|
|
223
|
+
throw new Error("declared runtime read root is missing");
|
|
224
|
+
return [directory, runtimeDeclaredPath(path, environment, base)];
|
|
225
|
+
}));
|
|
226
|
+
return [...new Set(roots.flat())];
|
|
227
|
+
}
|
|
228
|
+
export async function optionalRuntimeRoots(paths, environment, base, protectedRoots = []) {
|
|
229
|
+
const roots = [], skipped = [];
|
|
230
|
+
for (const path of paths) {
|
|
231
|
+
const declared = runtimeDeclaredPath(path, environment, base);
|
|
232
|
+
requireRuntimeReadRoots([declared, await canonicalRuntimeRoot(declared)], protectedRoots);
|
|
233
|
+
const directory = await existingDirectory(declared);
|
|
234
|
+
if (directory)
|
|
235
|
+
roots.push(directory, declared);
|
|
236
|
+
else
|
|
237
|
+
skipped.push(declared);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
roots: [...new Set(roots)].sort(),
|
|
241
|
+
skipped: [...new Set(skipped)].sort(),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=paths.js.map
|