@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
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { isRecord, isUnknownArray } from "../value-guards";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
codexExecCallReceipts,
|
|
4
|
+
type ExecCallReceipt,
|
|
5
|
+
} from "./codex-exec-controls";
|
|
3
6
|
import {
|
|
4
7
|
readNativeSession,
|
|
5
|
-
readNativeSessionText,
|
|
6
8
|
locateNativeSession as sessionPath,
|
|
7
9
|
} from "./codex-session-files";
|
|
8
|
-
import {
|
|
9
|
-
codexNativeReadDiagnostic,
|
|
10
|
-
type NativeReadDiagnostic,
|
|
11
|
-
} from "./codex-skill-reads";
|
|
12
10
|
|
|
13
11
|
const MAX_SESSION_ENTRIES = 10_000;
|
|
14
12
|
const MAX_RETAINED_CALLS = 128;
|
|
@@ -63,7 +61,6 @@ interface ChildSession {
|
|
|
63
61
|
threadId: string;
|
|
64
62
|
status: "available" | "unavailable" | "ambiguous" | "partial";
|
|
65
63
|
resultStatus?: "completed" | "unavailable";
|
|
66
|
-
readDiagnostics?: NativeReadDiagnostic;
|
|
67
64
|
nestedSpawns?: NestedSpawn[];
|
|
68
65
|
requestsTruncated?: boolean;
|
|
69
66
|
}
|
|
@@ -81,9 +78,7 @@ interface NestedSpawn {
|
|
|
81
78
|
readerResultStatus: "completed" | "unavailable";
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
interface
|
|
85
|
-
workspace: string;
|
|
86
|
-
installedPluginRoots: string[];
|
|
81
|
+
interface NativeCallContext {
|
|
87
82
|
followUpPrompt?: string;
|
|
88
83
|
}
|
|
89
84
|
|
|
@@ -95,9 +90,9 @@ interface NativeCallObservation {
|
|
|
95
90
|
calls: NativeCall[];
|
|
96
91
|
toolCalls: NativeToolCall[];
|
|
97
92
|
submittedExecCalls: number;
|
|
93
|
+
execCalls?: ExecCallReceipt[];
|
|
98
94
|
acceptedSpawns: AcceptedSpawn[];
|
|
99
95
|
feedbackCalls: NativeFeedbackCall[];
|
|
100
|
-
parentReadDiagnostics?: NativeReadDiagnostic;
|
|
101
96
|
childSessions: ChildSession[];
|
|
102
97
|
childrenTruncated: boolean;
|
|
103
98
|
};
|
|
@@ -531,6 +526,9 @@ function sessionObservation(
|
|
|
531
526
|
calls: state.calls,
|
|
532
527
|
toolCalls: state.toolCalls,
|
|
533
528
|
submittedExecCalls: state.submittedExecCalls,
|
|
529
|
+
...(state.submittedExecCalls > 0
|
|
530
|
+
? { execCalls: codexExecCallReceipts(state.entries) }
|
|
531
|
+
: {}),
|
|
534
532
|
...sessionReceipts(state, followUpPrompt),
|
|
535
533
|
childSessions: [],
|
|
536
534
|
childrenTruncated: false,
|
|
@@ -696,7 +694,6 @@ async function childSessionStatus(
|
|
|
696
694
|
home: string,
|
|
697
695
|
parentThread: string,
|
|
698
696
|
threadId: string,
|
|
699
|
-
skillContext?: NativeSkillContext,
|
|
700
697
|
rootThread?: string,
|
|
701
698
|
): Promise<ChildSession> {
|
|
702
699
|
if (threadId === parentThread) return { threadId, status: "partial" };
|
|
@@ -705,41 +702,21 @@ async function childSessionStatus(
|
|
|
705
702
|
try {
|
|
706
703
|
const text = await readNativeSession(located.path);
|
|
707
704
|
if (text === null) return { threadId, status: "partial" };
|
|
708
|
-
return await availableChildSession(
|
|
709
|
-
home,
|
|
710
|
-
threadId,
|
|
711
|
-
text,
|
|
712
|
-
skillContext,
|
|
713
|
-
rootThread,
|
|
714
|
-
);
|
|
705
|
+
return await availableChildSession(home, threadId, text, rootThread);
|
|
715
706
|
} catch {
|
|
716
707
|
return { threadId, status: "partial" };
|
|
717
708
|
}
|
|
718
709
|
}
|
|
719
710
|
|
|
720
|
-
async function readDiagnostics(
|
|
721
|
-
entries: NativeEntry[],
|
|
722
|
-
context: NativeSkillContext | undefined,
|
|
723
|
-
) {
|
|
724
|
-
return context
|
|
725
|
-
? codexNativeReadDiagnostic(
|
|
726
|
-
entries,
|
|
727
|
-
context.workspace,
|
|
728
|
-
context.installedPluginRoots,
|
|
729
|
-
)
|
|
730
|
-
: undefined;
|
|
731
|
-
}
|
|
732
711
|
async function availableChildSession(
|
|
733
712
|
home: string,
|
|
734
713
|
threadId: string,
|
|
735
714
|
text: string,
|
|
736
|
-
context: NativeSkillContext | undefined,
|
|
737
715
|
rootThread: string | undefined,
|
|
738
716
|
): Promise<ChildSession> {
|
|
739
717
|
const parsed = parseSession(text);
|
|
740
718
|
if (parsed.observation.completeness !== "complete")
|
|
741
719
|
return { threadId, status: "partial" };
|
|
742
|
-
const diagnostics = await readDiagnostics(parsed.entries, context);
|
|
743
720
|
const nested = rootThread
|
|
744
721
|
? await nestedSpawnReceipts(home, rootThread, threadId, parsed.entries)
|
|
745
722
|
: undefined;
|
|
@@ -749,7 +726,6 @@ async function availableChildSession(
|
|
|
749
726
|
resultStatus: childResultCompleted(parsed.entries)
|
|
750
727
|
? "completed"
|
|
751
728
|
: "unavailable",
|
|
752
|
-
...(diagnostics ? { readDiagnostics: diagnostics } : {}),
|
|
753
729
|
...nested,
|
|
754
730
|
};
|
|
755
731
|
}
|
|
@@ -779,7 +755,7 @@ function lastSessionOrdinal(text: string): number | null {
|
|
|
779
755
|
export async function codexNativeCallObservation(
|
|
780
756
|
home: string,
|
|
781
757
|
threadId: string,
|
|
782
|
-
|
|
758
|
+
context?: NativeCallContext,
|
|
783
759
|
): Promise<NativeCallObservation> {
|
|
784
760
|
const located = await sessionPath(home, threadId);
|
|
785
761
|
if (located.status !== "found")
|
|
@@ -791,7 +767,7 @@ export async function codexNativeCallObservation(
|
|
|
791
767
|
home,
|
|
792
768
|
threadId,
|
|
793
769
|
located.path,
|
|
794
|
-
|
|
770
|
+
context,
|
|
795
771
|
);
|
|
796
772
|
} catch {
|
|
797
773
|
return unavailable("partial");
|
|
@@ -802,109 +778,26 @@ async function nativeSessionObservation(
|
|
|
802
778
|
home: string,
|
|
803
779
|
threadId: string,
|
|
804
780
|
path: string,
|
|
805
|
-
|
|
781
|
+
context: NativeCallContext | undefined,
|
|
806
782
|
): Promise<NativeCallObservation> {
|
|
807
783
|
const text = await readNativeSession(path);
|
|
808
784
|
if (text === null) return unavailable("partial");
|
|
809
|
-
const { observation
|
|
810
|
-
text,
|
|
811
|
-
skillContext?.followUpPrompt,
|
|
812
|
-
);
|
|
785
|
+
const { observation } = parseSession(text, context?.followUpPrompt);
|
|
813
786
|
if (observation.completeness !== "complete") return observation;
|
|
814
|
-
const parentReadDiagnostics = await readDiagnostics(entries, skillContext);
|
|
815
787
|
const children = [
|
|
816
788
|
...new Set(observation.data.acceptedSpawns.map((spawn) => spawn.threadId)),
|
|
817
789
|
];
|
|
818
790
|
const childSessions = await Promise.all(
|
|
819
791
|
children
|
|
820
792
|
.slice(0, MAX_CHILD_SESSIONS)
|
|
821
|
-
.map((child) =>
|
|
822
|
-
childSessionStatus(home, threadId, child, skillContext, threadId),
|
|
823
|
-
),
|
|
793
|
+
.map((child) => childSessionStatus(home, threadId, child, threadId)),
|
|
824
794
|
);
|
|
825
795
|
return {
|
|
826
796
|
...observation,
|
|
827
797
|
data: {
|
|
828
798
|
...observation.data,
|
|
829
|
-
...(parentReadDiagnostics ? { parentReadDiagnostics } : {}),
|
|
830
799
|
childSessions,
|
|
831
800
|
childrenTruncated: children.length > MAX_CHILD_SESSIONS,
|
|
832
801
|
},
|
|
833
802
|
};
|
|
834
803
|
}
|
|
835
|
-
|
|
836
|
-
/** Actor-local recovery stays in host memory and never enters public observations. */
|
|
837
|
-
export async function codexNativeSkillReadRecovery(
|
|
838
|
-
home: string,
|
|
839
|
-
threadId: string,
|
|
840
|
-
): Promise<Map<string, { command: string; output: string }>> {
|
|
841
|
-
const found = await sessionPath(home, threadId);
|
|
842
|
-
if (found.status !== "found") return new Map();
|
|
843
|
-
try {
|
|
844
|
-
const text = await readNativeSessionText(found.path);
|
|
845
|
-
if (text === null) return new Map();
|
|
846
|
-
const parsed = parseSession(text);
|
|
847
|
-
if (parsed.observation.completeness !== "complete") return new Map();
|
|
848
|
-
return recoveredSessionReads(parsed.entries);
|
|
849
|
-
} catch {
|
|
850
|
-
return new Map();
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
function completedCommandItem(
|
|
855
|
-
payload: Record<string, unknown>,
|
|
856
|
-
): Record<string, unknown> | null {
|
|
857
|
-
if (payload.type !== "item_completed") return null;
|
|
858
|
-
if (!isRecord(payload.item) || payload.item.type !== "CommandExecution")
|
|
859
|
-
return null;
|
|
860
|
-
return payload.item;
|
|
861
|
-
}
|
|
862
|
-
function commandIdentityCounts(entries: NativeEntry[]): Map<string, number> {
|
|
863
|
-
const counts = new Map<string, number>();
|
|
864
|
-
for (const { payload } of entries) {
|
|
865
|
-
const item = completedCommandItem(payload);
|
|
866
|
-
if (typeof item?.id === "string")
|
|
867
|
-
counts.set(item.id, (counts.get(item.id) ?? 0) + 1);
|
|
868
|
-
}
|
|
869
|
-
return counts;
|
|
870
|
-
}
|
|
871
|
-
function uniqueRecoveryItem(
|
|
872
|
-
payload: Record<string, unknown>,
|
|
873
|
-
counts: Map<string, number>,
|
|
874
|
-
) {
|
|
875
|
-
const item = completedCommandItem(payload);
|
|
876
|
-
if (!item || typeof item.id !== "string" || counts.get(item.id) !== 1)
|
|
877
|
-
return null;
|
|
878
|
-
return { id: item.id, command: item.command };
|
|
879
|
-
}
|
|
880
|
-
function recoveredLiteralCommand(command: unknown): string | null {
|
|
881
|
-
if (!isUnknownArray(command) || command.length !== 3) return null;
|
|
882
|
-
if (
|
|
883
|
-
!["-c", "-lc"].includes(String(command[1])) ||
|
|
884
|
-
typeof command[2] !== "string"
|
|
885
|
-
)
|
|
886
|
-
return null;
|
|
887
|
-
return command[2];
|
|
888
|
-
}
|
|
889
|
-
function recoveredSessionReads(
|
|
890
|
-
entries: NativeEntry[],
|
|
891
|
-
): Map<string, { command: string; output: string }> {
|
|
892
|
-
const output = nativeCommandOutputs(entries);
|
|
893
|
-
const counts = commandIdentityCounts(entries);
|
|
894
|
-
const recovered = new Map<string, { command: string; output: string }>();
|
|
895
|
-
for (const { ordinal, payload } of entries) {
|
|
896
|
-
const item = uniqueRecoveryItem(payload, counts);
|
|
897
|
-
if (!item) continue;
|
|
898
|
-
const read = recoveredCommandRead(
|
|
899
|
-
item.command,
|
|
900
|
-
output.get(ordinal)?.output,
|
|
901
|
-
);
|
|
902
|
-
if (read) recovered.set(item.id, read);
|
|
903
|
-
}
|
|
904
|
-
return recovered;
|
|
905
|
-
}
|
|
906
|
-
function recoveredCommandRead(value: unknown, output: string | undefined) {
|
|
907
|
-
if (output === undefined) return null;
|
|
908
|
-
const command = recoveredLiteralCommand(value);
|
|
909
|
-
return command === null ? null : { command, output };
|
|
910
|
-
}
|
|
@@ -1,61 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
HostProcessFailure,
|
|
3
|
+
runHostProcess,
|
|
4
|
+
type HostProcessOptions,
|
|
5
|
+
type HostProcessResult,
|
|
6
|
+
type Phase,
|
|
5
7
|
} from "./process-lifecycle";
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
11
|
-
return;
|
|
12
|
-
} catch {
|
|
13
|
-
// The group may have exited before cancellation.
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
proc.kill("SIGKILL");
|
|
18
|
-
} catch {
|
|
19
|
-
// The process already exited.
|
|
9
|
+
export class CodexProcessFailure extends HostProcessFailure {
|
|
10
|
+
constructor(phase: Phase, result: HostProcessResult) {
|
|
11
|
+
super("Codex", phase, result);
|
|
20
12
|
}
|
|
21
13
|
}
|
|
22
14
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
cwd: string;
|
|
26
|
-
env: Record<string, string>;
|
|
27
|
-
input?: string;
|
|
28
|
-
timeoutMs: number;
|
|
29
|
-
signal?: AbortSignal;
|
|
30
|
-
}): Promise<{ code: number; out: string }> {
|
|
31
|
-
if (options.signal?.aborted) throw new Error("Codex run cancelled");
|
|
32
|
-
const proc = Bun.spawn(options.argv, {
|
|
33
|
-
cwd: options.cwd,
|
|
34
|
-
env: options.env,
|
|
35
|
-
detached: process.platform !== "win32",
|
|
36
|
-
stdin: options.input === undefined ? "ignore" : "pipe",
|
|
37
|
-
stdout: "pipe",
|
|
38
|
-
stderr: "ignore",
|
|
39
|
-
});
|
|
40
|
-
const written = (async () => {
|
|
41
|
-
if (options.input === undefined) return;
|
|
42
|
-
if (!proc.stdin || typeof proc.stdin === "number")
|
|
43
|
-
throw new Error("Codex input pipe unavailable");
|
|
44
|
-
await proc.stdin.write(options.input);
|
|
45
|
-
await proc.stdin.end();
|
|
46
|
-
})();
|
|
47
|
-
const completed = Promise.all([
|
|
48
|
-
boundedProcessText(
|
|
49
|
-
proc.stdout,
|
|
50
|
-
MAX_EVENT_BYTES,
|
|
51
|
-
"Codex event stream exceeds the size limit",
|
|
52
|
-
),
|
|
53
|
-
proc.exited,
|
|
54
|
-
written,
|
|
55
|
-
]).then(([out, code]) => ({ out, code }));
|
|
56
|
-
return awaitHostProcess(proc, completed, {
|
|
57
|
-
...options,
|
|
58
|
-
host: "Codex",
|
|
59
|
-
stop: stopProcess,
|
|
60
|
-
});
|
|
15
|
+
export function runCodexProcess(options: Omit<HostProcessOptions, "host">) {
|
|
16
|
+
return runHostProcess({ ...options, host: "Codex" });
|
|
61
17
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { isAbsolute, sep } from "node:path";
|
|
2
|
-
import {
|
|
1
|
+
import { isAbsolute, join, sep } from "node:path";
|
|
2
|
+
import { minimalRoots as minimalProtectedRoots } from "../paths";
|
|
3
|
+
import { containsControl } from "../value-guards";
|
|
4
|
+
import { insideRuntimeRoot, isRuntimeHomeRoot } from "../runtime/paths";
|
|
5
|
+
import { workspaceBoundaryException } from "../fixtures/workspace-state";
|
|
3
6
|
import {
|
|
4
7
|
nativeStatePath,
|
|
5
8
|
nativeStateProtectedRoots,
|
|
@@ -17,12 +20,7 @@ const LINUX_MINIMAL_READ_ROOTS = [
|
|
|
17
20
|
];
|
|
18
21
|
|
|
19
22
|
function toml(value: string): string {
|
|
20
|
-
if (
|
|
21
|
-
Array.from(value).some(
|
|
22
|
-
(character) =>
|
|
23
|
-
character.charCodeAt(0) < 0x20 || character.charCodeAt(0) === 0x7f,
|
|
24
|
-
)
|
|
25
|
-
)
|
|
23
|
+
if (containsControl(value))
|
|
26
24
|
throw new Error("Codex profile value contains a control character");
|
|
27
25
|
return JSON.stringify(value);
|
|
28
26
|
}
|
|
@@ -39,7 +37,9 @@ export function codexPermissionProfile(options: {
|
|
|
39
37
|
commandEnvironment?: Record<string, string>;
|
|
40
38
|
runtimeReadRoots?: string[];
|
|
41
39
|
nativeReadRoots?: string[];
|
|
40
|
+
nativeWriteRoots?: string[];
|
|
42
41
|
runtimeWriteRoot?: string;
|
|
42
|
+
localHttp?: boolean;
|
|
43
43
|
}): string {
|
|
44
44
|
if (!/^[a-z][a-z0-9_]*$/.test(options.id))
|
|
45
45
|
throw new Error("invalid Codex permission profile ID");
|
|
@@ -50,8 +50,7 @@ export function codexPermissionProfile(options: {
|
|
|
50
50
|
`default_permissions = ${toml(name)}`,
|
|
51
51
|
'approval_policy = "never"',
|
|
52
52
|
"allow_login_shell = false",
|
|
53
|
-
|
|
54
|
-
"hooks = false",
|
|
53
|
+
...codexFeatureLines(options.localHttp),
|
|
55
54
|
"",
|
|
56
55
|
"[shell_environment_policy]",
|
|
57
56
|
'inherit = "none"',
|
|
@@ -64,6 +63,7 @@ export function codexPermissionProfile(options: {
|
|
|
64
63
|
),
|
|
65
64
|
`HOME = ${toml(options.commandHome)}`,
|
|
66
65
|
`TMPDIR = ${toml(options.commandTemp)}`,
|
|
66
|
+
`TMPPREFIX = ${toml(join(options.commandTemp, sep))}`,
|
|
67
67
|
"",
|
|
68
68
|
`[permissions.${name}]`,
|
|
69
69
|
'extends = ":workspace"',
|
|
@@ -78,30 +78,55 @@ export function codexPermissionProfile(options: {
|
|
|
78
78
|
.sort()
|
|
79
79
|
.map((root) => `${toml(root)} = "deny"`),
|
|
80
80
|
...readRootLines(options),
|
|
81
|
+
...(options.nativeWriteRoots ?? []).map(
|
|
82
|
+
(root) => `${toml(root)} = "write"`,
|
|
83
|
+
),
|
|
81
84
|
`${toml(options.commandHome)} = "write"`,
|
|
82
85
|
`${toml(options.commandTemp)} = "write"`,
|
|
83
86
|
...optionalRootLines(options.runtimeWriteRoot, "write"),
|
|
87
|
+
// Override Codex's metadata carveout only for this isolated fixture.
|
|
88
|
+
`${toml(join(options.workspace, ".git"))} = "write"`,
|
|
84
89
|
"",
|
|
85
90
|
`[permissions.${name}.filesystem.":workspace_roots"]`,
|
|
86
91
|
'"." = "write"',
|
|
87
92
|
"",
|
|
88
93
|
`[permissions.${name}.network]`,
|
|
89
|
-
|
|
94
|
+
...networkProfile(options.localHttp),
|
|
90
95
|
"",
|
|
91
96
|
].join("\n");
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
function codexFeatureLines(localHttp: boolean | undefined): string[] {
|
|
100
|
+
return [
|
|
101
|
+
"[features]",
|
|
102
|
+
"hooks = false",
|
|
103
|
+
...(localHttp ? ["network_proxy = true"] : []),
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function networkProfile(localHttp: boolean | undefined): string[] {
|
|
108
|
+
if (!localHttp) return ["enabled = false"];
|
|
109
|
+
return [
|
|
110
|
+
"enabled = true",
|
|
111
|
+
"allow_local_binding = true",
|
|
112
|
+
"allow_upstream_proxy = false",
|
|
113
|
+
'domains = { "localhost" = "allow", "127.0.0.1" = "allow", "::1" = "allow" }',
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
94
117
|
function profileProtectedRoots(
|
|
95
118
|
options: Parameters<typeof codexPermissionProfile>[0],
|
|
96
119
|
): string[] {
|
|
97
120
|
const readRoots = profileReadRoots(options);
|
|
98
121
|
const trustedRoots = [
|
|
99
122
|
...readRoots,
|
|
123
|
+
...(options.nativeWriteRoots ?? []),
|
|
100
124
|
...(options.runtimeWriteRoot ? [options.runtimeWriteRoot] : []),
|
|
101
125
|
];
|
|
102
126
|
const baselines = [
|
|
103
127
|
...LINUX_MINIMAL_READ_ROOTS,
|
|
104
128
|
...readRoots,
|
|
129
|
+
...(options.nativeWriteRoots ?? []),
|
|
105
130
|
options.workspace,
|
|
106
131
|
options.commandHome,
|
|
107
132
|
options.commandTemp,
|
|
@@ -111,7 +136,20 @@ function profileProtectedRoots(
|
|
|
111
136
|
(root) =>
|
|
112
137
|
(!implicitLinuxHomeBoundary(root, readRoots, baselines) ||
|
|
113
138
|
process.platform !== "linux") &&
|
|
114
|
-
!implicitNativeBoundary(root, trustedRoots, baselines)
|
|
139
|
+
!implicitNativeBoundary(root, trustedRoots, baselines) &&
|
|
140
|
+
!implicitWorkspaceBoundary(root, options.workspace, baselines),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function implicitWorkspaceBoundary(
|
|
145
|
+
root: string,
|
|
146
|
+
workspace: string,
|
|
147
|
+
baselines: string[],
|
|
148
|
+
): boolean {
|
|
149
|
+
return (
|
|
150
|
+
process.platform === "linux" &&
|
|
151
|
+
workspaceBoundaryException(root, workspace).length > 0 &&
|
|
152
|
+
!hasBroaderBaseline(root, root, baselines)
|
|
115
153
|
);
|
|
116
154
|
}
|
|
117
155
|
|
|
@@ -178,15 +216,6 @@ function implicitLinuxHomeBoundary(
|
|
|
178
216
|
);
|
|
179
217
|
}
|
|
180
218
|
|
|
181
|
-
function minimalProtectedRoots(roots: string[]): string[] {
|
|
182
|
-
return [...new Set(roots)].filter(
|
|
183
|
-
(root) =>
|
|
184
|
-
!roots.some(
|
|
185
|
-
(other) => other !== root && root.startsWith(`${other}${sep}`),
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
219
|
function commandEnvironmentLines(
|
|
191
220
|
environment: Record<string, string> = {},
|
|
192
221
|
executableReadRoots: string[],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Dirent } from "node:fs";
|
|
2
2
|
import { lstat, readFile, readdir, stat } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { isMissingFile } from "../
|
|
4
|
+
import { isMissingFile } from "../fixtures/tools";
|
|
5
5
|
|
|
6
6
|
const MAX_SESSION_BYTES = 8 * 1024 * 1024;
|
|
7
7
|
const MAX_SESSION_FILES = 1024;
|
|
@@ -104,11 +104,3 @@ export async function readNativeSession(path: string): Promise<string | null> {
|
|
|
104
104
|
if (bytes.byteLength > MAX_SESSION_BYTES) return null;
|
|
105
105
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
export async function readNativeSessionText(
|
|
109
|
-
path: string,
|
|
110
|
-
): Promise<string | null> {
|
|
111
|
-
if ((await stat(path)).size > MAX_SESSION_BYTES) return null;
|
|
112
|
-
const text = await readFile(path, "utf8");
|
|
113
|
-
return Buffer.byteLength(text) > MAX_SESSION_BYTES ? null : text;
|
|
114
|
-
}
|