@fusionkit/ensemble 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/agent.d.ts +21 -0
  2. package/dist/agent.js +186 -0
  3. package/dist/artifacts.d.ts +21 -0
  4. package/dist/artifacts.js +36 -0
  5. package/dist/claude-code.d.ts +25 -0
  6. package/dist/claude-code.js +398 -0
  7. package/dist/codex.d.ts +69 -0
  8. package/dist/codex.js +467 -0
  9. package/dist/command.d.ts +15 -0
  10. package/dist/command.js +82 -0
  11. package/dist/dashboard.d.ts +62 -0
  12. package/dist/dashboard.js +788 -0
  13. package/dist/external-executor.d.ts +56 -0
  14. package/dist/external-executor.js +288 -0
  15. package/dist/harness.d.ts +337 -0
  16. package/dist/harness.js +1 -0
  17. package/dist/index.d.ts +30 -0
  18. package/dist/index.js +15 -0
  19. package/dist/isolation.d.ts +25 -0
  20. package/dist/isolation.js +509 -0
  21. package/dist/judge.d.ts +77 -0
  22. package/dist/judge.js +16 -0
  23. package/dist/mock.d.ts +20 -0
  24. package/dist/mock.js +56 -0
  25. package/dist/run.d.ts +5 -0
  26. package/dist/run.js +520 -0
  27. package/dist/synthesis.d.ts +25 -0
  28. package/dist/synthesis.js +221 -0
  29. package/dist/test/codex.test.d.ts +1 -0
  30. package/dist/test/codex.test.js +237 -0
  31. package/dist/test/dashboard.test.d.ts +1 -0
  32. package/dist/test/dashboard.test.js +214 -0
  33. package/dist/test/ensemble.test.d.ts +1 -0
  34. package/dist/test/ensemble.test.js +780 -0
  35. package/dist/test/external-executor.test.d.ts +1 -0
  36. package/dist/test/external-executor.test.js +273 -0
  37. package/dist/test/isolation.test.d.ts +1 -0
  38. package/dist/test/isolation.test.js +359 -0
  39. package/dist/test/tool-executor.test.d.ts +1 -0
  40. package/dist/test/tool-executor.test.js +113 -0
  41. package/dist/test/unified.test.d.ts +1 -0
  42. package/dist/test/unified.test.js +150 -0
  43. package/dist/tool-executor.d.ts +14 -0
  44. package/dist/tool-executor.js +156 -0
  45. package/dist/trace.d.ts +8 -0
  46. package/dist/trace.js +7 -0
  47. package/dist/unified.d.ts +101 -0
  48. package/dist/unified.js +422 -0
  49. package/dist/worktree.d.ts +25 -0
  50. package/dist/worktree.js +75 -0
  51. package/package.json +35 -0
