@fusionkit/ensemble 0.1.5 → 0.1.7

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/dist/cursor.js DELETED
@@ -1,440 +0,0 @@
1
- import { spawn, spawnSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { delimiter, join } from "node:path";
4
- import { artifactHash } from "@fusionkit/protocol";
5
- const DEFAULT_CURSOR_COMMAND = "cursor-agent";
6
- const DEFAULT_BRIDGE_MODEL_NAME = "local-fusion";
7
- const DEFAULT_BRIDGE_PROVIDER_MODEL = "fusion-panel";
8
- const BRIDGE_START_TIMEOUT_MS = 20_000;
9
- function definedEnv(env) {
10
- const result = {};
11
- for (const [key, value] of Object.entries(env)) {
12
- if (value !== undefined)
13
- result[key] = value;
14
- }
15
- return result;
16
- }
17
- function normalizeModelBaseUrl(fusionBackendUrl) {
18
- const trimmed = fusionBackendUrl.replace(/\/+$/, "");
19
- return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
20
- }
21
- function commandOnPath(command, env) {
22
- if (command.includes("/")) {
23
- return existsSync(command);
24
- }
25
- const pathValue = env.PATH ?? process.env.PATH ?? "";
26
- return pathValue
27
- .split(delimiter)
28
- .filter((entry) => entry.length > 0)
29
- .some((dir) => existsSync(join(dir, command)));
30
- }
31
- function resolveCursorKitDir(options, env) {
32
- return (options.cursorKitDir ??
33
- env.WARRANT_CURSORKIT_DIR ??
34
- env.FUSIONKIT_CURSORKIT_DIR);
35
- }
36
- function resolveAvailability(options, env) {
37
- const command = options.command ?? DEFAULT_CURSOR_COMMAND;
38
- if (options.runner !== undefined) {
39
- return {
40
- available: true,
41
- cursorKitDir: resolveCursorKitDir(options, env) ?? ".",
42
- command
43
- };
44
- }
45
- const cursorKitDir = resolveCursorKitDir(options, env);
46
- if (cursorKitDir === undefined) {
47
- return {
48
- available: false,
49
- reason: "Cursorkit checkout is not configured; set WARRANT_CURSORKIT_DIR or pass cursorKitDir."
50
- };
51
- }
52
- if (!existsSync(join(cursorKitDir, "dist/src/cli.js"))) {
53
- return {
54
- available: false,
55
- reason: `Cursorkit bridge build was not found at ${join(cursorKitDir, "dist/src/cli.js")}; run pnpm build in the Cursorkit checkout.`
56
- };
57
- }
58
- if (!commandOnPath(command, env)) {
59
- return {
60
- available: false,
61
- reason: `Cursor CLI "${command}" was not found on PATH; install the Cursor CLI (https://cursor.com/cli) and log in.`
62
- };
63
- }
64
- return { available: true, cursorKitDir, command };
65
- }
66
- export function cursorHarnessUnavailableReason(env = process.env, options = {}) {
67
- const availability = resolveAvailability(options, definedEnv(env));
68
- return availability.available ? undefined : availability.reason;
69
- }
70
- function modeFor(descriptor, override) {
71
- if (override !== undefined)
72
- return override;
73
- switch (descriptor.policy.sideEffects) {
74
- case "none":
75
- case "read_only":
76
- return "ask";
77
- case "writes_workspace":
78
- case "network":
79
- case "tool_execution":
80
- case "unknown":
81
- return "agent";
82
- default: {
83
- const exhausted = descriptor.policy.sideEffects;
84
- throw new Error(`unsupported side effects policy: ${String(exhausted)}`);
85
- }
86
- }
87
- }
88
- function skippedCandidate(input) {
89
- const transcript = `Cursor adapter skipped: ${input.reason}`;
90
- const hash = artifactHash(transcript);
91
- return {
92
- candidateId: `${input.descriptor.id}_${input.model.id}_${input.ordinal}`,
93
- model: input.model,
94
- status: "skipped",
95
- transcript,
96
- log: transcript,
97
- artifacts: [
98
- {
99
- artifact_id: `artifact_${input.descriptor.id}_${input.model.id}_cursor_skip`,
100
- kind: "log",
101
- hash,
102
- redaction_status: "synthetic"
103
- }
104
- ],
105
- verification: {
106
- status: "skipped",
107
- evidence: [input.reason]
108
- },
109
- error: {
110
- kind: "capability_missing",
111
- message: input.reason,
112
- retryable: false
113
- },
114
- metadata: {
115
- adapter: "cursor",
116
- skip_reason: input.reason
117
- }
118
- };
119
- }
120
- /**
121
- * Drives the real cursor-agent CLI in ACP mode against a freshly spawned
122
- * Cursorkit bridge whose local-model backend points at the fusion gateway.
123
- * The bridge runs with BRIDGE_AGENT_TOOL_POLICY=all so Cursor can read, edit
124
- * (apply_patch/write_file), and run shell commands inside the worktree.
125
- */
126
- async function defaultCursorRunner(input) {
127
- const bridgePort = 9700 + Math.floor(Math.random() * 250);
128
- const bridgeEnv = { ...input.env };
129
- for (const key of Object.keys(bridgeEnv)) {
130
- if (key.startsWith("BRIDGE_") ||
131
- key.startsWith("MODEL_") ||
132
- key.startsWith("CURSOR_UPSTREAM")) {
133
- delete bridgeEnv[key];
134
- }
135
- }
136
- Object.assign(bridgeEnv, {
137
- BRIDGE_PORT: String(bridgePort),
138
- BRIDGE_ROUTE_INVENTORY: "true",
139
- BRIDGE_AGENT_TOOL_POLICY: "all",
140
- BRIDGE_AGENT_TOOL_MAX_ITERATIONS: "24",
141
- CURSOR_UPSTREAM_BASE_URL: "https://api2.cursor.sh",
142
- MODEL_BASE_URL: normalizeModelBaseUrl(input.fusionBackendUrl),
143
- MODEL_API_KEY: input.apiKey ?? "local",
144
- MODEL_NAME: input.modelName,
145
- MODEL_PROVIDER_MODEL: input.providerModel,
146
- MODEL_CONTEXT_TOKEN_LIMIT: "128000"
147
- });
148
- let bridgeOut = "";
149
- const bridge = spawn(process.execPath, ["dist/src/cli.js", "serve"], {
150
- cwd: input.cursorKitDir,
151
- env: bridgeEnv,
152
- stdio: ["ignore", "pipe", "pipe"]
153
- });
154
- bridge.stdout.on("data", (chunk) => {
155
- bridgeOut += chunk.toString("utf8");
156
- });
157
- bridge.stderr.on("data", (chunk) => {
158
- bridgeOut += chunk.toString("utf8");
159
- });
160
- const timeoutMs = input.timeoutMs ?? 180_000;
161
- try {
162
- const deadline = Date.now() + BRIDGE_START_TIMEOUT_MS;
163
- while (!/bridge listening/.test(bridgeOut) && Date.now() < deadline) {
164
- await new Promise((resolve) => setTimeout(resolve, 250));
165
- }
166
- if (!/bridge listening/.test(bridgeOut)) {
167
- return {
168
- status: "failed",
169
- transcript: bridgeOut,
170
- toolEvents: 0,
171
- reason: "Cursorkit bridge did not start in time."
172
- };
173
- }
174
- const printResult = await driveCursorAgentPrint({
175
- command: input.command,
176
- bridgePort,
177
- modelName: input.modelName,
178
- mode: input.mode,
179
- cwd: input.cwd,
180
- prompt: input.prompt,
181
- timeoutMs
182
- });
183
- const diff = captureWorktreeDiff(input.cwd);
184
- return {
185
- status: printResult.status,
186
- transcript: printResult.transcript,
187
- toolEvents: diff !== undefined && diff.length > 0 ? 1 : 0,
188
- ...(printResult.exitCode !== undefined ? { exitCode: printResult.exitCode } : {}),
189
- ...(diff !== undefined ? { diff } : {}),
190
- ...(printResult.reason !== undefined ? { reason: printResult.reason } : {})
191
- };
192
- }
193
- finally {
194
- bridge.kill("SIGTERM");
195
- }
196
- }
197
- /**
198
- * Drives cursor-agent in headless print mode (`-p`), which "has access to all
199
- * tools, including write and shell". The bridge runs the Cursor tool loop over
200
- * the SSE/BidiAppend transport, so the agent can read, apply_patch/write, and
201
- * run shell inside the worktree. `--trust` skips the workspace-trust prompt and
202
- * `--force` auto-approves tool actions. For read-only tasks we pass `--mode ask`.
203
- */
204
- async function driveCursorAgentPrint(input) {
205
- const args = [
206
- "-p",
207
- "--force",
208
- "--trust",
209
- "--output-format",
210
- "text",
211
- "--model",
212
- input.modelName,
213
- "--endpoint",
214
- `http://127.0.0.1:${input.bridgePort}`
215
- ];
216
- if (input.mode === "ask") {
217
- args.push("--mode", "ask");
218
- }
219
- args.push(input.prompt);
220
- return await new Promise((resolve) => {
221
- const child = spawn(input.command, args, {
222
- cwd: input.cwd,
223
- stdio: ["ignore", "pipe", "pipe"]
224
- });
225
- let stdout = "";
226
- let stderr = "";
227
- let timedOut = false;
228
- const timer = setTimeout(() => {
229
- timedOut = true;
230
- child.kill("SIGTERM");
231
- }, input.timeoutMs);
232
- child.stdout.on("data", (chunk) => {
233
- stdout += chunk.toString("utf8");
234
- });
235
- child.stderr.on("data", (chunk) => {
236
- stderr += chunk.toString("utf8");
237
- });
238
- child.on("error", (error) => {
239
- clearTimeout(timer);
240
- resolve({
241
- status: "failed",
242
- transcript: stdout,
243
- reason: error instanceof Error ? error.message : String(error)
244
- });
245
- });
246
- child.on("exit", (code) => {
247
- clearTimeout(timer);
248
- const transcript = [stdout, stderr].filter(Boolean).join("\n");
249
- if (timedOut) {
250
- resolve({
251
- status: "failed",
252
- transcript,
253
- reason: "cursor-agent timed out"
254
- });
255
- return;
256
- }
257
- resolve({
258
- status: code === 0 ? "succeeded" : "failed",
259
- transcript,
260
- exitCode: code ?? 0,
261
- ...(code === 0 ? {} : { reason: stderr.slice(0, 500) })
262
- });
263
- });
264
- });
265
- }
266
- function captureWorktreeDiff(cwd) {
267
- try {
268
- const result = spawnSync("git", ["-C", cwd, "diff"], { encoding: "utf8" });
269
- const stdout = result.stdout ?? "";
270
- return result.status === 0 && stdout.length > 0 ? stdout : undefined;
271
- }
272
- catch {
273
- return undefined;
274
- }
275
- }
276
- export function createCursorHarness(options = {}) {
277
- const id = options.id ?? "cursor";
278
- const runner = options.runner ?? defaultCursorRunner;
279
- const skipWhenUnavailable = options.skipWhenUnavailable ?? true;
280
- return {
281
- id,
282
- harnessKind: "cursor",
283
- prepare: () => {
284
- const env = definedEnv(options.env ?? process.env);
285
- return { env, availability: resolveAvailability(options, env) };
286
- },
287
- capabilities: () => {
288
- const env = definedEnv(options.env ?? process.env);
289
- const available = resolveAvailability(options, env).available;
290
- const status = available ? "supported" : "degraded";
291
- return {
292
- workspace_read: status,
293
- workspace_write: status,
294
- apply_patch: status,
295
- tool_call_loop: status,
296
- tool_records: status,
297
- verification: status,
298
- route_observation: "supported",
299
- adapter_available: available ? "supported" : "unsupported"
300
- };
301
- },
302
- verificationProfile: () => ({
303
- id: `${id}-verification`,
304
- requiredEvidence: [
305
- "cursor-agent transcript",
306
- "session status",
307
- "worktree diff or skip reason"
308
- ]
309
- }),
310
- run: async ({ descriptor, model, ordinal, prepared, worktree }) => {
311
- const state = prepared;
312
- if (!state.availability.available) {
313
- if (!skipWhenUnavailable) {
314
- throw new Error(state.availability.reason);
315
- }
316
- return skippedCandidate({
317
- descriptor,
318
- model,
319
- ordinal,
320
- reason: state.availability.reason
321
- });
322
- }
323
- const fusionBackendUrl = options.fusionBackendUrl ?? state.env.FUSIONKIT_BASE_URL;
324
- if (fusionBackendUrl === undefined || fusionBackendUrl.length === 0) {
325
- return skippedCandidate({
326
- descriptor,
327
- model,
328
- ordinal,
329
- reason: "Fusion backend URL is not configured for the Cursor harness."
330
- });
331
- }
332
- const cwd = worktree?.path ?? descriptor.workspace ?? process.cwd();
333
- let result;
334
- try {
335
- result = await runner({
336
- prompt: descriptor.prompt,
337
- cwd,
338
- fusionBackendUrl,
339
- ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
340
- model,
341
- cursorKitDir: state.availability.cursorKitDir,
342
- command: state.availability.command,
343
- modelName: options.modelName ?? DEFAULT_BRIDGE_MODEL_NAME,
344
- providerModel: options.providerModel ?? model.model ?? DEFAULT_BRIDGE_PROVIDER_MODEL,
345
- mode: modeFor(descriptor, options.mode),
346
- ...(options.timeoutMs !== undefined
347
- ? { timeoutMs: options.timeoutMs }
348
- : descriptor.policy.timeoutMs !== undefined
349
- ? { timeoutMs: descriptor.policy.timeoutMs }
350
- : {}),
351
- env: state.env
352
- });
353
- }
354
- catch (error) {
355
- return skippedCandidate({
356
- descriptor,
357
- model,
358
- ordinal,
359
- reason: error instanceof Error ? error.message : String(error)
360
- });
361
- }
362
- const transcript = result.transcript;
363
- const outputHash = artifactHash(transcript.length > 0 ? transcript : `cursor:${descriptor.id}`);
364
- const status = result.status;
365
- const candidateId = `${descriptor.id}_${model.id}_${ordinal}`;
366
- const artifacts = [
367
- {
368
- artifact_id: `artifact_${descriptor.id}_${model.id}_cursor_transcript`,
369
- kind: "transcript",
370
- hash: outputHash,
371
- redaction_status: "synthetic"
372
- }
373
- ];
374
- if (result.diff !== undefined && result.diff.length > 0) {
375
- artifacts.push({
376
- artifact_id: `artifact_${descriptor.id}_${model.id}_cursor_patch`,
377
- kind: "patch",
378
- hash: artifactHash(result.diff),
379
- redaction_status: "synthetic"
380
- });
381
- }
382
- return {
383
- candidateId,
384
- model,
385
- status,
386
- ...(worktree
387
- ? { branchName: worktree.branchName, worktreePath: worktree.path }
388
- : {}),
389
- transcript,
390
- ...(result.diff !== undefined ? { diff: result.diff } : {}),
391
- log: transcript,
392
- artifacts,
393
- toolRecords: [
394
- {
395
- execution_id: `exec_${candidateId}_cursor`,
396
- plan_id: `plan_${candidateId}_cursor`,
397
- status,
398
- output_hash: outputHash,
399
- ...(status === "failed"
400
- ? {
401
- error: {
402
- kind: "provider_error",
403
- message: result.reason ?? "Cursor run failed.",
404
- retryable: false
405
- }
406
- }
407
- : {})
408
- }
409
- ],
410
- verification: {
411
- status,
412
- evidence: [
413
- `tool_events=${result.toolEvents}`,
414
- outputHash,
415
- ...(result.diff !== undefined ? ["worktree_diff"] : [])
416
- ],
417
- ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {})
418
- },
419
- ...(status === "failed"
420
- ? {
421
- error: {
422
- kind: "provider_error",
423
- message: result.reason ?? "Cursor run failed.",
424
- retryable: false
425
- }
426
- }
427
- : {}),
428
- metadata: {
429
- adapter: "cursor",
430
- mode: modeFor(descriptor, options.mode),
431
- tool_events: result.toolEvents,
432
- has_diff: result.diff !== undefined && result.diff.length > 0
433
- }
434
- };
435
- },
436
- collectArtifacts: () => [],
437
- cleanup: () => undefined
438
- };
439
- }
440
- export const cursorHarness = createCursorHarness;
@@ -1,62 +0,0 @@
1
- import type { HarnessRunResultV1, ModelFusionHarnessKind } from "@fusionkit/protocol";
2
- import type { HarnessAdapter, HarnessCapabilities } from "./harness.js";
3
- declare const LIVE_SMOKE_TARGETS: readonly ["claude-code", "codex", "cursor"];
4
- export type HarnessCapabilityTarget = "cursor" | "claude-code" | "codex" | "command" | "mock";
5
- export type HarnessAvailability = "available" | "credential_gated" | "missing";
6
- export type HarnessLiveSmokeTarget = (typeof LIVE_SMOKE_TARGETS)[number];
7
- export type HarnessSmokePurpose = "contract" | "credential-skip" | "live" | "missing";
8
- export type HarnessAdapterReadiness = {
9
- harnessId: HarnessCapabilityTarget;
10
- displayName: string;
11
- contractReadiness: string;
12
- credentialState: string;
13
- liveSmoke: string;
14
- evidence: string[];
15
- artifactRefs: string[];
16
- };
17
- export type HarnessCapabilityMatrixRow = {
18
- harnessId: HarnessCapabilityTarget;
19
- harnessKind: ModelFusionHarnessKind;
20
- displayName: string;
21
- availability: HarnessAvailability;
22
- capabilities: HarnessCapabilities;
23
- notes: string[];
24
- };
25
- export type HarnessCapabilityMatrix = {
26
- capabilities: string[];
27
- rows: HarnessCapabilityMatrixRow[];
28
- };
29
- export type HarnessSmokeOutcome = "success" | "failure" | "missing" | "skipped";
30
- export type HarnessSmokeRecord = {
31
- taskId: string;
32
- harnessId: HarnessCapabilityTarget;
33
- purpose: HarnessSmokePurpose;
34
- outcome: HarnessSmokeOutcome;
35
- result: HarnessRunResultV1;
36
- resultPath: string;
37
- };
38
- export type HarnessSmokeDashboard = {
39
- outputRoot: string;
40
- dashboardPath: string;
41
- matrix: HarnessCapabilityMatrix;
42
- records: HarnessSmokeRecord[];
43
- readiness: HarnessAdapterReadiness[];
44
- };
45
- export type HarnessSmokeDashboardOptions = {
46
- repo?: string;
47
- outputRoot?: string;
48
- timeoutMs?: number;
49
- createdAt?: string;
50
- env?: Record<string, string | undefined>;
51
- commandSuccess?: string;
52
- commandFailure?: string;
53
- liveSmoke?: readonly HarnessLiveSmokeTarget[];
54
- liveSmokeHarnesses?: Partial<Record<HarnessLiveSmokeTarget, HarnessAdapter>>;
55
- };
56
- export declare function createHarnessCapabilityMatrix(options?: HarnessSmokeDashboardOptions): HarnessCapabilityMatrix;
57
- export declare function runHarnessSmokeDashboard(options?: HarnessSmokeDashboardOptions): Promise<HarnessSmokeDashboard>;
58
- export declare const harnessDashboard: {
59
- readonly capabilities: typeof createHarnessCapabilityMatrix;
60
- readonly run: typeof runHarnessSmokeDashboard;
61
- };
62
- export {};