@bjoernrochel/sevro 1.0.0 → 1.1.1
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 +158 -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/claude-settings.ts +3 -1
- package/src/hosts/claude.ts +1 -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":"claude-login-store.js","sourceRoot":"","sources":["claude-login-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n readFile,\n realpath,\n rename,\n rm,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, userInfo } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const MAX_CREDENTIAL_BYTES = 1024 * 1024;\n\n/** The authoritative store that holds one saved Claude login. */\nexport interface ClaudeLoginStore {\n /** Credential-free identity of the store, used in errors and recovery. */\n origin: string;\n /** Claude configuration directory whose refresh lock guards this store. */\n lockRoot: string;\n read(): Promise<Buffer>;\n write(bytes: Buffer): Promise<void>;\n}\n\n/** Keychain operations; the macOS module supplies them only on Darwin. */\nexport interface ClaudeKeychain {\n read(service: string, account: string): Promise<Buffer | undefined>;\n write(service: string, account: string, bytes: Buffer): Promise<void>;\n}\n\n/** Resolve the store Claude Code itself uses for the selected login. */\nexport async function claudeLoginStore(\n sourceFile?: string,\n keychain?: ClaudeKeychain,\n): Promise<ClaudeLoginStore> {\n if (sourceFile) return fileLoginStore(sourceFile, dirname(sourceFile));\n const configRoot = claudeConfigRoot();\n const file = join(configRoot, \".credentials.json\");\n const saved = await keychainLoginStore(configRoot, keychain).catch(\n async (cause: unknown) => {\n // Claude falls back to its saved file when the Keychain cannot be read.\n if (await savedFileExists(file)) return undefined;\n throw cause;\n },\n );\n if (saved) return saved;\n await requireSavedFile(file);\n return fileLoginStore(file, configRoot);\n}\n\nasync function savedFileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction claudeConfigRoot(): string {\n return (\n process.env.CLAUDE_CONFIG_DIR || join(homedir(), \".claude\")\n ).normalize(\"NFC\");\n}\n\n/** Claude keys a non-default configuration's Keychain item by its path. */\nexport function claudeKeychainService(): string {\n const configured = process.env.CLAUDE_CONFIG_DIR;\n if (!configured) return \"Claude Code-credentials\";\n const digest = createHash(\"sha256\")\n .update(configured.normalize(\"NFC\"))\n .digest(\"hex\");\n return `Claude Code-credentials-${digest.substring(0, 8)}`;\n}\n\nexport function claudeKeychainAccount(): string {\n let account: string;\n try {\n account = process.env.USER || userInfo().username;\n } catch {\n return \"claude-code-user\";\n }\n return /^[a-zA-Z0-9._-]+$/.test(account) ? account : \"claude-code-user\";\n}\n\nasync function keychainLoginStore(\n configRoot: string,\n keychain: ClaudeKeychain | undefined,\n): Promise<ClaudeLoginStore | undefined> {\n if (!keychain) return undefined;\n const service = claudeKeychainService();\n const account = claudeKeychainAccount();\n if (!(await keychain.read(service, account))) return undefined;\n return {\n origin: `macOS Keychain item \"${service}\"`,\n lockRoot: configRoot,\n read: async () => requireStoredLogin(await keychain.read(service, account)),\n write: (bytes) => keychain.write(service, account, bytes),\n };\n}\n\nasync function requireSavedFile(path: string): Promise<void> {\n try {\n await stat(path);\n } catch (cause) {\n const code = (cause as NodeJS.ErrnoException).code;\n throw new Error(\n code === \"ENOENT\"\n ? \"Claude saved login is unavailable; run claude and /login\"\n : \"Claude saved credential is unreadable or unavailable\",\n { cause },\n );\n }\n}\n\nfunction requireStoredLogin(bytes: Buffer | undefined): Buffer {\n if (!bytes) throw new Error(\"Claude credential is unreadable or unavailable\");\n return bytes;\n}\n\nexport function requireBoundedCredential(credential: Buffer): Buffer {\n if (\n credential.byteLength === 0 ||\n credential.byteLength > MAX_CREDENTIAL_BYTES ||\n !credential.toString(\"utf8\").trim()\n )\n throw new Error(\"Claude credential is empty or oversized\");\n return credential;\n}\n\nfunction fileLoginStore(path: string, lockRoot: string): ClaudeLoginStore {\n return {\n origin: path,\n lockRoot,\n read: () => readLoginFile(path),\n write: (bytes) => replaceLoginFile(path, bytes),\n };\n}\n\nasync function readLoginFile(path: string): Promise<Buffer> {\n try {\n if ((await stat(path)).size > MAX_CREDENTIAL_BYTES)\n throw new Error(\"oversized credential\");\n return await readFile(path);\n } catch (cause) {\n throw new Error(\"Claude credential is unreadable or unavailable\", {\n cause,\n });\n }\n}\n\n/** Replace atomically so concurrent Claude readers never see a partial login. */\nasync function replaceLoginFile(path: string, bytes: Buffer): Promise<void> {\n const target = await realpath(path);\n const temporary = join(dirname(target), `.sevro-login-${randomUUID()}.tmp`);\n try {\n await writeFile(temporary, bytes, { flag: \"wx\", mode: 0o600 });\n await rename(temporary, target);\n } finally {\n await rm(temporary, { force: true });\n }\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isRecord, isStringArray } from "../value-guards.js";
|
|
3
|
+
/** Fields replaced by a rotation; every other stored field is preserved. */
|
|
4
|
+
const ROTATED_FIELDS = [
|
|
5
|
+
"accessToken",
|
|
6
|
+
"refreshToken",
|
|
7
|
+
"expiresAt",
|
|
8
|
+
"scopes",
|
|
9
|
+
"refreshTokenExpiresAt",
|
|
10
|
+
];
|
|
11
|
+
/** Parse a stored login without echoing any of its bytes in errors. */
|
|
12
|
+
export function parseClaudeLogin(bytes) {
|
|
13
|
+
const document = loginDocument(bytes);
|
|
14
|
+
const oauth = document.claudeAiOauth;
|
|
15
|
+
if (!isRecord(oauth) || !nonblank(oauth.accessToken))
|
|
16
|
+
throw new Error("Claude credential has no claude.ai login");
|
|
17
|
+
return {
|
|
18
|
+
document,
|
|
19
|
+
oauth,
|
|
20
|
+
accessToken: oauth.accessToken,
|
|
21
|
+
refreshToken: nonblank(oauth.refreshToken) ? oauth.refreshToken : undefined,
|
|
22
|
+
expiresAt: loginExpiry(oauth.expiresAt),
|
|
23
|
+
scopes: isStringArray(oauth.scopes) ? oauth.scopes : [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function loginDocument(bytes) {
|
|
27
|
+
let value;
|
|
28
|
+
try {
|
|
29
|
+
value = JSON.parse(bytes.toString("utf8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Parser messages quote input, so the cause is deliberately not retained.
|
|
33
|
+
throw new Error("Claude credential is not a JSON login document");
|
|
34
|
+
}
|
|
35
|
+
if (!isRecord(value))
|
|
36
|
+
throw new Error("Claude credential is not a JSON login document");
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function nonblank(value) {
|
|
40
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
41
|
+
}
|
|
42
|
+
function loginExpiry(value) {
|
|
43
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
44
|
+
? value
|
|
45
|
+
: undefined;
|
|
46
|
+
}
|
|
47
|
+
/** A login without a recorded expiry is treated as not expiring. */
|
|
48
|
+
export function coversLifetime(login, lifetimeMs, now = Date.now()) {
|
|
49
|
+
return login.expiresAt === undefined || login.expiresAt - now >= lifetimeMs;
|
|
50
|
+
}
|
|
51
|
+
/** Apply a rotated token pair to the latest stored document. */
|
|
52
|
+
export function rotatedLoginBytes(latest, rotated) {
|
|
53
|
+
const oauth = { ...latest.oauth };
|
|
54
|
+
for (const field of ROTATED_FIELDS)
|
|
55
|
+
if (Object.hasOwn(rotated.oauth, field))
|
|
56
|
+
oauth[field] = rotated.oauth[field];
|
|
57
|
+
return Buffer.from(JSON.stringify({ ...latest.document, claudeAiOauth: oauth }));
|
|
58
|
+
}
|
|
59
|
+
/** Rotated token fields, as retained for recovery. */
|
|
60
|
+
export function rotatedOAuth(login) {
|
|
61
|
+
return Object.fromEntries(ROTATED_FIELDS.filter((field) => Object.hasOwn(login.oauth, field)).map((field) => [field, login.oauth[field]]));
|
|
62
|
+
}
|
|
63
|
+
export function sameTokens(left, right) {
|
|
64
|
+
return (left.accessToken === right.accessToken &&
|
|
65
|
+
left.refreshToken === right.refreshToken &&
|
|
66
|
+
left.expiresAt === right.expiresAt);
|
|
67
|
+
}
|
|
68
|
+
/** Identify a refresh token without retaining its value. */
|
|
69
|
+
export function tokenDigest(token) {
|
|
70
|
+
return createHash("sha256")
|
|
71
|
+
.update(token ?? "")
|
|
72
|
+
.digest("hex");
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=claude-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-login.js","sourceRoot":"","sources":["claude-login.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { isRecord, isStringArray } from \"../value-guards\";\n\n/** A claude.ai OAuth login in the document form Claude Code persists. */\nexport interface ClaudeLogin {\n document: Record<string, unknown>;\n oauth: Record<string, unknown>;\n accessToken: string;\n refreshToken: string | undefined;\n expiresAt: number | undefined;\n scopes: string[];\n}\n\n/** Fields replaced by a rotation; every other stored field is preserved. */\nconst ROTATED_FIELDS = [\n \"accessToken\",\n \"refreshToken\",\n \"expiresAt\",\n \"scopes\",\n \"refreshTokenExpiresAt\",\n] as const;\n\n/** Parse a stored login without echoing any of its bytes in errors. */\nexport function parseClaudeLogin(bytes: Buffer): ClaudeLogin {\n const document = loginDocument(bytes);\n const oauth = document.claudeAiOauth;\n if (!isRecord(oauth) || !nonblank(oauth.accessToken))\n throw new Error(\"Claude credential has no claude.ai login\");\n return {\n document,\n oauth,\n accessToken: oauth.accessToken,\n refreshToken: nonblank(oauth.refreshToken) ? oauth.refreshToken : undefined,\n expiresAt: loginExpiry(oauth.expiresAt),\n scopes: isStringArray(oauth.scopes) ? oauth.scopes : [],\n };\n}\n\nfunction loginDocument(bytes: Buffer): Record<string, unknown> {\n let value: unknown;\n try {\n value = JSON.parse(bytes.toString(\"utf8\"));\n } catch {\n // Parser messages quote input, so the cause is deliberately not retained.\n throw new Error(\"Claude credential is not a JSON login document\");\n }\n if (!isRecord(value))\n throw new Error(\"Claude credential is not a JSON login document\");\n return value;\n}\n\nfunction nonblank(value: unknown): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction loginExpiry(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value)\n ? value\n : undefined;\n}\n\n/** A login without a recorded expiry is treated as not expiring. */\nexport function coversLifetime(\n login: ClaudeLogin,\n lifetimeMs: number,\n now = Date.now(),\n): boolean {\n return login.expiresAt === undefined || login.expiresAt - now >= lifetimeMs;\n}\n\n/** Apply a rotated token pair to the latest stored document. */\nexport function rotatedLoginBytes(\n latest: ClaudeLogin,\n rotated: ClaudeLogin,\n): Buffer {\n const oauth = { ...latest.oauth };\n for (const field of ROTATED_FIELDS)\n if (Object.hasOwn(rotated.oauth, field))\n oauth[field] = rotated.oauth[field];\n return Buffer.from(\n JSON.stringify({ ...latest.document, claudeAiOauth: oauth }),\n );\n}\n\n/** Rotated token fields, as retained for recovery. */\nexport function rotatedOAuth(login: ClaudeLogin): Record<string, unknown> {\n return Object.fromEntries(\n ROTATED_FIELDS.filter((field) => Object.hasOwn(login.oauth, field)).map(\n (field) => [field, login.oauth[field]],\n ),\n );\n}\n\nexport function sameTokens(left: ClaudeLogin, right: ClaudeLogin): boolean {\n return (\n left.accessToken === right.accessToken &&\n left.refreshToken === right.refreshToken &&\n left.expiresAt === right.expiresAt\n );\n}\n\n/** Identify a refresh token without retaining its value. */\nexport function tokenDigest(token: string | undefined): string {\n return createHash(\"sha256\")\n .update(token ?? \"\")\n .digest(\"hex\");\n}\n"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { parseClaudeEvents } from "./claude-events.js";
|
|
2
|
+
import { lstat, readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { requireRecord } from "../value-guards.js";
|
|
5
|
+
function record(value) {
|
|
6
|
+
return requireRecord(value, "Malformed native session record");
|
|
7
|
+
}
|
|
8
|
+
function records(text) {
|
|
9
|
+
return parseClaudeEvents(text).lines.map((line) => {
|
|
10
|
+
if (!line.parsed)
|
|
11
|
+
throw line.error;
|
|
12
|
+
return record(line.value);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function goalAttachment(event) {
|
|
16
|
+
if (event.isSidechain === true || event.type !== "attachment")
|
|
17
|
+
return [];
|
|
18
|
+
const attachment = record(event.attachment);
|
|
19
|
+
if (attachment.type !== "goal_status")
|
|
20
|
+
return [];
|
|
21
|
+
const characters = goalCharacters(attachment);
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
status: attachmentStatus(attachment),
|
|
25
|
+
characters,
|
|
26
|
+
...(typeof event.timestamp === "string" ? { at: event.timestamp } : {}),
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function goalCharacters(attachment) {
|
|
31
|
+
const characters = typeof attachment.condition === "string"
|
|
32
|
+
? Array.from(attachment.condition).length
|
|
33
|
+
: 0;
|
|
34
|
+
if (!characters || characters > 4000 || typeof attachment.met !== "boolean")
|
|
35
|
+
throw new Error("Malformed native goal attachment");
|
|
36
|
+
return characters;
|
|
37
|
+
}
|
|
38
|
+
function attachmentStatus(attachment) {
|
|
39
|
+
if (attachment.failed === true)
|
|
40
|
+
return "blocked";
|
|
41
|
+
if (!attachment.met)
|
|
42
|
+
return "active";
|
|
43
|
+
return attachment.sentinel === true ? "cleared" : "complete";
|
|
44
|
+
}
|
|
45
|
+
function requireSuccessfulResult(stream, sessionId) {
|
|
46
|
+
const result = records(stream)
|
|
47
|
+
.filter((event) => event.type === "result")
|
|
48
|
+
.at(-1);
|
|
49
|
+
if (!result ||
|
|
50
|
+
result.session_id !== sessionId ||
|
|
51
|
+
result.subtype !== "success" ||
|
|
52
|
+
result.is_error !== false)
|
|
53
|
+
throw new Error("Native session did not finish successfully");
|
|
54
|
+
}
|
|
55
|
+
/** Claude's native persisted goal attachments; sentinel clearance is not success. */
|
|
56
|
+
export function claudeNativeGoalEvidence(stream, transcript, sessionId) {
|
|
57
|
+
const events = records(transcript);
|
|
58
|
+
requireBoundSession(events, sessionId);
|
|
59
|
+
requireSuccessfulResult(stream, sessionId);
|
|
60
|
+
const goals = events.flatMap(goalAttachment);
|
|
61
|
+
return {
|
|
62
|
+
method: "native_session",
|
|
63
|
+
threadId: sessionId,
|
|
64
|
+
goals,
|
|
65
|
+
goalStatus: goals.at(-1)?.status ?? null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function requireBoundSession(events, sessionId) {
|
|
69
|
+
for (const event of events) {
|
|
70
|
+
if (event.sessionId && event.sessionId !== sessionId)
|
|
71
|
+
throw new Error("Foreign native session record");
|
|
72
|
+
}
|
|
73
|
+
if (!events.some((event) => event.sessionId === sessionId))
|
|
74
|
+
throw new Error("Original native session not retained");
|
|
75
|
+
}
|
|
76
|
+
async function sessionPaths(path, sessionId, bound = { entries: 0 }) {
|
|
77
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
78
|
+
bound.entries += entries.length;
|
|
79
|
+
if (bound.entries > 1024)
|
|
80
|
+
throw new Error("Native transcript tree exceeds limit");
|
|
81
|
+
if (entries.some((entry) => entry.isSymbolicLink()))
|
|
82
|
+
throw new Error("Native transcript tree contains a symlink");
|
|
83
|
+
return (await Promise.all(entries.map(async (entry) => entry.isDirectory()
|
|
84
|
+
? sessionPaths(join(path, entry.name), sessionId, bound)
|
|
85
|
+
: basename(entry.name) === `${sessionId}.jsonl`
|
|
86
|
+
? [join(path, entry.name)]
|
|
87
|
+
: []))).flat();
|
|
88
|
+
}
|
|
89
|
+
/** Read only this trial's original native session; never manufacture a goal. */
|
|
90
|
+
export async function observeClaudeNativeGoal(configRoot, stream) {
|
|
91
|
+
try {
|
|
92
|
+
const id = originalSessionId(stream);
|
|
93
|
+
const bytes = await nativeTranscript(configRoot, id);
|
|
94
|
+
return {
|
|
95
|
+
id: "sevro.host.native-goal",
|
|
96
|
+
completeness: "complete",
|
|
97
|
+
data: claudeNativeGoalEvidence(stream, new TextDecoder("utf-8", { fatal: true }).decode(bytes), id),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return {
|
|
102
|
+
id: "sevro.host.native-goal",
|
|
103
|
+
completeness: "unavailable",
|
|
104
|
+
data: {
|
|
105
|
+
method: "native_session",
|
|
106
|
+
failure: error instanceof Error
|
|
107
|
+
? error.message
|
|
108
|
+
: "Native observation unavailable",
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function originalSessionId(stream) {
|
|
114
|
+
const initializations = records(stream).filter((event) => event.type === "system" && event.subtype === "init");
|
|
115
|
+
const ids = new Set(initializations.map((event) => event.session_id));
|
|
116
|
+
const [id] = ids;
|
|
117
|
+
if (ids.size !== 1 || typeof id !== "string" || !/^[a-f0-9-]{36}$/i.test(id))
|
|
118
|
+
throw new Error("Missing or ambiguous original Claude session");
|
|
119
|
+
return id;
|
|
120
|
+
}
|
|
121
|
+
async function nativeTranscript(configRoot, id) {
|
|
122
|
+
const paths = await sessionPaths(join(configRoot, "projects"), id);
|
|
123
|
+
const path = uniqueTranscriptPath(paths);
|
|
124
|
+
const entry = await lstat(path);
|
|
125
|
+
if (!entry.isFile() || entry.size > 8 * 1024 * 1024)
|
|
126
|
+
throw new Error("Native transcript is unsafe or oversized");
|
|
127
|
+
const bytes = await readFile(path);
|
|
128
|
+
if (bytes.length > 8 * 1024 * 1024)
|
|
129
|
+
throw new Error("Native transcript exceeds limit");
|
|
130
|
+
return bytes;
|
|
131
|
+
}
|
|
132
|
+
function uniqueTranscriptPath(paths) {
|
|
133
|
+
const [path] = paths;
|
|
134
|
+
if (paths.length !== 1 || path === undefined)
|
|
135
|
+
throw new Error("Missing or ambiguous native transcript");
|
|
136
|
+
return path;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=claude-native-goal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-native-goal.js","sourceRoot":"","sources":["claude-native-goal.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseClaudeEvents, type ClaudeEvents } from \"./claude-events\";\nimport { lstat, readdir, readFile } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { requireRecord } from \"../value-guards\";\n\ntype RecordValue = Record<string, unknown>;\nfunction record(value: unknown): RecordValue {\n return requireRecord(value, \"Malformed native session record\");\n}\nfunction records(text: string | ClaudeEvents): RecordValue[] {\n return parseClaudeEvents(text).lines.map((line) => {\n if (!line.parsed) throw line.error;\n return record(line.value);\n });\n}\n\nfunction goalAttachment(event: RecordValue) {\n if (event.isSidechain === true || event.type !== \"attachment\") return [];\n const attachment = record(event.attachment);\n if (attachment.type !== \"goal_status\") return [];\n const characters = goalCharacters(attachment);\n return [\n {\n status: attachmentStatus(attachment),\n characters,\n ...(typeof event.timestamp === \"string\" ? { at: event.timestamp } : {}),\n },\n ];\n}\n\nfunction goalCharacters(attachment: RecordValue): number {\n const characters =\n typeof attachment.condition === \"string\"\n ? Array.from(attachment.condition).length\n : 0;\n if (!characters || characters > 4000 || typeof attachment.met !== \"boolean\")\n throw new Error(\"Malformed native goal attachment\");\n return characters;\n}\n\nfunction attachmentStatus(attachment: RecordValue): string {\n if (attachment.failed === true) return \"blocked\";\n if (!attachment.met) return \"active\";\n return attachment.sentinel === true ? \"cleared\" : \"complete\";\n}\n\nfunction requireSuccessfulResult(\n stream: string | ClaudeEvents,\n sessionId: string,\n): void {\n const result = records(stream)\n .filter((event) => event.type === \"result\")\n .at(-1);\n if (\n !result ||\n result.session_id !== sessionId ||\n result.subtype !== \"success\" ||\n result.is_error !== false\n )\n throw new Error(\"Native session did not finish successfully\");\n}\n\n/** Claude's native persisted goal attachments; sentinel clearance is not success. */\nexport function claudeNativeGoalEvidence(\n stream: string | ClaudeEvents,\n transcript: string,\n sessionId: string,\n) {\n const events = records(transcript);\n requireBoundSession(events, sessionId);\n requireSuccessfulResult(stream, sessionId);\n const goals = events.flatMap(goalAttachment);\n return {\n method: \"native_session\",\n threadId: sessionId,\n goals,\n goalStatus: goals.at(-1)?.status ?? null,\n };\n}\n\nfunction requireBoundSession(events: RecordValue[], sessionId: string): void {\n for (const event of events) {\n if (event.sessionId && event.sessionId !== sessionId)\n throw new Error(\"Foreign native session record\");\n }\n if (!events.some((event) => event.sessionId === sessionId))\n throw new Error(\"Original native session not retained\");\n}\n\nasync function sessionPaths(\n path: string,\n sessionId: string,\n bound = { entries: 0 },\n): Promise<string[]> {\n const entries = await readdir(path, { withFileTypes: true });\n bound.entries += entries.length;\n if (bound.entries > 1024)\n throw new Error(\"Native transcript tree exceeds limit\");\n if (entries.some((entry) => entry.isSymbolicLink()))\n throw new Error(\"Native transcript tree contains a symlink\");\n return (\n await Promise.all(\n entries.map(async (entry) =>\n entry.isDirectory()\n ? sessionPaths(join(path, entry.name), sessionId, bound)\n : basename(entry.name) === `${sessionId}.jsonl`\n ? [join(path, entry.name)]\n : [],\n ),\n )\n ).flat();\n}\n\n/** Read only this trial's original native session; never manufacture a goal. */\nexport async function observeClaudeNativeGoal(\n configRoot: string,\n stream: string | ClaudeEvents,\n): Promise<{\n id: string;\n completeness: \"complete\" | \"unavailable\";\n data: Record<string, unknown>;\n}> {\n try {\n const id = originalSessionId(stream);\n const bytes = await nativeTranscript(configRoot, id);\n return {\n id: \"sevro.host.native-goal\",\n completeness: \"complete\",\n data: claudeNativeGoalEvidence(\n stream,\n new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes),\n id,\n ),\n };\n } catch (error) {\n return {\n id: \"sevro.host.native-goal\",\n completeness: \"unavailable\",\n data: {\n method: \"native_session\",\n failure:\n error instanceof Error\n ? error.message\n : \"Native observation unavailable\",\n },\n };\n }\n}\n\nfunction originalSessionId(stream: string | ClaudeEvents): string {\n const initializations = records(stream).filter(\n (event) => event.type === \"system\" && event.subtype === \"init\",\n );\n const ids = new Set(initializations.map((event) => event.session_id));\n const [id] = ids;\n if (ids.size !== 1 || typeof id !== \"string\" || !/^[a-f0-9-]{36}$/i.test(id))\n throw new Error(\"Missing or ambiguous original Claude session\");\n return id;\n}\n\nasync function nativeTranscript(\n configRoot: string,\n id: string,\n): Promise<Buffer> {\n const paths = await sessionPaths(join(configRoot, \"projects\"), id);\n const path = uniqueTranscriptPath(paths);\n const entry = await lstat(path);\n if (!entry.isFile() || entry.size > 8 * 1024 * 1024)\n throw new Error(\"Native transcript is unsafe or oversized\");\n const bytes = await readFile(path);\n if (bytes.length > 8 * 1024 * 1024)\n throw new Error(\"Native transcript exceeds limit\");\n return bytes;\n}\n\nfunction uniqueTranscriptPath(paths: string[]): string {\n const [path] = paths;\n if (paths.length !== 1 || path === undefined)\n throw new Error(\"Missing or ambiguous native transcript\");\n return path;\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { runHostProcess } from "./process-lifecycle.js";
|
|
2
|
+
export async function runClaudeProcess(options) {
|
|
3
|
+
const result = await runHostProcess({ ...options, host: "Claude" });
|
|
4
|
+
return {
|
|
5
|
+
code: result.code,
|
|
6
|
+
out: result.out,
|
|
7
|
+
err: Buffer.from(result.stderr).toString("utf8"),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=claude-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-process.js","sourceRoot":"","sources":["claude-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA2B,+BAA4B;AAE9E,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAA6D;IAE7D,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IACpE,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;KACjD,CAAC;AACJ,CAAC","sourcesContent":["import { runHostProcess, type HostProcessOptions } from \"./process-lifecycle\";\n\nexport async function runClaudeProcess(\n options: Omit<HostProcessOptions, \"host\" | \"input\" | \"phase\">,\n) {\n const result = await runHostProcess({ ...options, host: \"Claude\" });\n return {\n code: result.code,\n out: result.out,\n err: Buffer.from(result.stderr).toString(\"utf8\"),\n };\n}\n"]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
|
+
import { mkdir, rmdir, stat, utimes } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Claude Code serializes OAuth refresh through this directory lock in the
|
|
6
|
+
* configuration directory. These values match its lockfile settings, observed
|
|
7
|
+
* in Claude Code 2.1.284 and 2.1.309: stale after 60s, refreshed every 5s.
|
|
8
|
+
*/
|
|
9
|
+
const LOCK_NAME = ".oauth_refresh.lock";
|
|
10
|
+
const STALE_MS = 60_000;
|
|
11
|
+
const UPDATE_MS = 5_000;
|
|
12
|
+
const RETRY_MS = 250;
|
|
13
|
+
const DEFAULT_WAIT_MS = 120_000;
|
|
14
|
+
/** Run an action while holding the refresh lock Claude Code also honors. */
|
|
15
|
+
export async function withClaudeRefreshLock(root, action, { waitMs = DEFAULT_WAIT_MS, updateMs = UPDATE_MS } = {}) {
|
|
16
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
17
|
+
const lock = join(root, LOCK_NAME);
|
|
18
|
+
await acquireLock(lock, Date.now() + waitMs);
|
|
19
|
+
const heartbeat = setInterval(() => {
|
|
20
|
+
void touchLock(lock);
|
|
21
|
+
}, updateMs);
|
|
22
|
+
try {
|
|
23
|
+
return await action();
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
clearInterval(heartbeat);
|
|
27
|
+
await releaseLock(lock);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function acquireLock(lock, deadline) {
|
|
31
|
+
while (!(await createLock(lock))) {
|
|
32
|
+
if (Date.now() >= deadline)
|
|
33
|
+
throw new Error("Timed out waiting for another process to refresh the Claude login");
|
|
34
|
+
await sleep(RETRY_MS);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function createLock(lock) {
|
|
38
|
+
try {
|
|
39
|
+
await mkdir(lock);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
if (cause.code !== "EEXIST")
|
|
44
|
+
throw new Error("Claude refresh lock is unavailable", { cause });
|
|
45
|
+
}
|
|
46
|
+
await removeStaleLock(lock);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/** An abandoned holder stops touching the lock; reclaim it as Claude does. */
|
|
50
|
+
async function removeStaleLock(lock) {
|
|
51
|
+
try {
|
|
52
|
+
if (Date.now() - (await stat(lock)).mtimeMs > STALE_MS)
|
|
53
|
+
await rmdir(lock);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Another contender released or reclaimed it first.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function touchLock(lock) {
|
|
60
|
+
const now = new Date();
|
|
61
|
+
try {
|
|
62
|
+
await utimes(lock, now, now);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Release or reclamation races are resolved by the post-lock re-read.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function releaseLock(lock) {
|
|
69
|
+
try {
|
|
70
|
+
await rmdir(lock);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Already removed; the heartbeat keeps a live holder from being reclaimed.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=claude-refresh-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-refresh-lock.js","sourceRoot":"","sources":["claude-refresh-lock.ts"],"names":[],"mappings":"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","sourcesContent":["import { setTimeout as sleep } from \"node:timers/promises\";\nimport { mkdir, rmdir, stat, utimes } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * Claude Code serializes OAuth refresh through this directory lock in the\n * configuration directory. These values match its lockfile settings, observed\n * in Claude Code 2.1.284 and 2.1.309: stale after 60s, refreshed every 5s.\n */\nconst LOCK_NAME = \".oauth_refresh.lock\";\nconst STALE_MS = 60_000;\nconst UPDATE_MS = 5_000;\nconst RETRY_MS = 250;\nconst DEFAULT_WAIT_MS = 120_000;\n\n/** Run an action while holding the refresh lock Claude Code also honors. */\nexport async function withClaudeRefreshLock<T>(\n root: string,\n action: () => Promise<T>,\n { waitMs = DEFAULT_WAIT_MS, updateMs = UPDATE_MS } = {},\n): Promise<T> {\n await mkdir(root, { recursive: true, mode: 0o700 });\n const lock = join(root, LOCK_NAME);\n await acquireLock(lock, Date.now() + waitMs);\n const heartbeat = setInterval(() => {\n void touchLock(lock);\n }, updateMs);\n try {\n return await action();\n } finally {\n clearInterval(heartbeat);\n await releaseLock(lock);\n }\n}\n\nasync function acquireLock(lock: string, deadline: number): Promise<void> {\n while (!(await createLock(lock))) {\n if (Date.now() >= deadline)\n throw new Error(\n \"Timed out waiting for another process to refresh the Claude login\",\n );\n await sleep(RETRY_MS);\n }\n}\n\nasync function createLock(lock: string): Promise<boolean> {\n try {\n await mkdir(lock);\n return true;\n } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code !== \"EEXIST\")\n throw new Error(\"Claude refresh lock is unavailable\", { cause });\n }\n await removeStaleLock(lock);\n return false;\n}\n\n/** An abandoned holder stops touching the lock; reclaim it as Claude does. */\nasync function removeStaleLock(lock: string): Promise<void> {\n try {\n if (Date.now() - (await stat(lock)).mtimeMs > STALE_MS) await rmdir(lock);\n } catch {\n // Another contender released or reclaimed it first.\n }\n}\n\nasync function touchLock(lock: string): Promise<void> {\n const now = new Date();\n try {\n await utimes(lock, now, now);\n } catch {\n // Release or reclamation races are resolved by the post-lock re-read.\n }\n}\n\nasync function releaseLock(lock: string): Promise<void> {\n try {\n await rmdir(lock);\n } catch {\n // Already removed; the heartbeat keeps a live holder from being reclaimed.\n }\n}\n"]}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { isRecord } from "../value-guards.js";
|
|
4
|
+
import { parseClaudeEvents } from "./claude-events.js";
|
|
5
|
+
function entries(text) {
|
|
6
|
+
return parseClaudeEvents(text).lines.map((line) => {
|
|
7
|
+
if (!line.parsed)
|
|
8
|
+
throw line.error;
|
|
9
|
+
if (!isRecord(line.value))
|
|
10
|
+
throw new Error("invalid Claude session entry");
|
|
11
|
+
return line.value;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function content(entry) {
|
|
15
|
+
const blocks = isRecord(entry.message) ? entry.message.content : null;
|
|
16
|
+
if (typeof blocks === "string")
|
|
17
|
+
return blocks;
|
|
18
|
+
if (!Array.isArray(blocks))
|
|
19
|
+
return "";
|
|
20
|
+
return blocks
|
|
21
|
+
.filter(isRecord)
|
|
22
|
+
.filter((block) => block.type === "text")
|
|
23
|
+
.map((block) => (typeof block.text === "string" ? block.text : ""))
|
|
24
|
+
.join("\n");
|
|
25
|
+
}
|
|
26
|
+
/** Validate the native project mount before candidate execution. */
|
|
27
|
+
export async function verifyClaudeRepositoryInvocation(request) {
|
|
28
|
+
const selected = request.explicitSkillInvocation;
|
|
29
|
+
requireRepositorySelection(selected);
|
|
30
|
+
const { skillName, token } = selected;
|
|
31
|
+
if (!validRepositoryDeclaration(skillName, token, request.prompt))
|
|
32
|
+
throw new Error("invalid Claude repository skill invocation");
|
|
33
|
+
let path = await realpath(request.workspace);
|
|
34
|
+
for (const [index, part] of [
|
|
35
|
+
".claude",
|
|
36
|
+
"skills",
|
|
37
|
+
skillName,
|
|
38
|
+
"SKILL.md",
|
|
39
|
+
].entries()) {
|
|
40
|
+
path = join(path, part);
|
|
41
|
+
const entry = await lstat(path).catch(() => null);
|
|
42
|
+
if (!safeSkillEntry(entry, index))
|
|
43
|
+
throw new Error("invoked Claude repository skill is unavailable or unsafe");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
skillName,
|
|
47
|
+
skillDir: join(path, ".."),
|
|
48
|
+
skillText: await readFile(path, "utf8"),
|
|
49
|
+
prompt: request.prompt,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function requireRepositorySelection(selected) {
|
|
53
|
+
if (!selected || selected.scope !== "repository")
|
|
54
|
+
throw new Error("Claude repository invocation is not declared");
|
|
55
|
+
}
|
|
56
|
+
function validRepositoryDeclaration(skillName, token, prompt) {
|
|
57
|
+
return (validRepositorySkillName(skillName) &&
|
|
58
|
+
token === `/${skillName}` &&
|
|
59
|
+
prompt.split(token).length - 1 === 1 &&
|
|
60
|
+
repositoryPromptMatches(prompt, token));
|
|
61
|
+
}
|
|
62
|
+
function validRepositorySkillName(name) {
|
|
63
|
+
return /^[A-Za-z0-9._-]+$/.test(name) && ![".", ".."].includes(name);
|
|
64
|
+
}
|
|
65
|
+
function repositoryPromptMatches(prompt, token) {
|
|
66
|
+
return (prompt === token ||
|
|
67
|
+
prompt.startsWith(token + " ") ||
|
|
68
|
+
prompt.startsWith(token + "\n"));
|
|
69
|
+
}
|
|
70
|
+
function safeSkillEntry(entry, index) {
|
|
71
|
+
if (!entry || entry.isSymbolicLink())
|
|
72
|
+
return false;
|
|
73
|
+
return index === 3
|
|
74
|
+
? entry.isFile() && entry.size <= 1024 * 1024
|
|
75
|
+
: entry.isDirectory();
|
|
76
|
+
}
|
|
77
|
+
/** Match one exact command and complete body before the assistant starts. */
|
|
78
|
+
export function matchClaudeRepositoryInvocation(input) {
|
|
79
|
+
let native;
|
|
80
|
+
try {
|
|
81
|
+
native = entries(input.transcript);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { accepted: null, reason: "malformed native session" };
|
|
85
|
+
}
|
|
86
|
+
const boundary = assistantBoundary(native, input.sessionId);
|
|
87
|
+
if ("receipt" in boundary)
|
|
88
|
+
return boundary.receipt;
|
|
89
|
+
return matchCommandAndBody(boundary.before, input);
|
|
90
|
+
}
|
|
91
|
+
function assistantBoundary(native, sessionId) {
|
|
92
|
+
const firstAssistant = native.findIndex((entry) => entry.type === "assistant");
|
|
93
|
+
const assistant = native.at(firstAssistant);
|
|
94
|
+
if (firstAssistant < 0 || assistant === undefined)
|
|
95
|
+
return { receipt: { accepted: null, reason: "missing assistant turn" } };
|
|
96
|
+
if (assistant.sessionId !== sessionId || assistant.isSidechain === true)
|
|
97
|
+
return {
|
|
98
|
+
receipt: { accepted: false, reason: "assistant session differs" },
|
|
99
|
+
};
|
|
100
|
+
return { before: native.slice(0, firstAssistant) };
|
|
101
|
+
}
|
|
102
|
+
function nativeCommand(input, token) {
|
|
103
|
+
const args = input.prompt.slice(token.length).trim();
|
|
104
|
+
return (`<command-message>${input.skillName}</command-message>\n<command-name>${token}</command-name>` +
|
|
105
|
+
(args ? `\n<command-args>${args}</command-args>` : ""));
|
|
106
|
+
}
|
|
107
|
+
function matchCommandAndBody(before, input) {
|
|
108
|
+
const token = `/${input.skillName}`;
|
|
109
|
+
if (!repositoryPromptMatches(input.prompt, token))
|
|
110
|
+
return { accepted: false, reason: "prompt differs from mounted command" };
|
|
111
|
+
const command = commandBoundary(before, input, token);
|
|
112
|
+
if ("receipt" in command)
|
|
113
|
+
return command.receipt;
|
|
114
|
+
return mountedBodyReceipt(before, input, command.called);
|
|
115
|
+
}
|
|
116
|
+
function commandBoundary(before, input, token) {
|
|
117
|
+
const commands = before.filter((entry) => entry.type === "user" &&
|
|
118
|
+
content(entry).includes(`<command-name>${token}</command-name>`));
|
|
119
|
+
const [called] = commands;
|
|
120
|
+
if (commands.length !== 1 || called === undefined)
|
|
121
|
+
return {
|
|
122
|
+
receipt: {
|
|
123
|
+
accepted: false,
|
|
124
|
+
reason: "missing or duplicate native command",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
if (!commandCorrelated(called, input, nativeCommand(input, token)))
|
|
128
|
+
return {
|
|
129
|
+
receipt: {
|
|
130
|
+
accepted: false,
|
|
131
|
+
reason: "command session or arguments differ",
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
return { called };
|
|
135
|
+
}
|
|
136
|
+
function commandCorrelated(called, input, command) {
|
|
137
|
+
return (called.sessionId === input.sessionId &&
|
|
138
|
+
called.isMeta !== true &&
|
|
139
|
+
called.isSidechain !== true &&
|
|
140
|
+
content(called) === command);
|
|
141
|
+
}
|
|
142
|
+
function mountedBodyReceipt(before, input, called) {
|
|
143
|
+
const body = input.skillText
|
|
144
|
+
.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "")
|
|
145
|
+
.trim();
|
|
146
|
+
if (!body)
|
|
147
|
+
return { accepted: null, reason: "empty mounted body" };
|
|
148
|
+
const prefix = `Base directory for this skill: ${input.skillDir}\n\n${body}`;
|
|
149
|
+
const expanded = before.filter((entry) => mountedBodyMatches(entry, prefix));
|
|
150
|
+
const [loaded] = expanded;
|
|
151
|
+
if (expanded.length !== 1 || loaded === undefined)
|
|
152
|
+
return {
|
|
153
|
+
accepted: false,
|
|
154
|
+
reason: "missing, partial, or duplicate mounted body",
|
|
155
|
+
};
|
|
156
|
+
if (!bodyCorrelated(loaded, input.sessionId, before, called))
|
|
157
|
+
return { accepted: false, reason: "mounted body is uncorrelated" };
|
|
158
|
+
return {
|
|
159
|
+
accepted: true,
|
|
160
|
+
reason: "native command and complete mounted body matched",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function mountedBodyMatches(entry, prefix) {
|
|
164
|
+
return (entry.type === "user" &&
|
|
165
|
+
(content(entry) === prefix || content(entry).startsWith(prefix + "\n")));
|
|
166
|
+
}
|
|
167
|
+
function bodyCorrelated(loaded, sessionId, before, called) {
|
|
168
|
+
return (loaded.sessionId === sessionId &&
|
|
169
|
+
loaded.isMeta === true &&
|
|
170
|
+
loaded.isSidechain !== true &&
|
|
171
|
+
before.indexOf(loaded) > before.indexOf(called));
|
|
172
|
+
}
|
|
173
|
+
/** Read only the completed bound native session; retain no arguments or body. */
|
|
174
|
+
export async function claudeRepositoryInvocationObservation(input) {
|
|
175
|
+
let receipt = {
|
|
176
|
+
accepted: null,
|
|
177
|
+
reason: "native session evidence unavailable",
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
receipt = await repositoryReceipt(input);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
/* Unavailable or malformed native evidence stays partial. */
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
id: "sevro.claude.repository-invocation",
|
|
187
|
+
completeness: receipt.accepted === true && input.tools.completeness === "complete"
|
|
188
|
+
? "complete"
|
|
189
|
+
: "partial",
|
|
190
|
+
data: {
|
|
191
|
+
method: "native_repository_command",
|
|
192
|
+
...receipt,
|
|
193
|
+
skill: input.invocation.skillName,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function repositorySessionId(stream) {
|
|
198
|
+
const sessions = [
|
|
199
|
+
...new Set(entries(stream)
|
|
200
|
+
.filter((entry) => entry.type === "result" ||
|
|
201
|
+
(entry.type === "system" && entry.subtype === "init"))
|
|
202
|
+
.map((entry) => entry.session_id)),
|
|
203
|
+
];
|
|
204
|
+
const [id] = sessions;
|
|
205
|
+
if (sessions.length !== 1 ||
|
|
206
|
+
typeof id !== "string" ||
|
|
207
|
+
!/^[A-Za-z0-9_-]{1,128}$/.test(id))
|
|
208
|
+
return null;
|
|
209
|
+
return id;
|
|
210
|
+
}
|
|
211
|
+
async function repositoryReceipt(input) {
|
|
212
|
+
const sessionId = repositorySessionId(input.stream);
|
|
213
|
+
if (sessionId === null)
|
|
214
|
+
return { accepted: null, reason: "native session evidence unavailable" };
|
|
215
|
+
const workspace = await realpath(input.workspace);
|
|
216
|
+
const path = join(await realpath(input.configRoot), "projects", workspace.replace(/[^A-Za-z0-9]/g, "-"), `${sessionId}.jsonl`);
|
|
217
|
+
const entry = await lstat(path);
|
|
218
|
+
if (!regularTranscript(entry) || (await realpath(path)) !== path)
|
|
219
|
+
return { accepted: null, reason: "native session evidence unavailable" };
|
|
220
|
+
return matchClaudeRepositoryInvocation({
|
|
221
|
+
...input.invocation,
|
|
222
|
+
sessionId,
|
|
223
|
+
transcript: await readFile(path, "utf8"),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function regularTranscript(entry) {
|
|
227
|
+
return (entry.isFile() && !entry.isSymbolicLink() && entry.size <= 8 * 1024 * 1024);
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=claude-repository-invocation.js.map
|