@@ -0,0 +1,56 @@
1
+ import type { Server } from "node:http";
2
+ import type { JsonValue, ToolCallPlanV1, ToolExecutionRecordV1, ToolPolicyDecision } from "@fusionkit/protocol";
3
+ import type { ToolExecutor } from "./tool-executor.js";
4
+ export type FusionKitToolExecutionRequest = {
5
+ candidate_id: string;
6
+ tool_call_id: string;
7
+ plan: ToolCallPlanV1;
8
+ arguments: JsonValue;
9
+ environment_id: string;
10
+ tool_policy_id: string;
11
+ };
12
+ export type FusionKitToolExecutionBatch = {
13
+ requests: FusionKitToolExecutionRequest[];
14
+ };
15
+ export type FusionKitToolExecutionResult = {
16
+ candidate_id: string;
17
+ tool_call_id: string;
18
+ record: ToolExecutionRecordV1;
19
+ output?: JsonValue;
20
+ deduped: boolean;
21
+ decision: ToolPolicyDecision;
22
+ };
23
+ export type FusionKitToolExecutionResponse = {
24
+ results: FusionKitToolExecutionResult[];
25
+ };
26
+ export type FusionKitToolExecutorServerOptions = {
27
+ executor: ToolExecutor;
28
+ port: number;
29
+ host?: string;
30
+ authToken?: string;
31
+ maxBodyBytes?: number;
32
+ };
33
+ export type FusionKitToolExecutorServer = {
34
+ server: Server;
35
+ host: string;
36
+ port: number;
37
+ url: string;
38
+ };
39
+ export declare class FusionKitToolExecutorError extends Error {
40
+ readonly status: number;
41
+ readonly code: string;
42
+ constructor(status: number, code: string, message: string);
43
+ }
44
+ export declare class FusionKitToolExecutorClientError extends Error {
45
+ readonly status: number;
46
+ readonly body: unknown;
47
+ constructor(status: number, body: unknown);
48
+ }
49
+ export declare class FusionKitToolExecutorClient {
50
+ readonly baseUrl: string;
51
+ private readonly authToken?;
52
+ constructor(baseUrl: string, authToken?: string);
53
+ execute(batch: FusionKitToolExecutionBatch): Promise<FusionKitToolExecutionResponse>;
54
+ }
55
+ export declare function executeFusionKitToolBatch(executor: ToolExecutor, batch: unknown): Promise<FusionKitToolExecutionResponse>;
56
+ export declare function startFusionKitToolExecutorServer(options: FusionKitToolExecutorServerOptions): Promise<FusionKitToolExecutorServer>;
@@ -0,0 +1,288 @@
1
+ import { createServer } from "node:http";
2
+ import { assertToolCallPlanV1, toolArgumentsHash, toolSideEffectClassFromModelFusion } from "@fusionkit/protocol";
3
+ const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
4
+ const HEALTH_RESPONSE = { ok: true, service: "warrant-tool-executor" };
5
+ export class FusionKitToolExecutorError extends Error {
6
+ status;
7
+ code;
8
+ constructor(status, code, message) {
9
+ super(message);
10
+ this.name = "FusionKitToolExecutorError";
11
+ this.status = status;
12
+ this.code = code;
13
+ }
14
+ }
15
+ export class FusionKitToolExecutorClientError extends Error {
16
+ status;
17
+ body;
18
+ constructor(status, body) {
19
+ const message = typeof body === "object" && body !== null && "error" in body
20
+ ? String(body.error)
21
+ : `tool executor request failed with status ${status}`;
22
+ super(message);
23
+ this.name = "FusionKitToolExecutorClientError";
24
+ this.status = status;
25
+ this.body = body;
26
+ }
27
+ }
28
+ export class FusionKitToolExecutorClient {
29
+ baseUrl;
30
+ authToken;
31
+ constructor(baseUrl, authToken) {
32
+ this.baseUrl = baseUrl.replace(/\/$/, "");
33
+ this.authToken = authToken;
34
+ }
35
+ async execute(batch) {
36
+ const headers = {
37
+ "content-type": "application/json"
38
+ };
39
+ if (this.authToken)
40
+ headers.authorization = `Bearer ${this.authToken}`;
41
+ const response = await fetch(`${this.baseUrl}/v1/fusionkit/tool-executions`, {
42
+ method: "POST",
43
+ headers,
44
+ body: JSON.stringify(batch)
45
+ });
46
+ const payload = await parseJsonResponse(response);
47
+ if (!response.ok) {
48
+ throw new FusionKitToolExecutorClientError(response.status, payload);
49
+ }
50
+ return parseFusionKitToolExecutionResponse(payload);
51
+ }
52
+ }
53
+ export async function executeFusionKitToolBatch(executor, batch) {
54
+ const parsed = parseFusionKitToolExecutionBatch(batch);
55
+ const results = [];
56
+ for (const request of parsed.requests) {
57
+ validateRequestPolicy(executor, request);
58
+ validatePlanArguments(request);
59
+ const sideEffects = sideEffectsForPlan(request.plan);
60
+ const result = await executor.execute({
61
+ candidate_id: request.candidate_id,
62
+ plan_id: request.plan.plan_id,
63
+ tool_name: request.plan.tool_name,
64
+ arguments: request.arguments,
65
+ side_effects: sideEffects
66
+ });
67
+ results.push({
68
+ candidate_id: request.candidate_id,
69
+ tool_call_id: request.tool_call_id,
70
+ record: result.record,
71
+ ...(result.output !== undefined ? { output: result.output } : {}),
72
+ deduped: result.deduped,
73
+ decision: result.decision
74
+ });
75
+ }
76
+ return { results };
77
+ }
78
+ export function startFusionKitToolExecutorServer(options) {
79
+ const { executor, port, host = "127.0.0.1" } = options;
80
+ const context = {
81
+ executor,
82
+ authToken: options.authToken,
83
+ maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES
84
+ };
85
+ const server = createServer((req, res) => {
86
+ handleRequest(context, req, res).catch((error) => {
87
+ if (error instanceof FusionKitToolExecutorError) {
88
+ sendJson(res, error.status, { error: error.message, code: error.code });
89
+ return;
90
+ }
91
+ sendJson(res, 500, { error: "internal server error", code: "internal_error" });
92
+ });
93
+ });
94
+ return new Promise((resolve) => {
95
+ server.listen(port, host, () => {
96
+ const address = server.address();
97
+ const boundPort = typeof address === "object" && address !== null ? address.port : port;
98
+ resolve({
99
+ server,
100
+ host,
101
+ port: boundPort,
102
+ url: `http://${host}:${boundPort}`
103
+ });
104
+ });
105
+ });
106
+ }
107
+ async function handleRequest(context, req, res) {
108
+ const method = req.method ?? "GET";
109
+ const url = new URL(req.url ?? "/", "http://localhost");
110
+ if (method === "GET" && url.pathname === "/v1/health") {
111
+ sendJson(res, 200, HEALTH_RESPONSE);
112
+ return;
113
+ }
114
+ if (method === "POST" && url.pathname === "/v1/fusionkit/tool-executions") {
115
+ requireAuth(context, req);
116
+ const body = await readJson(req, context.maxBodyBytes);
117
+ const response = await executeFusionKitToolBatch(context.executor, body);
118
+ sendJson(res, 200, response);
119
+ return;
120
+ }
121
+ sendJson(res, 404, { error: "not found", code: "not_found" });
122
+ }
123
+ function validateRequestPolicy(executor, request) {
124
+ if (request.environment_id !== executor.contract.environment_id) {
125
+ throw new FusionKitToolExecutorError(403, "environment_mismatch", "request environment_id does not match executor environment");
126
+ }
127
+ if (request.tool_policy_id !== executor.contract.tool_policy_id) {
128
+ throw new FusionKitToolExecutorError(403, "policy_mismatch", "request tool_policy_id does not match executor policy");
129
+ }
130
+ }
131
+ function validatePlanArguments(request) {
132
+ const argumentsHash = toolArgumentsHash(request.arguments);
133
+ if (request.plan.arguments_hash !== argumentsHash) {
134
+ throw new FusionKitToolExecutorError(400, "arguments_hash_mismatch", "tool-call-plan arguments_hash does not match request arguments");
135
+ }
136
+ }
137
+ function parseFusionKitToolExecutionBatch(value) {
138
+ const object = assertRecord(value, "batch");
139
+ assertKnownKeys(object, ["requests"], "batch");
140
+ if (!Array.isArray(object.requests)) {
141
+ throw invalid("batch.requests must be an array");
142
+ }
143
+ return {
144
+ requests: object.requests.map((request, index) => parseFusionKitToolExecutionRequest(request, `batch.requests[${index}]`))
145
+ };
146
+ }
147
+ function parseFusionKitToolExecutionRequest(value, context) {
148
+ const object = assertRecord(value, context);
149
+ assertKnownKeys(object, ["candidate_id", "tool_call_id", "plan", "arguments", "environment_id", "tool_policy_id"], context);
150
+ assertString(object.candidate_id, `${context}.candidate_id`);
151
+ assertString(object.tool_call_id, `${context}.tool_call_id`);
152
+ assertString(object.environment_id, `${context}.environment_id`);
153
+ assertString(object.tool_policy_id, `${context}.tool_policy_id`);
154
+ assertJsonValue(object.arguments, `${context}.arguments`);
155
+ try {
156
+ assertToolCallPlanV1(object.plan);
157
+ }
158
+ catch (error) {
159
+ const message = error instanceof Error ? error.message : String(error);
160
+ throw invalid(`${context}.plan invalid: ${message}`);
161
+ }
162
+ return {
163
+ candidate_id: object.candidate_id,
164
+ tool_call_id: object.tool_call_id,
165
+ plan: object.plan,
166
+ arguments: object.arguments,
167
+ environment_id: object.environment_id,
168
+ tool_policy_id: object.tool_policy_id
169
+ };
170
+ }
171
+ function parseFusionKitToolExecutionResponse(value) {
172
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
173
+ throw new FusionKitToolExecutorClientError(500, {
174
+ error: "tool executor response must be an object"
175
+ });
176
+ }
177
+ const object = value;
178
+ if (!Array.isArray(object.results)) {
179
+ throw new FusionKitToolExecutorClientError(500, {
180
+ error: "tool executor response results must be an array"
181
+ });
182
+ }
183
+ return value;
184
+ }
185
+ function sideEffectsForPlan(plan) {
186
+ try {
187
+ return toolSideEffectClassFromModelFusion(plan.side_effects);
188
+ }
189
+ catch (error) {
190
+ const message = error instanceof Error ? error.message : String(error);
191
+ throw invalid(message);
192
+ }
193
+ }
194
+ function assertRecord(value, context) {
195
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
196
+ throw invalid(`${context} must be an object`);
197
+ }
198
+ return value;
199
+ }
200
+ function assertKnownKeys(object, allowed, context) {
201
+ for (const key of Object.keys(object)) {
202
+ if (!allowed.includes(key)) {
203
+ throw invalid(`${context}.${key} is not supported`);
204
+ }
205
+ }
206
+ }
207
+ function assertString(value, context) {
208
+ if (typeof value !== "string" || value.length === 0) {
209
+ throw invalid(`${context} must be a non-empty string`);
210
+ }
211
+ }
212
+ function assertJsonValue(value, context) {
213
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
214
+ return;
215
+ }
216
+ if (typeof value === "number") {
217
+ if (!Number.isFinite(value))
218
+ throw invalid(`${context} must be JSON-safe`);
219
+ return;
220
+ }
221
+ if (Array.isArray(value)) {
222
+ value.forEach((item, index) => assertJsonValue(item, `${context}[${index}]`));
223
+ return;
224
+ }
225
+ if (typeof value === "object") {
226
+ for (const [key, item] of Object.entries(value)) {
227
+ assertJsonValue(item, `${context}.${key}`);
228
+ }
229
+ return;
230
+ }
231
+ throw invalid(`${context} must be JSON-safe`);
232
+ }
233
+ function invalid(message) {
234
+ return new FusionKitToolExecutorError(400, "invalid_request", message);
235
+ }
236
+ function readBody(req, maxBodyBytes) {
237
+ return new Promise((resolve, reject) => {
238
+ const chunks = [];
239
+ let total = 0;
240
+ req.on("data", (chunk) => {
241
+ total += chunk.length;
242
+ if (total > maxBodyBytes) {
243
+ reject(new FusionKitToolExecutorError(413, "body_too_large", "body too large"));
244
+ req.destroy();
245
+ return;
246
+ }
247
+ chunks.push(chunk);
248
+ });
249
+ req.on("end", () => resolve(Buffer.concat(chunks)));
250
+ req.on("error", reject);
251
+ });
252
+ }
253
+ async function readJson(req, maxBodyBytes) {
254
+ const raw = (await readBody(req, maxBodyBytes)).toString("utf8");
255
+ try {
256
+ return JSON.parse(raw);
257
+ }
258
+ catch {
259
+ throw new FusionKitToolExecutorError(400, "invalid_json", "request body is not valid JSON");
260
+ }
261
+ }
262
+ async function parseJsonResponse(response) {
263
+ const text = await response.text();
264
+ if (text.length === 0)
265
+ return undefined;
266
+ try {
267
+ return JSON.parse(text);
268
+ }
269
+ catch {
270
+ return { error: text };
271
+ }
272
+ }
273
+ function requireAuth(context, req) {
274
+ if (context.authToken === undefined)
275
+ return;
276
+ const header = req.headers.authorization;
277
+ if (header !== `Bearer ${context.authToken}`) {
278
+ throw new FusionKitToolExecutorError(401, "unauthorized", "invalid bearer token");
279
+ }
280
+ }
281
+ function sendJson(res, status, body) {
282
+ const payload = JSON.stringify(body);
283
+ res.writeHead(status, {
284
+ "content-type": "application/json",
285
+ "content-length": Buffer.byteLength(payload)
286
+ });
287
+ res.end(payload);
288
+ }
@@ -0,0 +1,337 @@
1
+ import type { ArtifactRef, HarnessCandidateRecordV1, HarnessRunRequestV1, HarnessRunResultV1, JudgeSynthesisRecordV1, JsonValue, ModelFusionHarnessKind, ModelCallRecordV1, ModelFusionCapabilityStatus, ModelFusionSideEffects, ModelFusionStatus, ToolExecutionRecordV1 } from "@fusionkit/protocol";
2
+ import type { CandidateWorktree } from "./worktree.js";
3
+ import type { JudgeSynthesizer, SynthesisFailureSummary, SynthesisRepairAttempt } from "./judge.js";
4
+ export type EnsembleModel = {
5
+ id: string;
6
+ model: string;
7
+ endpointId?: string;
8
+ };
9
+ export type TrajectoryStepType = "reasoning" | "tool_call" | "observation" | "output";
10
+ /** One step of an agent trajectory (mirrors harness-trajectory.v1 steps). */
11
+ export type TrajectoryStep = {
12
+ index: number;
13
+ type: TrajectoryStepType;
14
+ text?: string;
15
+ tool_name?: string;
16
+ tool_call_id?: string;
17
+ tool_input?: string;
18
+ is_error?: boolean;
19
+ };
20
+ export type TrajectoryVerification = {
21
+ status: ModelFusionStatus;
22
+ evidence: string[];
23
+ exitCode?: number;
24
+ };
25
+ /**
26
+ * A normalized agent trajectory produced by one panel model: the ordered
27
+ * reasoning/tool-call/observation/output sequence plus the final output and
28
+ * any verification. This is the unit of trajectory-level fusion.
29
+ */
30
+ export type HarnessTrajectory = {
31
+ trajectoryId: string;
32
+ modelId: string;
33
+ model?: string;
34
+ candidateId?: string;
35
+ harnessKind?: ModelFusionHarnessKind;
36
+ status: ModelFusionStatus;
37
+ steps: TrajectoryStep[];
38
+ finalOutput: string;
39
+ diff?: string;
40
+ verification?: TrajectoryVerification;
41
+ };
42
+ export type CandidateIsolationKind = "process" | "container" | "microvm";
43
+ export type CandidateActualIsolationKind = CandidateIsolationKind | "vercel-sandbox";
44
+ export type CandidateIsolationNetworkPolicy = {
45
+ defaultDeny: boolean;
46
+ allowHosts: string[];
47
+ enforce?: boolean;
48
+ };
49
+ export type CandidateIsolationMountPolicy = {
50
+ workdir?: string;
51
+ worktreeWritable?: boolean;
52
+ readOnlyCachePaths?: string[];
53
+ ignoredDirs?: string[];
54
+ };
55
+ export type CandidateIsolationSecretPolicy = {
56
+ secretNames?: string[];
57
+ secretValueHashes?: string[];
58
+ injectedEnvNames?: string[];
59
+ };
60
+ export type CandidateContainerDriverInput = {
61
+ command: string;
62
+ cwd: string;
63
+ timeoutMs?: number;
64
+ image: string;
65
+ workdir: string;
66
+ mountPolicy: Required<CandidateIsolationMountPolicy>;
67
+ networkPolicy: Required<CandidateIsolationNetworkPolicy>;
68
+ };
69
+ export type CandidateContainerDriverResult = {
70
+ stdout: string;
71
+ stderr: string;
72
+ exitCode: number;
73
+ timedOut?: boolean;
74
+ cleanup?: {
75
+ attempted: boolean;
76
+ succeeded: boolean;
77
+ error?: string;
78
+ };
79
+ };
80
+ export type CandidateContainerDriver = {
81
+ id: string;
82
+ supportsNetworkPolicy: boolean;
83
+ execute(input: CandidateContainerDriverInput): Promise<CandidateContainerDriverResult> | CandidateContainerDriverResult;
84
+ };
85
+ export type CandidateMicrovmProvider = "vercel-sandbox" | (string & {});
86
+ export type CandidateMicrovmRuntimeMetadata = {
87
+ provider?: CandidateMicrovmProvider;
88
+ runtime?: string;
89
+ snapshotId?: string;
90
+ sandboxId?: string;
91
+ imageDigest?: string;
92
+ runtimeDigest?: string;
93
+ };
94
+ export type CandidateMicrovmDriverInput = {
95
+ command: string;
96
+ cwd: string;
97
+ timeoutMs?: number;
98
+ provider: CandidateMicrovmProvider;
99
+ runtime: string;
100
+ snapshotId?: string;
101
+ workdir: string;
102
+ mountPolicy: Required<CandidateIsolationMountPolicy>;
103
+ networkPolicy: Required<CandidateIsolationNetworkPolicy>;
104
+ secretPolicy: Required<CandidateIsolationSecretPolicy>;
105
+ };
106
+ export type CandidateMicrovmDriverResult = {
107
+ stdout: string;
108
+ stderr: string;
109
+ exitCode: number;
110
+ timedOut?: boolean;
111
+ actualIsolation?: Extract<CandidateActualIsolationKind, "microvm" | "vercel-sandbox">;
112
+ runtime?: CandidateMicrovmRuntimeMetadata;
113
+ cleanup?: {
114
+ attempted: boolean;
115
+ succeeded: boolean;
116
+ timedOut?: boolean;
117
+ error?: string;
118
+ };
119
+ };
120
+ export type CandidateMicrovmDriver = {
121
+ id: string;
122
+ provider: CandidateMicrovmProvider;
123
+ supportsNetworkPolicy: boolean;
124
+ execute(input: CandidateMicrovmDriverInput): Promise<CandidateMicrovmDriverResult> | CandidateMicrovmDriverResult;
125
+ };
126
+ export type CandidateIsolationConfig = {
127
+ kind: "process";
128
+ networkPolicy?: CandidateIsolationNetworkPolicy;
129
+ mountPolicy?: CandidateIsolationMountPolicy;
130
+ secretPolicy?: CandidateIsolationSecretPolicy;
131
+ } | {
132
+ kind: "container";
133
+ image?: string;
134
+ engine?: "docker" | "podman";
135
+ driver?: CandidateContainerDriver;
136
+ networkPolicy?: CandidateIsolationNetworkPolicy;
137
+ mountPolicy?: CandidateIsolationMountPolicy;
138
+ secretPolicy?: CandidateIsolationSecretPolicy;
139
+ } | {
140
+ kind: "microvm";
141
+ provider?: CandidateMicrovmProvider;
142
+ runtime?: string;
143
+ snapshotId?: string;
144
+ sandboxId?: string;
145
+ imageDigest?: string;
146
+ runtimeDigest?: string;
147
+ driver?: CandidateMicrovmDriver;
148
+ networkPolicy?: CandidateIsolationNetworkPolicy;
149
+ mountPolicy?: CandidateIsolationMountPolicy;
150
+ secretPolicy?: CandidateIsolationSecretPolicy;
151
+ };
152
+ export type CandidateHardeningMetadata = {
153
+ requested_isolation: CandidateIsolationKind;
154
+ actual_isolation: CandidateActualIsolationKind;
155
+ runtime: {
156
+ image?: string;
157
+ driver?: string;
158
+ provider?: CandidateMicrovmProvider;
159
+ runtime?: string;
160
+ snapshot_id?: string;
161
+ sandbox_id?: string;
162
+ image_digest?: string;
163
+ runtime_digest?: string;
164
+ workdir: string;
165
+ };
166
+ mount_policy: {
167
+ worktree_writable: boolean;
168
+ read_only_caches: string[];
169
+ ignored_dirs: string[];
170
+ };
171
+ network_policy: {
172
+ default_deny: boolean;
173
+ allow_hosts: string[];
174
+ enforced: boolean;
175
+ };
176
+ cleanup: {
177
+ attempted: boolean;
178
+ succeeded: boolean;
179
+ status: "not_required" | "succeeded" | "failed" | "timed_out";
180
+ timed_out?: boolean;
181
+ error?: string;
182
+ };
183
+ secret_absence: {
184
+ secret_names: string[];
185
+ secret_value_hashes: string[];
186
+ injected_env_names: string[];
187
+ scanned: boolean;
188
+ leaks_found: boolean;
189
+ scan_scope: string[];
190
+ leak_count: number;
191
+ };
192
+ };
193
+ export type EnsembleRuntime = {
194
+ id: string;
195
+ environmentId?: string;
196
+ isolation?: CandidateIsolationConfig;
197
+ };
198
+ export type EnsembleJudge = {
199
+ id: string;
200
+ model?: string;
201
+ synthesizer?: JudgeSynthesizer;
202
+ };
203
+ export type EnsemblePolicy = {
204
+ id: string;
205
+ allowedTools: string[];
206
+ sideEffects: ModelFusionSideEffects;
207
+ timeoutMs?: number;
208
+ budgetUsd?: number;
209
+ };
210
+ export type VerificationProfile = {
211
+ id: string;
212
+ command?: string;
213
+ requiredEvidence: string[];
214
+ };
215
+ export type HarnessCapabilities = Record<string, ModelFusionCapabilityStatus>;
216
+ export type HarnessArtifact = ArtifactRef;
217
+ export type HarnessToolRecord = Pick<ToolExecutionRecordV1, "execution_id" | "plan_id" | "status" | "output_hash" | "error">;
218
+ export type HarnessCandidateOutput = {
219
+ candidateId?: string;
220
+ model: EnsembleModel;
221
+ status: ModelFusionStatus;
222
+ modelCallId?: string;
223
+ modelCallRecord?: ModelCallRecordV1;
224
+ branchName?: string;
225
+ worktreePath?: string;
226
+ transcript?: string;
227
+ trajectory?: HarnessTrajectory;
228
+ diff?: string;
229
+ log?: string;
230
+ summary?: string;
231
+ screenshots?: HarnessArtifact[];
232
+ score?: number;
233
+ artifacts?: HarnessArtifact[];
234
+ toolRecords?: HarnessToolRecord[];
235
+ verification?: {
236
+ status: ModelFusionStatus;
237
+ evidence: string[];
238
+ exitCode?: number;
239
+ };
240
+ error?: HarnessCandidateRecordV1["error"];
241
+ metadata?: Record<string, JsonValue>;
242
+ };
243
+ export type HarnessPrepareInput = {
244
+ descriptor: EnsembleDescriptor;
245
+ request: HarnessRunRequestV1;
246
+ };
247
+ export type HarnessRunInput = {
248
+ descriptor: EnsembleDescriptor;
249
+ request: HarnessRunRequestV1;
250
+ model: EnsembleModel;
251
+ ordinal: number;
252
+ prepared: unknown;
253
+ worktree?: CandidateWorktree;
254
+ };
255
+ export type HarnessCollectInput = {
256
+ descriptor: EnsembleDescriptor;
257
+ request: HarnessRunRequestV1;
258
+ candidates: readonly HarnessCandidateOutput[];
259
+ prepared: unknown;
260
+ };
261
+ export type HarnessAdapter = {
262
+ id: string;
263
+ harnessKind?: ModelFusionHarnessKind;
264
+ prepare(input: HarnessPrepareInput): Promise<unknown> | unknown;
265
+ run(input: HarnessRunInput): Promise<HarnessCandidateOutput> | HarnessCandidateOutput;
266
+ collectArtifacts(input: HarnessCollectInput): Promise<HarnessArtifact[]> | HarnessArtifact[];
267
+ cleanup?(input: HarnessCollectInput): Promise<void> | void;
268
+ verificationProfile(descriptor: EnsembleDescriptor): VerificationProfile;
269
+ capabilities(descriptor: EnsembleDescriptor): HarnessCapabilities;
270
+ };
271
+ export type ReviewEvidence = {
272
+ strategy: string;
273
+ scorecards: Record<string, JsonValue>[];
274
+ reason?: string;
275
+ };
276
+ export type EnsembleDescriptor = {
277
+ id: string;
278
+ harness: HarnessAdapter;
279
+ models: EnsembleModel[];
280
+ runtime: EnsembleRuntime;
281
+ judge: EnsembleJudge;
282
+ policy: EnsemblePolicy;
283
+ prompt: string;
284
+ sourceRepo: string;
285
+ baseGitSha: string;
286
+ workspace?: string;
287
+ outputRoot?: string;
288
+ cleanupWorktrees?: boolean;
289
+ metadata?: Record<string, JsonValue>;
290
+ reviewEvidence?: ReviewEvidence;
291
+ checks?: never;
292
+ };
293
+ export type EnsembleRunResult = {
294
+ descriptorId: string;
295
+ harnessRunRequest: HarnessRunRequestV1;
296
+ harnessRunResult: HarnessRunResultV1;
297
+ candidates: readonly HarnessCandidateRecordV1[];
298
+ artifacts: readonly HarnessArtifact[];
299
+ toolRecords: readonly HarnessToolRecord[];
300
+ modelCallRecords: readonly ModelCallRecordV1[];
301
+ verification: VerificationProfile;
302
+ summaryPath?: string;
303
+ summary?: EnsembleRunSummary;
304
+ judgeSynthesisRecord?: JudgeSynthesisRecordV1;
305
+ finalPatchPath?: string | null;
306
+ repairAttempts?: readonly SynthesisRepairAttempt[];
307
+ failureSummary?: SynthesisFailureSummary;
308
+ reviewEvidence?: ReviewEvidence;
309
+ };
310
+ export type EnsembleCandidateSummary = {
311
+ candidateId: string;
312
+ modelId: string;
313
+ model: string;
314
+ modelCallId?: string;
315
+ status: ModelFusionStatus;
316
+ branchName?: string;
317
+ worktreePath?: string;
318
+ toolExecutionIds: string[];
319
+ diffArtifacts: HarnessArtifact[];
320
+ verification?: HarnessCandidateOutput["verification"];
321
+ hardening?: CandidateHardeningMetadata;
322
+ };
323
+ export type EnsembleRunSummary = {
324
+ descriptorId: string;
325
+ snapshot?: {
326
+ baseGitSha: string;
327
+ snapshotHash: string;
328
+ workspace: string;
329
+ };
330
+ candidates: EnsembleCandidateSummary[];
331
+ artifacts: HarnessArtifact[];
332
+ modelCallRecords: ModelCallRecordV1[];
333
+ judgeSynthesisRecord?: JudgeSynthesisRecordV1;
334
+ finalPatchPath: string | null;
335
+ repairAttempts?: SynthesisRepairAttempt[];
336
+ failureSummary?: SynthesisFailureSummary;
337
+ };
@@ -0,0 +1 @@
1
+ export {};