harnery 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +6 -0
- package/dist/commands/agents.d.ts +24 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +244 -13
- package/dist/commands/backup.d.ts +5 -4
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +15 -14
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +27 -1
- package/dist/commands/claude-desktop.d.ts +19 -0
- package/dist/commands/claude-desktop.d.ts.map +1 -0
- package/dist/commands/claude-desktop.js +168 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +149 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -3
- package/dist/commands/eml.d.ts +32 -0
- package/dist/commands/eml.d.ts.map +1 -1
- package/dist/commands/eml.js +16 -3
- package/dist/commands/harness.d.ts +11 -0
- package/dist/commands/harness.d.ts.map +1 -0
- package/dist/commands/harness.js +118 -0
- package/dist/commands/presence.d.ts +9 -4
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +88 -5
- package/dist/commands/relay.d.ts +9 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +143 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +8 -15
- package/dist/core/agents/canonical-emit.d.ts +15 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +41 -3
- package/dist/core/agents/cli.js +4 -2
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +44 -3
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +26 -1
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +4 -37
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +16 -43
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +11 -2
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +3 -3
- package/dist/core/agents/state/names.d.ts +38 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +46 -7
- package/dist/core/agents/state/pidmap.d.ts +5 -1
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +58 -2
- package/dist/core/agents/state/stale-sweep.d.ts +3 -2
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +6 -6
- package/dist/core/config.d.ts +68 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +191 -25
- package/dist/core/context/index.d.ts +144 -0
- package/dist/core/context/index.d.ts.map +1 -0
- package/dist/core/context/index.js +380 -0
- package/dist/core/harnesses/bench.d.ts +29 -0
- package/dist/core/harnesses/bench.d.ts.map +1 -0
- package/dist/core/harnesses/bench.js +151 -0
- package/dist/core/harnesses/index.d.ts +9 -0
- package/dist/core/harnesses/index.d.ts.map +1 -0
- package/dist/core/harnesses/index.js +4 -0
- package/dist/core/harnesses/profiles.d.ts +62 -0
- package/dist/core/harnesses/profiles.d.ts.map +1 -0
- package/dist/core/harnesses/profiles.js +115 -0
- package/dist/core/harnesses/registry.d.ts +15 -0
- package/dist/core/harnesses/registry.d.ts.map +1 -0
- package/dist/core/harnesses/registry.js +113 -0
- package/dist/core/harnesses/types.d.ts +64 -0
- package/dist/core/harnesses/types.d.ts.map +1 -0
- package/dist/core/harnesses/types.js +20 -0
- package/dist/core/hooks/cli.js +280 -63
- package/dist/core/hooks/events/schema.d.ts +38 -1
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.js +3 -0
- package/dist/core/hooks/harness/parse.d.ts +1 -1
- package/dist/core/hooks/harness/parse.d.ts.map +1 -1
- package/dist/core/hooks/harness/parse.js +4 -0
- package/dist/core/presence/blob.d.ts +40 -0
- package/dist/core/presence/blob.d.ts.map +1 -0
- package/dist/core/presence/blob.js +91 -0
- package/dist/core/presence/git.d.ts +68 -0
- package/dist/core/presence/git.d.ts.map +1 -0
- package/dist/core/presence/git.js +173 -0
- package/dist/core/presence/index.d.ts +74 -0
- package/dist/core/presence/index.d.ts.map +1 -0
- package/dist/core/presence/index.js +224 -0
- package/dist/core/presence/relay-client.d.ts +39 -0
- package/dist/core/presence/relay-client.d.ts.map +1 -0
- package/dist/core/presence/relay-client.js +295 -0
- package/dist/core/presence/relay-protocol.d.ts +94 -0
- package/dist/core/presence/relay-protocol.d.ts.map +1 -0
- package/dist/core/presence/relay-protocol.js +169 -0
- package/dist/core/workflow/billing.d.ts +1 -1
- package/dist/core/workflow/billing.d.ts.map +1 -1
- package/dist/core/workflow/billing.js +7 -1
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +11 -2
- package/dist/core/workflow/harnesses.d.ts +3 -3
- package/dist/core/workflow/harnesses.d.ts.map +1 -1
- package/dist/core/workflow/harnesses.js +8 -17
- package/dist/core/workflow/spawn-claude.d.ts +4 -1
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +39 -18
- package/dist/core/workflow/spawn-codex.d.ts +4 -1
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +44 -22
- package/dist/core/workflow/spawn-cursor.d.ts +4 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +35 -16
- package/dist/core/workflow/types.d.ts +9 -2
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/lib/browser/client.d.ts +16 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +22 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -0
- package/dist/lib/browser/launch-args.d.ts +21 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -0
- package/dist/lib/browser/launch-args.js +32 -0
- package/dist/lib/claude-desktop.d.ts +105 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -0
- package/dist/lib/claude-desktop.js +217 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +5 -2
- package/dist/lib/identities/assume.d.ts +51 -0
- package/dist/lib/identities/assume.d.ts.map +1 -0
- package/dist/lib/identities/assume.js +274 -0
- package/dist/lib/identities/index.d.ts +7 -7
- package/dist/lib/identities/index.d.ts.map +1 -1
- package/dist/lib/identities/index.js +24 -24
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +15 -2
- package/package.json +12 -1
- package/schemas/config.schema.json +88 -19
- package/src/commander.ts +6 -0
- package/src/commands/agents.ts +280 -14
- package/src/commands/backup.ts +17 -16
- package/src/commands/browse.ts +32 -0
- package/src/commands/claude-desktop.ts +215 -0
- package/src/commands/context.ts +174 -1
- package/src/commands/doctor.ts +2 -3
- package/src/commands/eml.ts +22 -4
- package/src/commands/harness.ts +147 -0
- package/src/commands/presence.ts +111 -5
- package/src/commands/relay.ts +160 -0
- package/src/commands/sync.ts +4 -0
- package/src/commands/workflow.ts +9 -18
- package/src/core/agents/canonical-emit.ts +41 -3
- package/src/core/agents/cli.ts +4 -2
- package/src/core/agents/render/prompt-context.ts +48 -3
- package/src/core/agents/render/session-context.ts +27 -1
- package/src/core/agents/rules/commit-conflict.ts +4 -33
- package/src/core/agents/session-events.ts +15 -39
- package/src/core/agents/state/heartbeat-projector.ts +10 -1
- package/src/core/agents/state/heartbeat-writer.ts +3 -2
- package/src/core/agents/state/names.ts +68 -9
- package/src/core/agents/state/pidmap.ts +59 -2
- package/src/core/agents/state/stale-sweep.ts +6 -11
- package/src/core/config.ts +248 -24
- package/src/core/context/index.ts +575 -0
- package/src/core/harnesses/bench.ts +214 -0
- package/src/core/harnesses/index.ts +31 -0
- package/src/core/harnesses/profiles.ts +141 -0
- package/src/core/harnesses/registry.ts +140 -0
- package/src/core/harnesses/types.ts +90 -0
- package/src/core/hooks/cli.ts +331 -62
- package/src/core/hooks/events/schema.ts +68 -0
- package/src/core/hooks/harness/events.ts +3 -0
- package/src/core/hooks/harness/parse.ts +7 -1
- package/src/core/presence/blob.ts +125 -0
- package/src/core/presence/git.ts +191 -0
- package/src/core/presence/index.ts +274 -0
- package/src/core/presence/relay-client.ts +322 -0
- package/src/core/presence/relay-protocol.ts +246 -0
- package/src/core/workflow/billing.ts +8 -2
- package/src/core/workflow/engine.ts +11 -2
- package/src/core/workflow/harnesses.ts +14 -19
- package/src/core/workflow/spawn-claude.ts +39 -20
- package/src/core/workflow/spawn-codex.ts +47 -28
- package/src/core/workflow/spawn-cursor.ts +37 -18
- package/src/core/workflow/types.ts +9 -2
- package/src/lib/browser/client.ts +37 -0
- package/src/lib/browser/index.ts +1 -0
- package/src/lib/browser/launch-args.ts +33 -0
- package/src/lib/claude-desktop.ts +293 -0
- package/src/lib/docs-lint.ts +5 -2
- package/src/lib/identities/assume.ts +363 -0
- package/src/lib/identities/index.ts +28 -24
- package/src/lib/instructions/templates.ts +16 -2
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { exec } from "../../lib/exec.ts";
|
|
22
|
+
import { validateHarnessEffort } from "../harnesses/profiles.ts";
|
|
23
|
+
import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.ts";
|
|
22
24
|
import { buildChildEnv } from "./child-env.ts";
|
|
23
25
|
import { notFoundError } from "./harnesses.ts";
|
|
24
26
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
@@ -33,9 +35,8 @@ interface ClaudeEnvelope {
|
|
|
33
35
|
errors?: string[];
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
export function buildClaudeInvocation(req: SpawnRequest): HarnessInvocation {
|
|
39
|
+
validateHarnessEffort("claude-code", req.effort);
|
|
39
40
|
const argv = [
|
|
40
41
|
"claude",
|
|
41
42
|
"-p",
|
|
@@ -46,35 +47,37 @@ export const claudeCodeSpawner: Spawner = async (req: SpawnRequest): Promise<Spa
|
|
|
46
47
|
String(req.maxTurns),
|
|
47
48
|
];
|
|
48
49
|
if (req.model) argv.push("--model", req.model);
|
|
50
|
+
if (req.effort) argv.push("--effort", req.effort);
|
|
51
|
+
return { argv };
|
|
52
|
+
}
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return { ok: false, text: "", durationMs, error: notFoundError("claude-code") };
|
|
54
|
+
export function normalizeClaudeResult(raw: HarnessRawResult): SpawnResult {
|
|
55
|
+
if (raw.exitCode === 127) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
text: "",
|
|
59
|
+
durationMs: raw.durationMs,
|
|
60
|
+
error: notFoundError("claude-code"),
|
|
61
|
+
};
|
|
59
62
|
}
|
|
60
|
-
if (
|
|
63
|
+
if (raw.exitCode !== 0) {
|
|
61
64
|
return {
|
|
62
65
|
ok: false,
|
|
63
66
|
text: "",
|
|
64
|
-
durationMs,
|
|
65
|
-
error: `claude exited ${
|
|
67
|
+
durationMs: raw.durationMs,
|
|
68
|
+
error: `claude exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
let envelope: ClaudeEnvelope;
|
|
70
73
|
try {
|
|
71
|
-
envelope = JSON.parse(
|
|
74
|
+
envelope = JSON.parse(raw.stdout) as ClaudeEnvelope;
|
|
72
75
|
} catch {
|
|
73
76
|
return {
|
|
74
77
|
ok: false,
|
|
75
78
|
text: "",
|
|
76
|
-
durationMs,
|
|
77
|
-
error: `result envelope was not JSON: ${
|
|
79
|
+
durationMs: raw.durationMs,
|
|
80
|
+
error: `result envelope was not JSON: ${raw.stdout.slice(0, 300)}`,
|
|
78
81
|
};
|
|
79
82
|
}
|
|
80
83
|
|
|
@@ -84,7 +87,7 @@ export const claudeCodeSpawner: Spawner = async (req: SpawnRequest): Promise<Spa
|
|
|
84
87
|
text: String(envelope.result ?? ""),
|
|
85
88
|
sessionId: envelope.session_id,
|
|
86
89
|
costUsd: envelope.total_cost_usd,
|
|
87
|
-
durationMs,
|
|
90
|
+
durationMs: raw.durationMs,
|
|
88
91
|
error: `harness error (${envelope.subtype ?? "unknown"}): ${(envelope.errors ?? []).join("; ") || "see envelope"}`,
|
|
89
92
|
};
|
|
90
93
|
}
|
|
@@ -94,6 +97,22 @@ export const claudeCodeSpawner: Spawner = async (req: SpawnRequest): Promise<Spa
|
|
|
94
97
|
text: String(envelope.result ?? ""),
|
|
95
98
|
sessionId: envelope.session_id,
|
|
96
99
|
costUsd: envelope.total_cost_usd,
|
|
97
|
-
durationMs,
|
|
100
|
+
durationMs: raw.durationMs,
|
|
98
101
|
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const claudeCodeSpawner: Spawner = async (req: SpawnRequest): Promise<SpawnResult> => {
|
|
105
|
+
const t0 = Date.now();
|
|
106
|
+
let invocation: HarnessInvocation;
|
|
107
|
+
try {
|
|
108
|
+
invocation = buildClaudeInvocation(req);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return { ok: false, text: "", durationMs: 0, error: (error as Error).message };
|
|
111
|
+
}
|
|
112
|
+
const r = await exec(invocation.argv, {
|
|
113
|
+
cwd: req.cwd,
|
|
114
|
+
env: buildChildEnv(req.runId, { subscriptionOnly: req.subscriptionOnly }),
|
|
115
|
+
timeout: req.timeoutMs,
|
|
116
|
+
});
|
|
117
|
+
return normalizeClaudeResult({ ...r, durationMs: Date.now() - t0 });
|
|
99
118
|
};
|
|
@@ -20,54 +20,73 @@ import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
|
20
20
|
import { tmpdir } from "node:os";
|
|
21
21
|
import { join } from "node:path";
|
|
22
22
|
import { exec } from "../../lib/exec.ts";
|
|
23
|
+
import { validateHarnessEffort } from "../harnesses/profiles.ts";
|
|
24
|
+
import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.ts";
|
|
23
25
|
import { buildChildEnv } from "./child-env.ts";
|
|
24
26
|
import { notFoundError } from "./harnesses.ts";
|
|
25
27
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
26
28
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
tmpdir(),
|
|
31
|
-
`harnery-codex-${process.pid}-${randomBytes(4).toString("hex")}.txt`,
|
|
32
|
-
);
|
|
33
|
-
|
|
29
|
+
export function buildCodexInvocation(req: SpawnRequest, resultFile?: string): HarnessInvocation {
|
|
30
|
+
validateHarnessEffort("codex", req.effort);
|
|
31
|
+
if (!resultFile) throw new Error("codex adapter requires a final-message result file");
|
|
34
32
|
const argv = [
|
|
35
33
|
"codex",
|
|
36
34
|
"exec",
|
|
37
35
|
req.prompt,
|
|
38
36
|
"--output-last-message",
|
|
39
|
-
|
|
37
|
+
resultFile,
|
|
40
38
|
"--skip-git-repo-check",
|
|
41
39
|
"--sandbox",
|
|
42
40
|
"workspace-write",
|
|
43
41
|
];
|
|
44
42
|
if (req.model) argv.push("--model", req.model);
|
|
43
|
+
if (req.effort) argv.push("-c", `model_reasoning_effort=${JSON.stringify(req.effort)}`);
|
|
44
|
+
return { argv, resultFile };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function normalizeCodexResult(raw: HarnessRawResult): SpawnResult {
|
|
48
|
+
if (raw.exitCode === 127) {
|
|
49
|
+
return { ok: false, text: "", durationMs: raw.durationMs, error: notFoundError("codex") };
|
|
50
|
+
}
|
|
51
|
+
if (raw.exitCode !== 0) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
text: "",
|
|
55
|
+
durationMs: raw.durationMs,
|
|
56
|
+
error: `codex exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
text: (raw.resultFileText ?? raw.stdout).trim(),
|
|
62
|
+
durationMs: raw.durationMs,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const codexSpawner: Spawner = async (req: SpawnRequest): Promise<SpawnResult> => {
|
|
67
|
+
const t0 = Date.now();
|
|
68
|
+
const outFile = join(
|
|
69
|
+
tmpdir(),
|
|
70
|
+
`harnery-codex-${process.pid}-${randomBytes(4).toString("hex")}.txt`,
|
|
71
|
+
);
|
|
45
72
|
|
|
46
73
|
try {
|
|
47
|
-
|
|
74
|
+
let invocation: HarnessInvocation;
|
|
75
|
+
try {
|
|
76
|
+
invocation = buildCodexInvocation(req, outFile);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return { ok: false, text: "", durationMs: 0, error: (error as Error).message };
|
|
79
|
+
}
|
|
80
|
+
const r = await exec(invocation.argv, {
|
|
48
81
|
cwd: req.cwd,
|
|
49
82
|
env: buildChildEnv(req.runId, { subscriptionOnly: req.subscriptionOnly }),
|
|
50
83
|
timeout: req.timeoutMs,
|
|
51
84
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
if (r.exitCode !== 0) {
|
|
58
|
-
return {
|
|
59
|
-
ok: false,
|
|
60
|
-
text: "",
|
|
61
|
-
durationMs,
|
|
62
|
-
error: `codex exited ${r.exitCode}: ${(r.stderr || r.stdout).slice(0, 500)}`,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!existsSync(outFile)) {
|
|
67
|
-
// Exit 0 but no last-message file: fall back to stdout (contract drift guard).
|
|
68
|
-
return { ok: true, text: r.stdout, durationMs };
|
|
69
|
-
}
|
|
70
|
-
return { ok: true, text: readFileSync(outFile, "utf8").trim(), durationMs };
|
|
85
|
+
return normalizeCodexResult({
|
|
86
|
+
...r,
|
|
87
|
+
durationMs: Date.now() - t0,
|
|
88
|
+
resultFileText: existsSync(outFile) ? readFileSync(outFile, "utf8") : undefined,
|
|
89
|
+
});
|
|
71
90
|
} finally {
|
|
72
91
|
rmSync(outFile, { force: true });
|
|
73
92
|
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { exec } from "../../lib/exec.ts";
|
|
20
|
+
import { validateHarnessEffort } from "../harnesses/profiles.ts";
|
|
21
|
+
import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.ts";
|
|
20
22
|
import { buildChildEnv } from "./child-env.ts";
|
|
21
23
|
import { notFoundError } from "./harnesses.ts";
|
|
22
24
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
@@ -46,44 +48,61 @@ export function parseCursorOutput(stdout: string): {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
export function buildCursorInvocation(req: SpawnRequest): HarnessInvocation {
|
|
52
|
+
validateHarnessEffort("cursor", req.effort);
|
|
52
53
|
// --trust: headless cursor-agent refuses untrusted workspaces (exit 1,
|
|
53
54
|
// "Workspace Trust Required"). Workflow children run in the engine's cwd
|
|
54
55
|
// deliberately and may edit files — the same posture as the codex adapter's
|
|
55
56
|
// `--sandbox workspace-write` — so trusting that directory is implied.
|
|
56
57
|
const argv = ["cursor-agent", "-p", req.prompt, "--output-format", "json", "--trust"];
|
|
57
58
|
if (req.model) argv.push("--model", req.model);
|
|
59
|
+
return { argv };
|
|
60
|
+
}
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
timeout: req.timeoutMs,
|
|
63
|
-
});
|
|
64
|
-
const durationMs = Date.now() - t0;
|
|
65
|
-
|
|
66
|
-
if (r.exitCode === 127) {
|
|
67
|
-
return { ok: false, text: "", durationMs, error: notFoundError("cursor") };
|
|
62
|
+
export function normalizeCursorResult(raw: HarnessRawResult): SpawnResult {
|
|
63
|
+
if (raw.exitCode === 127) {
|
|
64
|
+
return { ok: false, text: "", durationMs: raw.durationMs, error: notFoundError("cursor") };
|
|
68
65
|
}
|
|
69
|
-
if (
|
|
66
|
+
if (raw.exitCode !== 0) {
|
|
70
67
|
return {
|
|
71
68
|
ok: false,
|
|
72
69
|
text: "",
|
|
73
|
-
durationMs,
|
|
74
|
-
error: `cursor-agent exited ${
|
|
70
|
+
durationMs: raw.durationMs,
|
|
71
|
+
error: `cursor-agent exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
|
|
75
72
|
};
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
const parsed = parseCursorOutput(
|
|
75
|
+
const parsed = parseCursorOutput(raw.stdout);
|
|
79
76
|
if (parsed.isError) {
|
|
80
77
|
return {
|
|
81
78
|
ok: false,
|
|
82
79
|
text: parsed.text,
|
|
83
80
|
sessionId: parsed.sessionId,
|
|
84
|
-
durationMs,
|
|
81
|
+
durationMs: raw.durationMs,
|
|
85
82
|
error: `cursor-agent reported is_error: ${parsed.text.slice(0, 300)}`,
|
|
86
83
|
};
|
|
87
84
|
}
|
|
88
|
-
return {
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
text: parsed.text,
|
|
88
|
+
sessionId: parsed.sessionId,
|
|
89
|
+
durationMs: raw.durationMs,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const cursorSpawner: Spawner = async (req: SpawnRequest): Promise<SpawnResult> => {
|
|
94
|
+
const t0 = Date.now();
|
|
95
|
+
let invocation: HarnessInvocation;
|
|
96
|
+
try {
|
|
97
|
+
invocation = buildCursorInvocation(req);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return { ok: false, text: "", durationMs: 0, error: (error as Error).message };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const r = await exec(invocation.argv, {
|
|
103
|
+
cwd: req.cwd,
|
|
104
|
+
env: buildChildEnv(req.runId, { subscriptionOnly: req.subscriptionOnly }),
|
|
105
|
+
timeout: req.timeoutMs,
|
|
106
|
+
});
|
|
107
|
+
return normalizeCursorResult({ ...r, durationMs: Date.now() - t0 });
|
|
89
108
|
};
|
|
@@ -28,6 +28,9 @@ export interface AgentOpts {
|
|
|
28
28
|
schema?: StageSchema;
|
|
29
29
|
/** Model slug passed through to the harness CLI (default: the CLI's default). */
|
|
30
30
|
model?: string;
|
|
31
|
+
/** Reasoning effort mapped through the selected harness profile. Unsupported
|
|
32
|
+
* values fail before the vendor process starts. */
|
|
33
|
+
effort?: string;
|
|
31
34
|
/** Attempt ceiling for the schema-retry loop (default 2). */
|
|
32
35
|
maxAttempts?: number;
|
|
33
36
|
/** Subprocess timeout ms (default 300_000). */
|
|
@@ -43,7 +46,10 @@ export interface AgentOpts {
|
|
|
43
46
|
harness?: HarnessName;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
/** Open registry key. The built-in catalog currently contains Claude Code,
|
|
50
|
+
* Codex, and Cursor; consumers may register another adapter without widening
|
|
51
|
+
* a package-owned union first. */
|
|
52
|
+
export type HarnessName = string;
|
|
47
53
|
|
|
48
54
|
/** What a spawn adapter returns for one subagent run. */
|
|
49
55
|
export interface SpawnResult {
|
|
@@ -61,6 +67,7 @@ export interface SpawnResult {
|
|
|
61
67
|
export interface SpawnRequest {
|
|
62
68
|
prompt: string;
|
|
63
69
|
model?: string;
|
|
70
|
+
effort?: string;
|
|
64
71
|
timeoutMs: number;
|
|
65
72
|
maxTurns: number;
|
|
66
73
|
cwd: string;
|
|
@@ -105,7 +112,7 @@ export interface EngineOpts {
|
|
|
105
112
|
coordRoot: string;
|
|
106
113
|
/** Spawner registry keyed by harness. A single-harness caller registers one
|
|
107
114
|
* entry and names it in `defaultHarness`. */
|
|
108
|
-
spawners:
|
|
115
|
+
spawners: Readonly<Record<HarnessName, Spawner | undefined>>;
|
|
109
116
|
/** Harness used when an agent() call doesn't name one (default "claude-code"). */
|
|
110
117
|
defaultHarness?: HarnessName;
|
|
111
118
|
/** Resume: run id of a prior run whose journal supplies cached results.
|
|
@@ -74,6 +74,14 @@ export interface BrowserOptions {
|
|
|
74
74
|
* via this callback (e.g., a Cloudflare-bypass header for specific zones).
|
|
75
75
|
*/
|
|
76
76
|
extraHeaders?: (url: string) => Record<string, string>;
|
|
77
|
+
/**
|
|
78
|
+
* Extra Chromium command-line flags, passed through to Playwright's
|
|
79
|
+
* `launchPersistentContext` `args`. Used for environment-specific
|
|
80
|
+
* workarounds — most notably `--disable-gpu` for headed windows under
|
|
81
|
+
* WSLg (see `./launch-args.ts`). Empty/undefined means Playwright's
|
|
82
|
+
* defaults only.
|
|
83
|
+
*/
|
|
84
|
+
launchArgs?: string[];
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
export interface NavigateResult {
|
|
@@ -98,6 +106,14 @@ export interface FailedRequest {
|
|
|
98
106
|
method: string;
|
|
99
107
|
failure: string;
|
|
100
108
|
resourceType: string;
|
|
109
|
+
/** HTTP status for kind "http" entries; null for network-level failures. */
|
|
110
|
+
status: number | null;
|
|
111
|
+
/** "http" = request completed with a >=400 response; "network" = never completed (DNS, TLS, aborts, tunnel). */
|
|
112
|
+
kind: "http" | "network";
|
|
113
|
+
/** True when the entry is the main frame's document response — lets consumers
|
|
114
|
+
* distinguish an expected error-page status (a 404 route under test) from a
|
|
115
|
+
* broken subresource. */
|
|
116
|
+
document?: boolean;
|
|
101
117
|
}
|
|
102
118
|
|
|
103
119
|
export interface Diagnostics {
|
|
@@ -137,6 +153,9 @@ export class Browser {
|
|
|
137
153
|
this.context = await chromium.launchPersistentContext(this.profileDir, {
|
|
138
154
|
headless: !this.opts.headed,
|
|
139
155
|
viewport: this.opts.viewport ?? { width: 1280, height: 800 },
|
|
156
|
+
...(this.opts.launchArgs && this.opts.launchArgs.length > 0
|
|
157
|
+
? { args: this.opts.launchArgs }
|
|
158
|
+
: {}),
|
|
140
159
|
...(this.opts.recordHarPath
|
|
141
160
|
? { recordHar: { path: this.opts.recordHarPath, mode: "full" as const } }
|
|
142
161
|
: {}),
|
|
@@ -192,6 +211,24 @@ export class Browser {
|
|
|
192
211
|
method: req.method(),
|
|
193
212
|
failure: req.failure()?.errorText ?? "unknown",
|
|
194
213
|
resourceType: req.resourceType(),
|
|
214
|
+
status: null,
|
|
215
|
+
kind: "network",
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
// HTTP-level failures: `requestfailed` only fires for requests that never
|
|
219
|
+
// complete (DNS, TLS, aborts), so a script/stylesheet answered with a
|
|
220
|
+
// 4xx/5xx would otherwise be invisible to failedRequests-based gates.
|
|
221
|
+
page.on("response", (res) => {
|
|
222
|
+
if (res.status() < 400) return;
|
|
223
|
+
const req = res.request();
|
|
224
|
+
this.failedRequests.push({
|
|
225
|
+
url: res.url(),
|
|
226
|
+
method: req.method(),
|
|
227
|
+
failure: `HTTP ${res.status()}`,
|
|
228
|
+
resourceType: req.resourceType(),
|
|
229
|
+
status: res.status(),
|
|
230
|
+
kind: "http",
|
|
231
|
+
document: req.resourceType() === "document" && res.frame() === page.mainFrame(),
|
|
195
232
|
});
|
|
196
233
|
});
|
|
197
234
|
}
|
package/src/lib/browser/index.ts
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chromium launch-arg helpers for environment-specific workarounds.
|
|
5
|
+
*
|
|
6
|
+
* The one that matters today is WSLg: a headed Chromium window renders its
|
|
7
|
+
* page to a GPU-backed surface that WSLg composites over an RDP stream to the
|
|
8
|
+
* Windows host. On a range of Windows / GPU-driver / WSLg combinations that
|
|
9
|
+
* GPU-composited surface never presents, so the window shows in the taskbar
|
|
10
|
+
* but paints blank — even though the page itself runs fine (JS, navigation,
|
|
11
|
+
* and DOM all work). Forcing Chromium onto its software compositor with
|
|
12
|
+
* `--disable-gpu` restores on-screen paint. It only matters for headed mode
|
|
13
|
+
* (headless never composites to a display) and only under WSL.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** True when running under WSL (WSL1 or WSL2). */
|
|
17
|
+
export function isWSL(): boolean {
|
|
18
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
19
|
+
try {
|
|
20
|
+
return /microsoft|wsl/i.test(readFileSync("/proc/version", "utf8"));
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Chromium launch flags to make a headed window paint under WSLg. Returns
|
|
28
|
+
* `["--disable-gpu"]` on WSL, `[]` elsewhere. Callers apply these only for
|
|
29
|
+
* headed launches. See the module doc for the failure mode.
|
|
30
|
+
*/
|
|
31
|
+
export function wslHeadedLaunchArgs(): string[] {
|
|
32
|
+
return isWSL() ? ["--disable-gpu"] : [];
|
|
33
|
+
}
|