@bjoernrochel/sevro 0.1.0-rc.2 → 0.1.0-rc.21
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 +170 -0
- package/LICENSE +63 -136
- package/README.md +83 -66
- package/docs/README.md +44 -0
- package/docs/acknowledgements.md +14 -0
- package/docs/architecture.md +146 -0
- package/docs/assets/README.md +27 -0
- package/docs/assets/sevro-logo.png +0 -0
- package/docs/assets/sevro-symbol.svg +1 -0
- package/docs/assets/sevro-wordmark.svg +1 -0
- package/docs/builtin-graders-v1.md +41 -13
- package/docs/codex-host.md +184 -52
- package/docs/contracts.md +19 -0
- package/docs/creating-cases.md +139 -0
- package/docs/development-cli.md +190 -30
- package/docs/documentation-quality.md +61 -0
- package/docs/evaluation-workflow.md +110 -0
- package/docs/evidence/code-mode-controls-71.md +64 -0
- package/docs/evidence/code-mode-programs-85.md +64 -0
- package/docs/evidence/codex-state-73/README.md +204 -0
- package/docs/evidence/codex-state-73/darrow-migration.patch +182 -0
- package/docs/evidence/codex-state-73/results.json +661 -0
- package/docs/evidence/local-http-74.md +104 -0
- package/docs/evidence/runtime-startup-30/README.md +185 -0
- package/docs/evidence/runtime-startup-30/after.json +319 -0
- package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +2 -0
- package/docs/evidence/runtime-startup-30/before.json +403 -0
- package/docs/evidence/runtime-startup-30/consumer-smoke.json +63 -0
- package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +3 -0
- package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +20 -0
- package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +2 -0
- package/docs/evidence/skill-reads-70/results.json +823 -0
- package/docs/evidence.md +21 -0
- package/docs/extension-protocol-v1.md +50 -16
- package/docs/fixture-cleanup-validation.md +3 -0
- package/docs/getting-started.md +102 -0
- package/docs/guide-evaluation.md +89 -0
- package/docs/guide-verification.json +239 -0
- package/docs/identity-v1.md +35 -1
- package/docs/installing.md +88 -0
- package/docs/licenses/BUSL-1.1-0.1.0-rc.2.txt +159 -0
- package/docs/licensing.md +42 -0
- package/docs/native-controls.md +76 -5
- package/docs/native-hosts.md +228 -0
- package/docs/reading-results.md +141 -0
- package/docs/recipes.md +489 -0
- package/docs/releases.md +81 -40
- package/docs/report-v1.md +29 -1
- package/docs/results-v1.md +180 -0
- package/docs/run-settings-v1.md +147 -0
- package/docs/running-evaluations.md +31 -0
- package/docs/runtime-v1.md +614 -0
- package/docs/specs/repository-guide.md +58 -0
- package/docs/troubleshooting.md +30 -0
- package/docs/typescript-quality.md +234 -0
- package/docs/usability-study.md +114 -0
- package/examples/basic/host.ts +2 -2
- 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 +39 -6
- package/schemas/report-v1.schema.json +9 -0
- package/schemas/run-evidence-v1.schema.json +2 -1
- package/schemas/runtime-v1.schema.json +193 -0
- package/src/advisory.ts +10 -5
- package/src/candidate-artifacts.ts +208 -0
- package/src/candidate-codex-state.ts +185 -0
- package/src/case-validation.ts +105 -0
- package/src/cli/cancellation.ts +20 -0
- package/src/cli/command.ts +81 -0
- package/src/cli/completion.ts +15 -0
- package/src/cli/demo.ts +59 -0
- package/src/cli/evaluation.ts +55 -0
- package/src/cli/extension.ts +79 -0
- package/src/cli/help.ts +222 -0
- package/src/cli/hosts.ts +59 -0
- package/src/cli/init-settings.ts +30 -0
- package/src/cli/init.ts +111 -0
- package/src/cli/invocation.ts +627 -0
- package/src/cli/loading.ts +151 -0
- package/src/cli/preparation.ts +163 -0
- package/src/cli/presentation.ts +91 -0
- package/src/cli/progress.ts +6 -0
- package/src/cli/run-manifest.ts +133 -0
- package/src/cli/run-options.ts +101 -0
- package/src/cli/run-overrides.ts +83 -0
- package/src/cli/settings.ts +160 -0
- package/src/cli/summary.ts +43 -0
- package/src/cli.ts +13 -843
- package/src/contract-types.ts +94 -0
- package/src/engine.ts +19 -2373
- package/src/evaluation/advisory.ts +150 -0
- package/src/evaluation/check-plan.ts +510 -0
- package/src/evaluation/collection.ts +88 -0
- package/src/evaluation/context.ts +10 -0
- package/src/evaluation/grading-host.ts +39 -0
- package/src/evaluation/grading.ts +258 -0
- package/src/evaluation/invocation-plan.ts +548 -0
- package/src/evaluation/plan.ts +736 -0
- package/src/evaluation/progress.ts +19 -0
- package/src/evaluation/resources.ts +241 -0
- package/src/evaluation/run.ts +206 -0
- package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +3 -1
- package/src/evaluation/semantic.ts +312 -0
- package/src/evaluation/trial.ts +866 -0
- package/src/evaluation/types.ts +207 -0
- package/src/evidence/host-results.ts +92 -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 +228 -148
- package/src/extension-session.ts +326 -195
- package/src/fixtures/advisory.ts +338 -0
- package/src/fixtures/bin.ts +57 -0
- package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
- package/src/fixtures/create.ts +187 -0
- package/src/fixtures/generated.ts +300 -0
- package/src/fixtures/git-hooks.ts +24 -0
- package/src/fixtures/preparation.ts +270 -0
- package/src/{repository-fixture.ts → fixtures/repository.ts} +207 -85
- package/src/fixtures/setup.ts +224 -0
- package/src/fixtures/tools.ts +88 -0
- package/src/fixtures/workspace-state.ts +212 -0
- package/src/generated/cli-result.cjs +2 -0
- package/src/generated/cli-result.d.cts +3 -0
- package/src/generated/extension.cjs +2 -0
- package/src/generated/extension.d.cts +3 -0
- package/src/generated/report.cjs +2 -0
- package/src/generated/report.d.cts +3 -0
- package/src/generated/run-evidence.cjs +2 -0
- package/src/generated/run-evidence.d.cts +3 -0
- package/src/generated/runtime.cjs +2 -0
- package/src/generated/runtime.d.cts +3 -0
- package/src/graders/artifact.ts +56 -23
- package/src/graders/git-head.ts +79 -75
- package/src/graders/output.ts +222 -140
- package/src/graders/semantic.ts +102 -43
- package/src/graders/shell.ts +379 -207
- package/src/host-diagnostic-redaction.ts +52 -0
- package/src/host-diagnostics.ts +319 -0
- package/src/hosts/claude-continuation.ts +63 -18
- package/src/hosts/claude-credential.ts +43 -62
- package/src/hosts/claude-events.ts +86 -51
- package/src/hosts/claude-keychain.ts +59 -0
- package/src/hosts/claude-native-goal.ts +57 -33
- package/src/hosts/claude-process.ts +111 -0
- package/src/hosts/claude-repository-invocation.ts +208 -117
- package/src/hosts/claude-settings.ts +240 -21
- package/src/hosts/claude-skill-activations.ts +166 -0
- package/src/hosts/claude-state.ts +403 -0
- package/src/hosts/claude-tool-calls.ts +171 -106
- package/src/hosts/claude.ts +561 -451
- package/src/hosts/codex-app-server.ts +326 -178
- package/src/hosts/codex-configuration.ts +44 -18
- package/src/hosts/codex-events.ts +97 -60
- package/src/hosts/codex-exec-controls.ts +516 -0
- package/src/hosts/codex-invocation.ts +70 -0
- package/src/hosts/codex-marketplace.ts +283 -0
- package/src/hosts/codex-native-calls.ts +546 -500
- package/src/hosts/codex-process.ts +202 -0
- package/src/hosts/codex-profile.ts +257 -28
- package/src/hosts/codex-session-files.ts +106 -0
- package/src/hosts/codex-state.ts +492 -0
- package/src/hosts/codex.ts +783 -832
- package/src/hosts/isolation-common.ts +94 -0
- package/src/hosts/isolation-roots.ts +54 -27
- package/src/hosts/isolation.ts +10 -0
- package/src/hosts/linux-sandbox.ts +159 -0
- package/src/hosts/mac-sandbox.ts +187 -61
- package/src/hosts/native-controls.ts +152 -62
- package/src/hosts/process-lifecycle.ts +37 -0
- package/src/hosts/protected-worktrees.ts +51 -19
- package/src/hosts/skill-hook-catalog.ts +131 -0
- package/src/hosts/skill-hook-handler.ts +374 -0
- package/src/hosts/skill-hooks.ts +580 -0
- package/src/hosts/workspace-fingerprint.ts +55 -35
- package/src/identity.ts +102 -48
- package/src/instrumentation.ts +109 -48
- package/src/native-transcript-state.ts +131 -0
- package/src/native-transcript-view.ts +90 -0
- package/src/native-transcripts.ts +286 -0
- package/src/process-lifecycle.ts +150 -0
- package/src/provenance.ts +161 -81
- package/src/prune.ts +215 -0
- package/src/report.ts +172 -140
- package/src/resolved-case.ts +33 -0
- package/src/results-lock.ts +29 -0
- package/src/results.ts +133 -86
- package/src/run-owner.ts +154 -63
- package/src/runtime/config.ts +456 -0
- package/src/runtime/discovery-apple.ts +90 -0
- package/src/runtime/discovery-providers.ts +63 -0
- package/src/runtime/discovery.ts +70 -0
- package/src/runtime/hook-processes.ts +97 -0
- package/src/runtime/hooks.ts +476 -0
- package/src/runtime/paths.ts +313 -0
- package/src/runtime/query.ts +85 -0
- package/src/runtime/seeds.ts +134 -0
- package/src/runtime/shared-cache.ts +143 -0
- package/src/runtime/state.ts +143 -0
- package/src/schema-types.ts +198 -0
- package/src/schema.ts +15 -19
- package/src/transcript-retention.ts +146 -0
- package/src/value-guards.ts +20 -0
- package/src/advisory-fixture.ts +0 -282
- package/src/fixture-bin.ts +0 -87
- package/src/fixture-setup.ts +0 -163
- package/src/generated-fixture.ts +0 -220
- package/src/git-hooks.ts +0 -63
- package/src/hosts/claude-nested-skills.ts +0 -218
- package/src/hosts/codex-command-output.ts +0 -399
- package/src/hosts/codex-executor-command.ts +0 -195
- package/src/hosts/codex-skill-reads.ts +0 -391
- package/src/preparation.ts +0 -211
package/src/hosts/codex.ts
CHANGED
|
@@ -1,880 +1,831 @@
|
|
|
1
|
-
import { runCodexAppServer } from "./codex-app-server";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
1
|
import { existsSync } from "node:fs";
|
|
4
2
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
realpath,
|
|
11
|
-
rm,
|
|
12
|
-
stat,
|
|
13
|
-
writeFile,
|
|
14
|
-
} from "node:fs/promises";
|
|
3
|
+
requireCandidateRuntime,
|
|
4
|
+
requireCandidateArtifactRoots,
|
|
5
|
+
} from "../candidate-artifacts";
|
|
6
|
+
import { candidateCodexStateRetained } from "../candidate-codex-state";
|
|
7
|
+
import { lstat, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
15
8
|
import { tmpdir } from "node:os";
|
|
16
|
-
import {
|
|
17
|
-
import type { HostAdapter } from "../engine";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
import { isAbsolute, join } from "node:path";
|
|
10
|
+
import type { HostAdapter, HostResult } from "../engine";
|
|
11
|
+
import {
|
|
12
|
+
allocateNativeState,
|
|
13
|
+
requireNativeTranscriptView,
|
|
14
|
+
} from "../native-transcript-state";
|
|
15
|
+
import {
|
|
16
|
+
failedNativeTranscripts,
|
|
17
|
+
retainEnabledNativeTranscripts,
|
|
18
|
+
} from "../native-transcripts";
|
|
19
|
+
import { runtimeNativeGoalEnabled } from "../runtime/config";
|
|
20
|
+
import {
|
|
21
|
+
releaseRuntimeHooks,
|
|
22
|
+
runtimeHookObservation,
|
|
23
|
+
type HookMounts,
|
|
24
|
+
} from "../runtime/hooks";
|
|
25
|
+
import { runtimeObservations } from "../runtime/state";
|
|
26
|
+
import { isOptionalNonblankString } from "../value-guards";
|
|
27
|
+
import { runCodexAppServer } from "./codex-app-server";
|
|
28
|
+
import { summarizeCodexEvents, type CodexEventSummary } from "./codex-events";
|
|
29
|
+
import { verifyCodexInvocation } from "./codex-invocation";
|
|
30
|
+
import { installCodexPlugins } from "./codex-marketplace";
|
|
21
31
|
import {
|
|
22
32
|
codexNativeCallObservation,
|
|
23
|
-
codexNativeSkillReadRecovery,
|
|
24
33
|
codexNativeSessionLastOrdinal,
|
|
25
34
|
} from "./codex-native-calls";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
|
|
35
|
+
import { runCodexProcess, CodexProcessFailure } from "./codex-process";
|
|
36
|
+
import {
|
|
37
|
+
codexHookMounts,
|
|
38
|
+
prepareCodexState,
|
|
39
|
+
type CodexHostOptions,
|
|
40
|
+
} from "./codex-state";
|
|
41
|
+
import { codexNativeControls } from "./native-controls";
|
|
29
42
|
import { workspaceFingerprint } from "./workspace-fingerprint";
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
import { codexSkillCatalogRoots } from "./skill-hook-catalog";
|
|
44
|
+
import {
|
|
45
|
+
prepareSkillTracking,
|
|
46
|
+
skillTrackingObservation,
|
|
47
|
+
skillActivationClaims,
|
|
48
|
+
skillExposureClaims,
|
|
49
|
+
type SkillTracking,
|
|
50
|
+
} from "./skill-hooks";
|
|
51
|
+
import {
|
|
52
|
+
cleanupNativeState,
|
|
53
|
+
createHostDiagnostics,
|
|
54
|
+
diagnoseHostRequest,
|
|
55
|
+
diagnosticConfiguration,
|
|
56
|
+
diagnosticStage,
|
|
57
|
+
readDiagnosticCredentials,
|
|
58
|
+
retainReturnedHostFailure,
|
|
59
|
+
type HostDiagnostics,
|
|
60
|
+
} from "../host-diagnostics";
|
|
61
|
+
export type { CodexHostOptions } from "./codex-state";
|
|
33
62
|
const DEFAULT_TIMEOUT_MS = 10 * 60_000;
|
|
34
63
|
const MAX_TIMEOUT_MS = 30 * 60_000;
|
|
35
64
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw new Error("invalid Codex explicit skill invocation");
|
|
57
|
-
let path = request.workspace;
|
|
58
|
-
for (const [index, part] of [
|
|
59
|
-
".agents",
|
|
60
|
-
"skills",
|
|
61
|
-
skillName,
|
|
62
|
-
"SKILL.md",
|
|
63
|
-
].entries()) {
|
|
64
|
-
path = join(path, part);
|
|
65
|
-
const entry = await lstat(path).catch(() => null);
|
|
66
|
-
if (
|
|
67
|
-
!entry ||
|
|
68
|
-
entry.isSymbolicLink() ||
|
|
69
|
-
(index === 3
|
|
70
|
-
? !entry.isFile() || entry.size > 1024 * 1024
|
|
71
|
-
: !entry.isDirectory())
|
|
72
|
-
)
|
|
73
|
-
throw new Error(
|
|
74
|
-
"invoked Codex repository skill is unavailable or unsafe",
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const { pluginName } = selected;
|
|
80
|
-
if (
|
|
81
|
-
!request.codexMarketplace?.pluginNames.includes(pluginName) ||
|
|
82
|
-
!/^[a-z][a-z0-9-]*$/.test(pluginName) ||
|
|
83
|
-
token !== `$${pluginName}:${skillName}`
|
|
84
|
-
)
|
|
85
|
-
throw new Error("invalid Codex explicit skill invocation");
|
|
65
|
+
type Request = Parameters<HostAdapter["run"]>[0];
|
|
66
|
+
type ProcessResult = Pick<
|
|
67
|
+
Awaited<ReturnType<typeof runCodexProcess>>,
|
|
68
|
+
"code" | "out"
|
|
69
|
+
>;
|
|
70
|
+
type AppServerResult = Awaited<ReturnType<typeof runCodexAppServer>>;
|
|
71
|
+
type Observation = {
|
|
72
|
+
id: string;
|
|
73
|
+
completeness: "complete" | "partial";
|
|
74
|
+
data: Record<string, unknown>;
|
|
75
|
+
};
|
|
76
|
+
type CodexState = Awaited<ReturnType<typeof prepareCodexState>>;
|
|
77
|
+
interface ExecutionContext {
|
|
78
|
+
options: CodexHostOptions;
|
|
79
|
+
request: Request;
|
|
80
|
+
state: CodexState;
|
|
81
|
+
timeoutMs: number;
|
|
82
|
+
hooks: HookMounts;
|
|
83
|
+
skillTracking: SkillTracking;
|
|
84
|
+
diagnostics?: HostDiagnostics;
|
|
86
85
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
resultsRoot: string;
|
|
95
|
-
additionalProtectedRoots: string[];
|
|
96
|
-
agentConcurrencyLimit?: number | null;
|
|
97
|
-
timeoutMs?: number;
|
|
98
|
-
entrypoint?: "exec" | "app-server";
|
|
99
|
-
/** Integration-test seam; production uses the same CLI for execution and preflight. */
|
|
100
|
-
sandboxBinary?: string;
|
|
86
|
+
interface TurnResult {
|
|
87
|
+
execution: ProcessResult;
|
|
88
|
+
summary: CodexEventSummary;
|
|
89
|
+
followUp: ProcessResult | null;
|
|
90
|
+
followUpSummary: CodexEventSummary | null;
|
|
91
|
+
appServer: AppServerResult | null;
|
|
92
|
+
continuation: Observation | undefined;
|
|
101
93
|
}
|
|
102
94
|
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
95
|
+
function validCodexPaths(options: CodexHostOptions): boolean {
|
|
96
|
+
return (
|
|
97
|
+
[
|
|
98
|
+
options.binary,
|
|
99
|
+
options.authFile,
|
|
100
|
+
options.projectRoot,
|
|
101
|
+
options.resultsRoot,
|
|
102
|
+
...options.additionalProtectedRoots,
|
|
103
|
+
].every(isAbsolute) &&
|
|
104
|
+
(options.sandboxBinary === undefined || isAbsolute(options.sandboxBinary))
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function validConcurrencyLimit(limit: number | null): boolean {
|
|
108
|
+
return limit === null || (Number.isSafeInteger(limit) && limit >= 1);
|
|
109
|
+
}
|
|
110
|
+
function validEntrypoint(entrypoint: CodexHostOptions["entrypoint"]): boolean {
|
|
111
|
+
return (
|
|
112
|
+
entrypoint === undefined || ["exec", "app-server"].includes(entrypoint)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
function validCodexTimeout(timeoutMs: number): boolean {
|
|
116
|
+
return (
|
|
117
|
+
Number.isSafeInteger(timeoutMs) &&
|
|
118
|
+
timeoutMs >= 1 &&
|
|
119
|
+
timeoutMs <= MAX_TIMEOUT_MS
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
function validCodexIdentity(
|
|
123
|
+
options: CodexHostOptions,
|
|
124
|
+
limit: number | null,
|
|
125
|
+
timeoutMs: number,
|
|
126
|
+
): boolean {
|
|
127
|
+
return (
|
|
128
|
+
!!options.model &&
|
|
129
|
+
!!options.effort &&
|
|
130
|
+
validEntrypoint(options.entrypoint) &&
|
|
131
|
+
validConcurrencyLimit(limit) &&
|
|
132
|
+
validCodexTimeout(timeoutMs)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function codexCapabilities(
|
|
136
|
+
entrypoint: CodexHostOptions["entrypoint"],
|
|
137
|
+
): string[] {
|
|
138
|
+
return [
|
|
139
|
+
"sevro.host.continuation",
|
|
140
|
+
"sevro.host.runtime",
|
|
141
|
+
"sevro.host.native-transcripts",
|
|
142
|
+
"sevro.host.hooks",
|
|
143
|
+
"sevro.host.skill-activations",
|
|
144
|
+
"sevro.host.skill-activation-claims",
|
|
145
|
+
"sevro.host.skill-exposure-claims",
|
|
146
|
+
...(entrypoint === "app-server" ? ["sevro.host.native-goal"] : []),
|
|
147
|
+
"sevro.codex.plugin-marketplace",
|
|
148
|
+
"sevro.codex.explicit-invocation",
|
|
149
|
+
"sevro.codex.repository-invocation",
|
|
150
|
+
"sevro.codex.native-calls",
|
|
151
|
+
"sevro.host.native-controls",
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
function codexConfiguration(
|
|
155
|
+
limit: number | null,
|
|
156
|
+
entrypoint: CodexHostOptions["entrypoint"],
|
|
157
|
+
retainDiagnostics?: boolean,
|
|
158
|
+
) {
|
|
159
|
+
return {
|
|
160
|
+
...diagnosticConfiguration(retainDiagnostics),
|
|
161
|
+
"sevro.codex.agent-concurrency-limit": limit,
|
|
162
|
+
...(entrypoint === "app-server"
|
|
163
|
+
? { "sevro.codex.entrypoint": "app-server" }
|
|
164
|
+
: {}),
|
|
165
|
+
};
|
|
117
166
|
}
|
|
118
167
|
|
|
119
|
-
|
|
120
|
-
|
|
168
|
+
/** Construct one Codex route without inheriting user settings or credentials. */
|
|
169
|
+
export function createCodexHost(options: CodexHostOptions): HostAdapter {
|
|
170
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
171
|
+
const limit = options.agentConcurrencyLimit ?? null;
|
|
172
|
+
if (
|
|
173
|
+
!validCodexPaths(options) ||
|
|
174
|
+
!validCodexIdentity(options, limit, timeoutMs)
|
|
175
|
+
)
|
|
176
|
+
throw new Error("invalid Codex host configuration");
|
|
177
|
+
return {
|
|
178
|
+
id: "sevro.host.codex",
|
|
179
|
+
hostCapabilities: codexCapabilities(options.entrypoint),
|
|
180
|
+
model: options.model,
|
|
181
|
+
effort: options.effort,
|
|
182
|
+
configuration: codexConfiguration(
|
|
183
|
+
limit,
|
|
184
|
+
options.entrypoint,
|
|
185
|
+
options.retainHostDiagnostics,
|
|
186
|
+
),
|
|
187
|
+
run(request) {
|
|
188
|
+
return runCodexRequest(options, timeoutMs, limit, request);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
121
191
|
}
|
|
122
192
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
193
|
+
function requireCodexFollowUp(request: Request): void {
|
|
194
|
+
if (!isOptionalNonblankString(request.followUpPrompt))
|
|
195
|
+
throw new Error("Codex follow-up prompt must be nonempty");
|
|
196
|
+
}
|
|
197
|
+
function requireCodexFixtureBin(request: Request): void {
|
|
198
|
+
if (
|
|
199
|
+
request.fixtureBinDir !== undefined &&
|
|
200
|
+
request.fixtureBinDir !== join(request.workspace, ".git", "fixture-bin")
|
|
201
|
+
)
|
|
202
|
+
throw new Error("fixture binary path is outside the workspace");
|
|
203
|
+
}
|
|
204
|
+
function requirePassiveCodexRequest(request: Request): void {
|
|
205
|
+
if (request.instrumentation?.length)
|
|
206
|
+
throw new Error("Codex instrumentation is unavailable");
|
|
207
|
+
if (request.condition !== "passive")
|
|
208
|
+
throw new Error("Codex enforcement instrumentation is unavailable");
|
|
209
|
+
}
|
|
210
|
+
function allocateCodexRequestState(request: Request): Promise<string> {
|
|
211
|
+
return allocateNativeState(
|
|
212
|
+
"codex-",
|
|
213
|
+
request.runtimePolicy ? request.workspace : undefined,
|
|
140
214
|
);
|
|
141
215
|
}
|
|
142
216
|
|
|
143
|
-
async function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
stderr: "ignore",
|
|
217
|
+
async function runCodexRequest(
|
|
218
|
+
options: CodexHostOptions,
|
|
219
|
+
timeoutMs: number,
|
|
220
|
+
limit: number | null,
|
|
221
|
+
request: Request,
|
|
222
|
+
): Promise<HostResult> {
|
|
223
|
+
const diagnostics = createHostDiagnostics(
|
|
224
|
+
"codex",
|
|
225
|
+
options.retainHostDiagnostics,
|
|
226
|
+
request,
|
|
227
|
+
);
|
|
228
|
+
return diagnoseHostRequest(diagnostics, async () => {
|
|
229
|
+
diagnosticStage(diagnostics, "request-validation");
|
|
230
|
+
await readDiagnosticCredentials(diagnostics, options.authFile);
|
|
231
|
+
return executeCodexRequest(options, timeoutMs, limit, request, diagnostics);
|
|
159
232
|
});
|
|
160
|
-
|
|
161
|
-
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function executeCodexRequest(
|
|
236
|
+
options: CodexHostOptions,
|
|
237
|
+
timeoutMs: number,
|
|
238
|
+
limit: number | null,
|
|
239
|
+
request: Request,
|
|
240
|
+
diagnostics?: HostDiagnostics,
|
|
241
|
+
): Promise<HostResult> {
|
|
242
|
+
await validateCodexRequest(options, request);
|
|
243
|
+
diagnosticStage(diagnostics, "state-allocation");
|
|
244
|
+
const stateRoot = await allocateCodexRequestState(request);
|
|
245
|
+
const helperRoot =
|
|
246
|
+
process.platform === "linux"
|
|
247
|
+
? await mkdtemp(join(tmpdir(), "sevro-codex-helper-"))
|
|
248
|
+
: undefined;
|
|
249
|
+
let activeHooks: HookMounts | undefined;
|
|
162
250
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
() => reject(new Error("Codex run timed out")),
|
|
178
|
-
options.timeoutMs,
|
|
179
|
-
);
|
|
180
|
-
});
|
|
181
|
-
const aborted = new Promise<never>((_resolve, reject) => {
|
|
182
|
-
if (!options.signal) return;
|
|
183
|
-
cancel = () => reject(new Error("Codex run cancelled"));
|
|
184
|
-
if (options.signal.aborted) cancel();
|
|
185
|
-
else options.signal.addEventListener("abort", cancel, { once: true });
|
|
251
|
+
diagnosticStage(diagnostics, "state-preparation");
|
|
252
|
+
const state = await prepareCodexState(
|
|
253
|
+
stateRoot,
|
|
254
|
+
options,
|
|
255
|
+
limit,
|
|
256
|
+
request,
|
|
257
|
+
helperRoot,
|
|
258
|
+
);
|
|
259
|
+
const installedPluginRoots = await installCodexPlugins({
|
|
260
|
+
binary: options.binary,
|
|
261
|
+
env: state.env,
|
|
262
|
+
pluginCacheRoot: state.pluginCacheRoot,
|
|
263
|
+
request,
|
|
264
|
+
diagnostics,
|
|
186
265
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
await
|
|
191
|
-
|
|
266
|
+
diagnosticStage(diagnostics, "hooks-preparation");
|
|
267
|
+
const hooks = await codexHookMounts(request, state, installedPluginRoots);
|
|
268
|
+
activeHooks = hooks;
|
|
269
|
+
const skillTracking = await prepareCodexSkillTracking(
|
|
270
|
+
state,
|
|
271
|
+
request,
|
|
272
|
+
installedPluginRoots,
|
|
273
|
+
);
|
|
274
|
+
await codexPreflight(options, request, state, diagnostics);
|
|
275
|
+
const context: ExecutionContext = {
|
|
276
|
+
options,
|
|
277
|
+
request,
|
|
278
|
+
state,
|
|
279
|
+
timeoutMs,
|
|
280
|
+
hooks,
|
|
281
|
+
skillTracking,
|
|
282
|
+
diagnostics,
|
|
283
|
+
};
|
|
284
|
+
const turns = await new CodexTurnExecution(context).run();
|
|
285
|
+
if (turns.summary.complete)
|
|
286
|
+
diagnosticStage(diagnostics, "result-processing");
|
|
287
|
+
return await retainCodexResult(context, turns);
|
|
288
|
+
} catch (cause) {
|
|
289
|
+
return await failedNativeTranscripts(
|
|
290
|
+
cause,
|
|
291
|
+
request.runtimePolicy,
|
|
292
|
+
join(stateRoot, "codex-home", "sessions"),
|
|
293
|
+
"codex",
|
|
294
|
+
request.runtimeRole,
|
|
295
|
+
diagnostics,
|
|
296
|
+
);
|
|
192
297
|
} finally {
|
|
193
|
-
|
|
194
|
-
|
|
298
|
+
await cleanupNativeState(diagnostics, async () => {
|
|
299
|
+
await releaseRuntimeHooks(activeHooks);
|
|
300
|
+
if (!candidateCodexStateRetained(request.candidateRuntimeRoot, stateRoot))
|
|
301
|
+
await rm(stateRoot, { recursive: true, force: true });
|
|
302
|
+
if (helperRoot) await rm(helperRoot, { recursive: true, force: true });
|
|
303
|
+
});
|
|
195
304
|
}
|
|
196
305
|
}
|
|
197
306
|
|
|
198
|
-
async function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
307
|
+
async function retainCodexResult(context: ExecutionContext, turns: TurnResult) {
|
|
308
|
+
const result = await codexHostResult(context, turns);
|
|
309
|
+
await retainReturnedHostFailure(
|
|
310
|
+
context.diagnostics,
|
|
311
|
+
result,
|
|
312
|
+
"Codex host execution did not complete",
|
|
313
|
+
);
|
|
314
|
+
await retainEnabledNativeTranscripts(
|
|
315
|
+
result,
|
|
316
|
+
context.request.runtimePolicy,
|
|
317
|
+
join(context.state.codexHome, "sessions"),
|
|
318
|
+
"codex",
|
|
319
|
+
context.request.runtimeRole,
|
|
320
|
+
turns.summary.threadId,
|
|
321
|
+
);
|
|
322
|
+
return result;
|
|
210
323
|
}
|
|
211
324
|
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
325
|
+
async function prepareCodexSkillTracking(
|
|
326
|
+
state: CodexState,
|
|
327
|
+
request: Request,
|
|
328
|
+
pluginRoots: string[],
|
|
329
|
+
): Promise<SkillTracking> {
|
|
330
|
+
const roots = await codexSkillCatalogRoots(request.workspace, pluginRoots, [
|
|
331
|
+
{ directory: join(state.codexHome, "skills"), boundary: state.codexHome },
|
|
332
|
+
{
|
|
333
|
+
directory: join(state.parentHome, ".agents/skills"),
|
|
334
|
+
boundary: state.parentHome,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
directory: join(state.commandHome, ".agents/skills"),
|
|
338
|
+
boundary: state.commandHome,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
directory: join(state.commandHome, ".codex/skills"),
|
|
342
|
+
boundary: state.commandHome,
|
|
343
|
+
},
|
|
344
|
+
]);
|
|
345
|
+
const tracking = await prepareSkillTracking({
|
|
346
|
+
privateRoot: state.codexHome,
|
|
347
|
+
host: "codex",
|
|
348
|
+
roots,
|
|
349
|
+
});
|
|
350
|
+
await writeFile(
|
|
351
|
+
join(state.codexHome, "hooks.json"),
|
|
352
|
+
JSON.stringify({ hooks: tracking.hooks }),
|
|
353
|
+
{
|
|
354
|
+
flag: "wx",
|
|
355
|
+
mode: 0o600,
|
|
356
|
+
},
|
|
217
357
|
);
|
|
358
|
+
return tracking;
|
|
218
359
|
}
|
|
219
360
|
|
|
220
|
-
async function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
|
|
361
|
+
async function validateCodexRequest(
|
|
362
|
+
options: CodexHostOptions,
|
|
363
|
+
request: Request,
|
|
364
|
+
): Promise<void> {
|
|
365
|
+
requireCodexFollowUp(request);
|
|
366
|
+
await requireNativeTranscriptView(request.candidateTranscriptRoot);
|
|
367
|
+
await requireCandidateRuntime(
|
|
368
|
+
request.candidateRuntimeRoot,
|
|
369
|
+
request.runtimeRole,
|
|
370
|
+
);
|
|
371
|
+
await requireCandidateArtifactRoots(request.candidateArtifactRoots);
|
|
372
|
+
requireCodexFixtureBin(request);
|
|
373
|
+
requirePassiveCodexRequest(request);
|
|
374
|
+
requireCodexGoalRoute(request, options);
|
|
375
|
+
await verifyCodexInvocation(request);
|
|
376
|
+
await requireCodexFixtureConfiguration(request);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function requireCodexFixtureConfiguration(
|
|
380
|
+
request: Request,
|
|
381
|
+
): Promise<void> {
|
|
382
|
+
const path = join(request.workspace, ".codex");
|
|
383
|
+
if (!existsSync(path)) return;
|
|
384
|
+
if (request.runtimePolicy?.codexState !== true)
|
|
385
|
+
throw new Error("fixture Codex configuration is unsupported");
|
|
386
|
+
if (!(await lstat(path)).isDirectory())
|
|
387
|
+
throw new Error("fixture Codex configuration must be a physical directory");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function requireCodexGoalRoute(
|
|
391
|
+
request: Request,
|
|
392
|
+
options: CodexHostOptions,
|
|
393
|
+
): void {
|
|
228
394
|
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
pluginNames.some((name) => !/^[a-z][a-z0-9-]*$/.test(name)) ||
|
|
232
|
-
new Set(pluginNames).size !== pluginNames.length ||
|
|
233
|
-
!declaration.artifactPaths.length ||
|
|
234
|
-
expectedPaths.size !== declaration.artifactPaths.length ||
|
|
235
|
-
declaration.artifactPaths.some(
|
|
236
|
-
(path) =>
|
|
237
|
-
!path.startsWith(`${artifactRoot}/`) ||
|
|
238
|
-
fixtureParts(path).join("/") !== path,
|
|
239
|
-
)
|
|
395
|
+
runtimeNativeGoalEnabled(request.runtimePolicy) &&
|
|
396
|
+
options.entrypoint !== "app-server"
|
|
240
397
|
)
|
|
241
|
-
throw new Error("
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
398
|
+
throw new Error("native-goal runtime policy requires Codex app-server");
|
|
399
|
+
}
|
|
400
|
+
function sandboxArgs(
|
|
401
|
+
state: CodexState,
|
|
402
|
+
request: Request,
|
|
403
|
+
command: string[],
|
|
404
|
+
): string[] {
|
|
405
|
+
return [
|
|
406
|
+
state.sandboxBinary,
|
|
407
|
+
"sandbox",
|
|
408
|
+
"-P",
|
|
409
|
+
state.profileId,
|
|
410
|
+
"-C",
|
|
411
|
+
request.workspace,
|
|
412
|
+
...command,
|
|
413
|
+
];
|
|
414
|
+
}
|
|
415
|
+
async function sandboxPreflight(
|
|
416
|
+
state: CodexState,
|
|
417
|
+
request: Request,
|
|
418
|
+
command: string[],
|
|
419
|
+
diagnostics?: HostDiagnostics,
|
|
420
|
+
) {
|
|
421
|
+
return runCodexProcess({
|
|
422
|
+
diagnostics,
|
|
423
|
+
argv: sandboxArgs(state, request, command),
|
|
424
|
+
cwd: request.workspace,
|
|
425
|
+
env: state.env,
|
|
426
|
+
timeoutMs: 60_000,
|
|
427
|
+
phase: "isolation-preflight",
|
|
428
|
+
signal: request.signal,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async function codexPreflight(
|
|
432
|
+
options: CodexHostOptions,
|
|
433
|
+
request: Request,
|
|
434
|
+
state: CodexState,
|
|
435
|
+
diagnostics?: HostDiagnostics,
|
|
436
|
+
): Promise<void> {
|
|
437
|
+
diagnosticStage(diagnostics, "isolation-preflight");
|
|
438
|
+
const probe = join(state.commandTemp, "isolation-probe");
|
|
439
|
+
await writeFile(probe, "probe\n", { flag: "wx", mode: 0o600 });
|
|
440
|
+
const checked = await sandboxPreflight(
|
|
441
|
+
state,
|
|
442
|
+
request,
|
|
443
|
+
[
|
|
444
|
+
"/bin/sh",
|
|
445
|
+
"-c",
|
|
446
|
+
'/bin/cat "$1" >/dev/null && ! /bin/ls "$2" >/dev/null 2>&1 && ! /bin/cat "$3" >/dev/null 2>&1 && ! /bin/cat "$4" >/dev/null 2>&1 || exit 70; "$5" --version >/dev/null 2>&1 || exit 71',
|
|
447
|
+
"sevro-probe",
|
|
448
|
+
probe,
|
|
449
|
+
options.projectRoot,
|
|
450
|
+
join(state.codexHome, "auth.json"),
|
|
451
|
+
// The opt-in config read is tested separately; credentials always stay denied.
|
|
452
|
+
join(
|
|
453
|
+
state.codexHome,
|
|
454
|
+
request.runtimePolicy?.codexState === true
|
|
455
|
+
? "auth.json"
|
|
456
|
+
: "config.toml",
|
|
457
|
+
),
|
|
458
|
+
state.sandboxBinary,
|
|
459
|
+
],
|
|
460
|
+
diagnostics,
|
|
461
|
+
);
|
|
462
|
+
if (checked.code === 71)
|
|
463
|
+
throw new CodexProcessFailure("executable-preflight", checked);
|
|
464
|
+
if (checked.code !== 0)
|
|
465
|
+
throw new CodexProcessFailure("isolation-preflight", checked);
|
|
466
|
+
await rm(probe);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function routeArgs(context: ExecutionContext): string[] {
|
|
470
|
+
return [
|
|
471
|
+
"-m",
|
|
472
|
+
context.options.model,
|
|
473
|
+
"-c",
|
|
474
|
+
`model_reasoning_effort=${JSON.stringify(context.options.effort)}`,
|
|
475
|
+
"-c",
|
|
476
|
+
`default_permissions=${JSON.stringify(context.state.profileId)}`,
|
|
477
|
+
"-c",
|
|
478
|
+
'approval_policy="never"',
|
|
479
|
+
"-c",
|
|
480
|
+
"features.hooks=true",
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
function initialTurnArgs(context: ExecutionContext): string[] {
|
|
484
|
+
return [
|
|
485
|
+
context.options.binary,
|
|
486
|
+
"exec",
|
|
487
|
+
"--json",
|
|
488
|
+
"--strict-config",
|
|
489
|
+
"--skip-git-repo-check",
|
|
490
|
+
...(context.request.followUpPrompt ||
|
|
491
|
+
context.request.runtimePolicy?.nativeTranscripts
|
|
492
|
+
? []
|
|
493
|
+
: ["--ephemeral"]),
|
|
494
|
+
"--ignore-rules",
|
|
495
|
+
"--dangerously-bypass-hook-trust",
|
|
496
|
+
"-C",
|
|
497
|
+
context.request.workspace,
|
|
498
|
+
...routeArgs(context),
|
|
499
|
+
"-",
|
|
500
|
+
];
|
|
501
|
+
}
|
|
502
|
+
function followUpTurnArgs(
|
|
503
|
+
context: ExecutionContext,
|
|
504
|
+
threadId: string,
|
|
505
|
+
): string[] {
|
|
506
|
+
return [
|
|
507
|
+
context.options.binary,
|
|
508
|
+
"exec",
|
|
509
|
+
"resume",
|
|
510
|
+
"--json",
|
|
511
|
+
"--strict-config",
|
|
512
|
+
"--skip-git-repo-check",
|
|
513
|
+
"--ignore-rules",
|
|
514
|
+
"--dangerously-bypass-hook-trust",
|
|
515
|
+
...routeArgs(context),
|
|
516
|
+
threadId,
|
|
517
|
+
"-",
|
|
518
|
+
];
|
|
519
|
+
}
|
|
520
|
+
async function codexProcessTurn(
|
|
521
|
+
context: ExecutionContext,
|
|
522
|
+
argv: string[],
|
|
523
|
+
input: string,
|
|
524
|
+
): Promise<ProcessResult> {
|
|
525
|
+
diagnosticStage(context.diagnostics, "exec");
|
|
526
|
+
const result = await runCodexProcess({
|
|
527
|
+
diagnostics: context.diagnostics,
|
|
528
|
+
argv,
|
|
529
|
+
cwd: context.request.workspace,
|
|
530
|
+
env: context.state.env,
|
|
531
|
+
input,
|
|
532
|
+
timeoutMs: context.timeoutMs,
|
|
533
|
+
signal: context.request.signal,
|
|
534
|
+
});
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
async function initialFingerprint(request: Request): Promise<string | null> {
|
|
538
|
+
return request.followUpPrompt
|
|
539
|
+
? workspaceFingerprint(request.workspace)
|
|
540
|
+
: null;
|
|
541
|
+
}
|
|
542
|
+
async function continuationBoundary(
|
|
543
|
+
context: ExecutionContext,
|
|
544
|
+
threadId: string,
|
|
545
|
+
originalFingerprint: string | null,
|
|
546
|
+
goal: Record<string, unknown> = {},
|
|
547
|
+
): Promise<Observation> {
|
|
548
|
+
const boundary = await workspaceFingerprint(context.request.workspace);
|
|
549
|
+
const measured = originalFingerprint !== null && boundary !== null;
|
|
550
|
+
return {
|
|
551
|
+
id: "sevro.codex.continuation",
|
|
552
|
+
completeness: measured ? "complete" : "partial",
|
|
553
|
+
data: {
|
|
554
|
+
method: "same_thread_resume",
|
|
555
|
+
...goal,
|
|
556
|
+
threadId,
|
|
557
|
+
nativeAfterOrdinal: await codexNativeSessionLastOrdinal(
|
|
558
|
+
context.state.codexHome,
|
|
559
|
+
threadId,
|
|
253
560
|
),
|
|
561
|
+
preFollowUpWorktreeUnchanged: measured
|
|
562
|
+
? originalFingerprint === boundary
|
|
563
|
+
: null,
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function requireInitialCompletion(
|
|
568
|
+
summary: CodexEventSummary,
|
|
569
|
+
appServer: AppServerResult | null,
|
|
570
|
+
): void {
|
|
571
|
+
if (!summary.complete && !appServer)
|
|
572
|
+
throw new Error("Codex turn did not complete");
|
|
573
|
+
}
|
|
574
|
+
function requireFollowUpCompletion(
|
|
575
|
+
followUp: CodexEventSummary,
|
|
576
|
+
original: CodexEventSummary,
|
|
577
|
+
): void {
|
|
578
|
+
if (!followUp.complete || followUp.threadId !== original.threadId)
|
|
579
|
+
throw new Error(
|
|
580
|
+
"Codex follow-up turn did not complete in the original thread",
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
async function appServerSummary(
|
|
584
|
+
result: AppServerResult,
|
|
585
|
+
workspace: string,
|
|
586
|
+
): Promise<CodexEventSummary> {
|
|
587
|
+
return {
|
|
588
|
+
threadId: result.evidence.threadId ?? "",
|
|
589
|
+
complete: result.code === 0,
|
|
590
|
+
finalMessage:
|
|
591
|
+
result.code === 0
|
|
592
|
+
? await readFile(join(workspace, ".git", "last-message.md"), "utf8")
|
|
593
|
+
: null,
|
|
594
|
+
inputTokens: null,
|
|
595
|
+
outputTokens: null,
|
|
596
|
+
usageComplete: false,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async function initialSummary(
|
|
600
|
+
appServer: AppServerResult | null,
|
|
601
|
+
execution: ProcessResult,
|
|
602
|
+
request: Request,
|
|
603
|
+
): Promise<CodexEventSummary> {
|
|
604
|
+
return appServer
|
|
605
|
+
? appServerSummary(appServer, request.workspace)
|
|
606
|
+
: summarizeCodexEvents(execution.out, execution.code);
|
|
607
|
+
}
|
|
608
|
+
class CodexTurnExecution {
|
|
609
|
+
private appServerContinuation?: Observation;
|
|
610
|
+
constructor(private context: ExecutionContext) {}
|
|
611
|
+
async run(): Promise<TurnResult> {
|
|
612
|
+
const fingerprint = await initialFingerprint(this.context.request);
|
|
613
|
+
const appServer = await this.maybeAppServer(fingerprint);
|
|
614
|
+
const execution =
|
|
615
|
+
appServer ??
|
|
616
|
+
(await codexProcessTurn(
|
|
617
|
+
this.context,
|
|
618
|
+
initialTurnArgs(this.context),
|
|
619
|
+
this.context.request.prompt,
|
|
620
|
+
));
|
|
621
|
+
const summary = await initialSummary(
|
|
622
|
+
appServer,
|
|
623
|
+
execution,
|
|
624
|
+
this.context.request,
|
|
254
625
|
);
|
|
255
|
-
|
|
256
|
-
|
|
626
|
+
requireInitialCompletion(summary, appServer);
|
|
627
|
+
const follow = await this.maybeFollowUp(summary, appServer, fingerprint);
|
|
628
|
+
return {
|
|
629
|
+
execution,
|
|
630
|
+
summary,
|
|
631
|
+
appServer,
|
|
632
|
+
...follow,
|
|
633
|
+
};
|
|
257
634
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
635
|
+
|
|
636
|
+
private async maybeAppServer(
|
|
637
|
+
fingerprint: string | null,
|
|
638
|
+
): Promise<AppServerResult | null> {
|
|
639
|
+
const { options, request, state, timeoutMs } = this.context;
|
|
640
|
+
if (options.entrypoint !== "app-server") return null;
|
|
641
|
+
diagnosticStage(this.context.diagnostics, "app-server");
|
|
642
|
+
return runCodexAppServer({
|
|
643
|
+
diagnostics: this.context.diagnostics,
|
|
644
|
+
argv: [
|
|
645
|
+
options.binary,
|
|
646
|
+
"app-server",
|
|
647
|
+
"--stdio",
|
|
648
|
+
"--strict-config",
|
|
649
|
+
"-c",
|
|
650
|
+
`default_permissions=${JSON.stringify(state.profileId)}`,
|
|
651
|
+
"-c",
|
|
652
|
+
"features.hooks=true",
|
|
653
|
+
],
|
|
654
|
+
env: state.env,
|
|
655
|
+
permissionProfile: state.profileId,
|
|
656
|
+
vettedHooks: true,
|
|
657
|
+
request: {
|
|
658
|
+
repoDir: request.workspace,
|
|
659
|
+
prompt: request.prompt,
|
|
660
|
+
model: options.model,
|
|
661
|
+
effort: options.effort,
|
|
662
|
+
signal: request.signal,
|
|
663
|
+
control: {
|
|
664
|
+
appServerTimeoutMs: timeoutMs,
|
|
665
|
+
...(request.followUpPrompt
|
|
666
|
+
? { followUpPrompt: request.followUpPrompt }
|
|
667
|
+
: {}),
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
followUpBoundary: async (threadId, goal) => {
|
|
671
|
+
this.appServerContinuation = await continuationBoundary(
|
|
672
|
+
this.context,
|
|
673
|
+
threadId,
|
|
674
|
+
fingerprint,
|
|
675
|
+
goal,
|
|
676
|
+
);
|
|
677
|
+
return JSON.stringify({
|
|
678
|
+
type: "sevro.codex.feedback-boundary",
|
|
679
|
+
...this.appServerContinuation.data,
|
|
680
|
+
});
|
|
681
|
+
},
|
|
682
|
+
});
|
|
287
683
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
684
|
+
|
|
685
|
+
private async maybeFollowUp(
|
|
686
|
+
summary: CodexEventSummary,
|
|
687
|
+
appServer: AppServerResult | null,
|
|
688
|
+
fingerprint: string | null,
|
|
689
|
+
) {
|
|
690
|
+
const prompt = this.context.request.followUpPrompt;
|
|
691
|
+
if (!prompt || appServer)
|
|
692
|
+
return {
|
|
693
|
+
followUp: null,
|
|
694
|
+
followUpSummary: null,
|
|
695
|
+
continuation: this.appServerContinuation,
|
|
696
|
+
};
|
|
697
|
+
const continuation = await continuationBoundary(
|
|
698
|
+
this.context,
|
|
699
|
+
summary.threadId,
|
|
700
|
+
fingerprint,
|
|
701
|
+
);
|
|
702
|
+
const followUp = await codexProcessTurn(
|
|
703
|
+
this.context,
|
|
704
|
+
followUpTurnArgs(this.context, summary.threadId),
|
|
705
|
+
prompt,
|
|
706
|
+
);
|
|
707
|
+
const followUpSummary = summarizeCodexEvents(followUp.out, followUp.code);
|
|
708
|
+
requireFollowUpCompletion(followUpSummary, summary);
|
|
709
|
+
return { followUp, followUpSummary, continuation };
|
|
309
710
|
}
|
|
310
|
-
if (seen.size !== expectedPaths.size)
|
|
311
|
-
throw new Error("Codex marketplace package is missing declared files");
|
|
312
|
-
return actualRoot;
|
|
313
711
|
}
|
|
314
712
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
!isAbsolute(options.binary) ||
|
|
321
|
-
!isAbsolute(options.authFile) ||
|
|
322
|
-
!isAbsolute(options.projectRoot) ||
|
|
323
|
-
!isAbsolute(options.resultsRoot) ||
|
|
324
|
-
options.additionalProtectedRoots.some((root) => !isAbsolute(root)) ||
|
|
325
|
-
(options.sandboxBinary !== undefined &&
|
|
326
|
-
!isAbsolute(options.sandboxBinary)) ||
|
|
327
|
-
!options.model ||
|
|
328
|
-
!options.effort ||
|
|
329
|
-
(options.entrypoint !== undefined &&
|
|
330
|
-
!["exec", "app-server"].includes(options.entrypoint)) ||
|
|
331
|
-
(agentConcurrencyLimit !== null &&
|
|
332
|
-
(!Number.isSafeInteger(agentConcurrencyLimit) ||
|
|
333
|
-
agentConcurrencyLimit < 1)) ||
|
|
334
|
-
!Number.isSafeInteger(timeoutMs) ||
|
|
335
|
-
timeoutMs < 1 ||
|
|
336
|
-
timeoutMs > MAX_TIMEOUT_MS
|
|
337
|
-
)
|
|
338
|
-
throw new Error("invalid Codex host configuration");
|
|
713
|
+
function explicitInvocationReceipt(
|
|
714
|
+
selected: Request["explicitSkillInvocation"],
|
|
715
|
+
completed: boolean,
|
|
716
|
+
) {
|
|
717
|
+
if (!selected) return null;
|
|
339
718
|
return {
|
|
340
|
-
id: "sevro.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
: []),
|
|
346
|
-
"sevro.codex.plugin-marketplace",
|
|
347
|
-
"sevro.codex.explicit-invocation",
|
|
348
|
-
"sevro.codex.repository-invocation",
|
|
349
|
-
"sevro.codex.native-calls",
|
|
350
|
-
"sevro.host.native-controls",
|
|
351
|
-
"sevro.codex.initial-skill-reads",
|
|
352
|
-
"sevro.codex.follow-up-skill-reads",
|
|
353
|
-
],
|
|
354
|
-
model: options.model,
|
|
355
|
-
effort: options.effort,
|
|
356
|
-
configuration: {
|
|
357
|
-
"sevro.codex.agent-concurrency-limit": agentConcurrencyLimit,
|
|
358
|
-
...(options.entrypoint === "app-server"
|
|
359
|
-
? { "sevro.codex.entrypoint": "app-server" }
|
|
360
|
-
: {}),
|
|
719
|
+
id: "sevro.codex.explicit-invocation",
|
|
720
|
+
completeness: completed ? ("complete" as const) : ("partial" as const),
|
|
721
|
+
data: {
|
|
722
|
+
method: "explicit_invocation",
|
|
723
|
+
primarySkill: selected.skillName,
|
|
361
724
|
},
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
await writeFile(
|
|
420
|
-
join(codexHome, "config.toml"),
|
|
421
|
-
codexPermissionProfile({
|
|
422
|
-
id: profileId,
|
|
423
|
-
workspace: request.workspace,
|
|
424
|
-
commandHome,
|
|
425
|
-
commandTemp,
|
|
426
|
-
executableReadRoots,
|
|
427
|
-
...(request.codexMarketplace
|
|
428
|
-
? { pluginReadRoot: pluginCacheRoot }
|
|
429
|
-
: {}),
|
|
430
|
-
protectedRoots,
|
|
431
|
-
}) +
|
|
432
|
-
(agentConcurrencyLimit === null
|
|
433
|
-
? ""
|
|
434
|
-
: `\n[agents]\nmax_concurrent_threads_per_session = ${agentConcurrencyLimit}\n`),
|
|
435
|
-
{ flag: "wx", mode: 0o600 },
|
|
436
|
-
);
|
|
437
|
-
const shellRoot = join(request.workspace, ".git", "sevro-shell");
|
|
438
|
-
if (request.fixtureBinDir) {
|
|
439
|
-
await mkdir(shellRoot, { mode: 0o700 });
|
|
440
|
-
await writeFile(
|
|
441
|
-
join(shellRoot, ".zprofile"),
|
|
442
|
-
`export PATH=${shellQuote(request.fixtureBinDir)}:"$PATH"\n`,
|
|
443
|
-
{ flag: "wx", mode: 0o600 },
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
const env: Record<string, string> = {
|
|
447
|
-
PATH: request.fixtureBinDir
|
|
448
|
-
? `${request.fixtureBinDir}${delimiter}${process.env.PATH ?? "/usr/bin:/bin"}`
|
|
449
|
-
: (process.env.PATH ?? "/usr/bin:/bin"),
|
|
450
|
-
LANG: process.env.LANG ?? "C",
|
|
451
|
-
HOME: parentHome,
|
|
452
|
-
TMPDIR: parentTemp,
|
|
453
|
-
CODEX_HOME: codexHome,
|
|
454
|
-
NO_COLOR: "1",
|
|
455
|
-
...(request.fixtureBinDir ? { ZDOTDIR: shellRoot } : {}),
|
|
456
|
-
};
|
|
457
|
-
const installedPluginRoots: string[] = [];
|
|
458
|
-
if (request.codexMarketplace) {
|
|
459
|
-
const root = await marketplaceRoot(
|
|
460
|
-
request.workspace,
|
|
461
|
-
request.codexMarketplace,
|
|
462
|
-
);
|
|
463
|
-
const added = await runProcess({
|
|
464
|
-
argv: [
|
|
465
|
-
options.binary,
|
|
466
|
-
"plugin",
|
|
467
|
-
"marketplace",
|
|
468
|
-
"add",
|
|
469
|
-
root,
|
|
470
|
-
"--json",
|
|
471
|
-
],
|
|
472
|
-
cwd: request.workspace,
|
|
473
|
-
env,
|
|
474
|
-
timeoutMs: 30_000,
|
|
475
|
-
signal: request.signal,
|
|
476
|
-
});
|
|
477
|
-
if (added.code !== 0)
|
|
478
|
-
throw new Error("Codex local marketplace installation failed");
|
|
479
|
-
for (const pluginName of request.codexMarketplace.pluginNames) {
|
|
480
|
-
const installed = await runProcess({
|
|
481
|
-
argv: [
|
|
482
|
-
options.binary,
|
|
483
|
-
"plugin",
|
|
484
|
-
"add",
|
|
485
|
-
`${pluginName}@${request.codexMarketplace.marketplaceName}`,
|
|
486
|
-
"--json",
|
|
487
|
-
],
|
|
488
|
-
cwd: request.workspace,
|
|
489
|
-
env,
|
|
490
|
-
timeoutMs: 30_000,
|
|
491
|
-
signal: request.signal,
|
|
492
|
-
});
|
|
493
|
-
if (installed.code !== 0)
|
|
494
|
-
throw new Error("Codex local plugin installation failed");
|
|
495
|
-
let receipt: unknown;
|
|
496
|
-
try {
|
|
497
|
-
receipt = JSON.parse(installed.out);
|
|
498
|
-
} catch {
|
|
499
|
-
throw new Error("Codex plugin installation receipt is invalid");
|
|
500
|
-
}
|
|
501
|
-
const entry = receipt as Record<string, unknown>;
|
|
502
|
-
const actualInstalledPath =
|
|
503
|
-
typeof entry?.installedPath === "string"
|
|
504
|
-
? await realpath(entry.installedPath)
|
|
505
|
-
: null;
|
|
506
|
-
if (
|
|
507
|
-
!entry ||
|
|
508
|
-
entry.name !== pluginName ||
|
|
509
|
-
entry.marketplaceName !==
|
|
510
|
-
request.codexMarketplace.marketplaceName ||
|
|
511
|
-
!actualInstalledPath ||
|
|
512
|
-
!inside(await realpath(pluginCacheRoot), actualInstalledPath)
|
|
513
|
-
)
|
|
514
|
-
throw new Error("Codex plugin installation receipt is invalid");
|
|
515
|
-
installedPluginRoots.push(actualInstalledPath);
|
|
516
|
-
if (
|
|
517
|
-
request.explicitSkillInvocation?.pluginName === pluginName &&
|
|
518
|
-
!(
|
|
519
|
-
await stat(
|
|
520
|
-
join(
|
|
521
|
-
actualInstalledPath,
|
|
522
|
-
"skills",
|
|
523
|
-
request.explicitSkillInvocation.skillName,
|
|
524
|
-
"SKILL.md",
|
|
525
|
-
),
|
|
526
|
-
).catch(() => null)
|
|
527
|
-
)?.isFile()
|
|
528
|
-
)
|
|
529
|
-
throw new Error(
|
|
530
|
-
"invoked Codex skill is absent from the installation",
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
const probe = join(commandTemp, "isolation-probe");
|
|
535
|
-
await writeFile(probe, "probe\n", { flag: "wx", mode: 0o600 });
|
|
536
|
-
const checked = await runProcess({
|
|
537
|
-
argv: [
|
|
538
|
-
sandboxBinary,
|
|
539
|
-
"sandbox",
|
|
540
|
-
"-P",
|
|
541
|
-
profileId,
|
|
542
|
-
"-C",
|
|
543
|
-
request.workspace,
|
|
544
|
-
"/bin/sh",
|
|
545
|
-
"-c",
|
|
546
|
-
'/bin/cat "$1" >/dev/null && ! /bin/ls "$2" >/dev/null 2>&1 && ! /bin/cat "$3" >/dev/null 2>&1',
|
|
547
|
-
"sevro-probe",
|
|
548
|
-
probe,
|
|
549
|
-
options.projectRoot,
|
|
550
|
-
join(codexHome, "auth.json"),
|
|
551
|
-
],
|
|
552
|
-
cwd: request.workspace,
|
|
553
|
-
env,
|
|
554
|
-
timeoutMs: 10_000,
|
|
555
|
-
signal: request.signal,
|
|
556
|
-
});
|
|
557
|
-
if (checked.code !== 0)
|
|
558
|
-
throw new Error("Codex isolation preflight failed");
|
|
559
|
-
const executableCheck = await runProcess({
|
|
560
|
-
argv: [
|
|
561
|
-
sandboxBinary,
|
|
562
|
-
"sandbox",
|
|
563
|
-
"-P",
|
|
564
|
-
profileId,
|
|
565
|
-
"-C",
|
|
566
|
-
request.workspace,
|
|
567
|
-
sandboxBinary,
|
|
568
|
-
"--version",
|
|
569
|
-
],
|
|
570
|
-
cwd: request.workspace,
|
|
571
|
-
env,
|
|
572
|
-
timeoutMs: 10_000,
|
|
573
|
-
signal: request.signal,
|
|
574
|
-
});
|
|
575
|
-
if (executableCheck.code !== 0)
|
|
576
|
-
throw new Error("Codex executable preflight failed");
|
|
577
|
-
await rm(probe);
|
|
578
|
-
const initialWorkspaceFingerprint = request.followUpPrompt
|
|
579
|
-
? await workspaceFingerprint(request.workspace)
|
|
580
|
-
: null;
|
|
581
|
-
const executionArgs = [
|
|
582
|
-
options.binary,
|
|
583
|
-
"exec",
|
|
584
|
-
"--json",
|
|
585
|
-
"--strict-config",
|
|
586
|
-
"--skip-git-repo-check",
|
|
587
|
-
...(request.followUpPrompt ? [] : ["--ephemeral"]),
|
|
588
|
-
"--ignore-rules",
|
|
589
|
-
"-C",
|
|
590
|
-
request.workspace,
|
|
591
|
-
"-m",
|
|
592
|
-
options.model,
|
|
593
|
-
"-c",
|
|
594
|
-
`model_reasoning_effort=${JSON.stringify(options.effort)}`,
|
|
595
|
-
"-c",
|
|
596
|
-
`default_permissions=${JSON.stringify(profileId)}`,
|
|
597
|
-
"-c",
|
|
598
|
-
'approval_policy="never"',
|
|
599
|
-
];
|
|
600
|
-
let appServerContinuation:
|
|
601
|
-
| {
|
|
602
|
-
id: string;
|
|
603
|
-
completeness: "complete" | "partial";
|
|
604
|
-
data: Record<string, unknown>;
|
|
605
|
-
}
|
|
606
|
-
| undefined;
|
|
607
|
-
const appServer =
|
|
608
|
-
options.entrypoint === "app-server"
|
|
609
|
-
? await runCodexAppServer({
|
|
610
|
-
argv: [
|
|
611
|
-
options.binary,
|
|
612
|
-
"app-server",
|
|
613
|
-
"--stdio",
|
|
614
|
-
"--strict-config",
|
|
615
|
-
"-c",
|
|
616
|
-
`default_permissions=${JSON.stringify(profileId)}`,
|
|
617
|
-
],
|
|
618
|
-
env,
|
|
619
|
-
permissionProfile: profileId,
|
|
620
|
-
request: {
|
|
621
|
-
repoDir: request.workspace,
|
|
622
|
-
prompt: request.prompt,
|
|
623
|
-
model: options.model,
|
|
624
|
-
effort: options.effort,
|
|
625
|
-
signal: request.signal,
|
|
626
|
-
control: {
|
|
627
|
-
appServerTimeoutMs: timeoutMs,
|
|
628
|
-
...(request.followUpPrompt
|
|
629
|
-
? { followUpPrompt: request.followUpPrompt }
|
|
630
|
-
: {}),
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
|
-
followUpBoundary: async (threadId, goal) => {
|
|
634
|
-
const boundary = await workspaceFingerprint(
|
|
635
|
-
request.workspace,
|
|
636
|
-
);
|
|
637
|
-
const measured =
|
|
638
|
-
initialWorkspaceFingerprint !== null && boundary !== null;
|
|
639
|
-
appServerContinuation = {
|
|
640
|
-
id: "sevro.codex.continuation",
|
|
641
|
-
completeness: measured ? "complete" : "partial",
|
|
642
|
-
data: {
|
|
643
|
-
method: "same_thread_resume",
|
|
644
|
-
...goal,
|
|
645
|
-
threadId,
|
|
646
|
-
nativeAfterOrdinal: await codexNativeSessionLastOrdinal(
|
|
647
|
-
codexHome,
|
|
648
|
-
threadId,
|
|
649
|
-
),
|
|
650
|
-
preFollowUpWorktreeUnchanged: measured
|
|
651
|
-
? initialWorkspaceFingerprint === boundary
|
|
652
|
-
: null,
|
|
653
|
-
},
|
|
654
|
-
};
|
|
655
|
-
return JSON.stringify({
|
|
656
|
-
type: "sevro.codex.feedback-boundary",
|
|
657
|
-
...appServerContinuation.data,
|
|
658
|
-
});
|
|
659
|
-
},
|
|
660
|
-
})
|
|
661
|
-
: null;
|
|
662
|
-
const execution =
|
|
663
|
-
appServer ??
|
|
664
|
-
(await runProcess({
|
|
665
|
-
argv: [...executionArgs, "-"],
|
|
666
|
-
cwd: request.workspace,
|
|
667
|
-
env,
|
|
668
|
-
input: request.prompt,
|
|
669
|
-
timeoutMs,
|
|
670
|
-
signal: request.signal,
|
|
671
|
-
}));
|
|
672
|
-
const summary = appServer
|
|
673
|
-
? {
|
|
674
|
-
threadId: appServer.evidence.threadId ?? "",
|
|
675
|
-
complete: appServer.code === 0,
|
|
676
|
-
finalMessage:
|
|
677
|
-
appServer.code === 0
|
|
678
|
-
? await readFile(
|
|
679
|
-
join(request.workspace, ".git", "last-message.md"),
|
|
680
|
-
"utf8",
|
|
681
|
-
)
|
|
682
|
-
: null,
|
|
683
|
-
inputTokens: null,
|
|
684
|
-
outputTokens: null,
|
|
685
|
-
usageComplete: false,
|
|
686
|
-
}
|
|
687
|
-
: summarizeCodexEvents(execution.out, execution.code);
|
|
688
|
-
if (!summary.complete && !appServer)
|
|
689
|
-
throw new Error("Codex turn did not complete");
|
|
690
|
-
let followUp: { out: string; code: number } | null = null;
|
|
691
|
-
let followUpSummary: typeof summary | null = null;
|
|
692
|
-
let continuationObservation:
|
|
693
|
-
| {
|
|
694
|
-
id: string;
|
|
695
|
-
completeness: "complete" | "partial";
|
|
696
|
-
data: Record<string, unknown>;
|
|
697
|
-
}
|
|
698
|
-
| undefined = appServerContinuation;
|
|
699
|
-
if (request.followUpPrompt && !appServer) {
|
|
700
|
-
const beforeFollowUp = await workspaceFingerprint(request.workspace);
|
|
701
|
-
const nativeAfterOrdinal = await codexNativeSessionLastOrdinal(
|
|
702
|
-
codexHome,
|
|
703
|
-
summary.threadId,
|
|
704
|
-
);
|
|
705
|
-
const measured =
|
|
706
|
-
initialWorkspaceFingerprint !== null && beforeFollowUp !== null;
|
|
707
|
-
continuationObservation = {
|
|
708
|
-
id: "sevro.codex.continuation",
|
|
709
|
-
completeness: measured ? "complete" : "partial",
|
|
710
|
-
data: {
|
|
711
|
-
method: "same_thread_resume",
|
|
712
|
-
threadId: summary.threadId,
|
|
713
|
-
nativeAfterOrdinal,
|
|
714
|
-
preFollowUpWorktreeUnchanged: measured
|
|
715
|
-
? initialWorkspaceFingerprint === beforeFollowUp
|
|
716
|
-
: null,
|
|
717
|
-
},
|
|
718
|
-
};
|
|
719
|
-
followUp = await runProcess({
|
|
720
|
-
argv: [
|
|
721
|
-
options.binary,
|
|
722
|
-
"exec",
|
|
723
|
-
"resume",
|
|
724
|
-
"--json",
|
|
725
|
-
"--strict-config",
|
|
726
|
-
"--skip-git-repo-check",
|
|
727
|
-
"--ignore-rules",
|
|
728
|
-
"-m",
|
|
729
|
-
options.model,
|
|
730
|
-
"-c",
|
|
731
|
-
`model_reasoning_effort=${JSON.stringify(options.effort)}`,
|
|
732
|
-
"-c",
|
|
733
|
-
`default_permissions=${JSON.stringify(profileId)}`,
|
|
734
|
-
"-c",
|
|
735
|
-
'approval_policy="never"',
|
|
736
|
-
summary.threadId,
|
|
737
|
-
"-",
|
|
738
|
-
],
|
|
739
|
-
cwd: request.workspace,
|
|
740
|
-
env,
|
|
741
|
-
input: request.followUpPrompt,
|
|
742
|
-
timeoutMs,
|
|
743
|
-
signal: request.signal,
|
|
744
|
-
});
|
|
745
|
-
followUpSummary = summarizeCodexEvents(followUp.out, followUp.code);
|
|
746
|
-
if (
|
|
747
|
-
!followUpSummary.complete ||
|
|
748
|
-
followUpSummary.threadId !== summary.threadId
|
|
749
|
-
)
|
|
750
|
-
throw new Error(
|
|
751
|
-
"Codex follow-up turn did not complete in the original thread",
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
const observedEvents = followUp
|
|
755
|
-
? `${execution.out.trimEnd()}\n${followUp.out.trimStart()}`
|
|
756
|
-
: execution.out;
|
|
757
|
-
if (Buffer.byteLength(observedEvents, "utf8") > MAX_EVENT_BYTES)
|
|
758
|
-
throw new Error("Codex combined event stream exceeds the size limit");
|
|
759
|
-
const recoveredReads = await codexNativeSkillReadRecovery(
|
|
760
|
-
codexHome,
|
|
761
|
-
summary.threadId,
|
|
762
|
-
);
|
|
763
|
-
const skillReads = await codexSkillReadObservation(
|
|
764
|
-
observedEvents,
|
|
765
|
-
request.workspace,
|
|
766
|
-
installedPluginRoots,
|
|
767
|
-
recoveredReads,
|
|
768
|
-
);
|
|
769
|
-
const initialSkillReads = followUp
|
|
770
|
-
? {
|
|
771
|
-
...(await codexSkillReadObservation(
|
|
772
|
-
execution.out,
|
|
773
|
-
request.workspace,
|
|
774
|
-
installedPluginRoots,
|
|
775
|
-
recoveredReads,
|
|
776
|
-
)),
|
|
777
|
-
id: "sevro.codex.initial-skill-reads",
|
|
778
|
-
}
|
|
779
|
-
: null;
|
|
780
|
-
const followUpSkillReads = followUp
|
|
781
|
-
? {
|
|
782
|
-
...(await codexSkillReadObservation(
|
|
783
|
-
followUp.out,
|
|
784
|
-
request.workspace,
|
|
785
|
-
installedPluginRoots,
|
|
786
|
-
recoveredReads,
|
|
787
|
-
)),
|
|
788
|
-
id: "sevro.codex.follow-up-skill-reads",
|
|
789
|
-
}
|
|
790
|
-
: null;
|
|
791
|
-
const nativeCalls = await codexNativeCallObservation(
|
|
792
|
-
codexHome,
|
|
793
|
-
summary.threadId,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function appServerObservation(result: AppServerResult | null): Observation[] {
|
|
728
|
+
return result
|
|
729
|
+
? [
|
|
730
|
+
{
|
|
731
|
+
id: "sevro.host.native-goal",
|
|
732
|
+
completeness: result.code === 0 ? "complete" : "partial",
|
|
733
|
+
data: { ...result.evidence },
|
|
734
|
+
},
|
|
735
|
+
]
|
|
736
|
+
: [];
|
|
737
|
+
}
|
|
738
|
+
async function codexObservations(context: ExecutionContext, turns: TurnResult) {
|
|
739
|
+
const tracking = await skillTrackingObservation(
|
|
740
|
+
context.skillTracking,
|
|
741
|
+
turns.summary.threadId,
|
|
742
|
+
turns.summary.complete,
|
|
743
|
+
);
|
|
744
|
+
const native = await codexNativeCallObservation(
|
|
745
|
+
context.state.codexHome,
|
|
746
|
+
turns.summary.threadId,
|
|
747
|
+
{ followUpPrompt: context.request.followUpPrompt },
|
|
748
|
+
);
|
|
749
|
+
const explicit = explicitInvocationReceipt(
|
|
750
|
+
context.request.explicitSkillInvocation,
|
|
751
|
+
turns.summary.complete,
|
|
752
|
+
);
|
|
753
|
+
return [
|
|
754
|
+
...(await codexHookObservations(context)),
|
|
755
|
+
...runtimeObservations(
|
|
756
|
+
context.request.runtimePolicy,
|
|
757
|
+
context.request.runtimeRole,
|
|
758
|
+
),
|
|
759
|
+
...appServerObservation(turns.appServer),
|
|
760
|
+
native,
|
|
761
|
+
codexNativeControls(native),
|
|
762
|
+
...(turns.continuation ? [turns.continuation] : []),
|
|
763
|
+
...(explicit ? [explicit] : []),
|
|
764
|
+
tracking,
|
|
765
|
+
...skillActivationClaims(tracking),
|
|
766
|
+
...skillExposureClaims(tracking),
|
|
767
|
+
];
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async function codexHookObservations(context: ExecutionContext) {
|
|
771
|
+
if (!context.request.runtimePolicy) return [];
|
|
772
|
+
return [await runtimeHookObservation(context.hooks)];
|
|
773
|
+
}
|
|
774
|
+
function codexArtifacts(turns: TurnResult) {
|
|
775
|
+
return [
|
|
776
|
+
{
|
|
777
|
+
id: "sevro.codex.events",
|
|
778
|
+
bytes: Buffer.from(turns.execution.out, "utf8"),
|
|
779
|
+
},
|
|
780
|
+
...(turns.followUp
|
|
781
|
+
? [
|
|
794
782
|
{
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
...(request.followUpPrompt
|
|
798
|
-
? { followUpPrompt: request.followUpPrompt }
|
|
799
|
-
: {}),
|
|
783
|
+
id: "sevro.codex.follow-up-events",
|
|
784
|
+
bytes: Buffer.from(turns.followUp.out, "utf8"),
|
|
800
785
|
},
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
{
|
|
846
|
-
id: "sevro.codex.events",
|
|
847
|
-
bytes: Buffer.from(execution.out, "utf8"),
|
|
848
|
-
},
|
|
849
|
-
...(followUp
|
|
850
|
-
? [
|
|
851
|
-
{
|
|
852
|
-
id: "sevro.codex.follow-up-events",
|
|
853
|
-
bytes: Buffer.from(followUp.out, "utf8"),
|
|
854
|
-
},
|
|
855
|
-
]
|
|
856
|
-
: []),
|
|
857
|
-
],
|
|
858
|
-
actualCondition: "passive" as const,
|
|
859
|
-
inputTokens: followUpSummary
|
|
860
|
-
? summary.inputTokens !== null &&
|
|
861
|
-
followUpSummary.inputTokens !== null
|
|
862
|
-
? summary.inputTokens + followUpSummary.inputTokens
|
|
863
|
-
: null
|
|
864
|
-
: summary.inputTokens,
|
|
865
|
-
outputTokens: followUpSummary
|
|
866
|
-
? summary.outputTokens !== null &&
|
|
867
|
-
followUpSummary.outputTokens !== null
|
|
868
|
-
? summary.outputTokens + followUpSummary.outputTokens
|
|
869
|
-
: null
|
|
870
|
-
: summary.outputTokens,
|
|
871
|
-
usageComplete:
|
|
872
|
-
summary.usageComplete && (followUpSummary?.usageComplete ?? true),
|
|
873
|
-
costUsd: null,
|
|
874
|
-
};
|
|
875
|
-
} finally {
|
|
876
|
-
await rm(stateRoot, { recursive: true, force: true });
|
|
877
|
-
}
|
|
878
|
-
},
|
|
786
|
+
]
|
|
787
|
+
: []),
|
|
788
|
+
];
|
|
789
|
+
}
|
|
790
|
+
function tokenCount(
|
|
791
|
+
initial: number | null,
|
|
792
|
+
followUp: number | null | undefined,
|
|
793
|
+
): number | null {
|
|
794
|
+
if (followUp === undefined) return initial;
|
|
795
|
+
return initial !== null && followUp !== null ? initial + followUp : null;
|
|
796
|
+
}
|
|
797
|
+
function codexUsageComplete(
|
|
798
|
+
summary: CodexEventSummary,
|
|
799
|
+
followUp: CodexEventSummary | null,
|
|
800
|
+
): boolean {
|
|
801
|
+
return summary.usageComplete && (followUp?.usageComplete ?? true);
|
|
802
|
+
}
|
|
803
|
+
function codexUsage(turns: TurnResult) {
|
|
804
|
+
return {
|
|
805
|
+
inputTokens: tokenCount(
|
|
806
|
+
turns.summary.inputTokens,
|
|
807
|
+
turns.followUpSummary?.inputTokens,
|
|
808
|
+
),
|
|
809
|
+
outputTokens: tokenCount(
|
|
810
|
+
turns.summary.outputTokens,
|
|
811
|
+
turns.followUpSummary?.outputTokens,
|
|
812
|
+
),
|
|
813
|
+
usageComplete: codexUsageComplete(turns.summary, turns.followUpSummary),
|
|
814
|
+
costUsd: null,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
async function codexHostResult(
|
|
818
|
+
context: ExecutionContext,
|
|
819
|
+
turns: TurnResult,
|
|
820
|
+
): Promise<HostResult> {
|
|
821
|
+
const final = turns.followUpSummary ?? turns.summary;
|
|
822
|
+
return {
|
|
823
|
+
finalMessage: final.finalMessage,
|
|
824
|
+
...(turns.appServer ? { executionFailed: !turns.summary.complete } : {}),
|
|
825
|
+
complete: final.finalMessage !== null,
|
|
826
|
+
observations: await codexObservations(context, turns),
|
|
827
|
+
artifacts: codexArtifacts(turns),
|
|
828
|
+
actualCondition: "passive",
|
|
829
|
+
...codexUsage(turns),
|
|
879
830
|
};
|
|
880
831
|
}
|