@prismatic-io/lux 0.0.2-preview.18 → 0.0.2-preview.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/lib/answerers/persona/index.d.ts +5 -1
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.d.ts +3 -1
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/authoring.d.ts +3 -1
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +2 -1
- package/lib/authoring.js.map +1 -1
- package/lib/cli/harness-plan.d.ts +26 -0
- package/lib/cli/harness-plan.d.ts.map +1 -0
- package/lib/cli/harness-plan.js +75 -0
- package/lib/cli/harness-plan.js.map +1 -0
- package/lib/cli/init-templates.d.ts +2 -2
- package/lib/cli/init-templates.d.ts.map +1 -1
- package/lib/cli/init-templates.js +11 -4
- package/lib/cli/init-templates.js.map +1 -1
- package/lib/cli/init.d.ts +2 -0
- package/lib/cli/init.d.ts.map +1 -1
- package/lib/cli/init.js +6 -3
- package/lib/cli/init.js.map +1 -1
- package/lib/cli/output-schemas.d.ts +7 -0
- package/lib/cli/output-schemas.d.ts.map +1 -1
- package/lib/cli/program.d.ts +5 -10
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +44 -77
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/run-options.d.ts +19 -0
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js +92 -0
- package/lib/cli/run-options.js.map +1 -1
- package/lib/core/case.d.ts +2 -0
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +3 -0
- package/lib/core/case.js.map +1 -1
- package/lib/core/driver.d.ts +13 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js +10 -0
- package/lib/core/driver.js.map +1 -1
- package/lib/core/harness-catalog.d.ts +35 -0
- package/lib/core/harness-catalog.d.ts.map +1 -0
- package/lib/core/harness-catalog.js +45 -0
- package/lib/core/harness-catalog.js.map +1 -0
- package/lib/core/harness-catalogs/antigravity.d.ts +11 -0
- package/lib/core/harness-catalogs/antigravity.d.ts.map +1 -0
- package/lib/core/harness-catalogs/antigravity.js +23 -0
- package/lib/core/harness-catalogs/antigravity.js.map +1 -0
- package/lib/core/harness-catalogs/claude-code.d.ts +12 -0
- package/lib/core/harness-catalogs/claude-code.d.ts.map +1 -0
- package/lib/core/harness-catalogs/claude-code.js +24 -0
- package/lib/core/harness-catalogs/claude-code.js.map +1 -0
- package/lib/core/harness-catalogs/codex.d.ts +11 -0
- package/lib/core/harness-catalogs/codex.d.ts.map +1 -0
- package/lib/core/harness-catalogs/codex.js +26 -0
- package/lib/core/harness-catalogs/codex.js.map +1 -0
- package/lib/core/harness-catalogs/copilot.d.ts +29 -0
- package/lib/core/harness-catalogs/copilot.d.ts.map +1 -0
- package/lib/core/harness-catalogs/copilot.js +45 -0
- package/lib/core/harness-catalogs/copilot.js.map +1 -0
- package/lib/core/harness-catalogs/cursor.d.ts +2 -0
- package/lib/core/harness-catalogs/cursor.d.ts.map +1 -0
- package/lib/core/harness-catalogs/cursor.js +13 -0
- package/lib/core/harness-catalogs/cursor.js.map +1 -0
- package/lib/core/harness-catalogs/index.d.ts +67 -0
- package/lib/core/harness-catalogs/index.d.ts.map +1 -0
- package/lib/core/harness-catalogs/index.js +15 -0
- package/lib/core/harness-catalogs/index.js.map +1 -0
- package/lib/core/harness-matrix.d.ts +70 -0
- package/lib/core/harness-matrix.d.ts.map +1 -0
- package/lib/core/harness-matrix.js +194 -0
- package/lib/core/harness-matrix.js.map +1 -0
- package/lib/core/index.d.ts +3 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +3 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/model-cli.d.ts +4 -11
- package/lib/core/model-cli.d.ts.map +1 -1
- package/lib/core/model-cli.js +23 -17
- package/lib/core/model-cli.js.map +1 -1
- package/lib/core/platform-process.d.ts +1 -0
- package/lib/core/platform-process.d.ts.map +1 -1
- package/lib/core/platform-process.js +1 -1
- package/lib/core/platform-process.js.map +1 -1
- package/lib/core/run.d.ts +4 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +2 -0
- package/lib/core/run.js.map +1 -1
- package/lib/drivers/antigravity/config.d.ts +46 -0
- package/lib/drivers/antigravity/config.d.ts.map +1 -0
- package/lib/drivers/antigravity/config.js +39 -0
- package/lib/drivers/antigravity/config.js.map +1 -0
- package/lib/drivers/antigravity/events.d.ts +4 -0
- package/lib/drivers/antigravity/events.d.ts.map +1 -0
- package/lib/drivers/antigravity/events.js +193 -0
- package/lib/drivers/antigravity/events.js.map +1 -0
- package/lib/drivers/antigravity/index.d.ts +60 -0
- package/lib/drivers/antigravity/index.d.ts.map +1 -0
- package/lib/drivers/antigravity/index.js +90 -0
- package/lib/drivers/antigravity/index.js.map +1 -0
- package/lib/drivers/claude-code/index.d.ts +93 -7
- package/lib/drivers/claude-code/index.d.ts.map +1 -1
- package/lib/drivers/claude-code/index.js +14 -8
- package/lib/drivers/claude-code/index.js.map +1 -1
- package/lib/drivers/codex/config.d.ts +61 -4
- package/lib/drivers/codex/config.d.ts.map +1 -1
- package/lib/drivers/codex/config.js +19 -5
- package/lib/drivers/codex/config.js.map +1 -1
- package/lib/drivers/codex/index.d.ts +63 -5
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +11 -3
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/copilot/config.d.ts +81 -0
- package/lib/drivers/copilot/config.d.ts.map +1 -0
- package/lib/drivers/copilot/config.js +54 -0
- package/lib/drivers/copilot/config.js.map +1 -0
- package/lib/drivers/copilot/index.d.ts +103 -0
- package/lib/drivers/copilot/index.d.ts.map +1 -0
- package/lib/drivers/copilot/index.js +256 -0
- package/lib/drivers/copilot/index.js.map +1 -0
- package/lib/drivers/cursor/config.d.ts +6 -1
- package/lib/drivers/cursor/config.d.ts.map +1 -1
- package/lib/drivers/cursor/config.js +2 -3
- package/lib/drivers/cursor/config.js.map +1 -1
- package/lib/drivers/cursor/index.d.ts +7 -2
- package/lib/drivers/cursor/index.d.ts.map +1 -1
- package/lib/drivers/cursor/index.js +9 -6
- package/lib/drivers/cursor/index.js.map +1 -1
- package/lib/drivers/cursor/interaction.d.ts +1 -2
- package/lib/drivers/cursor/interaction.d.ts.map +1 -1
- package/lib/drivers/cursor/interaction.js +3 -14
- package/lib/drivers/cursor/interaction.js.map +1 -1
- package/lib/drivers/{cursor/events.d.ts → shared/acp-events.d.ts} +5 -3
- package/lib/drivers/shared/acp-events.d.ts.map +1 -0
- package/lib/drivers/{cursor/events.js → shared/acp-events.js} +20 -10
- package/lib/drivers/shared/acp-events.js.map +1 -0
- package/lib/drivers/shared/acp-interaction.d.ts +7 -0
- package/lib/drivers/shared/acp-interaction.d.ts.map +1 -0
- package/lib/drivers/shared/acp-interaction.js +20 -0
- package/lib/drivers/shared/acp-interaction.js.map +1 -0
- package/lib/drivers/{cursor → shared}/acp-transport.d.ts +2 -0
- package/lib/drivers/shared/acp-transport.d.ts.map +1 -0
- package/lib/drivers/{cursor → shared}/acp-transport.js +10 -10
- package/lib/drivers/shared/acp-transport.js.map +1 -0
- package/lib/index.d.ts +7 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -2
- package/lib/index.js.map +1 -1
- package/lib/optimization/reflective.d.ts +2 -0
- package/lib/optimization/reflective.d.ts.map +1 -1
- package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
- package/lib/orchestrator/comparison-identity.js +1 -0
- package/lib/orchestrator/comparison-identity.js.map +1 -1
- package/lib/orchestrator/config.d.ts +7 -0
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +8 -1
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +72 -2
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/experiment-context.d.ts +1 -1
- package/lib/orchestrator/harness-catalog-drift.d.ts +22 -0
- package/lib/orchestrator/harness-catalog-drift.d.ts.map +1 -0
- package/lib/orchestrator/harness-catalog-drift.js +90 -0
- package/lib/orchestrator/harness-catalog-drift.js.map +1 -0
- package/lib/orchestrator/orchestrator.d.ts +9 -1
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +14 -22
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/run-dir.d.ts +2 -0
- package/lib/orchestrator/run-dir.d.ts.map +1 -1
- package/lib/orchestrator/run-dir.js +1 -1
- package/lib/orchestrator/run-dir.js.map +1 -1
- package/lib/orchestrator/run-execution.d.ts +1 -0
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +2 -0
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts +3 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-compare.js +27 -17
- package/lib/orchestrator/suite-compare.js.map +1 -1
- package/lib/orchestrator/suite-runs.d.ts +3 -0
- package/lib/orchestrator/suite-runs.d.ts.map +1 -1
- package/lib/orchestrator/suite-runs.js +2 -0
- package/lib/orchestrator/suite-runs.js.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +9 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +14 -4
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/authoring.ts +10 -3
- package/src/cli/harness-plan.ts +117 -0
- package/src/cli/init-templates.ts +12 -5
- package/src/cli/init.ts +12 -5
- package/src/cli/program.ts +50 -92
- package/src/cli/run-options.ts +123 -0
- package/src/core/case.ts +3 -0
- package/src/core/driver.ts +26 -0
- package/src/core/harness-catalog.ts +100 -0
- package/src/core/harness-catalogs/antigravity.ts +24 -0
- package/src/core/harness-catalogs/claude-code.ts +25 -0
- package/src/core/harness-catalogs/codex.ts +27 -0
- package/src/core/harness-catalogs/copilot.ts +46 -0
- package/src/core/harness-catalogs/cursor.ts +13 -0
- package/src/core/harness-catalogs/index.ts +22 -0
- package/src/core/harness-matrix.ts +298 -0
- package/src/core/index.ts +3 -0
- package/src/core/model-cli.ts +38 -21
- package/src/core/platform-process.ts +4 -4
- package/src/core/run.ts +2 -0
- package/src/drivers/antigravity/README.md +46 -0
- package/src/drivers/antigravity/config.ts +44 -0
- package/src/drivers/antigravity/events.ts +235 -0
- package/src/drivers/antigravity/index.ts +109 -0
- package/src/drivers/claude-code/index.ts +18 -7
- package/src/drivers/codex/config.ts +23 -5
- package/src/drivers/codex/index.ts +15 -2
- package/src/drivers/copilot/README.md +52 -0
- package/src/drivers/copilot/config.ts +58 -0
- package/src/drivers/copilot/index.ts +292 -0
- package/src/drivers/cursor/README.md +9 -6
- package/src/drivers/cursor/config.ts +2 -3
- package/src/drivers/cursor/index.ts +10 -6
- package/src/drivers/cursor/interaction.ts +3 -19
- package/src/drivers/{cursor/events.ts → shared/acp-events.ts} +20 -9
- package/src/drivers/shared/acp-interaction.ts +27 -0
- package/src/drivers/{cursor → shared}/acp-transport.ts +15 -10
- package/src/index.ts +44 -3
- package/src/orchestrator/comparison-identity.ts +1 -0
- package/src/orchestrator/config.ts +8 -0
- package/src/orchestrator/doctor.ts +125 -3
- package/src/orchestrator/harness-catalog-drift.ts +132 -0
- package/src/orchestrator/orchestrator.ts +31 -26
- package/src/orchestrator/run-dir.ts +8 -1
- package/src/orchestrator/run-execution.ts +3 -0
- package/src/orchestrator/suite-compare.ts +29 -19
- package/src/orchestrator/suite-runs.ts +3 -1
- package/src/orchestrator/suite-summary.ts +19 -4
- package/lib/drivers/cursor/acp-transport.d.ts.map +0 -1
- package/lib/drivers/cursor/acp-transport.js.map +0 -1
- package/lib/drivers/cursor/events.d.ts.map +0 -1
- package/lib/drivers/cursor/events.js.map +0 -1
- package/skills/lux/SKILL.md +0 -89
- package/skills/lux/references/cli.md +0 -128
- package/skills/lux/references/eval-authoring.md +0 -266
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import {
|
|
3
|
+
type AgentDriver,
|
|
4
|
+
type Answer,
|
|
5
|
+
type Artifact,
|
|
6
|
+
type DriverEventMap,
|
|
7
|
+
defineDriver,
|
|
8
|
+
discoverCliVersion,
|
|
9
|
+
type ReadyState,
|
|
10
|
+
type StartContext,
|
|
11
|
+
selectModelAndReasoningEffort,
|
|
12
|
+
TypedEmitter,
|
|
13
|
+
} from "../../core/index.js";
|
|
14
|
+
import { AcpEvents } from "../shared/acp-events.js";
|
|
15
|
+
import { permissionAnswer, permissionRequestText, records } from "../shared/acp-interaction.js";
|
|
16
|
+
import { AcpTransport, asRecord, type RpcId, type RpcRecord } from "../shared/acp-transport.js";
|
|
17
|
+
import { walkArtifacts } from "../shared/artifacts.js";
|
|
18
|
+
import {
|
|
19
|
+
buildCopilotArgs,
|
|
20
|
+
COPILOT_MODE_IDS,
|
|
21
|
+
type CopilotDriverConfig,
|
|
22
|
+
CopilotDriverConfigSchema,
|
|
23
|
+
} from "./config.js";
|
|
24
|
+
|
|
25
|
+
class CopilotDriver implements AgentDriver {
|
|
26
|
+
private readonly emitter = new TypedEmitter<DriverEventMap>();
|
|
27
|
+
private readonly events: AcpEvents;
|
|
28
|
+
private transport: AcpTransport | undefined;
|
|
29
|
+
private sessionId: string | undefined;
|
|
30
|
+
private cwd: string | undefined;
|
|
31
|
+
private artifactsDir: string | undefined;
|
|
32
|
+
private ready = false;
|
|
33
|
+
private runPromise: Promise<void> | undefined;
|
|
34
|
+
private terminal = false;
|
|
35
|
+
private idleTimer: NodeJS.Timeout | undefined;
|
|
36
|
+
private interruptCount = 0;
|
|
37
|
+
private readonly interrupts = new Map<string, { id: RpcId; params: RpcRecord }>();
|
|
38
|
+
|
|
39
|
+
private readonly config: CopilotDriverConfig;
|
|
40
|
+
|
|
41
|
+
constructor(config: CopilotDriverConfig) {
|
|
42
|
+
this.config = config;
|
|
43
|
+
this.events = new AcpEvents("copilot", config.maxToolCalls, config.maxQueuedBytes);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
on<K extends keyof DriverEventMap>(
|
|
47
|
+
event: K,
|
|
48
|
+
handler: (e: DriverEventMap[K]) => void | Promise<void>,
|
|
49
|
+
): void {
|
|
50
|
+
this.emitter.on(event, handler);
|
|
51
|
+
}
|
|
52
|
+
off<K extends keyof DriverEventMap>(
|
|
53
|
+
event: K,
|
|
54
|
+
handler: (e: DriverEventMap[K]) => void | Promise<void>,
|
|
55
|
+
): void {
|
|
56
|
+
this.emitter.off(event, handler);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async start(ctx: StartContext): Promise<ReadyState> {
|
|
60
|
+
if (this.transport || this.terminal)
|
|
61
|
+
throw new Error("Copilot driver cannot be started twice or after close");
|
|
62
|
+
this.cwd = await realpath(this.config.cwd ?? ctx.artifactsDir);
|
|
63
|
+
this.artifactsDir = await realpath(ctx.artifactsDir);
|
|
64
|
+
const cliVersion = await discoverCliVersion(this.config.command);
|
|
65
|
+
const transport = new AcpTransport({
|
|
66
|
+
label: "Copilot ACP",
|
|
67
|
+
command: this.config.command,
|
|
68
|
+
args: buildCopilotArgs(this.config),
|
|
69
|
+
cwd: this.cwd,
|
|
70
|
+
env: { ...process.env, ...this.config.env },
|
|
71
|
+
signal: ctx.abortSignal,
|
|
72
|
+
maxLineBytes: this.config.maxLineBytes,
|
|
73
|
+
maxQueuedBytes: this.config.maxQueuedBytes,
|
|
74
|
+
onMessage: (message) => this.message(message),
|
|
75
|
+
onFailure: (error) => this.fail(error),
|
|
76
|
+
onActivity: () => this.bumpIdleTimer(),
|
|
77
|
+
});
|
|
78
|
+
this.transport = transport;
|
|
79
|
+
try {
|
|
80
|
+
await transport.spawned();
|
|
81
|
+
const initialized = await this.request("initialize", {
|
|
82
|
+
protocolVersion: 1,
|
|
83
|
+
clientInfo: { name: "lux", version: "1" },
|
|
84
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
85
|
+
});
|
|
86
|
+
if (initialized.protocolVersion !== 1)
|
|
87
|
+
throw new Error("Copilot ACP returned an unsupported protocol version");
|
|
88
|
+
if (!records(initialized.authMethods).some((method) => method.id === "copilot-login")) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
"Expected Copilot ACP with copilot-login authentication; check the configured command points to GitHub Copilot CLI",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const session = await this.request("session/new", { cwd: this.cwd, mcpServers: [] });
|
|
94
|
+
if (typeof session.sessionId !== "string") throw new Error("Copilot ACP omitted sessionId");
|
|
95
|
+
this.sessionId = session.sessionId;
|
|
96
|
+
const modeId = COPILOT_MODE_IDS[this.config.mode];
|
|
97
|
+
const currentModeId = asRecord(session.modes)?.currentModeId;
|
|
98
|
+
if (currentModeId !== modeId) {
|
|
99
|
+
if (!records(asRecord(session.modes)?.availableModes).some((mode) => mode.id === modeId))
|
|
100
|
+
throw new Error(`Copilot ACP does not advertise the '${this.config.mode}' mode`);
|
|
101
|
+
await this.request("session/set_mode", { sessionId: this.sessionId, modeId });
|
|
102
|
+
}
|
|
103
|
+
this.ready = true;
|
|
104
|
+
this.bumpIdleTimer();
|
|
105
|
+
this.runPromise = this.run(ctx.prompt).catch((error: unknown) => this.fail(error));
|
|
106
|
+
return {
|
|
107
|
+
agentId: "copilot",
|
|
108
|
+
model: this.config.model,
|
|
109
|
+
...(typeof asRecord(initialized.agentInfo)?.version === "string"
|
|
110
|
+
? { agentVersion: String(asRecord(initialized.agentInfo)?.version) }
|
|
111
|
+
: {}),
|
|
112
|
+
detail: {
|
|
113
|
+
sessionId: this.sessionId,
|
|
114
|
+
transport: "acp",
|
|
115
|
+
protocolVersion: 1,
|
|
116
|
+
...(cliVersion ? { cliVersion } : {}),
|
|
117
|
+
mode: this.config.mode,
|
|
118
|
+
reasoningEffort: this.config.reasoningEffort,
|
|
119
|
+
modelResolution: "requested",
|
|
120
|
+
nativeConfig: session.configOptions ?? [],
|
|
121
|
+
usageAvailability: "unknown",
|
|
122
|
+
isolation: "none",
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
await this.close();
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private request(method: string, params: RpcRecord): Promise<RpcRecord> {
|
|
132
|
+
if (!this.transport) throw new Error("Copilot ACP has not started");
|
|
133
|
+
return this.transport.request(method, params, this.config.startupTimeoutMs);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private async run(prompt: string): Promise<void> {
|
|
137
|
+
const result = await this.transport?.request("session/prompt", {
|
|
138
|
+
sessionId: this.sessionId,
|
|
139
|
+
prompt: [{ type: "text", text: prompt }],
|
|
140
|
+
});
|
|
141
|
+
await this.transport?.drained();
|
|
142
|
+
if (this.terminal) return;
|
|
143
|
+
if (result?.stopReason !== "end_turn")
|
|
144
|
+
throw new Error(`Copilot stopped with ${String(result?.stopReason ?? "unknown reason")}`);
|
|
145
|
+
this.terminal = true;
|
|
146
|
+
clearTimeout(this.idleTimer);
|
|
147
|
+
await this.emitter.emit("done", {
|
|
148
|
+
exitReason: "done",
|
|
149
|
+
summary: {
|
|
150
|
+
sessionId: this.sessionId,
|
|
151
|
+
stopReason: result.stopReason,
|
|
152
|
+
usageAvailability: "unknown",
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async message(message: RpcRecord): Promise<void> {
|
|
158
|
+
if (this.terminal) return;
|
|
159
|
+
const method = String(message.method);
|
|
160
|
+
const params = asRecord(message.params) ?? {};
|
|
161
|
+
const id = message.id;
|
|
162
|
+
if (typeof id === "number" || typeof id === "string") {
|
|
163
|
+
if (typeof params.sessionId === "string" && params.sessionId !== this.sessionId) {
|
|
164
|
+
this.transport?.write({ id, error: { code: -32602, message: "Unknown session" } });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (method !== "session/request_permission") {
|
|
168
|
+
this.transport?.write({
|
|
169
|
+
id,
|
|
170
|
+
error: { code: -32601, message: `Unsupported method: ${method}` },
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (++this.interruptCount > this.config.maxInterrupts) {
|
|
175
|
+
this.transport?.write({ id, result: { outcome: { outcome: "cancelled" } } });
|
|
176
|
+
throw new Error("Copilot exceeded maxInterrupts");
|
|
177
|
+
}
|
|
178
|
+
const interruptId = `copilot-${id}`;
|
|
179
|
+
if (this.interrupts.has(interruptId)) throw new Error("Copilot reused a pending request ID");
|
|
180
|
+
this.interrupts.set(interruptId, { id, params });
|
|
181
|
+
clearTimeout(this.idleTimer);
|
|
182
|
+
await this.emitter.emit("interrupt", {
|
|
183
|
+
kind: "approve",
|
|
184
|
+
id: interruptId,
|
|
185
|
+
request: permissionRequestText(params),
|
|
186
|
+
context: params,
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (method === "session/update") {
|
|
191
|
+
if (params.sessionId !== this.sessionId) return;
|
|
192
|
+
const update = asRecord(params.update);
|
|
193
|
+
if (update)
|
|
194
|
+
for (const event of this.events.update(update)) await this.emitter.emit("progress", event);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async respond(answer: Answer): Promise<void> {
|
|
199
|
+
const pending = this.interrupts.get(answer.id);
|
|
200
|
+
if (!pending) throw new Error(`Copilot has no pending interrupt '${answer.id}'`);
|
|
201
|
+
this.transport?.write({ id: pending.id, result: permissionAnswer(pending.params, answer) });
|
|
202
|
+
this.interrupts.delete(answer.id);
|
|
203
|
+
this.bumpIdleTimer();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private bumpIdleTimer(): void {
|
|
207
|
+
clearTimeout(this.idleTimer);
|
|
208
|
+
if (!this.ready || this.terminal || this.interrupts.size > 0) return;
|
|
209
|
+
this.idleTimer = setTimeout(
|
|
210
|
+
() =>
|
|
211
|
+
this.fail(
|
|
212
|
+
new Error(`Copilot emitted no event for ${this.config.idleTimeoutMs}ms`),
|
|
213
|
+
"idle-timeout",
|
|
214
|
+
),
|
|
215
|
+
this.config.idleTimeoutMs,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private fail(error: unknown, exitReason: "error" | "idle-timeout" = "error"): void {
|
|
220
|
+
if (this.terminal) return;
|
|
221
|
+
this.terminal = true;
|
|
222
|
+
clearTimeout(this.idleTimer);
|
|
223
|
+
if (this.ready)
|
|
224
|
+
void this.transport?.drained().then(() =>
|
|
225
|
+
this.emitter.emit("error", {
|
|
226
|
+
exitReason,
|
|
227
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
void this.transport?.close();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async quiesce(): Promise<void> {
|
|
234
|
+
clearTimeout(this.idleTimer);
|
|
235
|
+
const cancelling = !this.terminal && this.sessionId !== undefined;
|
|
236
|
+
this.terminal = true;
|
|
237
|
+
if (cancelling) {
|
|
238
|
+
for (const pending of this.interrupts.values())
|
|
239
|
+
this.transport?.write({ id: pending.id, result: { outcome: { outcome: "cancelled" } } });
|
|
240
|
+
this.transport?.write({ method: "session/cancel", params: { sessionId: this.sessionId } });
|
|
241
|
+
let timer: NodeJS.Timeout | undefined;
|
|
242
|
+
try {
|
|
243
|
+
await Promise.race([
|
|
244
|
+
this.runPromise,
|
|
245
|
+
new Promise<void>((resolve) => {
|
|
246
|
+
timer = setTimeout(resolve, 200);
|
|
247
|
+
}),
|
|
248
|
+
]);
|
|
249
|
+
} finally {
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
this.interrupts.clear();
|
|
254
|
+
await this.transport?.close();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async collect(): Promise<Artifact[]> {
|
|
258
|
+
await this.quiesce();
|
|
259
|
+
if (!this.cwd) return [];
|
|
260
|
+
const artifacts = await walkArtifacts(this.cwd);
|
|
261
|
+
return this.cwd === this.artifactsDir
|
|
262
|
+
? artifacts
|
|
263
|
+
: artifacts.map((artifact) => ({ ...artifact, root: this.cwd as string }));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async close(): Promise<void> {
|
|
267
|
+
await this.quiesce();
|
|
268
|
+
this.emitter.removeAllListeners();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export const copilotDriver = defineDriver({
|
|
273
|
+
name: "copilot",
|
|
274
|
+
configSchema: CopilotDriverConfigSchema,
|
|
275
|
+
runtime: (config) => ({
|
|
276
|
+
commands: [config.command],
|
|
277
|
+
capabilities: {
|
|
278
|
+
interactive: !config.allowAllTools,
|
|
279
|
+
"tool-calls": true,
|
|
280
|
+
"tool-results": true,
|
|
281
|
+
artifacts: true,
|
|
282
|
+
usage: false,
|
|
283
|
+
cost: false,
|
|
284
|
+
isolation: false,
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
selectHarness: selectModelAndReasoningEffort,
|
|
288
|
+
create: (config) => new CopilotDriver(config),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
export type { CopilotDriverConfig };
|
|
292
|
+
export { buildCopilotArgs, CopilotDriverConfigSchema };
|
|
@@ -10,17 +10,20 @@ set `command` to its absolute path if another application owns that name.
|
|
|
10
10
|
config: {
|
|
11
11
|
command: "/absolute/path/to/cursor/agent",
|
|
12
12
|
model: "YOUR_CURSOR_MODEL_ID",
|
|
13
|
-
// Optional: only
|
|
13
|
+
// Optional: one of low, medium, high, xhigh; applied only when ACP advertises it.
|
|
14
14
|
// reasoningEffort: "low",
|
|
15
15
|
},
|
|
16
16
|
}
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
`model` is required.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
`model` is required. Cursor's model list is account-scoped (`agent
|
|
20
|
+
--list-models` after login), so the harness catalog keeps it open: any ID is
|
|
21
|
+
accepted in configuration and in a `matrix`, and some IDs encode reasoning
|
|
22
|
+
effort. Lux passes that ID through `--model` and rejects a different model when
|
|
23
|
+
the session reports one. If `reasoningEffort` is supplied (`low`, `medium`,
|
|
24
|
+
`high`, or `xhigh`), Lux requires a matching advertised ACP thought-level
|
|
25
|
+
selector and checks the response confirms the requested value before sending
|
|
26
|
+
the prompt. No default effort is applied.
|
|
24
27
|
|
|
25
28
|
Supported interactions are Cursor's native multiple-choice questions, plan
|
|
26
29
|
approval, and ACP tool permissions. Lux grants one-operation permissions only;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { cursorCatalog } from "../../core/index.js";
|
|
2
3
|
|
|
3
4
|
export const CursorDriverConfigSchema = z
|
|
4
5
|
.object({
|
|
5
6
|
command: z.string().min(1).default("agent"),
|
|
6
|
-
/** Cursor model IDs may include an effort suffix; never inherit Auto. */
|
|
7
7
|
model: z.string().min(1),
|
|
8
|
-
|
|
9
|
-
reasoningEffort: z.string().min(1).optional(),
|
|
8
|
+
reasoningEffort: z.enum(cursorCatalog.efforts).optional(),
|
|
10
9
|
mode: z.enum(["agent", "plan", "ask"]).default("agent"),
|
|
11
10
|
cwd: z.string().optional(),
|
|
12
11
|
env: z.record(z.string(), z.string()).optional(),
|
|
@@ -8,17 +8,19 @@ import {
|
|
|
8
8
|
discoverCliVersion,
|
|
9
9
|
type ReadyState,
|
|
10
10
|
type StartContext,
|
|
11
|
+
selectModelAndReasoningEffort,
|
|
11
12
|
TypedEmitter,
|
|
12
13
|
} from "../../core/index.js";
|
|
14
|
+
import { AcpEvents } from "../shared/acp-events.js";
|
|
15
|
+
import { permissionRequestText, records } from "../shared/acp-interaction.js";
|
|
16
|
+
import { AcpTransport, asRecord, type RpcId, type RpcRecord } from "../shared/acp-transport.js";
|
|
13
17
|
import { walkArtifacts } from "../shared/artifacts.js";
|
|
14
|
-
import { AcpTransport, asRecord, type RpcId, type RpcRecord } from "./acp-transport.js";
|
|
15
18
|
import { buildCursorArgs, type CursorDriverConfig, CursorDriverConfigSchema } from "./config.js";
|
|
16
|
-
import {
|
|
17
|
-
import { cursorAnswer, cursorQuestionText, effortSelector, records } from "./interaction.js";
|
|
19
|
+
import { cursorAnswer, cursorQuestionText, effortSelector } from "./interaction.js";
|
|
18
20
|
|
|
19
21
|
class CursorDriver implements AgentDriver {
|
|
20
22
|
private readonly emitter = new TypedEmitter<DriverEventMap>();
|
|
21
|
-
private readonly events:
|
|
23
|
+
private readonly events: AcpEvents;
|
|
22
24
|
private transport: AcpTransport | undefined;
|
|
23
25
|
private sessionId: string | undefined;
|
|
24
26
|
private cwd: string | undefined;
|
|
@@ -34,7 +36,7 @@ class CursorDriver implements AgentDriver {
|
|
|
34
36
|
|
|
35
37
|
constructor(config: CursorDriverConfig) {
|
|
36
38
|
this.config = config;
|
|
37
|
-
this.events = new
|
|
39
|
+
this.events = new AcpEvents("cursor", config.maxToolCalls, config.maxQueuedBytes);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
on<K extends keyof DriverEventMap>(
|
|
@@ -57,6 +59,7 @@ class CursorDriver implements AgentDriver {
|
|
|
57
59
|
this.artifactsDir = await realpath(ctx.artifactsDir);
|
|
58
60
|
const cliVersion = await discoverCliVersion(this.config.command);
|
|
59
61
|
const transport = new AcpTransport({
|
|
62
|
+
label: "Cursor ACP",
|
|
60
63
|
command: this.config.command,
|
|
61
64
|
args: buildCursorArgs(this.config),
|
|
62
65
|
cwd: this.cwd,
|
|
@@ -198,7 +201,7 @@ class CursorDriver implements AgentDriver {
|
|
|
198
201
|
request:
|
|
199
202
|
method === "cursor/create_plan"
|
|
200
203
|
? String(params.plan ?? params.overview ?? "Approve this plan?")
|
|
201
|
-
:
|
|
204
|
+
: permissionRequestText(params),
|
|
202
205
|
context: params,
|
|
203
206
|
});
|
|
204
207
|
}
|
|
@@ -309,6 +312,7 @@ export const cursorDriver = defineDriver({
|
|
|
309
312
|
isolation: false,
|
|
310
313
|
},
|
|
311
314
|
}),
|
|
315
|
+
selectHarness: selectModelAndReasoningEffort,
|
|
312
316
|
create: (config) => new CursorDriver(config),
|
|
313
317
|
});
|
|
314
318
|
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type { Answer } from "../../core/index.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export const records = (value: unknown): RpcRecord[] =>
|
|
5
|
-
Array.isArray(value)
|
|
6
|
-
? value.flatMap((entry): RpcRecord[] => {
|
|
7
|
-
const record = asRecord(entry);
|
|
8
|
-
return record ? [record] : [];
|
|
9
|
-
})
|
|
10
|
-
: [];
|
|
2
|
+
import { permissionAnswer, records } from "../shared/acp-interaction.js";
|
|
3
|
+
import { asRecord, type RpcRecord } from "../shared/acp-transport.js";
|
|
11
4
|
|
|
12
5
|
export const cursorQuestionText = (params: RpcRecord): string =>
|
|
13
6
|
`Return a JSON object mapping each question ID to its selected option ID. For questions allowing multiple selections, use an array of option IDs.\n\n${records(
|
|
@@ -57,16 +50,7 @@ export const cursorAnswer = (method: string, params: RpcRecord, answer: Answer):
|
|
|
57
50
|
: { outcome: "rejected", ...(answer.reason ? { reason: answer.reason } : {}) },
|
|
58
51
|
};
|
|
59
52
|
}
|
|
60
|
-
|
|
61
|
-
(entry) => entry.kind === (answer.approved ? "allow_once" : "reject_once"),
|
|
62
|
-
);
|
|
63
|
-
// A Lux approval applies to this operation, never a remembered permission.
|
|
64
|
-
return {
|
|
65
|
-
outcome:
|
|
66
|
-
option && typeof option.optionId === "string"
|
|
67
|
-
? { outcome: "selected", optionId: option.optionId }
|
|
68
|
-
: { outcome: "cancelled" },
|
|
69
|
-
};
|
|
53
|
+
return permissionAnswer(params, answer);
|
|
70
54
|
};
|
|
71
55
|
|
|
72
56
|
const optionValues = (options: unknown): string[] =>
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { type Progress, toolResultProgress } from "../../core/index.js";
|
|
2
|
+
import { records } from "./acp-interaction.js";
|
|
2
3
|
import { asRecord, type RpcRecord } from "./acp-transport.js";
|
|
3
|
-
import { records } from "./interaction.js";
|
|
4
4
|
|
|
5
5
|
type ToolState = { value: RpcRecord; bytes: number; name: string };
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/** Normalizes ACP `session/update` notifications into Lux progress events. */
|
|
8
|
+
export class AcpEvents {
|
|
8
9
|
private seq = 0;
|
|
9
10
|
private retainedBytes = 0;
|
|
10
11
|
private readonly tools = new Map<string, ToolState>();
|
|
11
12
|
private readonly completed = new Set<string>();
|
|
12
13
|
|
|
14
|
+
private readonly prefix: string;
|
|
13
15
|
private readonly maxToolCalls: number;
|
|
14
16
|
private readonly maxBytes: number;
|
|
15
17
|
|
|
16
|
-
constructor(maxToolCalls: number, maxBytes: number) {
|
|
18
|
+
constructor(prefix: string, maxToolCalls: number, maxBytes: number) {
|
|
19
|
+
this.prefix = prefix;
|
|
17
20
|
this.maxToolCalls = maxToolCalls;
|
|
18
21
|
this.maxBytes = maxBytes;
|
|
19
22
|
}
|
|
@@ -28,7 +31,7 @@ export class CursorEvents {
|
|
|
28
31
|
if (content?.type !== "text" || typeof content.text !== "string") return [];
|
|
29
32
|
return [
|
|
30
33
|
{
|
|
31
|
-
id:
|
|
34
|
+
id: `${this.prefix}-message-${++this.seq}`,
|
|
32
35
|
kind: update.sessionUpdate === "agent_message_chunk" ? "agent-message" : "reasoning",
|
|
33
36
|
payload: { text: content.text },
|
|
34
37
|
ts,
|
|
@@ -36,20 +39,28 @@ export class CursorEvents {
|
|
|
36
39
|
];
|
|
37
40
|
}
|
|
38
41
|
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
|
39
|
-
return [
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
id: `${this.prefix}-update-${++this.seq}`,
|
|
45
|
+
kind: `${this.prefix}-update`,
|
|
46
|
+
payload: update,
|
|
47
|
+
ts,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
40
50
|
}
|
|
41
51
|
const id = update.toolCallId;
|
|
42
|
-
if (typeof id !== "string")
|
|
52
|
+
if (typeof id !== "string")
|
|
53
|
+
throw new Error(`${this.prefix} ACP tool update omitted toolCallId`);
|
|
43
54
|
if (this.completed.has(id)) return [];
|
|
44
55
|
const old = this.tools.get(id);
|
|
45
56
|
if (!old && this.tools.size + this.completed.size >= this.maxToolCalls)
|
|
46
|
-
throw new Error(
|
|
57
|
+
throw new Error(`${this.prefix} ACP exceeded maxToolCalls`);
|
|
47
58
|
const value = { ...old?.value, ...update };
|
|
48
59
|
const bytes = Buffer.byteLength(JSON.stringify(value));
|
|
49
60
|
this.retainedBytes += bytes - (old?.bytes ?? 0);
|
|
50
61
|
if (this.retainedBytes > this.maxBytes)
|
|
51
|
-
throw new Error(
|
|
52
|
-
const name = old?.name ?? String(value.title ?? value.kind ??
|
|
62
|
+
throw new Error(`${this.prefix} ACP tool state exceeded maxQueuedBytes`);
|
|
63
|
+
const name = old?.name ?? String(value.title ?? value.kind ?? `${this.prefix}-tool`);
|
|
53
64
|
this.tools.set(id, { value, bytes, name });
|
|
54
65
|
const events: Progress[] = old
|
|
55
66
|
? []
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Answer } from "../../core/index.js";
|
|
2
|
+
import { asRecord, type RpcRecord } from "./acp-transport.js";
|
|
3
|
+
|
|
4
|
+
export const records = (value: unknown): RpcRecord[] =>
|
|
5
|
+
Array.isArray(value)
|
|
6
|
+
? value.flatMap((entry): RpcRecord[] => {
|
|
7
|
+
const record = asRecord(entry);
|
|
8
|
+
return record ? [record] : [];
|
|
9
|
+
})
|
|
10
|
+
: [];
|
|
11
|
+
|
|
12
|
+
/** A Lux approval applies to this operation only, never a remembered permission. */
|
|
13
|
+
export const permissionAnswer = (params: RpcRecord, answer: Answer): RpcRecord => {
|
|
14
|
+
if (answer.kind !== "approve") throw new Error("ACP permission requires an approval answer");
|
|
15
|
+
const option = records(params.options).find(
|
|
16
|
+
(entry) => entry.kind === (answer.approved ? "allow_once" : "reject_once"),
|
|
17
|
+
);
|
|
18
|
+
return {
|
|
19
|
+
outcome:
|
|
20
|
+
option && typeof option.optionId === "string"
|
|
21
|
+
? { outcome: "selected", optionId: option.optionId }
|
|
22
|
+
: { outcome: "cancelled" },
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const permissionRequestText = (params: RpcRecord): string =>
|
|
27
|
+
String(asRecord(params.toolCall)?.title ?? "Approve this tool call?");
|
|
@@ -8,6 +8,8 @@ export const asRecord = (value: unknown): RpcRecord | undefined =>
|
|
|
8
8
|
: undefined;
|
|
9
9
|
|
|
10
10
|
type TransportOptions = {
|
|
11
|
+
/** Harness name used in failure messages. */
|
|
12
|
+
label: string;
|
|
11
13
|
command: string;
|
|
12
14
|
args: string[];
|
|
13
15
|
cwd: string;
|
|
@@ -55,7 +57,7 @@ export class AcpTransport {
|
|
|
55
57
|
if (!this.stopped)
|
|
56
58
|
this.fail(
|
|
57
59
|
new Error(
|
|
58
|
-
|
|
60
|
+
`${this.options.label} exited before completion (code=${exitCode}, signal=${signal})${this.stderrTail ? `: ${this.stderrTail}` : ""}`,
|
|
59
61
|
),
|
|
60
62
|
);
|
|
61
63
|
});
|
|
@@ -66,14 +68,16 @@ export class AcpTransport {
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
request(method: string, params: RpcRecord, timeoutMs?: number): Promise<RpcRecord> {
|
|
69
|
-
if (this.stopped) return Promise.reject(new Error(
|
|
71
|
+
if (this.stopped) return Promise.reject(new Error(`${this.options.label} is closed`));
|
|
70
72
|
const id = this.nextId++;
|
|
71
73
|
return new Promise((resolve, reject) => {
|
|
72
74
|
const timer =
|
|
73
75
|
timeoutMs === undefined
|
|
74
76
|
? undefined
|
|
75
77
|
: setTimeout(() => {
|
|
76
|
-
this.fail(
|
|
78
|
+
this.fail(
|
|
79
|
+
new Error(`${this.options.label} ${method} timed out after ${timeoutMs}ms`),
|
|
80
|
+
);
|
|
77
81
|
}, timeoutMs);
|
|
78
82
|
this.pending.set(id, { resolve, reject, timer });
|
|
79
83
|
try {
|
|
@@ -85,10 +89,10 @@ export class AcpTransport {
|
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
write(message: RpcRecord): void {
|
|
88
|
-
if (this.stopped) throw new Error(
|
|
92
|
+
if (this.stopped) throw new Error(`${this.options.label} is closed`);
|
|
89
93
|
const line = `${JSON.stringify({ jsonrpc: "2.0", ...message })}\n`;
|
|
90
94
|
if (Buffer.byteLength(line) > this.options.maxLineBytes)
|
|
91
|
-
throw new Error(
|
|
95
|
+
throw new Error(`${this.options.label} outgoing message exceeded maxLineBytes`);
|
|
92
96
|
this.process.child.stdin.write(line);
|
|
93
97
|
}
|
|
94
98
|
|
|
@@ -98,7 +102,7 @@ export class AcpTransport {
|
|
|
98
102
|
|
|
99
103
|
async close(): Promise<void> {
|
|
100
104
|
this.stopped = true;
|
|
101
|
-
this.rejectPending(new Error(
|
|
105
|
+
this.rejectPending(new Error(`${this.options.label} is closed`));
|
|
102
106
|
this.buffer = Buffer.alloc(0);
|
|
103
107
|
await this.process.terminate();
|
|
104
108
|
}
|
|
@@ -126,7 +130,7 @@ export class AcpTransport {
|
|
|
126
130
|
const end = newline === -1 ? chunk.length : newline;
|
|
127
131
|
const segment = chunk.subarray(offset, end);
|
|
128
132
|
if (this.buffer.length + segment.length > this.options.maxLineBytes) {
|
|
129
|
-
this.fail(new Error(
|
|
133
|
+
this.fail(new Error(`${this.options.label} stdout line exceeded maxLineBytes`));
|
|
130
134
|
return;
|
|
131
135
|
}
|
|
132
136
|
this.buffer = Buffer.concat([this.buffer, segment]);
|
|
@@ -147,7 +151,7 @@ export class AcpTransport {
|
|
|
147
151
|
/* Rejected below. */
|
|
148
152
|
}
|
|
149
153
|
if (message?.jsonrpc !== "2.0") {
|
|
150
|
-
this.fail(new Error(
|
|
154
|
+
this.fail(new Error(`${this.options.label} returned an invalid JSON-RPC message`));
|
|
151
155
|
return;
|
|
152
156
|
}
|
|
153
157
|
this.options.onActivity();
|
|
@@ -157,13 +161,14 @@ export class AcpTransport {
|
|
|
157
161
|
this.pending.delete(message.id as RpcId);
|
|
158
162
|
clearTimeout(pending.timer);
|
|
159
163
|
const error = asRecord(message.error);
|
|
160
|
-
if (error)
|
|
164
|
+
if (error)
|
|
165
|
+
pending.reject(new Error(String(error.message ?? `${this.options.label} request failed`)));
|
|
161
166
|
else pending.resolve(asRecord(message.result) ?? {});
|
|
162
167
|
return;
|
|
163
168
|
}
|
|
164
169
|
this.queuedBytes += line.length;
|
|
165
170
|
if (this.queuedBytes > this.options.maxQueuedBytes) {
|
|
166
|
-
this.fail(new Error(
|
|
171
|
+
this.fail(new Error(`${this.options.label} pending events exceeded maxQueuedBytes`));
|
|
167
172
|
return;
|
|
168
173
|
}
|
|
169
174
|
const queued = message;
|