@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,212 @@
|
|
|
1
|
+
import { spawn } from "./runtime-process.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import validExchange from "./generated/extension.cjs";
|
|
4
|
+
import { isRecord, isStringArray } from "./value-guards.js";
|
|
5
|
+
import { captureBoundedBytes, ProcessDeadline, terminateOwnedProcessGroup, } from "./process-lifecycle.js";
|
|
6
|
+
const MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
7
|
+
const MAX_DIAGNOSTIC_BYTES = 64 * 1024;
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
9
|
+
const PROTOCOL = "sevro.extension.v1";
|
|
10
|
+
export class ExtensionProtocolError extends Error {
|
|
11
|
+
constructor(message, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.name = "ExtensionProtocolError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function validateExtensionCommand(command) {
|
|
17
|
+
if (!command.length || command.some((part) => !part))
|
|
18
|
+
throw new ExtensionProtocolError("extension command must be a nonempty argv array");
|
|
19
|
+
}
|
|
20
|
+
function extensionTimeout(value) {
|
|
21
|
+
const timeout = value ?? DEFAULT_TIMEOUT_MS;
|
|
22
|
+
if (!Number.isSafeInteger(timeout) || timeout <= 0)
|
|
23
|
+
throw new ExtensionProtocolError("extension timeout must be a positive integer");
|
|
24
|
+
return timeout;
|
|
25
|
+
}
|
|
26
|
+
function extensionPayload(request) {
|
|
27
|
+
const payload = new TextEncoder().encode(JSON.stringify(request));
|
|
28
|
+
if (payload.byteLength > MAX_MESSAGE_BYTES)
|
|
29
|
+
throw new ExtensionProtocolError("extension request exceeds 8 MiB");
|
|
30
|
+
return payload;
|
|
31
|
+
}
|
|
32
|
+
function spawnExtension(command, options) {
|
|
33
|
+
try {
|
|
34
|
+
return spawn(command, {
|
|
35
|
+
cwd: options.cwd,
|
|
36
|
+
stdin: "pipe",
|
|
37
|
+
stdout: "pipe",
|
|
38
|
+
stderr: "pipe",
|
|
39
|
+
detached: process.platform !== "win32",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
throw new ExtensionProtocolError("could not start extension process", {
|
|
44
|
+
cause,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function extensionWriter(proc) {
|
|
49
|
+
const stdin = proc.stdin;
|
|
50
|
+
if (!stdin || typeof stdin === "number")
|
|
51
|
+
throw new ExtensionProtocolError("extension pipes are unavailable");
|
|
52
|
+
return stdin;
|
|
53
|
+
}
|
|
54
|
+
function extensionReaders(proc) {
|
|
55
|
+
const { stdout, stderr } = proc;
|
|
56
|
+
return { stdout, stderr };
|
|
57
|
+
}
|
|
58
|
+
async function writeExtensionRequest(stdin, payload) {
|
|
59
|
+
await stdin.write(payload);
|
|
60
|
+
await stdin.end();
|
|
61
|
+
}
|
|
62
|
+
function hasResponseBody(value) {
|
|
63
|
+
return Object.hasOwn(value, "result") || Object.hasOwn(value, "error");
|
|
64
|
+
}
|
|
65
|
+
function assertExtensionResponse(value) {
|
|
66
|
+
if (!validExchange(value) || !isRecord(value) || !hasResponseBody(value))
|
|
67
|
+
throw new ExtensionProtocolError("invalid extension response");
|
|
68
|
+
}
|
|
69
|
+
function parseExtensionResponse(bytes) {
|
|
70
|
+
let response;
|
|
71
|
+
try {
|
|
72
|
+
response = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
73
|
+
}
|
|
74
|
+
catch (cause) {
|
|
75
|
+
throw new ExtensionProtocolError("extension did not return one UTF-8 JSON response", { cause });
|
|
76
|
+
}
|
|
77
|
+
assertExtensionResponse(response);
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
80
|
+
function validateResponseIdentity(response, request) {
|
|
81
|
+
if (response.id !== request.id ||
|
|
82
|
+
response.protocol !== request.protocol ||
|
|
83
|
+
response.method !== request.method)
|
|
84
|
+
throw new ExtensionProtocolError("extension response does not match request");
|
|
85
|
+
}
|
|
86
|
+
function assertSuccessfulResponse(response) {
|
|
87
|
+
if (response.error)
|
|
88
|
+
throw new ExtensionProtocolError(`extension reported ${response.error.code}`);
|
|
89
|
+
if (response.result === undefined)
|
|
90
|
+
throw new ExtensionProtocolError("invalid extension response");
|
|
91
|
+
}
|
|
92
|
+
class ExtensionExchange {
|
|
93
|
+
proc;
|
|
94
|
+
deadline;
|
|
95
|
+
constructor(proc, options, timeoutMs) {
|
|
96
|
+
this.proc = proc;
|
|
97
|
+
this.deadline = new ProcessDeadline({
|
|
98
|
+
timeoutMs,
|
|
99
|
+
signal: options.signal,
|
|
100
|
+
interruption: (reason) => new ExtensionProtocolError(reason === "timeout"
|
|
101
|
+
? "extension timed out"
|
|
102
|
+
: "extension request cancelled"),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async output(request, payload) {
|
|
106
|
+
const stdin = extensionWriter(this.proc);
|
|
107
|
+
const { stdout, stderr } = extensionReaders(this.proc);
|
|
108
|
+
const write = writeExtensionRequest(stdin, payload);
|
|
109
|
+
const [{ bytes }, , exitCode] = await Promise.all([
|
|
110
|
+
captureBoundedBytes(stdout, {
|
|
111
|
+
limit: MAX_MESSAGE_BYTES,
|
|
112
|
+
overflow: "reject",
|
|
113
|
+
exceeded: () => new ExtensionProtocolError("extension message exceeds 8 MiB"),
|
|
114
|
+
}),
|
|
115
|
+
captureBoundedBytes(stderr, {
|
|
116
|
+
limit: MAX_DIAGNOSTIC_BYTES,
|
|
117
|
+
overflow: "truncate",
|
|
118
|
+
}),
|
|
119
|
+
this.proc.exited,
|
|
120
|
+
write,
|
|
121
|
+
]);
|
|
122
|
+
if (exitCode !== 0)
|
|
123
|
+
throw new ExtensionProtocolError(`extension exited with code ${exitCode}`);
|
|
124
|
+
const response = parseExtensionResponse(bytes);
|
|
125
|
+
validateResponseIdentity(response, request);
|
|
126
|
+
assertSuccessfulResponse(response);
|
|
127
|
+
return response;
|
|
128
|
+
}
|
|
129
|
+
async run(request, payload) {
|
|
130
|
+
try {
|
|
131
|
+
return await this.deadline.wait(this.output(request, payload));
|
|
132
|
+
}
|
|
133
|
+
catch (cause) {
|
|
134
|
+
terminateOwnedProcessGroup(this.proc);
|
|
135
|
+
if (cause instanceof ExtensionProtocolError)
|
|
136
|
+
throw cause;
|
|
137
|
+
throw new ExtensionProtocolError("extension transport failed", { cause });
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
this.deadline.dispose();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Run exactly one request in a fresh extension process. */
|
|
145
|
+
export async function exchangeExtension(command, request, options = {}) {
|
|
146
|
+
validateExtensionCommand(command);
|
|
147
|
+
if (!validExchange(request))
|
|
148
|
+
throw new ExtensionProtocolError("invalid extension request");
|
|
149
|
+
if (options.signal?.aborted)
|
|
150
|
+
throw new ExtensionProtocolError("extension request cancelled");
|
|
151
|
+
const timeout = extensionTimeout(options.timeoutMs);
|
|
152
|
+
const payload = extensionPayload(request);
|
|
153
|
+
return new ExtensionExchange(spawnExtension(command, options), options, timeout).run(request, payload);
|
|
154
|
+
}
|
|
155
|
+
function descriptionStrings(value) {
|
|
156
|
+
if (!isStringArray(value))
|
|
157
|
+
throw new ExtensionProtocolError("invalid extension response");
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
function describedIdentity(value) {
|
|
161
|
+
if (!isRecord(value) ||
|
|
162
|
+
typeof value.id !== "string" ||
|
|
163
|
+
typeof value.version !== "string")
|
|
164
|
+
throw new ExtensionProtocolError("invalid extension response");
|
|
165
|
+
return { id: value.id, version: value.version };
|
|
166
|
+
}
|
|
167
|
+
function descriptionResult(value) {
|
|
168
|
+
return {
|
|
169
|
+
protocols: descriptionStrings(value.protocols),
|
|
170
|
+
requiredCapabilities: descriptionStrings(value.requiredCapabilities),
|
|
171
|
+
optionalCapabilities: descriptionStrings(value.optionalCapabilities),
|
|
172
|
+
extension: describedIdentity(value.extension),
|
|
173
|
+
graders: descriptionStrings(value.graders),
|
|
174
|
+
taskVerdictPolicies: descriptionStrings(value.taskVerdictPolicies),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Discover an extension and select the v1 protocol and available capabilities. */
|
|
178
|
+
export async function negotiateExtension(command, available, options = {}) {
|
|
179
|
+
const response = await exchangeExtension(command, {
|
|
180
|
+
protocol: "sevro.discovery.v1",
|
|
181
|
+
id: randomUUID(),
|
|
182
|
+
method: "describe",
|
|
183
|
+
params: {
|
|
184
|
+
protocols: [PROTOCOL],
|
|
185
|
+
engineCapabilities: available.engineCapabilities,
|
|
186
|
+
hostCapabilities: available.hostCapabilities,
|
|
187
|
+
},
|
|
188
|
+
}, options);
|
|
189
|
+
const result = descriptionResult(response.result);
|
|
190
|
+
const offered = result.protocols;
|
|
191
|
+
if (!offered.includes(PROTOCOL))
|
|
192
|
+
throw new ExtensionProtocolError("extension has no compatible protocol");
|
|
193
|
+
const supported = new Set([
|
|
194
|
+
...available.engineCapabilities,
|
|
195
|
+
...available.hostCapabilities,
|
|
196
|
+
]);
|
|
197
|
+
const required = result.requiredCapabilities;
|
|
198
|
+
for (const capability of required)
|
|
199
|
+
if (!supported.has(capability))
|
|
200
|
+
throw new ExtensionProtocolError(`unsupported required capability: ${capability}`);
|
|
201
|
+
const optional = result.optionalCapabilities.filter((item) => supported.has(item));
|
|
202
|
+
const extension = result.extension;
|
|
203
|
+
return {
|
|
204
|
+
id: extension.id,
|
|
205
|
+
version: extension.version,
|
|
206
|
+
protocol: PROTOCOL,
|
|
207
|
+
capabilities: [...new Set([...required, ...optional])],
|
|
208
|
+
graders: result.graders,
|
|
209
|
+
taskVerdictPolicies: result.taskVerdictPolicies,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=extension-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-client.js","sourceRoot":"","sources":["extension-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type Subprocess } from \"./runtime-process\";\nimport { randomUUID } from \"node:crypto\";\nimport validExchange from \"./generated/extension.cjs\";\nimport { isRecord, isStringArray } from \"./value-guards\";\nimport {\n captureBoundedBytes,\n ProcessDeadline,\n terminateOwnedProcessGroup,\n} from \"./process-lifecycle\";\n\nconst MAX_MESSAGE_BYTES = 8 * 1024 * 1024;\nconst MAX_DIAGNOSTIC_BYTES = 64 * 1024;\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst PROTOCOL = \"sevro.extension.v1\";\n\nexport class ExtensionProtocolError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ExtensionProtocolError\";\n }\n}\n\nexport interface ExtensionRequest {\n protocol: string;\n id: string;\n method: \"describe\" | \"resolve\" | \"prepare\" | \"evaluate\";\n params: Record<string, unknown>;\n}\n\nexport interface ExtensionResponse {\n protocol: string;\n id: string;\n method: ExtensionRequest[\"method\"];\n result?: Record<string, unknown>;\n error?: { code: string; message: string };\n}\n\nexport interface ExchangeOptions {\n cwd?: string;\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\ninterface SuccessfulExtensionResponse extends ExtensionResponse {\n result: Record<string, unknown>;\n error?: never;\n}\n\nfunction validateExtensionCommand(command: string[]): void {\n if (!command.length || command.some((part) => !part))\n throw new ExtensionProtocolError(\n \"extension command must be a nonempty argv array\",\n );\n}\n\nfunction extensionTimeout(value: number | undefined): number {\n const timeout = value ?? DEFAULT_TIMEOUT_MS;\n if (!Number.isSafeInteger(timeout) || timeout <= 0)\n throw new ExtensionProtocolError(\n \"extension timeout must be a positive integer\",\n );\n return timeout;\n}\n\nfunction extensionPayload(request: ExtensionRequest): Uint8Array {\n const payload = new TextEncoder().encode(JSON.stringify(request));\n if (payload.byteLength > MAX_MESSAGE_BYTES)\n throw new ExtensionProtocolError(\"extension request exceeds 8 MiB\");\n return payload;\n}\n\nfunction spawnExtension(\n command: string[],\n options: ExchangeOptions,\n): Subprocess {\n try {\n return spawn(command, {\n cwd: options.cwd,\n stdin: \"pipe\",\n stdout: \"pipe\",\n stderr: \"pipe\",\n detached: process.platform !== \"win32\",\n });\n } catch (cause) {\n throw new ExtensionProtocolError(\"could not start extension process\", {\n cause,\n });\n }\n}\n\nfunction extensionWriter(proc: Subprocess) {\n const stdin = proc.stdin;\n if (!stdin || typeof stdin === \"number\")\n throw new ExtensionProtocolError(\"extension pipes are unavailable\");\n return stdin;\n}\n\nfunction extensionReaders(proc: Subprocess) {\n const { stdout, stderr } = proc;\n return { stdout, stderr };\n}\n\nasync function writeExtensionRequest(\n stdin: ReturnType<typeof extensionWriter>,\n payload: Uint8Array,\n): Promise<void> {\n await stdin.write(payload);\n await stdin.end();\n}\n\nfunction hasResponseBody(value: Record<string, unknown>): boolean {\n return Object.hasOwn(value, \"result\") || Object.hasOwn(value, \"error\");\n}\n\nfunction assertExtensionResponse(\n value: unknown,\n): asserts value is ExtensionResponse {\n if (!validExchange(value) || !isRecord(value) || !hasResponseBody(value))\n throw new ExtensionProtocolError(\"invalid extension response\");\n}\n\nfunction parseExtensionResponse(bytes: Uint8Array): ExtensionResponse {\n let response: unknown;\n try {\n response = JSON.parse(\n new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes),\n );\n } catch (cause) {\n throw new ExtensionProtocolError(\n \"extension did not return one UTF-8 JSON response\",\n { cause },\n );\n }\n assertExtensionResponse(response);\n return response;\n}\n\nfunction validateResponseIdentity(\n response: ExtensionResponse,\n request: ExtensionRequest,\n): void {\n if (\n response.id !== request.id ||\n response.protocol !== request.protocol ||\n response.method !== request.method\n )\n throw new ExtensionProtocolError(\n \"extension response does not match request\",\n );\n}\n\nfunction assertSuccessfulResponse(\n response: ExtensionResponse,\n): asserts response is SuccessfulExtensionResponse {\n if (response.error)\n throw new ExtensionProtocolError(\n `extension reported ${response.error.code}`,\n );\n if (response.result === undefined)\n throw new ExtensionProtocolError(\"invalid extension response\");\n}\n\nclass ExtensionExchange {\n private readonly deadline: ProcessDeadline;\n constructor(\n private readonly proc: Subprocess,\n options: ExchangeOptions,\n timeoutMs: number,\n ) {\n this.deadline = new ProcessDeadline({\n timeoutMs,\n signal: options.signal,\n interruption: (reason) =>\n new ExtensionProtocolError(\n reason === \"timeout\"\n ? \"extension timed out\"\n : \"extension request cancelled\",\n ),\n });\n }\n\n private async output(\n request: ExtensionRequest,\n payload: Uint8Array,\n ): Promise<SuccessfulExtensionResponse> {\n const stdin = extensionWriter(this.proc);\n const { stdout, stderr } = extensionReaders(this.proc);\n const write = writeExtensionRequest(stdin, payload);\n const [{ bytes }, , exitCode] = await Promise.all([\n captureBoundedBytes(stdout, {\n limit: MAX_MESSAGE_BYTES,\n overflow: \"reject\",\n exceeded: () =>\n new ExtensionProtocolError(\"extension message exceeds 8 MiB\"),\n }),\n captureBoundedBytes(stderr, {\n limit: MAX_DIAGNOSTIC_BYTES,\n overflow: \"truncate\",\n }),\n this.proc.exited,\n write,\n ]);\n if (exitCode !== 0)\n throw new ExtensionProtocolError(\n `extension exited with code ${exitCode}`,\n );\n const response = parseExtensionResponse(bytes);\n validateResponseIdentity(response, request);\n assertSuccessfulResponse(response);\n return response;\n }\n\n async run(\n request: ExtensionRequest,\n payload: Uint8Array,\n ): Promise<SuccessfulExtensionResponse> {\n try {\n return await this.deadline.wait(this.output(request, payload));\n } catch (cause) {\n terminateOwnedProcessGroup(this.proc);\n if (cause instanceof ExtensionProtocolError) throw cause;\n throw new ExtensionProtocolError(\"extension transport failed\", { cause });\n } finally {\n this.deadline.dispose();\n }\n }\n}\n\n/** Run exactly one request in a fresh extension process. */\nexport async function exchangeExtension(\n command: string[],\n request: ExtensionRequest,\n options: ExchangeOptions = {},\n): Promise<SuccessfulExtensionResponse> {\n validateExtensionCommand(command);\n if (!validExchange(request))\n throw new ExtensionProtocolError(\"invalid extension request\");\n if (options.signal?.aborted)\n throw new ExtensionProtocolError(\"extension request cancelled\");\n const timeout = extensionTimeout(options.timeoutMs);\n const payload = extensionPayload(request);\n return new ExtensionExchange(\n spawnExtension(command, options),\n options,\n timeout,\n ).run(request, payload);\n}\n\nexport interface NegotiatedExtension {\n id: string;\n version: string;\n protocol: string;\n capabilities: string[];\n graders: string[];\n taskVerdictPolicies: string[];\n}\n\nfunction descriptionStrings(value: unknown): string[] {\n if (!isStringArray(value))\n throw new ExtensionProtocolError(\"invalid extension response\");\n return value;\n}\n\nfunction describedIdentity(value: unknown): { id: string; version: string } {\n if (\n !isRecord(value) ||\n typeof value.id !== \"string\" ||\n typeof value.version !== \"string\"\n )\n throw new ExtensionProtocolError(\"invalid extension response\");\n return { id: value.id, version: value.version };\n}\n\nfunction descriptionResult(value: Record<string, unknown>) {\n return {\n protocols: descriptionStrings(value.protocols),\n requiredCapabilities: descriptionStrings(value.requiredCapabilities),\n optionalCapabilities: descriptionStrings(value.optionalCapabilities),\n extension: describedIdentity(value.extension),\n graders: descriptionStrings(value.graders),\n taskVerdictPolicies: descriptionStrings(value.taskVerdictPolicies),\n };\n}\n\n/** Discover an extension and select the v1 protocol and available capabilities. */\nexport async function negotiateExtension(\n command: string[],\n available: { engineCapabilities: string[]; hostCapabilities: string[] },\n options: ExchangeOptions = {},\n): Promise<NegotiatedExtension> {\n const response = await exchangeExtension(\n command,\n {\n protocol: \"sevro.discovery.v1\",\n id: randomUUID(),\n method: \"describe\",\n params: {\n protocols: [PROTOCOL],\n engineCapabilities: available.engineCapabilities,\n hostCapabilities: available.hostCapabilities,\n },\n },\n options,\n );\n const result = descriptionResult(response.result);\n const offered = result.protocols;\n if (!offered.includes(PROTOCOL))\n throw new ExtensionProtocolError(\"extension has no compatible protocol\");\n const supported = new Set([\n ...available.engineCapabilities,\n ...available.hostCapabilities,\n ]);\n const required = result.requiredCapabilities;\n for (const capability of required)\n if (!supported.has(capability))\n throw new ExtensionProtocolError(\n `unsupported required capability: ${capability}`,\n );\n const optional = result.optionalCapabilities.filter((item) =>\n supported.has(item),\n );\n const extension = result.extension;\n return {\n id: extension.id,\n version: extension.version,\n protocol: PROTOCOL,\n capabilities: [...new Set([...required, ...optional])],\n graders: result.graders,\n taskVerdictPolicies: result.taskVerdictPolicies,\n };\n}\n"]}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import validExchange from "./generated/extension.cjs";
|
|
2
|
+
import { isRecord } from "./value-guards.js";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { createReadStream } from "node:fs";
|
|
5
|
+
import { isAbsolute } from "node:path";
|
|
6
|
+
import { canonicalJson, hashJson } from "./identity.js";
|
|
7
|
+
import { ExtensionProtocolError, exchangeExtension, negotiateExtension, } from "./extension-client.js";
|
|
8
|
+
const PROTOCOL = "sevro.extension.v1";
|
|
9
|
+
export class ExtensionSourceChangedError extends ExtensionProtocolError {
|
|
10
|
+
constructor() {
|
|
11
|
+
super("extension source changed during run");
|
|
12
|
+
this.name = "ExtensionSourceChangedError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function digestSource(path) {
|
|
16
|
+
try {
|
|
17
|
+
const hash = createHash("sha256");
|
|
18
|
+
for await (const value of createReadStream(path)) {
|
|
19
|
+
const chunk = value;
|
|
20
|
+
if (!(chunk instanceof Uint8Array))
|
|
21
|
+
throw new Error("invalid extension source stream");
|
|
22
|
+
hash.update(chunk);
|
|
23
|
+
}
|
|
24
|
+
return hash.digest("hex");
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
throw new ExtensionProtocolError("declared extension source is unreadable", { cause });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function digestSources(paths) {
|
|
31
|
+
const hashes = [];
|
|
32
|
+
for (const path of paths)
|
|
33
|
+
hashes.push(await digestSource(path));
|
|
34
|
+
return hashJson(hashes);
|
|
35
|
+
}
|
|
36
|
+
function jsonCopy(value) {
|
|
37
|
+
return JSON.parse(canonicalJson(value));
|
|
38
|
+
}
|
|
39
|
+
function absoluteSessionCommand(command) {
|
|
40
|
+
const executable = command[0];
|
|
41
|
+
return executable !== undefined && isAbsolute(executable);
|
|
42
|
+
}
|
|
43
|
+
function uniqueAbsoluteSources(sources) {
|
|
44
|
+
return (sources.length > 0 &&
|
|
45
|
+
sources.every((path) => isAbsolute(path)) &&
|
|
46
|
+
new Set(sources).size === sources.length);
|
|
47
|
+
}
|
|
48
|
+
function sessionSourceClosure(command, sources) {
|
|
49
|
+
if (!absoluteSessionCommand(command) || !uniqueAbsoluteSources(sources))
|
|
50
|
+
throw new ExtensionProtocolError("extension source files must be unique absolute paths");
|
|
51
|
+
return [...new Set([command[0], ...sources])];
|
|
52
|
+
}
|
|
53
|
+
function validateGraderReplacements(graders) {
|
|
54
|
+
if (new Set(graders).size !== graders.length)
|
|
55
|
+
throw new ExtensionProtocolError("duplicate built-in grader replacement");
|
|
56
|
+
}
|
|
57
|
+
function sessionConfiguration(options) {
|
|
58
|
+
const command = [...options.command];
|
|
59
|
+
const sourceFiles = [...options.sourceFiles];
|
|
60
|
+
const taskVerdictPolicy = options.taskVerdictPolicy;
|
|
61
|
+
const replacedBuiltinGraders = [
|
|
62
|
+
...(options.replaceBuiltinGraders ?? []),
|
|
63
|
+
].sort();
|
|
64
|
+
const exchangeOptions = {
|
|
65
|
+
cwd: options.cwd,
|
|
66
|
+
timeoutMs: options.timeoutMs,
|
|
67
|
+
signal: options.signal,
|
|
68
|
+
};
|
|
69
|
+
const sourceClosure = sessionSourceClosure(command, sourceFiles);
|
|
70
|
+
validateGraderReplacements(replacedBuiltinGraders);
|
|
71
|
+
const configuration = jsonCopy(options.configuration);
|
|
72
|
+
const redactedConfiguration = jsonCopy(options.redactedConfiguration);
|
|
73
|
+
return {
|
|
74
|
+
command,
|
|
75
|
+
sourceFiles,
|
|
76
|
+
taskVerdictPolicy,
|
|
77
|
+
replacedBuiltinGraders,
|
|
78
|
+
exchangeOptions,
|
|
79
|
+
sourceClosure,
|
|
80
|
+
configuration,
|
|
81
|
+
redactedConfiguration,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function sessionIdentity(context, negotiated, sourceDigest) {
|
|
85
|
+
const identity = {
|
|
86
|
+
...negotiated,
|
|
87
|
+
sourceDigest,
|
|
88
|
+
configurationDigest: hashJson({
|
|
89
|
+
command: context.command,
|
|
90
|
+
configuration: context.redactedConfiguration,
|
|
91
|
+
taskVerdictPolicy: context.taskVerdictPolicy ?? null,
|
|
92
|
+
replacedBuiltinGraders: context.replacedBuiltinGraders,
|
|
93
|
+
}),
|
|
94
|
+
selectedTaskVerdictPolicy: context.taskVerdictPolicy ?? null,
|
|
95
|
+
replacedBuiltinGraders: context.replacedBuiltinGraders,
|
|
96
|
+
};
|
|
97
|
+
Object.freeze(identity.capabilities);
|
|
98
|
+
Object.freeze(identity.graders);
|
|
99
|
+
Object.freeze(identity.taskVerdictPolicies);
|
|
100
|
+
Object.freeze(identity.replacedBuiltinGraders);
|
|
101
|
+
Object.freeze(identity);
|
|
102
|
+
return identity;
|
|
103
|
+
}
|
|
104
|
+
async function discoverSession(configuration, options) {
|
|
105
|
+
const sourceDigest = await digestSources(configuration.sourceClosure);
|
|
106
|
+
const negotiated = await negotiateExtension(configuration.command, {
|
|
107
|
+
engineCapabilities: options.engineCapabilities,
|
|
108
|
+
hostCapabilities: options.hostCapabilities,
|
|
109
|
+
}, configuration.exchangeOptions);
|
|
110
|
+
if ((await digestSources(configuration.sourceClosure)) !== sourceDigest)
|
|
111
|
+
throw new ExtensionProtocolError("extension source changed during discovery");
|
|
112
|
+
if (configuration.taskVerdictPolicy &&
|
|
113
|
+
!negotiated.taskVerdictPolicies.includes(configuration.taskVerdictPolicy))
|
|
114
|
+
throw new ExtensionProtocolError("extension did not advertise the selected task policy");
|
|
115
|
+
return {
|
|
116
|
+
...configuration,
|
|
117
|
+
sourceDigest,
|
|
118
|
+
identity: sessionIdentity(configuration, negotiated, sourceDigest),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function sessionResponse(value, method) {
|
|
122
|
+
return (validExchange(value) &&
|
|
123
|
+
isRecord(value) &&
|
|
124
|
+
value.method === method &&
|
|
125
|
+
Object.hasOwn(value, "result"));
|
|
126
|
+
}
|
|
127
|
+
async function verifySessionSources(context) {
|
|
128
|
+
if ((await digestSources(context.sourceClosure)) !== context.sourceDigest)
|
|
129
|
+
throw new ExtensionSourceChangedError();
|
|
130
|
+
}
|
|
131
|
+
async function callSession(context, method, params) {
|
|
132
|
+
await verifySessionSources(context);
|
|
133
|
+
const response = await exchangeExtension(context.command, {
|
|
134
|
+
protocol: PROTOCOL,
|
|
135
|
+
id: randomUUID(),
|
|
136
|
+
method,
|
|
137
|
+
params: { ...params, configuration: context.configuration },
|
|
138
|
+
}, context.exchangeOptions);
|
|
139
|
+
await verifySessionSources(context);
|
|
140
|
+
if (!sessionResponse(response, method))
|
|
141
|
+
throw new ExtensionProtocolError("invalid extension response");
|
|
142
|
+
return response.result;
|
|
143
|
+
}
|
|
144
|
+
async function resolveSession(context, projectRoot, selectors, host) {
|
|
145
|
+
const result = await callSession(context, "resolve", {
|
|
146
|
+
projectRoot,
|
|
147
|
+
selectors,
|
|
148
|
+
...(host ? { host } : {}),
|
|
149
|
+
});
|
|
150
|
+
const ids = result.cases.map((item) => item.id);
|
|
151
|
+
if (new Set(ids).size !== ids.length)
|
|
152
|
+
throw new ExtensionProtocolError("extension resolved duplicate case IDs");
|
|
153
|
+
return result.cases;
|
|
154
|
+
}
|
|
155
|
+
async function prepareSession(context, resolvedCase, host, condition) {
|
|
156
|
+
return callSession(context, "prepare", {
|
|
157
|
+
case: resolvedCase,
|
|
158
|
+
host,
|
|
159
|
+
condition,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function validateEvaluationIds(result, outcomes, extensionId) {
|
|
163
|
+
const ids = [
|
|
164
|
+
...result.checks.map((check) => check.id),
|
|
165
|
+
...outcomes.map((outcome) => outcome.id),
|
|
166
|
+
];
|
|
167
|
+
if (new Set(ids).size !== ids.length ||
|
|
168
|
+
ids.some((id) => !id.startsWith(`${extensionId}.`)))
|
|
169
|
+
throw new ExtensionProtocolError("extension returned duplicate or foreign result IDs");
|
|
170
|
+
}
|
|
171
|
+
function validateSelectedTaskPolicy(result, policy) {
|
|
172
|
+
if (result.taskVerdictRecommendation && !policy)
|
|
173
|
+
throw new ExtensionProtocolError("extension task policy was not selected");
|
|
174
|
+
if (policy && !result.taskVerdictRecommendation)
|
|
175
|
+
throw new ExtensionProtocolError("extension omitted the selected task policy recommendation");
|
|
176
|
+
}
|
|
177
|
+
function availableEvidence(request) {
|
|
178
|
+
const available = new Map(request.observations.map((item) => [item.id, item.completeness]));
|
|
179
|
+
for (const item of request.artifacts)
|
|
180
|
+
available.set(item.id, "complete");
|
|
181
|
+
for (const item of request.builtinChecks)
|
|
182
|
+
available.set(item.id, item.status === "unavailable" ? "unavailable" : "complete");
|
|
183
|
+
return available;
|
|
184
|
+
}
|
|
185
|
+
function passedWithoutEvidence(item) {
|
|
186
|
+
return item.status === "passed" && item.evidenceRefs.length === 0;
|
|
187
|
+
}
|
|
188
|
+
function passedWithIncompleteEvidence(item, available) {
|
|
189
|
+
return (item.status === "passed" &&
|
|
190
|
+
item.evidenceRefs.some((id) => available.get(id) !== "complete"));
|
|
191
|
+
}
|
|
192
|
+
function validateResultEvidence(item, available) {
|
|
193
|
+
if (passedWithoutEvidence(item))
|
|
194
|
+
throw new ExtensionProtocolError("extension passed a result without evidence");
|
|
195
|
+
if (item.evidenceRefs.some((id) => !available.has(id)))
|
|
196
|
+
throw new ExtensionProtocolError("extension result cites unknown evidence");
|
|
197
|
+
if (passedWithIncompleteEvidence(item, available))
|
|
198
|
+
throw new ExtensionProtocolError("extension passed a result with incomplete evidence");
|
|
199
|
+
}
|
|
200
|
+
async function evaluateSession(context, request) {
|
|
201
|
+
const result = await callSession(context, "evaluate", {
|
|
202
|
+
...request,
|
|
203
|
+
...(context.taskVerdictPolicy
|
|
204
|
+
? { selectedTaskVerdictPolicy: context.taskVerdictPolicy }
|
|
205
|
+
: {}),
|
|
206
|
+
});
|
|
207
|
+
const outcomes = result.domainOutcomes ?? [];
|
|
208
|
+
validateEvaluationIds(result, outcomes, context.identity.id);
|
|
209
|
+
validateSelectedTaskPolicy(result, context.taskVerdictPolicy);
|
|
210
|
+
const available = availableEvidence(request);
|
|
211
|
+
for (const item of [...result.checks, ...outcomes])
|
|
212
|
+
validateResultEvidence(item, available);
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
function extensionSession(context) {
|
|
216
|
+
return {
|
|
217
|
+
identity: context.identity,
|
|
218
|
+
get redactedConfiguration() {
|
|
219
|
+
return jsonCopy(context.redactedConfiguration);
|
|
220
|
+
},
|
|
221
|
+
resolve(projectRoot, selectors, host) {
|
|
222
|
+
return resolveSession(context, projectRoot, selectors, host);
|
|
223
|
+
},
|
|
224
|
+
prepare(resolvedCase, host, condition) {
|
|
225
|
+
return prepareSession(context, resolvedCase, host, condition);
|
|
226
|
+
},
|
|
227
|
+
evaluate(request) {
|
|
228
|
+
return evaluateSession(context, request);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** Bind a stateless extension command to a negotiated identity and source snapshot. */
|
|
233
|
+
export async function openExtensionSession(options) {
|
|
234
|
+
return extensionSession(await discoverSession(sessionConfiguration(options), options));
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=extension-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-session.js","sourceRoot":"","sources":["extension-session.ts"],"names":[],"mappings":"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","sourcesContent":["import validExchange from \"./generated/extension.cjs\";\nimport { isRecord } from \"./value-guards\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\nimport { isAbsolute } from \"node:path\";\nimport { canonicalJson, hashJson } from \"./identity\";\nimport type { GeneratedFixture } from \"./fixtures/generated\";\nimport type { RepositoryFixture } from \"./fixtures/repository\";\nimport type { FixtureSetupDeclaration } from \"./fixtures/setup\";\nimport type {\n ArtifactReference,\n Assessment,\n CheckDeclaration,\n DomainOutcome,\n ExtensionCheckOutcome,\n MetricEvidence,\n ObservationEvidence,\n TaskVerdict,\n} from \"./contract-types\";\nimport type { InstrumentationRequest } from \"./instrumentation\";\nimport {\n ExtensionProtocolError,\n exchangeExtension,\n negotiateExtension,\n type ExchangeOptions,\n type NegotiatedExtension,\n} from \"./extension-client\";\n\nconst PROTOCOL = \"sevro.extension.v1\";\n\nexport class ExtensionSourceChangedError extends ExtensionProtocolError {\n constructor() {\n super(\"extension source changed during run\");\n this.name = \"ExtensionSourceChangedError\";\n }\n}\n\nexport interface ExtensionCase {\n id: string;\n prompt: string;\n followUpPrompt?: string;\n fixture:\n | { kind: \"inline\"; files: Record<string, string> }\n | RepositoryFixture\n | GeneratedFixture;\n checks: CheckDeclaration[];\n requiredEvidence: string[];\n extensionData: Record<string, unknown>;\n}\n\nexport interface PreparationResult {\n artifacts: {\n id: string;\n relativePath: string;\n sha256: string;\n contentBase64?: string;\n sourceRef?: string;\n gitExclude?: boolean;\n executable?: boolean;\n }[];\n requestedInstrumentation: InstrumentationRequest[];\n fixtureSetup?: FixtureSetupDeclaration;\n codexMarketplace?: {\n artifactRoot: string;\n marketplaceName: string;\n pluginNames: string[];\n };\n claudePluginDirs?: { artifactRoots: string[] };\n codexSkillInvocation?: {\n pluginName: string;\n skillName: string;\n };\n codexRepositorySkillInvocation?: { skillName: string };\n claudeRepositorySkillInvocation?: { skillName: string };\n claudeSkillInvocation?: {\n pluginName: string;\n skillName: string;\n };\n extensionData: Record<string, unknown>;\n}\n\nexport interface EvaluationRequest {\n caseId: string;\n execution: Assessment[\"execution\"] & {\n errorCode?: string;\n };\n observations: ObservationEvidence[];\n builtinChecks: ExtensionCheckOutcome[];\n artifacts: ArtifactReference[];\n extensionData: Record<string, unknown>;\n}\n\nexport interface EvaluationResult {\n checks: ExtensionCheckOutcome[];\n metrics: MetricEvidence[];\n domainOutcomes?: DomainOutcome[];\n taskVerdictRecommendation?: TaskVerdict;\n}\n\nexport interface ExtensionSessionOptions extends ExchangeOptions {\n command: string[];\n sourceFiles: string[];\n configuration: Record<string, unknown>;\n redactedConfiguration: Record<string, unknown>;\n engineCapabilities: string[];\n hostCapabilities: string[];\n taskVerdictPolicy?: string;\n replaceBuiltinGraders?: string[];\n}\n\nexport interface ExtensionIdentity extends NegotiatedExtension {\n sourceDigest: string;\n configurationDigest: string;\n selectedTaskVerdictPolicy: string | null;\n replacedBuiltinGraders: string[];\n}\n\nasync function digestSource(path: string): Promise<string> {\n try {\n const hash = createHash(\"sha256\");\n for await (const value of createReadStream(path)) {\n const chunk: unknown = value;\n if (!(chunk instanceof Uint8Array))\n throw new Error(\"invalid extension source stream\");\n hash.update(chunk);\n }\n return hash.digest(\"hex\");\n } catch (cause) {\n throw new ExtensionProtocolError(\n \"declared extension source is unreadable\",\n { cause },\n );\n }\n}\n\nasync function digestSources(paths: string[]): Promise<string> {\n const hashes: string[] = [];\n for (const path of paths) hashes.push(await digestSource(path));\n return hashJson(hashes);\n}\n\nfunction jsonCopy<T>(value: T): T {\n return JSON.parse(canonicalJson(value)) as T;\n}\n\nfunction absoluteSessionCommand(\n command: string[],\n): command is [string, ...string[]] {\n const executable = command[0];\n return executable !== undefined && isAbsolute(executable);\n}\n\nfunction uniqueAbsoluteSources(sources: string[]): boolean {\n return (\n sources.length > 0 &&\n sources.every((path) => isAbsolute(path)) &&\n new Set(sources).size === sources.length\n );\n}\n\nfunction sessionSourceClosure(command: string[], sources: string[]): string[] {\n if (!absoluteSessionCommand(command) || !uniqueAbsoluteSources(sources))\n throw new ExtensionProtocolError(\n \"extension source files must be unique absolute paths\",\n );\n return [...new Set([command[0], ...sources])];\n}\n\nfunction validateGraderReplacements(graders: string[]): void {\n if (new Set(graders).size !== graders.length)\n throw new ExtensionProtocolError(\"duplicate built-in grader replacement\");\n}\n\nfunction sessionConfiguration(options: ExtensionSessionOptions) {\n const command = [...options.command];\n const sourceFiles = [...options.sourceFiles];\n const taskVerdictPolicy = options.taskVerdictPolicy;\n const replacedBuiltinGraders = [\n ...(options.replaceBuiltinGraders ?? []),\n ].sort();\n const exchangeOptions: ExchangeOptions = {\n cwd: options.cwd,\n timeoutMs: options.timeoutMs,\n signal: options.signal,\n };\n const sourceClosure = sessionSourceClosure(command, sourceFiles);\n validateGraderReplacements(replacedBuiltinGraders);\n const configuration = jsonCopy(options.configuration);\n const redactedConfiguration = jsonCopy(options.redactedConfiguration);\n return {\n command,\n sourceFiles,\n taskVerdictPolicy,\n replacedBuiltinGraders,\n exchangeOptions,\n sourceClosure,\n configuration,\n redactedConfiguration,\n };\n}\n\ntype SessionConfiguration = ReturnType<typeof sessionConfiguration>;\n\nfunction sessionIdentity(\n context: SessionConfiguration,\n negotiated: NegotiatedExtension,\n sourceDigest: string,\n): ExtensionIdentity {\n const identity: ExtensionIdentity = {\n ...negotiated,\n sourceDigest,\n configurationDigest: hashJson({\n command: context.command,\n configuration: context.redactedConfiguration,\n taskVerdictPolicy: context.taskVerdictPolicy ?? null,\n replacedBuiltinGraders: context.replacedBuiltinGraders,\n }),\n selectedTaskVerdictPolicy: context.taskVerdictPolicy ?? null,\n replacedBuiltinGraders: context.replacedBuiltinGraders,\n };\n Object.freeze(identity.capabilities);\n Object.freeze(identity.graders);\n Object.freeze(identity.taskVerdictPolicies);\n Object.freeze(identity.replacedBuiltinGraders);\n Object.freeze(identity);\n return identity;\n}\n\nasync function discoverSession(\n configuration: SessionConfiguration,\n options: ExtensionSessionOptions,\n) {\n const sourceDigest = await digestSources(configuration.sourceClosure);\n const negotiated = await negotiateExtension(\n configuration.command,\n {\n engineCapabilities: options.engineCapabilities,\n hostCapabilities: options.hostCapabilities,\n },\n configuration.exchangeOptions,\n );\n if ((await digestSources(configuration.sourceClosure)) !== sourceDigest)\n throw new ExtensionProtocolError(\n \"extension source changed during discovery\",\n );\n if (\n configuration.taskVerdictPolicy &&\n !negotiated.taskVerdictPolicies.includes(configuration.taskVerdictPolicy)\n )\n throw new ExtensionProtocolError(\n \"extension did not advertise the selected task policy\",\n );\n return {\n ...configuration,\n sourceDigest,\n identity: sessionIdentity(configuration, negotiated, sourceDigest),\n };\n}\n\ntype SessionContext = Awaited<ReturnType<typeof discoverSession>>;\ninterface SessionResults {\n resolve: { cases: ExtensionCase[] };\n prepare: PreparationResult;\n evaluate: EvaluationResult;\n}\n\nfunction sessionResponse<M extends keyof SessionResults>(\n value: unknown,\n method: M,\n): value is { method: M; result: SessionResults[M] } {\n return (\n validExchange(value) &&\n isRecord(value) &&\n value.method === method &&\n Object.hasOwn(value, \"result\")\n );\n}\n\nasync function verifySessionSources(context: SessionContext): Promise<void> {\n if ((await digestSources(context.sourceClosure)) !== context.sourceDigest)\n throw new ExtensionSourceChangedError();\n}\n\nasync function callSession<M extends keyof SessionResults>(\n context: SessionContext,\n method: M,\n params: object,\n): Promise<SessionResults[M]> {\n await verifySessionSources(context);\n const response = await exchangeExtension(\n context.command,\n {\n protocol: PROTOCOL,\n id: randomUUID(),\n method,\n params: { ...params, configuration: context.configuration },\n },\n context.exchangeOptions,\n );\n await verifySessionSources(context);\n if (!sessionResponse(response, method))\n throw new ExtensionProtocolError(\"invalid extension response\");\n return response.result;\n}\n\ninterface ResolvedHostRoute {\n id: string;\n model: string;\n effort: string;\n capabilities: string[];\n}\n\nasync function resolveSession(\n context: SessionContext,\n projectRoot: string,\n selectors: Record<string, unknown>,\n host: ResolvedHostRoute | undefined,\n): Promise<ExtensionCase[]> {\n const result = await callSession(context, \"resolve\", {\n projectRoot,\n selectors,\n ...(host ? { host } : {}),\n });\n const ids = result.cases.map((item) => item.id);\n if (new Set(ids).size !== ids.length)\n throw new ExtensionProtocolError(\"extension resolved duplicate case IDs\");\n return result.cases;\n}\n\nasync function prepareSession(\n context: SessionContext,\n resolvedCase: ExtensionCase,\n host: { id: string; capabilities: string[] },\n condition: \"passive\" | \"enforced\",\n): Promise<PreparationResult> {\n return callSession(context, \"prepare\", {\n case: resolvedCase,\n host,\n condition,\n });\n}\n\nfunction validateEvaluationIds(\n result: EvaluationResult,\n outcomes: NonNullable<EvaluationResult[\"domainOutcomes\"]>,\n extensionId: string,\n): void {\n const ids = [\n ...result.checks.map((check) => check.id),\n ...outcomes.map((outcome) => outcome.id),\n ];\n if (\n new Set(ids).size !== ids.length ||\n ids.some((id) => !id.startsWith(`${extensionId}.`))\n )\n throw new ExtensionProtocolError(\n \"extension returned duplicate or foreign result IDs\",\n );\n}\n\nfunction validateSelectedTaskPolicy(\n result: EvaluationResult,\n policy: string | undefined,\n): void {\n if (result.taskVerdictRecommendation && !policy)\n throw new ExtensionProtocolError(\"extension task policy was not selected\");\n if (policy && !result.taskVerdictRecommendation)\n throw new ExtensionProtocolError(\n \"extension omitted the selected task policy recommendation\",\n );\n}\n\nfunction availableEvidence(request: EvaluationRequest): Map<string, string> {\n const available = new Map<string, string>(\n request.observations.map((item) => [item.id, item.completeness]),\n );\n for (const item of request.artifacts) available.set(item.id, \"complete\");\n for (const item of request.builtinChecks)\n available.set(\n item.id,\n item.status === \"unavailable\" ? \"unavailable\" : \"complete\",\n );\n return available;\n}\n\ntype EvidenceOutcome = EvaluationResult[\"checks\"][number];\nfunction passedWithoutEvidence(item: EvidenceOutcome): boolean {\n return item.status === \"passed\" && item.evidenceRefs.length === 0;\n}\nfunction passedWithIncompleteEvidence(\n item: EvidenceOutcome,\n available: Map<string, string>,\n): boolean {\n return (\n item.status === \"passed\" &&\n item.evidenceRefs.some((id) => available.get(id) !== \"complete\")\n );\n}\n\nfunction validateResultEvidence(\n item: EvidenceOutcome,\n available: Map<string, string>,\n): void {\n if (passedWithoutEvidence(item))\n throw new ExtensionProtocolError(\n \"extension passed a result without evidence\",\n );\n if (item.evidenceRefs.some((id) => !available.has(id)))\n throw new ExtensionProtocolError(\"extension result cites unknown evidence\");\n if (passedWithIncompleteEvidence(item, available))\n throw new ExtensionProtocolError(\n \"extension passed a result with incomplete evidence\",\n );\n}\n\nasync function evaluateSession(\n context: SessionContext,\n request: EvaluationRequest,\n): Promise<EvaluationResult> {\n const result = await callSession(context, \"evaluate\", {\n ...request,\n ...(context.taskVerdictPolicy\n ? { selectedTaskVerdictPolicy: context.taskVerdictPolicy }\n : {}),\n });\n const outcomes = result.domainOutcomes ?? [];\n validateEvaluationIds(result, outcomes, context.identity.id);\n validateSelectedTaskPolicy(result, context.taskVerdictPolicy);\n const available = availableEvidence(request);\n for (const item of [...result.checks, ...outcomes])\n validateResultEvidence(item, available);\n return result;\n}\n\nfunction extensionSession(context: SessionContext) {\n return {\n identity: context.identity,\n get redactedConfiguration() {\n return jsonCopy(context.redactedConfiguration);\n },\n resolve(\n projectRoot: string,\n selectors: Record<string, unknown>,\n host?: ResolvedHostRoute,\n ): Promise<ExtensionCase[]> {\n return resolveSession(context, projectRoot, selectors, host);\n },\n prepare(\n resolvedCase: ExtensionCase,\n host: { id: string; capabilities: string[] },\n condition: \"passive\" | \"enforced\",\n ): Promise<PreparationResult> {\n return prepareSession(context, resolvedCase, host, condition);\n },\n evaluate(request: EvaluationRequest): Promise<EvaluationResult> {\n return evaluateSession(context, request);\n },\n };\n}\n\n/** Bind a stateless extension command to a negotiated identity and source snapshot. */\nexport async function openExtensionSession(options: ExtensionSessionOptions) {\n return extensionSession(\n await discoverSession(sessionConfiguration(options), options),\n );\n}\n"]}
|