@bjoernrochel/sevro 0.1.0-rc.7 → 1.0.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 +22 -6
- package/README.md +5 -1
- package/docs/README.md +19 -15
- package/docs/architecture.md +138 -17
- package/docs/assets/README.md +5 -4
- package/docs/builtin-graders-v1.md +17 -3
- package/docs/codex-host.md +155 -49
- package/docs/contracts.md +2 -1
- package/docs/creating-cases.md +9 -2
- package/docs/development-cli.md +187 -19
- package/docs/documentation-quality.md +12 -2
- package/docs/evaluation-workflow.md +110 -0
- package/docs/extension-protocol-v1.md +26 -3
- package/docs/getting-started.md +23 -2
- package/docs/guide-evaluation.md +33 -1
- package/docs/identity-v1.md +25 -2
- package/docs/installing.md +11 -11
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +196 -7
- package/docs/reading-results.md +102 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +31 -12
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +141 -4
- package/docs/run-settings-v1.md +147 -0
- package/docs/runtime-v1.md +307 -18
- package/docs/troubleshooting.md +12 -12
- package/docs/typescript-quality.md +89 -26
- package/examples/recipes/candidate.ts +26 -0
- package/examples/recipes/cases/coding-change.json +21 -0
- package/examples/recipes/cases/host-tools.json +21 -0
- package/examples/recipes/cases/mise.json +21 -0
- package/examples/recipes/cases/node.json +21 -0
- package/examples/recipes/cases/python-uv.json +21 -0
- package/examples/recipes/cases/semantic.json +17 -0
- package/examples/recipes/coding-change/check.ts +23 -0
- package/examples/recipes/extension-artifacts.ts +65 -0
- package/examples/recipes/extension-methods.ts +174 -0
- package/examples/recipes/extension.ts +37 -0
- package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
- package/examples/recipes/repository-skill/README.md +3 -0
- package/examples/recipes/run-inputs.ts +188 -0
- package/examples/recipes/run-options.ts +131 -0
- package/examples/recipes/run.ts +63 -0
- package/examples/recipes/runtime/empty.json +1 -0
- package/examples/recipes/runtime/goals.json +1 -0
- package/examples/recipes/runtime/mise.json +6 -0
- package/examples/recipes/runtime/path.json +1 -0
- package/examples/recipes/runtime/plugins.json +4 -0
- package/examples/recipes/runtime/python-uv.json +10 -0
- package/examples/recipes/semantic-judge.ts +29 -0
- package/examples/templates/coding/adapter.ts +17 -0
- package/examples/templates/coding/case.json +15 -0
- package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
- package/examples/templates/repository-skill/extension-data.ts +64 -0
- package/examples/templates/repository-skill/extension.ts +31 -0
- package/examples/templates/response/adapter.ts +12 -0
- package/examples/templates/response/case.json +13 -0
- package/package.json +11 -5
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +63 -7
- package/src/advisory.ts +2 -2
- package/src/candidate-artifacts.ts +206 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +73 -50
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +87 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +60 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +198 -0
- package/src/cli/hosts.ts +55 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +154 -0
- package/src/cli/presentation.ts +93 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +128 -0
- package/src/cli/run-options.ts +403 -0
- package/src/cli/run-overrides.ts +69 -0
- package/src/cli/settings.ts +154 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -591
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +13 -5
- package/src/evaluation/advisory.ts +150 -0
- package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
- package/src/evaluation/grading.ts +235 -0
- package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
- package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +133 -115
- package/src/evaluation/trial.ts +800 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +85 -0
- package/src/evidence/retained-artifacts.ts +18 -0
- package/src/evidence/trial-writer.ts +203 -0
- package/src/evidence/usage.ts +17 -0
- package/src/extension-client.ts +34 -96
- package/src/extension-session.ts +24 -48
- package/src/{advisory-fixture.ts → fixtures/advisory.ts} +15 -76
- package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +183 -0
- package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
- package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
- package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
- package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
- package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
- package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.d.cts +2 -1
- package/src/generated/extension.d.cts +2 -1
- package/src/generated/report.cjs +1 -1
- package/src/generated/report.d.cts +2 -1
- package/src/generated/run-evidence.cjs +1 -1
- package/src/generated/run-evidence.d.cts +2 -1
- package/src/generated/runtime.cjs +1 -1
- package/src/generated/runtime.d.cts +2 -1
- package/src/git.ts +93 -0
- package/src/graders/git-head.ts +29 -84
- package/src/graders/shell.ts +57 -68
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +331 -0
- package/src/hosts/claude-continuation.ts +57 -40
- package/src/hosts/claude-credential.ts +160 -63
- package/src/hosts/claude-events.ts +34 -18
- package/src/hosts/claude-keychain.ts +78 -21
- package/src/hosts/claude-login-recovery.ts +85 -0
- package/src/hosts/claude-login-refresh.ts +178 -0
- package/src/hosts/claude-login-store.ts +162 -0
- package/src/hosts/claude-login.ts +107 -0
- package/src/hosts/claude-native-goal.ts +15 -12
- package/src/hosts/claude-process.ts +10 -50
- package/src/hosts/claude-refresh-lock.ts +81 -0
- package/src/hosts/claude-repository-invocation.ts +9 -34
- package/src/hosts/claude-settings.ts +91 -14
- package/src/hosts/claude-skill-activations.ts +169 -0
- package/src/hosts/claude-state.ts +46 -16
- package/src/hosts/claude-tool-calls.ts +13 -10
- package/src/hosts/claude.ts +122 -73
- package/src/hosts/codex-app-server.ts +78 -52
- package/src/hosts/codex-configuration.ts +5 -10
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-marketplace.ts +12 -10
- package/src/hosts/codex-native-calls.ts +15 -122
- package/src/hosts/codex-process.ts +10 -54
- package/src/hosts/codex-profile.ts +50 -21
- package/src/hosts/codex-session-files.ts +1 -9
- package/src/hosts/codex-state.ts +141 -19
- package/src/hosts/codex.ts +221 -179
- package/src/hosts/isolation-common.ts +15 -11
- package/src/hosts/isolation-roots.ts +13 -20
- package/src/hosts/linux-sandbox.ts +27 -3
- package/src/hosts/mac-sandbox.ts +111 -18
- package/src/hosts/native-controls.ts +72 -22
- package/src/hosts/native-request.ts +55 -0
- package/src/hosts/process-lifecycle.ts +221 -45
- package/src/hosts/protected-worktrees.ts +1 -1
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +577 -0
- package/src/instrumentation.ts +7 -8
- package/src/native-transcript-state.ts +22 -3
- package/src/native-transcript-view.ts +52 -13
- package/src/native-transcripts.ts +34 -15
- package/src/paths.ts +22 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +27 -7
- package/src/prune.ts +300 -0
- package/src/report.ts +63 -36
- package/src/resolved-case.ts +1 -1
- package/src/results-lock.ts +42 -0
- package/src/results.ts +14 -18
- package/src/run-owner.ts +4 -4
- package/src/{runtime-config.ts → runtime/config.ts} +182 -44
- package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
- package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
- package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
- package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
- package/src/{runtime-hooks.ts → runtime/hooks.ts} +33 -22
- package/src/{runtime-paths.ts → runtime/paths.ts} +6 -18
- package/src/{runtime-query.ts → runtime/query.ts} +5 -5
- package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
- package/src/runtime/shared-cache.ts +130 -0
- package/src/{runtime-state.ts → runtime/state.ts} +28 -11
- package/src/schema-types.ts +73 -161
- package/src/shell.ts +4 -0
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +22 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +0 -1
- package/docs/assets/sevro-wordmark.svg +0 -1
- package/docs/cache-ownership-validation.md +0 -49
- package/docs/claude-continuation-validation.md +0 -47
- package/docs/concurrency-validation.md +0 -86
- package/docs/configuration-root-validation.md +0 -70
- package/docs/engine-slice.md +0 -114
- package/docs/evidence/runtime-startup-30/README.md +0 -185
- package/docs/evidence/runtime-startup-30/after.json +0 -319
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
- package/docs/evidence/runtime-startup-30/before.json +0 -403
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
- package/docs/evidence.md +0 -17
- package/docs/fixture-cleanup-validation.md +0 -70
- package/docs/guide-verification.json +0 -239
- package/src/evaluation-context.ts +0 -257
- package/src/evaluation-fixture.ts +0 -313
- package/src/evaluation-run.ts +0 -198
- package/src/evaluation-trial.ts +0 -946
- package/src/evaluation-types.ts +0 -149
- package/src/hosts/claude-nested-skills.ts +0 -272
- package/src/hosts/codex-command-output.ts +0 -510
- package/src/hosts/codex-executor-command.ts +0 -291
- package/src/hosts/codex-skill-reads.ts +0 -609
- /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mkdir, rmdir, stat, utimes } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
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
|
+
|
|
15
|
+
/** Run an action while holding the refresh lock Claude Code also honors. */
|
|
16
|
+
export async function withClaudeRefreshLock<T>(
|
|
17
|
+
root: string,
|
|
18
|
+
action: () => Promise<T>,
|
|
19
|
+
{ waitMs = DEFAULT_WAIT_MS, updateMs = UPDATE_MS } = {},
|
|
20
|
+
): Promise<T> {
|
|
21
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
22
|
+
const lock = join(root, LOCK_NAME);
|
|
23
|
+
await acquireLock(lock, Date.now() + waitMs);
|
|
24
|
+
const heartbeat = setInterval(() => {
|
|
25
|
+
void touchLock(lock);
|
|
26
|
+
}, updateMs);
|
|
27
|
+
try {
|
|
28
|
+
return await action();
|
|
29
|
+
} finally {
|
|
30
|
+
clearInterval(heartbeat);
|
|
31
|
+
await releaseLock(lock);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function acquireLock(lock: string, deadline: number): Promise<void> {
|
|
36
|
+
while (!(await createLock(lock))) {
|
|
37
|
+
if (Date.now() >= deadline)
|
|
38
|
+
throw new Error(
|
|
39
|
+
"Timed out waiting for another process to refresh the Claude login",
|
|
40
|
+
);
|
|
41
|
+
await Bun.sleep(RETRY_MS);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function createLock(lock: string): Promise<boolean> {
|
|
46
|
+
try {
|
|
47
|
+
await mkdir(lock);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (cause) {
|
|
50
|
+
if ((cause as NodeJS.ErrnoException).code !== "EEXIST")
|
|
51
|
+
throw new Error("Claude refresh lock is unavailable", { cause });
|
|
52
|
+
}
|
|
53
|
+
await removeStaleLock(lock);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** An abandoned holder stops touching the lock; reclaim it as Claude does. */
|
|
58
|
+
async function removeStaleLock(lock: string): Promise<void> {
|
|
59
|
+
try {
|
|
60
|
+
if (Date.now() - (await stat(lock)).mtimeMs > STALE_MS) await rmdir(lock);
|
|
61
|
+
} catch {
|
|
62
|
+
// Another contender released or reclaimed it first.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function touchLock(lock: string): Promise<void> {
|
|
67
|
+
const now = new Date();
|
|
68
|
+
try {
|
|
69
|
+
await utimes(lock, now, now);
|
|
70
|
+
} catch {
|
|
71
|
+
// Release or reclamation races are resolved by the post-lock re-read.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function releaseLock(lock: string): Promise<void> {
|
|
76
|
+
try {
|
|
77
|
+
await rmdir(lock);
|
|
78
|
+
} catch {
|
|
79
|
+
// Already removed; the heartbeat keeps a live holder from being reclaimed.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import type { HostAdapter } from "../engine";
|
|
5
5
|
import { isRecord } from "../value-guards";
|
|
6
6
|
import type { ClaudeToolCallsObservation } from "./claude-tool-calls";
|
|
7
|
+
import { parseClaudeEvents, type ClaudeEvents } from "./claude-events";
|
|
7
8
|
|
|
8
9
|
type Entry = Record<string, unknown>;
|
|
9
10
|
export interface ClaudeRepositoryInvocation {
|
|
@@ -13,15 +14,12 @@ export interface ClaudeRepositoryInvocation {
|
|
|
13
14
|
prompt: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
function entries(text: string): Entry[] {
|
|
17
|
-
return text
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
.
|
|
21
|
-
|
|
22
|
-
if (!isRecord(value)) throw new Error("invalid Claude session entry");
|
|
23
|
-
return value;
|
|
24
|
-
});
|
|
17
|
+
function entries(text: string | ClaudeEvents): Entry[] {
|
|
18
|
+
return parseClaudeEvents(text).lines.map((line) => {
|
|
19
|
+
if (!line.parsed) throw line.error;
|
|
20
|
+
if (!isRecord(line.value)) throw new Error("invalid Claude session entry");
|
|
21
|
+
return line.value;
|
|
22
|
+
});
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
function content(entry: Entry): string {
|
|
@@ -250,7 +248,7 @@ function bodyCorrelated(
|
|
|
250
248
|
/** Read only the completed bound native session; retain no arguments or body. */
|
|
251
249
|
export async function claudeRepositoryInvocationObservation(input: {
|
|
252
250
|
invocation: ClaudeRepositoryInvocation;
|
|
253
|
-
stream: string;
|
|
251
|
+
stream: string | ClaudeEvents;
|
|
254
252
|
configRoot: string;
|
|
255
253
|
workspace: string;
|
|
256
254
|
tools: ClaudeToolCallsObservation;
|
|
@@ -264,14 +262,6 @@ export async function claudeRepositoryInvocationObservation(input: {
|
|
|
264
262
|
} catch {
|
|
265
263
|
/* Unavailable or malformed native evidence stays partial. */
|
|
266
264
|
}
|
|
267
|
-
const observed = input.tools.data.calls
|
|
268
|
-
.filter((call) => call.name === "Skill")
|
|
269
|
-
.map((call) => call.skill);
|
|
270
|
-
const observedSkills = repositoryObservedSkills(
|
|
271
|
-
receipt,
|
|
272
|
-
input.invocation.skillName,
|
|
273
|
-
observed,
|
|
274
|
-
);
|
|
275
265
|
return {
|
|
276
266
|
id: "sevro.claude.repository-invocation" as const,
|
|
277
267
|
completeness:
|
|
@@ -282,26 +272,11 @@ export async function claudeRepositoryInvocationObservation(input: {
|
|
|
282
272
|
method: "native_repository_command",
|
|
283
273
|
...receipt,
|
|
284
274
|
skill: input.invocation.skillName,
|
|
285
|
-
primarySkill: observedSkills[0] ?? null,
|
|
286
|
-
observedSkills,
|
|
287
275
|
},
|
|
288
276
|
};
|
|
289
277
|
}
|
|
290
278
|
|
|
291
|
-
function
|
|
292
|
-
receipt: Receipt,
|
|
293
|
-
skillName: string,
|
|
294
|
-
observed: string[],
|
|
295
|
-
): string[] {
|
|
296
|
-
return [
|
|
297
|
-
...new Set([
|
|
298
|
-
...(receipt.accepted === true ? [skillName] : []),
|
|
299
|
-
...observed,
|
|
300
|
-
]),
|
|
301
|
-
];
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function repositorySessionId(stream: string): string | null {
|
|
279
|
+
function repositorySessionId(stream: string | ClaudeEvents): string | null {
|
|
305
280
|
const sessions = [
|
|
306
281
|
...new Set(
|
|
307
282
|
entries(stream)
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { isAbsolute } from "node:path";
|
|
2
2
|
import { CLAUDE_AUTH_VARIABLES } from "./claude-credential";
|
|
3
|
-
import type { RuntimePolicy } from "../runtime
|
|
4
|
-
import { runtimeHooksEnabled } from "../runtime
|
|
5
|
-
import { requireRuntimeReadRoots, isRuntimeHomeRoot } from "../runtime
|
|
3
|
+
import type { RuntimePolicy } from "../runtime/config";
|
|
4
|
+
import { runtimeHooksEnabled } from "../runtime/hooks";
|
|
5
|
+
import { requireRuntimeReadRoots, isRuntimeHomeRoot } from "../runtime/paths";
|
|
6
6
|
import { nativeStatePath } from "../native-transcript-state";
|
|
7
|
+
import {
|
|
8
|
+
workspaceStatePath,
|
|
9
|
+
workspaceBoundaryException,
|
|
10
|
+
} from "../fixtures/workspace-state";
|
|
7
11
|
|
|
8
12
|
interface WritableRuntime {
|
|
9
13
|
root: string;
|
|
10
14
|
workspace: string;
|
|
15
|
+
cacheRoots?: string[];
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
function absoluteRule(root: string, tool: "Read" | "Edit"): string {
|
|
@@ -38,6 +43,7 @@ export function claudeHostSettings(
|
|
|
38
43
|
runtimePolicy?: RuntimePolicy,
|
|
39
44
|
transcriptReadRoots: string[] = [],
|
|
40
45
|
runtime?: WritableRuntime,
|
|
46
|
+
workspace?: string,
|
|
41
47
|
): Record<string, unknown> {
|
|
42
48
|
if (
|
|
43
49
|
!validPrivatePaths(privateRoot, credentialFile, [
|
|
@@ -48,6 +54,10 @@ export function claudeHostSettings(
|
|
|
48
54
|
throw new Error("Claude private state paths are invalid");
|
|
49
55
|
const readRoots = runtimeReadRoots(runtimePolicy);
|
|
50
56
|
requireRuntimeReadRoots(readRoots, [privateRoot, ...protectedRoots]);
|
|
57
|
+
requireRuntimeReadRoots(runtimeCacheRoots(runtime), [
|
|
58
|
+
privateRoot,
|
|
59
|
+
...protectedRoots,
|
|
60
|
+
]);
|
|
51
61
|
return {
|
|
52
62
|
disableAllHooks: !runtimeHooksEnabled(runtimePolicy),
|
|
53
63
|
sandbox: {
|
|
@@ -61,6 +71,7 @@ export function claudeHostSettings(
|
|
|
61
71
|
transcriptReadRoots,
|
|
62
72
|
pluginRoots,
|
|
63
73
|
runtime,
|
|
74
|
+
workspace,
|
|
64
75
|
),
|
|
65
76
|
credentials: {
|
|
66
77
|
files: [{ path: credentialFile, mode: "deny" }],
|
|
@@ -74,6 +85,7 @@ export function claudeHostSettings(
|
|
|
74
85
|
runtimePolicy,
|
|
75
86
|
transcriptReadRoots,
|
|
76
87
|
runtime,
|
|
88
|
+
workspace,
|
|
77
89
|
),
|
|
78
90
|
};
|
|
79
91
|
}
|
|
@@ -85,21 +97,29 @@ function claudeFilesystemPolicy(
|
|
|
85
97
|
transcriptReadRoots: string[],
|
|
86
98
|
pluginRoots: string[],
|
|
87
99
|
runtime: WritableRuntime | undefined,
|
|
100
|
+
workspace: string | undefined,
|
|
88
101
|
) {
|
|
89
102
|
const runtimeWriteRoot = runtime?.root;
|
|
103
|
+
const writableRoots = runtimeWriteRoots(runtime);
|
|
90
104
|
return {
|
|
91
105
|
denyRead: [privateRoot, ...protectedRoots],
|
|
92
106
|
allowRead: [
|
|
107
|
+
...workspaceBoundaryException(workspaceStatePath(), workspace),
|
|
93
108
|
...readRoots,
|
|
94
109
|
...transcriptReadRoots,
|
|
95
110
|
...pluginRoots,
|
|
96
|
-
...
|
|
111
|
+
...writableRoots,
|
|
97
112
|
],
|
|
98
|
-
...(
|
|
113
|
+
...(writableRoots.length ? { allowWrite: writableRoots } : {}),
|
|
99
114
|
denyWrite: [
|
|
100
115
|
privateRoot,
|
|
101
116
|
...pluginRoots,
|
|
102
|
-
...writableProtectedRoots(
|
|
117
|
+
...writableProtectedRoots(
|
|
118
|
+
protectedRoots,
|
|
119
|
+
runtimeWriteRoot,
|
|
120
|
+
workspace,
|
|
121
|
+
runtimeCacheRoots(runtime),
|
|
122
|
+
),
|
|
103
123
|
...readRoots,
|
|
104
124
|
...transcriptReadRoots,
|
|
105
125
|
],
|
|
@@ -113,17 +133,27 @@ function claudePermissions(
|
|
|
113
133
|
policy: RuntimePolicy | undefined,
|
|
114
134
|
transcriptReadRoots: string[],
|
|
115
135
|
runtime: WritableRuntime | undefined,
|
|
136
|
+
workspace: string | undefined,
|
|
116
137
|
) {
|
|
117
138
|
const runtimeWriteRoot = runtime?.root;
|
|
118
|
-
const roots = [
|
|
139
|
+
const roots = [
|
|
140
|
+
privateRoot,
|
|
141
|
+
...protectedRoots.filter(
|
|
142
|
+
(root) => !workspaceBoundaryException(root, workspace).length,
|
|
143
|
+
),
|
|
144
|
+
];
|
|
145
|
+
const managedWorkspace = workspaceBoundaryException(
|
|
146
|
+
workspaceStatePath(),
|
|
147
|
+
workspace,
|
|
148
|
+
)[0];
|
|
119
149
|
const readRoots = policy
|
|
120
150
|
? roots.filter((root) => !isRuntimeHomeRoot(root))
|
|
121
151
|
: roots;
|
|
122
152
|
const toolRoots = [...runtimeReadRoots(policy), ...transcriptReadRoots];
|
|
123
|
-
const writableRoots =
|
|
153
|
+
const writableRoots = runtimeWriteRoots(runtime);
|
|
124
154
|
return {
|
|
125
|
-
allow: claudeToolAllowRules(policy, runtime),
|
|
126
|
-
...(policy
|
|
155
|
+
allow: claudeToolAllowRules(policy, runtime, managedWorkspace),
|
|
156
|
+
...(policy || managedWorkspace
|
|
127
157
|
? {
|
|
128
158
|
blockReadsOutsideWorkingDirectories: true,
|
|
129
159
|
additionalDirectories: [
|
|
@@ -134,7 +164,12 @@ function claudePermissions(
|
|
|
134
164
|
}
|
|
135
165
|
: {}),
|
|
136
166
|
deny: [
|
|
137
|
-
...writableProtectedRoots(
|
|
167
|
+
...writableProtectedRoots(
|
|
168
|
+
roots,
|
|
169
|
+
runtimeWriteRoot,
|
|
170
|
+
workspace,
|
|
171
|
+
runtimeCacheRoots(runtime),
|
|
172
|
+
).flatMap((root) =>
|
|
138
173
|
protectedToolRules(
|
|
139
174
|
root,
|
|
140
175
|
readRoots.includes(root) &&
|
|
@@ -153,8 +188,16 @@ function claudePermissions(
|
|
|
153
188
|
function claudeToolAllowRules(
|
|
154
189
|
policy: RuntimePolicy | undefined,
|
|
155
190
|
runtime: WritableRuntime | undefined,
|
|
191
|
+
workspace?: string,
|
|
156
192
|
): string[] {
|
|
157
|
-
if (!policy)
|
|
193
|
+
if (!policy)
|
|
194
|
+
return [
|
|
195
|
+
"Bash",
|
|
196
|
+
"Read",
|
|
197
|
+
"Skill",
|
|
198
|
+
"Agent",
|
|
199
|
+
workspace ? absoluteRule(workspace, "Edit") : "Edit",
|
|
200
|
+
];
|
|
158
201
|
if (!runtime) return ["Bash", "Edit", "Skill", "Agent"];
|
|
159
202
|
return [
|
|
160
203
|
"Bash",
|
|
@@ -162,6 +205,7 @@ function claudeToolAllowRules(
|
|
|
162
205
|
"Agent",
|
|
163
206
|
absoluteRule(runtime.workspace, "Edit"),
|
|
164
207
|
absoluteRule(runtime.root, "Edit"),
|
|
208
|
+
...(runtime.cacheRoots ?? []).map((root) => absoluteRule(root, "Edit")),
|
|
165
209
|
];
|
|
166
210
|
}
|
|
167
211
|
|
|
@@ -169,12 +213,45 @@ function claudeToolAllowRules(
|
|
|
169
213
|
function writableProtectedRoots(
|
|
170
214
|
roots: string[],
|
|
171
215
|
runtimeWriteRoot: string | undefined,
|
|
216
|
+
workspace?: string,
|
|
217
|
+
cacheRoots: string[] = [],
|
|
172
218
|
): string[] {
|
|
173
|
-
if (!runtimeWriteRoot) return roots;
|
|
174
219
|
const namespace = nativeStatePath();
|
|
175
220
|
return roots.filter(
|
|
176
221
|
(root) =>
|
|
177
|
-
root
|
|
222
|
+
!workspaceBoundaryException(root, workspace).length &&
|
|
223
|
+
!sharedHomeGrant(root, cacheRoots) &&
|
|
224
|
+
keepNativeWriteDeny(root, namespace, runtimeWriteRoot),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function sharedHomeGrant(root: string, cacheRoots: string[]): boolean {
|
|
229
|
+
return (
|
|
230
|
+
isRuntimeHomeRoot(root) &&
|
|
231
|
+
cacheRoots.some((cache) => cache.startsWith(`${root}/`))
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function runtimeCacheRoots(runtime: WritableRuntime | undefined): string[] {
|
|
236
|
+
return runtime?.cacheRoots ?? [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function runtimeWriteRoots(runtime: WritableRuntime | undefined): string[] {
|
|
240
|
+
return [
|
|
241
|
+
...optionalRuntimeRoot(runtime?.root),
|
|
242
|
+
...(runtime?.cacheRoots ?? []),
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function keepNativeWriteDeny(
|
|
247
|
+
root: string,
|
|
248
|
+
namespace: string,
|
|
249
|
+
runtimeWriteRoot?: string,
|
|
250
|
+
): boolean {
|
|
251
|
+
return (
|
|
252
|
+
root !== namespace ||
|
|
253
|
+
runtimeWriteRoot === undefined ||
|
|
254
|
+
!runtimeWriteRoot.startsWith(`${namespace}/`)
|
|
178
255
|
);
|
|
179
256
|
}
|
|
180
257
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { isRecord } from "../value-guards";
|
|
2
|
+
import {
|
|
3
|
+
summarizeClaudeEvents,
|
|
4
|
+
parseClaudeEvents,
|
|
5
|
+
type ClaudeEvents,
|
|
6
|
+
type ClaudeEventLine,
|
|
7
|
+
} from "./claude-events";
|
|
8
|
+
|
|
9
|
+
const MAX_CALLS = 128;
|
|
10
|
+
type Entry = Record<string, unknown>;
|
|
11
|
+
interface SkillCall {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
parentToolUseId: string | null;
|
|
14
|
+
toolUseId: string;
|
|
15
|
+
invocation: string;
|
|
16
|
+
status: "unknown" | "succeeded" | "failed";
|
|
17
|
+
}
|
|
18
|
+
interface State {
|
|
19
|
+
calls: Map<string, SkillCall>;
|
|
20
|
+
partial: boolean;
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function label(value: unknown): string | null {
|
|
25
|
+
return typeof value === "string" && /^[A-Za-z0-9_.:-]{1,256}$/.test(value)
|
|
26
|
+
? value
|
|
27
|
+
: null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function blocks(event: Entry): unknown[] {
|
|
31
|
+
const content: unknown = isRecord(event.message)
|
|
32
|
+
? event.message.content
|
|
33
|
+
: null;
|
|
34
|
+
return Array.isArray(content) ? content : [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function key(event: Entry, id: string): string {
|
|
38
|
+
return JSON.stringify([
|
|
39
|
+
event.session_id ?? event.sessionId,
|
|
40
|
+
event.parent_tool_use_id ?? null,
|
|
41
|
+
id,
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function call(event: Entry, block: Entry, state: State) {
|
|
46
|
+
const id = label(block.id);
|
|
47
|
+
const sessionId = label(event.session_id ?? event.sessionId);
|
|
48
|
+
const invocation = skillInput(block.input);
|
|
49
|
+
if (!id || !sessionId || !invocation) {
|
|
50
|
+
state.partial = true;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
addCall(
|
|
54
|
+
key(event, id),
|
|
55
|
+
{
|
|
56
|
+
sessionId,
|
|
57
|
+
parentToolUseId: label(event.parent_tool_use_id),
|
|
58
|
+
toolUseId: id,
|
|
59
|
+
invocation,
|
|
60
|
+
status: "unknown",
|
|
61
|
+
},
|
|
62
|
+
state,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function skillInput(input: unknown): string | null {
|
|
67
|
+
return isRecord(input) ? label(input.skill) : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function addCall(id: string, value: SkillCall, state: State) {
|
|
71
|
+
const previous = state.calls.get(id);
|
|
72
|
+
if (previous) {
|
|
73
|
+
state.partial ||= previous.invocation !== value.invocation;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (state.calls.size >= MAX_CALLS) {
|
|
77
|
+
state.truncated = true;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
state.calls.set(id, value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function result(event: Entry, block: Entry, state: State) {
|
|
84
|
+
const id = label(block.tool_use_id);
|
|
85
|
+
if (!id) return;
|
|
86
|
+
const pending = state.calls.get(key(event, id));
|
|
87
|
+
if (!pending) return;
|
|
88
|
+
const status = resultStatus(block.is_error);
|
|
89
|
+
if (!status) {
|
|
90
|
+
state.partial = true;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
state.partial ||= conflictingResult(pending.status, status);
|
|
94
|
+
pending.status = status;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resultStatus(value: unknown): "failed" | "succeeded" | null {
|
|
98
|
+
if (value !== undefined && typeof value !== "boolean") return null;
|
|
99
|
+
return value === true ? "failed" : "succeeded";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function conflictingResult(
|
|
103
|
+
previous: SkillCall["status"],
|
|
104
|
+
next: SkillCall["status"],
|
|
105
|
+
): boolean {
|
|
106
|
+
return previous !== "unknown" && previous !== next;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function visit(line: ClaudeEventLine, state: State) {
|
|
110
|
+
if (!line.parsed || !isRecord(line.value)) {
|
|
111
|
+
state.partial = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const block of blocks(line.value)) visitBlock(line.value, block, state);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function visitBlock(event: Entry, block: unknown, state: State) {
|
|
118
|
+
if (!isRecord(block)) return;
|
|
119
|
+
switch (event.type) {
|
|
120
|
+
case "assistant":
|
|
121
|
+
if (isSkillCall(block)) call(event, block, state);
|
|
122
|
+
break;
|
|
123
|
+
case "user":
|
|
124
|
+
if (block.type === "tool_result") result(event, block, state);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isSkillCall(block: Entry): boolean {
|
|
129
|
+
return block.type === "tool_use" && block.name === "Skill";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function completeCollection(completed: boolean, state: State): boolean {
|
|
133
|
+
return (
|
|
134
|
+
completed &&
|
|
135
|
+
!state.partial &&
|
|
136
|
+
!state.truncated &&
|
|
137
|
+
[...state.calls.values()].every((item) => item.status !== "unknown")
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Observe the native Skill seam and distinguish successful results from attempts. */
|
|
142
|
+
export function claudeSkillActivationsObservation(
|
|
143
|
+
stream: string | ClaudeEvents,
|
|
144
|
+
code: number,
|
|
145
|
+
) {
|
|
146
|
+
const events = parseClaudeEvents(stream);
|
|
147
|
+
const summary = summarizeClaudeEvents(events, code);
|
|
148
|
+
const state: State = { calls: new Map(), partial: false, truncated: false };
|
|
149
|
+
for (const line of events.lines) visit(line, state);
|
|
150
|
+
const calls = [...state.calls.values()];
|
|
151
|
+
const complete = completeCollection(summary.complete, state);
|
|
152
|
+
return {
|
|
153
|
+
id: "sevro.host.skill-activations",
|
|
154
|
+
completeness: complete ? ("complete" as const) : ("partial" as const),
|
|
155
|
+
data: {
|
|
156
|
+
method: "skill_tool_result",
|
|
157
|
+
visibility: "skill_tool",
|
|
158
|
+
observedSkills: [
|
|
159
|
+
...new Set(
|
|
160
|
+
calls
|
|
161
|
+
.filter((item) => item.status === "succeeded")
|
|
162
|
+
.map((item) => item.invocation),
|
|
163
|
+
),
|
|
164
|
+
],
|
|
165
|
+
calls,
|
|
166
|
+
truncated: state.truncated,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import { runtimeSeedSources } from "../runtime
|
|
1
|
+
import { runtimeSeedSources } from "../runtime/config";
|
|
2
|
+
import { minimalRoots } from "../paths";
|
|
3
|
+
import { shellQuote } from "../shell";
|
|
4
|
+
import {
|
|
5
|
+
candidateArtifactEnvironment,
|
|
6
|
+
candidateArtifactReadRoots,
|
|
7
|
+
} from "../candidate-artifacts";
|
|
2
8
|
import {
|
|
3
9
|
cp,
|
|
4
10
|
mkdir,
|
|
@@ -10,16 +16,24 @@ import {
|
|
|
10
16
|
import { delimiter, dirname, join } from "node:path";
|
|
11
17
|
import type { HostAdapter } from "../engine";
|
|
12
18
|
import { nativeStateParent } from "../native-transcript-state";
|
|
19
|
+
import {
|
|
20
|
+
workspaceBoundaryException,
|
|
21
|
+
workspaceStatePath,
|
|
22
|
+
} from "../fixtures/workspace-state";
|
|
13
23
|
import {
|
|
14
24
|
candidateTranscriptEnvironment,
|
|
15
25
|
candidateTranscriptRoots,
|
|
16
26
|
} from "../native-transcripts";
|
|
17
|
-
import { prepareHookMounts, runtimeHooksEnabled } from "../runtime
|
|
27
|
+
import { prepareHookMounts, runtimeHooksEnabled } from "../runtime/hooks";
|
|
18
28
|
import {
|
|
19
29
|
insideRuntimeRoot as inside,
|
|
20
30
|
runtimeExecutableProtection,
|
|
21
|
-
} from "../runtime
|
|
22
|
-
import { prepareRuntimeState } from "../runtime
|
|
31
|
+
} from "../runtime/paths";
|
|
32
|
+
import { prepareRuntimeState } from "../runtime/state";
|
|
33
|
+
import {
|
|
34
|
+
sharedCacheDenyRoots,
|
|
35
|
+
sharedCacheRoots,
|
|
36
|
+
} from "../runtime/shared-cache";
|
|
23
37
|
import { stageClaudeAuthentication } from "./claude-credential";
|
|
24
38
|
import { claudeHostSettings } from "./claude-settings";
|
|
25
39
|
import { evaluationProtectedRoots } from "./isolation-roots";
|
|
@@ -36,12 +50,7 @@ export interface ClaudeHostOptions {
|
|
|
36
50
|
toolchainBinDir?: string;
|
|
37
51
|
projectSettings?: boolean;
|
|
38
52
|
timeoutMs?: number;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
function minimalRoots(roots: string[]): string[] {
|
|
42
|
-
return roots.filter(
|
|
43
|
-
(root) => !roots.some((other) => other !== root && inside(other, root)),
|
|
44
|
-
);
|
|
53
|
+
retainHostDiagnostics?: boolean;
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
function claudeSeedSources(request: Request): string[] {
|
|
@@ -51,6 +60,7 @@ function claudeSeedSources(request: Request): string[] {
|
|
|
51
60
|
async function createClaudePrivateState(
|
|
52
61
|
stateRoot: string,
|
|
53
62
|
options: ClaudeHostOptions,
|
|
63
|
+
loginLifetimeMs: number,
|
|
54
64
|
) {
|
|
55
65
|
await mkdir(join(stateRoot, "private"), { mode: 0o700 });
|
|
56
66
|
const privateRoot = await realpath(join(stateRoot, "private"));
|
|
@@ -60,6 +70,7 @@ async function createClaudePrivateState(
|
|
|
60
70
|
const authentication = await stageClaudeAuthentication(
|
|
61
71
|
join(privateRoot, "config"),
|
|
62
72
|
options.credentialFile,
|
|
73
|
+
{ binary: options.binary, lifetimeMs: loginLifetimeMs },
|
|
63
74
|
);
|
|
64
75
|
return {
|
|
65
76
|
privateRoot,
|
|
@@ -181,14 +192,16 @@ function claudeRuntimeEnvironment(
|
|
|
181
192
|
: {};
|
|
182
193
|
}
|
|
183
194
|
|
|
195
|
+
/** `lifetimeMs` is the access-token lifetime the complete execution needs. */
|
|
184
196
|
export async function prepareClaudeState(
|
|
185
197
|
stateRoot: string,
|
|
186
198
|
options: ClaudeHostOptions,
|
|
187
199
|
request: Request,
|
|
188
200
|
pluginDirs: string[],
|
|
201
|
+
lifetimeMs: number,
|
|
189
202
|
) {
|
|
190
203
|
await requireClaudeRuntimeNamespace(request, stateRoot);
|
|
191
|
-
const state = await createClaudePrivateState(stateRoot, options);
|
|
204
|
+
const state = await createClaudePrivateState(stateRoot, options, lifetimeMs);
|
|
192
205
|
const transcripts = await claudeTranscriptAccess(
|
|
193
206
|
stateRoot,
|
|
194
207
|
state.credential,
|
|
@@ -213,6 +226,11 @@ export async function prepareClaudeState(
|
|
|
213
226
|
runtime,
|
|
214
227
|
stateRoot,
|
|
215
228
|
);
|
|
229
|
+
const cacheRoots = await sharedCacheRoots(
|
|
230
|
+
request.runtimePolicy,
|
|
231
|
+
request.runtimeRole,
|
|
232
|
+
roots,
|
|
233
|
+
);
|
|
216
234
|
await writeFile(
|
|
217
235
|
settingsPath,
|
|
218
236
|
JSON.stringify(
|
|
@@ -220,15 +238,20 @@ export async function prepareClaudeState(
|
|
|
220
238
|
state.privateRoot,
|
|
221
239
|
state.credential,
|
|
222
240
|
[...pluginDirs, ...effectivePlugins],
|
|
223
|
-
|
|
241
|
+
[
|
|
242
|
+
...roots,
|
|
243
|
+
...sharedCacheDenyRoots(request.runtimePolicy, request.runtimeRole),
|
|
244
|
+
],
|
|
224
245
|
transcripts.policy,
|
|
225
246
|
[
|
|
226
247
|
...transcripts.readRoots,
|
|
227
248
|
...candidateTranscriptRoots(request.candidateTranscriptRoot),
|
|
249
|
+
...candidateArtifactReadRoots(request.candidateArtifactRoots),
|
|
228
250
|
],
|
|
229
251
|
runtimeWriteRoot
|
|
230
|
-
? { root: runtimeWriteRoot, workspace: request.workspace }
|
|
252
|
+
? { root: runtimeWriteRoot, workspace: request.workspace, cacheRoots }
|
|
231
253
|
: undefined,
|
|
254
|
+
request.workspace,
|
|
232
255
|
),
|
|
233
256
|
),
|
|
234
257
|
{ flag: "wx", mode: 0o600 },
|
|
@@ -237,6 +260,7 @@ export async function prepareClaudeState(
|
|
|
237
260
|
Object.assign(
|
|
238
261
|
env,
|
|
239
262
|
candidateTranscriptEnvironment(request.candidateTranscriptRoot),
|
|
263
|
+
candidateArtifactEnvironment(request.candidateArtifactRoots),
|
|
240
264
|
);
|
|
241
265
|
if (runtime) await mkdir(join(runtime, "host-home"), { mode: 0o700 });
|
|
242
266
|
return {
|
|
@@ -253,7 +277,11 @@ async function requireClaudeRuntimeNamespace(
|
|
|
253
277
|
request: Request,
|
|
254
278
|
stateRoot: string,
|
|
255
279
|
) {
|
|
256
|
-
if (
|
|
280
|
+
if (
|
|
281
|
+
!request.runtimePolicy &&
|
|
282
|
+
!workspaceBoundaryException(workspaceStatePath(), request.workspace).length
|
|
283
|
+
)
|
|
284
|
+
return;
|
|
257
285
|
const implicitRoots = await Promise.all(
|
|
258
286
|
["/tmp/claude", "/private/tmp/claude"].map(implicitClaudeWritePaths),
|
|
259
287
|
);
|
|
@@ -293,7 +321,7 @@ async function claudeTranscriptAccess(
|
|
|
293
321
|
await mkdir(projects, { recursive: true, mode: 0o700 });
|
|
294
322
|
await symlink(projects, join(dirname(credential), "projects"));
|
|
295
323
|
const startup = join(view, "shell-env");
|
|
296
|
-
const script = `export CLAUDE_CONFIG_DIR
|
|
324
|
+
const script = `export CLAUDE_CONFIG_DIR=${shellQuote(view)}\n`;
|
|
297
325
|
await writeFile(startup, script, { flag: "wx", mode: 0o600 });
|
|
298
326
|
await writeFile(join(view, ".zshenv"), script, { flag: "wx", mode: 0o600 });
|
|
299
327
|
return {
|
|
@@ -350,7 +378,9 @@ async function applyClaudeRuntimeEnvironment(
|
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
function claudeRuntimeDirectory(request: Request, stateRoot: string) {
|
|
353
|
-
return request.runtimePolicy
|
|
381
|
+
return request.runtimePolicy
|
|
382
|
+
? (request.candidateRuntimeRoot ?? join(stateRoot, "runtime"))
|
|
383
|
+
: undefined;
|
|
354
384
|
}
|
|
355
385
|
|
|
356
386
|
function applyLegacyClaudeCommandEnvironment(
|