@revealui/harnesses 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.
- package/LICENSE +22 -0
- package/LICENSE.commercial +112 -0
- package/README.md +105 -0
- package/dist/chunk-BDA7D725.js +595 -0
- package/dist/chunk-BDA7D725.js.map +1 -0
- package/dist/chunk-JUNNIQS3.js +1 -0
- package/dist/chunk-JUNNIQS3.js.map +1 -0
- package/dist/chunk-PG4RAOWS.js +251 -0
- package/dist/chunk-PG4RAOWS.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/workboard/index.d.ts +120 -0
- package/dist/workboard/index.js +12 -0
- package/dist/workboard/index.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WorkboardManager,
|
|
3
|
+
deriveSessionId,
|
|
4
|
+
detectSessionType
|
|
5
|
+
} from "./chunk-PG4RAOWS.js";
|
|
6
|
+
|
|
7
|
+
// src/adapters/claude-code-adapter.ts
|
|
8
|
+
import { execFile } from "child_process";
|
|
9
|
+
import { promisify } from "util";
|
|
10
|
+
var execFileAsync = promisify(execFile);
|
|
11
|
+
var ClaudeCodeAdapter = class {
|
|
12
|
+
id = "claude-code";
|
|
13
|
+
name = "Claude Code";
|
|
14
|
+
eventHandlers = /* @__PURE__ */ new Set();
|
|
15
|
+
workboardPath;
|
|
16
|
+
constructor(workboardPath) {
|
|
17
|
+
this.workboardPath = workboardPath ?? process.env.REVEALUI_WORKBOARD_PATH;
|
|
18
|
+
}
|
|
19
|
+
getCapabilities() {
|
|
20
|
+
return {
|
|
21
|
+
generateCode: false,
|
|
22
|
+
// interactive only — no headless CLI interface
|
|
23
|
+
analyzeCode: false,
|
|
24
|
+
// interactive only — no headless CLI interface
|
|
25
|
+
applyEdit: false,
|
|
26
|
+
// interactive only — edits are applied inside Claude Code sessions
|
|
27
|
+
applyConfig: false,
|
|
28
|
+
// config managed interactively via ~/.claude/settings.json
|
|
29
|
+
readWorkboard: this.workboardPath !== void 0,
|
|
30
|
+
writeWorkboard: this.workboardPath !== void 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async getInfo() {
|
|
34
|
+
let version;
|
|
35
|
+
try {
|
|
36
|
+
const { stdout } = await execFileAsync("claude", ["--version"], {
|
|
37
|
+
timeout: 5e3
|
|
38
|
+
});
|
|
39
|
+
version = stdout.trim().split("\n")[0];
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
return { id: this.id, name: this.name, version, capabilities: this.getCapabilities() };
|
|
43
|
+
}
|
|
44
|
+
async isAvailable() {
|
|
45
|
+
try {
|
|
46
|
+
await execFileAsync("claude", ["--version"], { timeout: 3e3 });
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async execute(command) {
|
|
53
|
+
switch (command.type) {
|
|
54
|
+
case "get-status": {
|
|
55
|
+
const available = await this.isAvailable();
|
|
56
|
+
return { success: true, command: command.type, data: { available } };
|
|
57
|
+
}
|
|
58
|
+
case "get-running-instances": {
|
|
59
|
+
return { success: true, command: command.type, data: [] };
|
|
60
|
+
}
|
|
61
|
+
case "generate-code":
|
|
62
|
+
case "analyze-code": {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
command: command.type,
|
|
66
|
+
message: `${command.type} is not supported \u2014 Claude Code operates interactively`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
case "apply-config": {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
command: command.type,
|
|
73
|
+
message: "Config is managed interactively via ~/.claude/settings.json"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
case "read-workboard": {
|
|
77
|
+
if (!this.workboardPath) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
command: command.type,
|
|
81
|
+
message: "REVEALUI_WORKBOARD_PATH is not set"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const manager = new WorkboardManager(this.workboardPath);
|
|
85
|
+
const state = await manager.readAsync();
|
|
86
|
+
return { success: true, command: command.type, data: state };
|
|
87
|
+
}
|
|
88
|
+
case "update-workboard": {
|
|
89
|
+
if (!this.workboardPath) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
command: command.type,
|
|
93
|
+
message: "REVEALUI_WORKBOARD_PATH is not set"
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const manager = new WorkboardManager(this.workboardPath);
|
|
97
|
+
manager.updateSession(command.sessionId, {
|
|
98
|
+
...command.task !== void 0 && { task: command.task },
|
|
99
|
+
...command.files !== void 0 && { files: command.files.join(", ") },
|
|
100
|
+
updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
|
|
101
|
+
});
|
|
102
|
+
return { success: true, command: command.type };
|
|
103
|
+
}
|
|
104
|
+
default: {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
command: command.type,
|
|
108
|
+
message: `Command not supported by ${this.name}`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
onEvent(handler) {
|
|
114
|
+
this.eventHandlers.add(handler);
|
|
115
|
+
return () => this.eventHandlers.delete(handler);
|
|
116
|
+
}
|
|
117
|
+
async dispose() {
|
|
118
|
+
this.eventHandlers.clear();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/adapters/copilot-adapter.ts
|
|
123
|
+
var CopilotAdapter = class {
|
|
124
|
+
id = "copilot";
|
|
125
|
+
name = "GitHub Copilot";
|
|
126
|
+
eventHandlers = /* @__PURE__ */ new Set();
|
|
127
|
+
getCapabilities() {
|
|
128
|
+
return {
|
|
129
|
+
generateCode: false,
|
|
130
|
+
// stub — no CLI available
|
|
131
|
+
analyzeCode: false,
|
|
132
|
+
applyEdit: false,
|
|
133
|
+
applyConfig: false,
|
|
134
|
+
readWorkboard: false,
|
|
135
|
+
writeWorkboard: false
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async getInfo() {
|
|
139
|
+
return { id: this.id, name: this.name, capabilities: this.getCapabilities() };
|
|
140
|
+
}
|
|
141
|
+
async isAvailable() {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
async execute(command) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
command: command.type,
|
|
148
|
+
message: "Copilot adapter is a stub \u2014 no standalone CLI available"
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
onEvent(handler) {
|
|
152
|
+
this.eventHandlers.add(handler);
|
|
153
|
+
return () => this.eventHandlers.delete(handler);
|
|
154
|
+
}
|
|
155
|
+
async dispose() {
|
|
156
|
+
this.eventHandlers.clear();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/detection/auto-detector.ts
|
|
161
|
+
async function autoDetectHarnesses(registry) {
|
|
162
|
+
const candidates = [new ClaudeCodeAdapter(), new CopilotAdapter()];
|
|
163
|
+
const registered = [];
|
|
164
|
+
await Promise.all(
|
|
165
|
+
candidates.map(async (adapter) => {
|
|
166
|
+
try {
|
|
167
|
+
if (await adapter.isAvailable()) {
|
|
168
|
+
registry.register(adapter);
|
|
169
|
+
registered.push(adapter.id);
|
|
170
|
+
} else {
|
|
171
|
+
await adapter.dispose();
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
await adapter.dispose();
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
return registered;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/registry/harness-registry.ts
|
|
182
|
+
var HarnessRegistry = class {
|
|
183
|
+
adapters = /* @__PURE__ */ new Map();
|
|
184
|
+
/** Register an adapter. Throws if an adapter with the same id already exists. */
|
|
185
|
+
register(adapter) {
|
|
186
|
+
if (this.adapters.has(adapter.id)) {
|
|
187
|
+
throw new Error(`Harness adapter already registered: ${adapter.id}`);
|
|
188
|
+
}
|
|
189
|
+
this.adapters.set(adapter.id, adapter);
|
|
190
|
+
}
|
|
191
|
+
/** Unregister an adapter, disposing it in the process. */
|
|
192
|
+
async unregister(id) {
|
|
193
|
+
const adapter = this.adapters.get(id);
|
|
194
|
+
if (adapter) {
|
|
195
|
+
await adapter.dispose();
|
|
196
|
+
this.adapters.delete(id);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Retrieve an adapter by id. */
|
|
200
|
+
get(id) {
|
|
201
|
+
return this.adapters.get(id);
|
|
202
|
+
}
|
|
203
|
+
/** List all registered adapter ids. */
|
|
204
|
+
listAll() {
|
|
205
|
+
return Array.from(this.adapters.keys());
|
|
206
|
+
}
|
|
207
|
+
/** List ids of adapters that report isAvailable() === true. */
|
|
208
|
+
async listAvailable() {
|
|
209
|
+
const results = await Promise.all(
|
|
210
|
+
Array.from(this.adapters.entries()).map(async ([id, adapter]) => ({
|
|
211
|
+
id,
|
|
212
|
+
available: await adapter.isAvailable()
|
|
213
|
+
}))
|
|
214
|
+
);
|
|
215
|
+
return results.filter((r) => r.available).map((r) => r.id);
|
|
216
|
+
}
|
|
217
|
+
/** Dispose all adapters and clear the registry. */
|
|
218
|
+
async disposeAll() {
|
|
219
|
+
await Promise.all(Array.from(this.adapters.values()).map((a) => a.dispose()));
|
|
220
|
+
this.adapters.clear();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/config/harness-config-paths.ts
|
|
225
|
+
import { homedir } from "os";
|
|
226
|
+
import { join } from "path";
|
|
227
|
+
var HOME = homedir();
|
|
228
|
+
var SSD_BASE = process.env.REVEALUI_SSD_PATH ?? "/mnt/e/.revealui";
|
|
229
|
+
var LOCAL_CONFIG_PATHS = {
|
|
230
|
+
"claude-code": join(HOME, ".claude", "settings.json"),
|
|
231
|
+
cursor: join(HOME, ".cursor", "settings.json"),
|
|
232
|
+
copilot: join(HOME, ".config", "github-copilot", "hosts.json")
|
|
233
|
+
};
|
|
234
|
+
var SSD_CONFIG_FILES = {
|
|
235
|
+
"claude-code": "settings.json",
|
|
236
|
+
cursor: "settings.json",
|
|
237
|
+
copilot: "hosts.json"
|
|
238
|
+
};
|
|
239
|
+
function getLocalConfigPath(harnessId) {
|
|
240
|
+
return LOCAL_CONFIG_PATHS[harnessId];
|
|
241
|
+
}
|
|
242
|
+
function getSsdConfigPath(harnessId, ssdBase = SSD_BASE) {
|
|
243
|
+
const file = SSD_CONFIG_FILES[harnessId];
|
|
244
|
+
if (!file) return void 0;
|
|
245
|
+
return join(ssdBase, "harness-configs", harnessId, file);
|
|
246
|
+
}
|
|
247
|
+
function getConfigurableHarnesses() {
|
|
248
|
+
return Object.keys(LOCAL_CONFIG_PATHS);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/config/config-sync.ts
|
|
252
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
253
|
+
import { dirname } from "path";
|
|
254
|
+
function syncConfig(harnessId, direction, ssdBase) {
|
|
255
|
+
const localPath = getLocalConfigPath(harnessId);
|
|
256
|
+
const ssdPath = getSsdConfigPath(harnessId, ssdBase);
|
|
257
|
+
if (!(localPath && ssdPath)) {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
harnessId,
|
|
261
|
+
direction,
|
|
262
|
+
message: `No config path known for harness: ${harnessId}`
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
if (direction === "pull") {
|
|
267
|
+
if (!existsSync(ssdPath)) {
|
|
268
|
+
return { success: false, harnessId, direction, message: `SSD config not found: ${ssdPath}` };
|
|
269
|
+
}
|
|
270
|
+
mkdirSync(dirname(localPath), { recursive: true });
|
|
271
|
+
copyFileSync(ssdPath, localPath);
|
|
272
|
+
return { success: true, harnessId, direction, message: `Pulled ${ssdPath} \u2192 ${localPath}` };
|
|
273
|
+
} else {
|
|
274
|
+
if (!existsSync(localPath)) {
|
|
275
|
+
return {
|
|
276
|
+
success: false,
|
|
277
|
+
harnessId,
|
|
278
|
+
direction,
|
|
279
|
+
message: `Local config not found: ${localPath}`
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
mkdirSync(dirname(ssdPath), { recursive: true });
|
|
283
|
+
copyFileSync(localPath, ssdPath);
|
|
284
|
+
return { success: true, harnessId, direction, message: `Pushed ${localPath} \u2192 ${ssdPath}` };
|
|
285
|
+
}
|
|
286
|
+
} catch (err) {
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
harnessId,
|
|
290
|
+
direction,
|
|
291
|
+
message: err instanceof Error ? err.message : String(err)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function diffConfig(harnessId, ssdBase) {
|
|
296
|
+
const localPath = getLocalConfigPath(harnessId);
|
|
297
|
+
const ssdPath = getSsdConfigPath(harnessId, ssdBase);
|
|
298
|
+
const localExists = !!localPath && existsSync(localPath);
|
|
299
|
+
const ssdExists = !!ssdPath && existsSync(ssdPath);
|
|
300
|
+
if (!(localExists && ssdExists)) {
|
|
301
|
+
return { harnessId, localExists, ssdExists, identical: false };
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const localContent = readFileSync(localPath, "utf8");
|
|
305
|
+
const ssdContent = readFileSync(ssdPath, "utf8");
|
|
306
|
+
return { harnessId, localExists, ssdExists, identical: localContent === ssdContent };
|
|
307
|
+
} catch {
|
|
308
|
+
return { harnessId, localExists, ssdExists, identical: false };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/detection/process-detector.ts
|
|
313
|
+
import { execFile as execFile2 } from "child_process";
|
|
314
|
+
import { readdir } from "fs/promises";
|
|
315
|
+
import { join as join2 } from "path";
|
|
316
|
+
import { promisify as promisify2 } from "util";
|
|
317
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
318
|
+
async function findProcesses(pattern) {
|
|
319
|
+
try {
|
|
320
|
+
const { stdout } = await execFileAsync2("pgrep", ["-a", pattern], { timeout: 3e3 });
|
|
321
|
+
return stdout.trim().split("\n").filter(Boolean).map((line) => {
|
|
322
|
+
const spaceIdx = line.indexOf(" ");
|
|
323
|
+
const pid = parseInt(line.slice(0, spaceIdx), 10);
|
|
324
|
+
const command = line.slice(spaceIdx + 1);
|
|
325
|
+
return { pid, command };
|
|
326
|
+
});
|
|
327
|
+
} catch {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
var HARNESS_PROCESS_PATTERNS = {
|
|
332
|
+
"claude-code": ["claude"],
|
|
333
|
+
cursor: ["cursor", "Cursor"],
|
|
334
|
+
copilot: ["copilot"]
|
|
335
|
+
};
|
|
336
|
+
async function findHarnessProcesses(harnessId) {
|
|
337
|
+
const patterns = HARNESS_PROCESS_PATTERNS[harnessId];
|
|
338
|
+
if (!patterns) return [];
|
|
339
|
+
const results = await Promise.all(patterns.map((p) => findProcesses(p)));
|
|
340
|
+
return results.flat().map((p) => ({ ...p, harnessId }));
|
|
341
|
+
}
|
|
342
|
+
async function findAllHarnessProcesses() {
|
|
343
|
+
const results = await Promise.all(
|
|
344
|
+
Object.keys(HARNESS_PROCESS_PATTERNS).map((id) => findHarnessProcesses(id))
|
|
345
|
+
);
|
|
346
|
+
return results.flat();
|
|
347
|
+
}
|
|
348
|
+
async function findClaudeCodeSockets() {
|
|
349
|
+
const dirs = [
|
|
350
|
+
"/tmp",
|
|
351
|
+
process.env.XDG_RUNTIME_DIR,
|
|
352
|
+
join2(process.env.HOME ?? "/tmp", ".claude")
|
|
353
|
+
].filter(Boolean);
|
|
354
|
+
const sockets = [];
|
|
355
|
+
for (const dir of dirs) {
|
|
356
|
+
try {
|
|
357
|
+
const entries = await readdir(dir);
|
|
358
|
+
for (const entry of entries) {
|
|
359
|
+
if (/^claude.*\.sock$/.test(entry)) {
|
|
360
|
+
sockets.push(join2(dir, entry));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return sockets;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/server/rpc-server.ts
|
|
370
|
+
import { createServer } from "net";
|
|
371
|
+
var ERR_PARSE = -32700;
|
|
372
|
+
var ERR_INVALID_PARAMS = -32602;
|
|
373
|
+
var ERR_METHOD_NOT_FOUND = -32601;
|
|
374
|
+
var ERR_INTERNAL = -32603;
|
|
375
|
+
var RpcServer = class {
|
|
376
|
+
constructor(registry, socketPath) {
|
|
377
|
+
this.registry = registry;
|
|
378
|
+
this.socketPath = socketPath;
|
|
379
|
+
this.server.on("connection", (socket) => {
|
|
380
|
+
let buffer = "";
|
|
381
|
+
socket.on("data", (chunk) => {
|
|
382
|
+
buffer += chunk.toString();
|
|
383
|
+
const lines = buffer.split("\n");
|
|
384
|
+
buffer = lines.pop() ?? "";
|
|
385
|
+
for (const line of lines) {
|
|
386
|
+
this.handleLine(line.trim(), (response) => {
|
|
387
|
+
socket.write(`${JSON.stringify(response)}
|
|
388
|
+
`);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
server = createServer();
|
|
395
|
+
handleLine(line, reply) {
|
|
396
|
+
let req;
|
|
397
|
+
try {
|
|
398
|
+
req = JSON.parse(line);
|
|
399
|
+
} catch {
|
|
400
|
+
reply({ jsonrpc: "2.0", id: null, error: { code: ERR_PARSE, message: "Parse error" } });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.dispatch(req).then(reply).catch((err) => {
|
|
404
|
+
reply({
|
|
405
|
+
jsonrpc: "2.0",
|
|
406
|
+
id: req.id,
|
|
407
|
+
error: { code: ERR_INTERNAL, message: err instanceof Error ? err.message : String(err) }
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
async dispatch(req) {
|
|
412
|
+
const { id, method, params } = req;
|
|
413
|
+
const p = params ?? {};
|
|
414
|
+
switch (method) {
|
|
415
|
+
case "harness.list": {
|
|
416
|
+
const ids = await this.registry.listAvailable();
|
|
417
|
+
const infos = await Promise.all(ids.map((id2) => this.registry.get(id2)?.getInfo()));
|
|
418
|
+
return { jsonrpc: "2.0", id, result: infos };
|
|
419
|
+
}
|
|
420
|
+
case "harness.info": {
|
|
421
|
+
const harnessId = p.harnessId;
|
|
422
|
+
if (!harnessId) {
|
|
423
|
+
return {
|
|
424
|
+
jsonrpc: "2.0",
|
|
425
|
+
id,
|
|
426
|
+
error: { code: ERR_INVALID_PARAMS, message: "harnessId required" }
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const adapter = this.registry.get(harnessId);
|
|
430
|
+
if (!adapter) {
|
|
431
|
+
return {
|
|
432
|
+
jsonrpc: "2.0",
|
|
433
|
+
id,
|
|
434
|
+
error: { code: ERR_INVALID_PARAMS, message: `Harness not found: ${harnessId}` }
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return { jsonrpc: "2.0", id, result: await adapter.getInfo() };
|
|
438
|
+
}
|
|
439
|
+
case "harness.execute": {
|
|
440
|
+
const harnessId = p.harnessId;
|
|
441
|
+
const command = p.command;
|
|
442
|
+
if (!(harnessId && command)) {
|
|
443
|
+
return {
|
|
444
|
+
jsonrpc: "2.0",
|
|
445
|
+
id,
|
|
446
|
+
error: { code: ERR_INVALID_PARAMS, message: "harnessId and command required" }
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const adapter = this.registry.get(harnessId);
|
|
450
|
+
if (!adapter) {
|
|
451
|
+
return {
|
|
452
|
+
jsonrpc: "2.0",
|
|
453
|
+
id,
|
|
454
|
+
error: { code: ERR_INVALID_PARAMS, message: `Harness not found: ${harnessId}` }
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const result = await adapter.execute(command);
|
|
458
|
+
return { jsonrpc: "2.0", id, result };
|
|
459
|
+
}
|
|
460
|
+
case "harness.syncConfig": {
|
|
461
|
+
const harnessId = p.harnessId;
|
|
462
|
+
const direction = p.direction;
|
|
463
|
+
if (!(harnessId && direction)) {
|
|
464
|
+
return {
|
|
465
|
+
jsonrpc: "2.0",
|
|
466
|
+
id,
|
|
467
|
+
error: { code: ERR_INVALID_PARAMS, message: "harnessId and direction required" }
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return { jsonrpc: "2.0", id, result: syncConfig(harnessId, direction) };
|
|
471
|
+
}
|
|
472
|
+
case "harness.diffConfig": {
|
|
473
|
+
const harnessId = p.harnessId;
|
|
474
|
+
if (!harnessId) {
|
|
475
|
+
return {
|
|
476
|
+
jsonrpc: "2.0",
|
|
477
|
+
id,
|
|
478
|
+
error: { code: ERR_INVALID_PARAMS, message: "harnessId required" }
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return { jsonrpc: "2.0", id, result: diffConfig(harnessId) };
|
|
482
|
+
}
|
|
483
|
+
case "harness.listRunning": {
|
|
484
|
+
const harnessId = p.harnessId;
|
|
485
|
+
if (!harnessId) {
|
|
486
|
+
return {
|
|
487
|
+
jsonrpc: "2.0",
|
|
488
|
+
id,
|
|
489
|
+
error: { code: ERR_INVALID_PARAMS, message: "harnessId required" }
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
const processes = await findHarnessProcesses(harnessId);
|
|
493
|
+
return { jsonrpc: "2.0", id, result: processes };
|
|
494
|
+
}
|
|
495
|
+
default:
|
|
496
|
+
return {
|
|
497
|
+
jsonrpc: "2.0",
|
|
498
|
+
id,
|
|
499
|
+
error: { code: ERR_METHOD_NOT_FOUND, message: `Method not found: ${method}` }
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
start() {
|
|
504
|
+
return new Promise((resolve, reject) => {
|
|
505
|
+
this.server.listen(this.socketPath, () => resolve());
|
|
506
|
+
this.server.once("error", reject);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
stop() {
|
|
510
|
+
return new Promise((resolve) => this.server.close(() => resolve()));
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// src/coordinator.ts
|
|
515
|
+
import { join as join3 } from "path";
|
|
516
|
+
var HarnessCoordinator = class {
|
|
517
|
+
constructor(options) {
|
|
518
|
+
this.options = options;
|
|
519
|
+
const workboardPath = join3(options.projectRoot, ".claude", "workboard.md");
|
|
520
|
+
this.workboard = new WorkboardManager(workboardPath);
|
|
521
|
+
}
|
|
522
|
+
registry = new HarnessRegistry();
|
|
523
|
+
rpcServer = null;
|
|
524
|
+
sessionId = null;
|
|
525
|
+
workboard;
|
|
526
|
+
async start() {
|
|
527
|
+
await autoDetectHarnesses(this.registry);
|
|
528
|
+
const type = detectSessionType();
|
|
529
|
+
const state = this.workboard.read();
|
|
530
|
+
const existingIds = state.sessions.map((s) => s.id);
|
|
531
|
+
this.sessionId = deriveSessionId(type, existingIds);
|
|
532
|
+
const envLabels = {
|
|
533
|
+
zed: "Zed/ACP",
|
|
534
|
+
cursor: "Cursor",
|
|
535
|
+
terminal: "WSL/bash"
|
|
536
|
+
};
|
|
537
|
+
this.workboard.registerSession({
|
|
538
|
+
id: this.sessionId,
|
|
539
|
+
env: envLabels[type] ?? type,
|
|
540
|
+
started: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`,
|
|
541
|
+
task: this.options.task ?? "Harness coordination active",
|
|
542
|
+
files: "",
|
|
543
|
+
updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
|
|
544
|
+
});
|
|
545
|
+
const socketPath = this.options.socketPath ?? join3(process.env.HOME ?? "/tmp", ".local", "share", "revealui", "harness.sock");
|
|
546
|
+
this.rpcServer = new RpcServer(this.registry, socketPath);
|
|
547
|
+
await this.rpcServer.start();
|
|
548
|
+
}
|
|
549
|
+
async stop() {
|
|
550
|
+
if (this.sessionId) {
|
|
551
|
+
this.workboard.unregisterSession(this.sessionId);
|
|
552
|
+
this.workboard.addRecentEntry({
|
|
553
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " "),
|
|
554
|
+
sessionId: this.sessionId,
|
|
555
|
+
description: `Session ended \u2014 ${this.options.task ?? "harness coordination"}`
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (this.rpcServer) {
|
|
559
|
+
await this.rpcServer.stop();
|
|
560
|
+
this.rpcServer = null;
|
|
561
|
+
}
|
|
562
|
+
await this.registry.disposeAll();
|
|
563
|
+
}
|
|
564
|
+
/** The registry of detected harnesses. Available after start(). */
|
|
565
|
+
getRegistry() {
|
|
566
|
+
return this.registry;
|
|
567
|
+
}
|
|
568
|
+
/** The workboard manager. */
|
|
569
|
+
getWorkboard() {
|
|
570
|
+
return this.workboard;
|
|
571
|
+
}
|
|
572
|
+
/** Register a custom adapter (must be called before start()). */
|
|
573
|
+
registerAdapter(adapter) {
|
|
574
|
+
this.registry.register(adapter);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
export {
|
|
579
|
+
ClaudeCodeAdapter,
|
|
580
|
+
CopilotAdapter,
|
|
581
|
+
autoDetectHarnesses,
|
|
582
|
+
HarnessRegistry,
|
|
583
|
+
getLocalConfigPath,
|
|
584
|
+
getSsdConfigPath,
|
|
585
|
+
getConfigurableHarnesses,
|
|
586
|
+
syncConfig,
|
|
587
|
+
diffConfig,
|
|
588
|
+
findProcesses,
|
|
589
|
+
findHarnessProcesses,
|
|
590
|
+
findAllHarnessProcesses,
|
|
591
|
+
findClaudeCodeSockets,
|
|
592
|
+
RpcServer,
|
|
593
|
+
HarnessCoordinator
|
|
594
|
+
};
|
|
595
|
+
//# sourceMappingURL=chunk-BDA7D725.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/claude-code-adapter.ts","../src/adapters/copilot-adapter.ts","../src/detection/auto-detector.ts","../src/registry/harness-registry.ts","../src/config/harness-config-paths.ts","../src/config/config-sync.ts","../src/detection/process-detector.ts","../src/server/rpc-server.ts","../src/coordinator.ts"],"sourcesContent":["import { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport type { HarnessAdapter } from '../types/adapter.js'\nimport type {\n HarnessCapabilities,\n HarnessCommand,\n HarnessCommandResult,\n HarnessEvent,\n HarnessInfo,\n} from '../types/core.js'\nimport { WorkboardManager } from '../workboard/workboard-manager.js'\n\nconst execFileAsync = promisify(execFile)\n\n/**\n * Adapter for Anthropic Claude Code (CLI: `claude`).\n *\n * Claude Code communicates via its CLI. Config lives at\n * ~/.claude/settings.json and project-level .claude/settings.json.\n * MCP integration is handled separately by @revealui/mcp.\n *\n * Workboard read/write requires REVEALUI_WORKBOARD_PATH to be set to the\n * absolute path of the workboard.md file.\n */\nexport class ClaudeCodeAdapter implements HarnessAdapter {\n readonly id = 'claude-code'\n readonly name = 'Claude Code'\n\n private readonly eventHandlers = new Set<(event: HarnessEvent) => void>()\n private readonly workboardPath: string | undefined\n\n constructor(workboardPath?: string) {\n this.workboardPath = workboardPath ?? process.env.REVEALUI_WORKBOARD_PATH\n }\n\n getCapabilities(): HarnessCapabilities {\n return {\n generateCode: false, // interactive only — no headless CLI interface\n analyzeCode: false, // interactive only — no headless CLI interface\n applyEdit: false, // interactive only — edits are applied inside Claude Code sessions\n applyConfig: false, // config managed interactively via ~/.claude/settings.json\n readWorkboard: this.workboardPath !== undefined,\n writeWorkboard: this.workboardPath !== undefined,\n }\n }\n\n async getInfo(): Promise<HarnessInfo> {\n let version: string | undefined\n try {\n const { stdout } = await execFileAsync('claude', ['--version'], {\n timeout: 5000,\n })\n version = stdout.trim().split('\\n')[0]\n } catch {\n // Not installed or version flag unsupported.\n }\n return { id: this.id, name: this.name, version, capabilities: this.getCapabilities() }\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await execFileAsync('claude', ['--version'], { timeout: 3000 })\n return true\n } catch {\n return false\n }\n }\n\n async execute(command: HarnessCommand): Promise<HarnessCommandResult> {\n switch (command.type) {\n case 'get-status': {\n const available = await this.isAvailable()\n return { success: true, command: command.type, data: { available } }\n }\n case 'get-running-instances': {\n // Claude Code process enumeration is not supported.\n return { success: true, command: command.type, data: [] }\n }\n case 'generate-code':\n case 'analyze-code': {\n return {\n success: false,\n command: command.type,\n message: `${command.type} is not supported — Claude Code operates interactively`,\n }\n }\n case 'apply-config': {\n return {\n success: false,\n command: command.type,\n message: 'Config is managed interactively via ~/.claude/settings.json',\n }\n }\n case 'read-workboard': {\n if (!this.workboardPath) {\n return {\n success: false,\n command: command.type,\n message: 'REVEALUI_WORKBOARD_PATH is not set',\n }\n }\n const manager = new WorkboardManager(this.workboardPath)\n const state = await manager.readAsync()\n return { success: true, command: command.type, data: state }\n }\n case 'update-workboard': {\n if (!this.workboardPath) {\n return {\n success: false,\n command: command.type,\n message: 'REVEALUI_WORKBOARD_PATH is not set',\n }\n }\n const manager = new WorkboardManager(this.workboardPath)\n manager.updateSession(command.sessionId, {\n ...(command.task !== undefined && { task: command.task }),\n ...(command.files !== undefined && { files: command.files.join(', ') }),\n updated: `${new Date().toISOString().slice(0, 16)}Z`,\n })\n return { success: true, command: command.type }\n }\n default: {\n return {\n success: false,\n command: (command as HarnessCommand).type,\n message: `Command not supported by ${this.name}`,\n }\n }\n }\n }\n\n onEvent(handler: (event: HarnessEvent) => void): () => void {\n this.eventHandlers.add(handler)\n return () => this.eventHandlers.delete(handler)\n }\n\n async dispose(): Promise<void> {\n this.eventHandlers.clear()\n }\n}\n","import type { HarnessAdapter } from '../types/adapter.js'\nimport type {\n HarnessCapabilities,\n HarnessCommand,\n HarnessCommandResult,\n HarnessEvent,\n HarnessInfo,\n} from '../types/core.js'\n\n/**\n * Stub adapter for GitHub Copilot.\n *\n * Copilot has no standalone CLI — it runs as a VS Code extension.\n * This adapter is a stub for future integration once Copilot exposes\n * a programmatic interface.\n */\nexport class CopilotAdapter implements HarnessAdapter {\n readonly id = 'copilot'\n readonly name = 'GitHub Copilot'\n\n private readonly eventHandlers = new Set<(event: HarnessEvent) => void>()\n\n getCapabilities(): HarnessCapabilities {\n return {\n generateCode: false, // stub — no CLI available\n analyzeCode: false,\n applyEdit: false,\n applyConfig: false,\n readWorkboard: false,\n writeWorkboard: false,\n }\n }\n\n async getInfo(): Promise<HarnessInfo> {\n return { id: this.id, name: this.name, capabilities: this.getCapabilities() }\n }\n\n async isAvailable(): Promise<boolean> {\n // Copilot is a VS Code extension — treat it as unavailable for standalone harness use.\n return false\n }\n\n async execute(command: HarnessCommand): Promise<HarnessCommandResult> {\n return {\n success: false,\n command: command.type,\n message: 'Copilot adapter is a stub — no standalone CLI available',\n }\n }\n\n onEvent(handler: (event: HarnessEvent) => void): () => void {\n this.eventHandlers.add(handler)\n return () => this.eventHandlers.delete(handler)\n }\n\n async dispose(): Promise<void> {\n this.eventHandlers.clear()\n }\n}\n","import { ClaudeCodeAdapter } from '../adapters/claude-code-adapter.js'\nimport { CopilotAdapter } from '../adapters/copilot-adapter.js'\nimport type { HarnessRegistry } from '../registry/harness-registry.js'\n\n/**\n * Detects available AI harnesses and registers them in the registry.\n * Mirrors autoDetectEditors from packages/editors.\n *\n * Creates an adapter for each known harness, checks isAvailable(),\n * and registers those that respond. Unavailable adapters are disposed.\n */\nexport async function autoDetectHarnesses(registry: HarnessRegistry): Promise<string[]> {\n const candidates = [new ClaudeCodeAdapter(), new CopilotAdapter()]\n\n const registered: string[] = []\n\n await Promise.all(\n candidates.map(async (adapter) => {\n try {\n if (await adapter.isAvailable()) {\n registry.register(adapter)\n registered.push(adapter.id)\n } else {\n await adapter.dispose()\n }\n } catch {\n await adapter.dispose()\n }\n }),\n )\n\n return registered\n}\n","import type { HarnessAdapter } from '../types/adapter.js'\n\n/**\n * Manages the lifecycle of HarnessAdapter instances.\n * Mirrors EditorRegistry from packages/editors.\n */\nexport class HarnessRegistry {\n private readonly adapters = new Map<string, HarnessAdapter>()\n\n /** Register an adapter. Throws if an adapter with the same id already exists. */\n register(adapter: HarnessAdapter): void {\n if (this.adapters.has(adapter.id)) {\n throw new Error(`Harness adapter already registered: ${adapter.id}`)\n }\n this.adapters.set(adapter.id, adapter)\n }\n\n /** Unregister an adapter, disposing it in the process. */\n async unregister(id: string): Promise<void> {\n const adapter = this.adapters.get(id)\n if (adapter) {\n await adapter.dispose()\n this.adapters.delete(id)\n }\n }\n\n /** Retrieve an adapter by id. */\n get(id: string): HarnessAdapter | undefined {\n return this.adapters.get(id)\n }\n\n /** List all registered adapter ids. */\n listAll(): string[] {\n return Array.from(this.adapters.keys())\n }\n\n /** List ids of adapters that report isAvailable() === true. */\n async listAvailable(): Promise<string[]> {\n const results = await Promise.all(\n Array.from(this.adapters.entries()).map(async ([id, adapter]) => ({\n id,\n available: await adapter.isAvailable(),\n })),\n )\n return results.filter((r) => r.available).map((r) => r.id)\n }\n\n /** Dispose all adapters and clear the registry. */\n async disposeAll(): Promise<void> {\n await Promise.all(Array.from(this.adapters.values()).map((a) => a.dispose()))\n this.adapters.clear()\n }\n}\n","import { homedir } from 'node:os'\nimport { join } from 'node:path'\n\n/**\n * Config path mappings for AI harnesses.\n * Mirrors editor-config-paths.ts from packages/editors.\n *\n * Local paths: harness config on this machine.\n * SSD paths: backup/sync target on the DevPod (ext4 USB) or LTS (NTFS).\n */\n\nconst HOME = homedir()\nconst SSD_BASE = process.env.REVEALUI_SSD_PATH ?? '/mnt/e/.revealui'\n\nconst LOCAL_CONFIG_PATHS: Record<string, string> = {\n 'claude-code': join(HOME, '.claude', 'settings.json'),\n cursor: join(HOME, '.cursor', 'settings.json'),\n copilot: join(HOME, '.config', 'github-copilot', 'hosts.json'),\n}\n\nconst SSD_CONFIG_FILES: Record<string, string> = {\n 'claude-code': 'settings.json',\n cursor: 'settings.json',\n copilot: 'hosts.json',\n}\n\n/** Returns the local config file path for a given harness id, or undefined if unknown. */\nexport function getLocalConfigPath(harnessId: string): string | undefined {\n return LOCAL_CONFIG_PATHS[harnessId]\n}\n\n/** Returns the SSD config file path for a given harness id, or undefined if unknown. */\nexport function getSsdConfigPath(harnessId: string, ssdBase = SSD_BASE): string | undefined {\n const file = SSD_CONFIG_FILES[harnessId]\n if (!file) return undefined\n return join(ssdBase, 'harness-configs', harnessId, file)\n}\n\n/** Returns ids of all harnesses with known config paths. */\nexport function getConfigurableHarnesses(): string[] {\n return Object.keys(LOCAL_CONFIG_PATHS)\n}\n","import { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport type { ConfigDiffEntry, ConfigSyncDirection, ConfigSyncResult } from '../types/core.js'\nimport { getLocalConfigPath, getSsdConfigPath } from './harness-config-paths.js'\n\n/**\n * Syncs harness config between local filesystem and SSD backup.\n * Mirrors config-sync.ts from packages/editors.\n */\nexport function syncConfig(\n harnessId: string,\n direction: ConfigSyncDirection,\n ssdBase?: string,\n): ConfigSyncResult {\n const localPath = getLocalConfigPath(harnessId)\n const ssdPath = getSsdConfigPath(harnessId, ssdBase)\n\n if (!(localPath && ssdPath)) {\n return {\n success: false,\n harnessId,\n direction,\n message: `No config path known for harness: ${harnessId}`,\n }\n }\n\n try {\n if (direction === 'pull') {\n // Pull: SSD → local\n if (!existsSync(ssdPath)) {\n return { success: false, harnessId, direction, message: `SSD config not found: ${ssdPath}` }\n }\n mkdirSync(dirname(localPath), { recursive: true })\n copyFileSync(ssdPath, localPath)\n return { success: true, harnessId, direction, message: `Pulled ${ssdPath} → ${localPath}` }\n } else {\n // Push: local → SSD\n if (!existsSync(localPath)) {\n return {\n success: false,\n harnessId,\n direction,\n message: `Local config not found: ${localPath}`,\n }\n }\n mkdirSync(dirname(ssdPath), { recursive: true })\n copyFileSync(localPath, ssdPath)\n return { success: true, harnessId, direction, message: `Pushed ${localPath} → ${ssdPath}` }\n }\n } catch (err) {\n return {\n success: false,\n harnessId,\n direction,\n message: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\n/** Compares local vs SSD config for a harness. */\nexport function diffConfig(harnessId: string, ssdBase?: string): ConfigDiffEntry {\n const localPath = getLocalConfigPath(harnessId)\n const ssdPath = getSsdConfigPath(harnessId, ssdBase)\n\n const localExists = !!localPath && existsSync(localPath)\n const ssdExists = !!ssdPath && existsSync(ssdPath)\n\n if (!(localExists && ssdExists)) {\n return { harnessId, localExists, ssdExists, identical: false }\n }\n\n try {\n const localContent = readFileSync(localPath, 'utf8')\n const ssdContent = readFileSync(ssdPath, 'utf8')\n return { harnessId, localExists, ssdExists, identical: localContent === ssdContent }\n } catch {\n return { harnessId, localExists, ssdExists, identical: false }\n }\n}\n","import { execFile } from 'node:child_process'\nimport { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { promisify } from 'node:util'\nimport type { HarnessProcessInfo } from '../types/core.js'\n\nconst execFileAsync = promisify(execFile)\n\n/** Finds running processes matching a pattern using pgrep. */\nexport async function findProcesses(pattern: string): Promise<{ pid: number; command: string }[]> {\n try {\n const { stdout } = await execFileAsync('pgrep', ['-a', pattern], { timeout: 3000 })\n return stdout\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const spaceIdx = line.indexOf(' ')\n const pid = parseInt(line.slice(0, spaceIdx), 10)\n const command = line.slice(spaceIdx + 1)\n return { pid, command }\n })\n } catch {\n return []\n }\n}\n\nconst HARNESS_PROCESS_PATTERNS: Record<string, string[]> = {\n 'claude-code': ['claude'],\n cursor: ['cursor', 'Cursor'],\n copilot: ['copilot'],\n}\n\n/** Finds running process instances for a specific harness. */\nexport async function findHarnessProcesses(harnessId: string): Promise<HarnessProcessInfo[]> {\n const patterns = HARNESS_PROCESS_PATTERNS[harnessId]\n if (!patterns) return []\n\n const results = await Promise.all(patterns.map((p) => findProcesses(p)))\n return results.flat().map((p) => ({ ...p, harnessId }))\n}\n\n/** Finds running processes for all known harnesses. */\nexport async function findAllHarnessProcesses(): Promise<HarnessProcessInfo[]> {\n const results = await Promise.all(\n Object.keys(HARNESS_PROCESS_PATTERNS).map((id) => findHarnessProcesses(id)),\n )\n return results.flat()\n}\n\n/** Finds Claude Code Unix socket files (used for IPC). */\nexport async function findClaudeCodeSockets(): Promise<string[]> {\n const dirs = [\n '/tmp',\n process.env.XDG_RUNTIME_DIR,\n join(process.env.HOME ?? '/tmp', '.claude'),\n ].filter(Boolean) as string[]\n\n const sockets: string[] = []\n for (const dir of dirs) {\n try {\n const entries = await readdir(dir)\n for (const entry of entries) {\n if (/^claude.*\\.sock$/.test(entry)) {\n sockets.push(join(dir, entry))\n }\n }\n } catch {\n // Directory not accessible.\n }\n }\n return sockets\n}\n","import { createServer } from 'node:net'\nimport { diffConfig, syncConfig } from '../config/config-sync.js'\nimport { findHarnessProcesses } from '../detection/process-detector.js'\nimport type { HarnessRegistry } from '../registry/harness-registry.js'\nimport type { ConfigSyncDirection } from '../types/core.js'\n\ninterface JsonRpcRequest {\n jsonrpc: '2.0'\n id: number | string | null\n method: string\n params?: unknown\n}\n\ninterface JsonRpcResponse {\n jsonrpc: '2.0'\n id: number | string | null\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\nconst ERR_PARSE = -32700\nconst ERR_INVALID_PARAMS = -32602\nconst ERR_METHOD_NOT_FOUND = -32601\nconst ERR_INTERNAL = -32603\n\n/**\n * JSON-RPC 2.0 server over a Unix domain socket.\n * Mirrors RpcServer from packages/editors.\n *\n * Methods:\n * harness.list → HarnessInfo[]\n * harness.execute → HarnessCommandResult\n * harness.info → HarnessInfo\n * harness.listRunning → HarnessProcessInfo[]\n * harness.syncConfig → ConfigSyncResult\n * harness.diffConfig → ConfigDiffEntry\n */\nexport class RpcServer {\n private server = createServer()\n\n constructor(\n private readonly registry: HarnessRegistry,\n private readonly socketPath: string,\n ) {\n this.server.on('connection', (socket) => {\n let buffer = ''\n socket.on('data', (chunk) => {\n buffer += chunk.toString()\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n this.handleLine(line.trim(), (response) => {\n socket.write(`${JSON.stringify(response)}\\n`)\n })\n }\n })\n })\n }\n\n private handleLine(line: string, reply: (r: JsonRpcResponse) => void): void {\n let req: JsonRpcRequest\n try {\n req = JSON.parse(line) as JsonRpcRequest\n } catch {\n reply({ jsonrpc: '2.0', id: null, error: { code: ERR_PARSE, message: 'Parse error' } })\n return\n }\n\n this.dispatch(req)\n .then(reply)\n .catch((err) => {\n reply({\n jsonrpc: '2.0',\n id: req.id,\n error: { code: ERR_INTERNAL, message: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n private async dispatch(req: JsonRpcRequest): Promise<JsonRpcResponse> {\n const { id, method, params } = req\n const p = (params ?? {}) as Record<string, unknown>\n\n switch (method) {\n case 'harness.list': {\n const ids = await this.registry.listAvailable()\n const infos = await Promise.all(ids.map((id) => this.registry.get(id)?.getInfo()))\n return { jsonrpc: '2.0', id, result: infos }\n }\n\n case 'harness.info': {\n const harnessId = p.harnessId as string | undefined\n if (!harnessId) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_INVALID_PARAMS, message: 'harnessId required' },\n }\n }\n const adapter = this.registry.get(harnessId)\n if (!adapter) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_INVALID_PARAMS, message: `Harness not found: ${harnessId}` },\n }\n }\n return { jsonrpc: '2.0', id, result: await adapter.getInfo() }\n }\n\n case 'harness.execute': {\n const harnessId = p.harnessId as string | undefined\n const command = p.command as Record<string, unknown> | undefined\n if (!(harnessId && command)) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_INVALID_PARAMS, message: 'harnessId and command required' },\n }\n }\n const adapter = this.registry.get(harnessId)\n if (!adapter) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_INVALID_PARAMS, message: `Harness not found: ${harnessId}` },\n }\n }\n const result = await adapter.execute(command as Parameters<typeof adapter.execute>[0])\n return { jsonrpc: '2.0', id, result }\n }\n\n case 'harness.syncConfig': {\n const harnessId = p.harnessId as string | undefined\n const direction = p.direction as ConfigSyncDirection | undefined\n if (!(harnessId && direction)) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_INVALID_PARAMS, message: 'harnessId and direction required' },\n }\n }\n return { jsonrpc: '2.0', id, result: syncConfig(harnessId, direction) }\n }\n\n case 'harness.diffConfig': {\n const harnessId = p.harnessId as string | undefined\n if (!harnessId) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_INVALID_PARAMS, message: 'harnessId required' },\n }\n }\n return { jsonrpc: '2.0', id, result: diffConfig(harnessId) }\n }\n\n case 'harness.listRunning': {\n const harnessId = p.harnessId as string | undefined\n if (!harnessId) {\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_INVALID_PARAMS, message: 'harnessId required' },\n }\n }\n const processes = await findHarnessProcesses(harnessId)\n return { jsonrpc: '2.0', id, result: processes }\n }\n\n default:\n return {\n jsonrpc: '2.0',\n id,\n error: { code: ERR_METHOD_NOT_FOUND, message: `Method not found: ${method}` },\n }\n }\n }\n\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.server.listen(this.socketPath, () => resolve())\n this.server.once('error', reject)\n })\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => this.server.close(() => resolve()))\n }\n}\n","import { join } from 'node:path'\nimport { autoDetectHarnesses } from './detection/auto-detector.js'\nimport { HarnessRegistry } from './registry/harness-registry.js'\nimport { RpcServer } from './server/rpc-server.js'\nimport type { HarnessAdapter } from './types/adapter.js'\nimport { deriveSessionId, detectSessionType } from './workboard/session-identity.js'\nimport { WorkboardManager } from './workboard/workboard-manager.js'\n\nexport interface CoordinatorOptions {\n /** Absolute path to the project root (where .claude/workboard.md lives) */\n projectRoot: string\n /** Unix socket path for the RPC server */\n socketPath?: string\n /** Session task description shown in the workboard */\n task?: string\n}\n\n/**\n * HarnessCoordinator — single entry point for harness-to-harness coordination.\n *\n * On start:\n * 1. Auto-detects installed AI harnesses and registers them\n * 2. Registers this session in the workboard\n * 3. Starts the RPC server\n *\n * On stop:\n * 1. Unregisters this session from the workboard\n * 2. Stops the RPC server\n * 3. Disposes all adapters\n */\nexport class HarnessCoordinator {\n private readonly registry = new HarnessRegistry()\n private rpcServer: RpcServer | null = null\n private sessionId: string | null = null\n private readonly workboard: WorkboardManager\n\n constructor(private readonly options: CoordinatorOptions) {\n const workboardPath = join(options.projectRoot, '.claude', 'workboard.md')\n this.workboard = new WorkboardManager(workboardPath)\n }\n\n async start(): Promise<void> {\n // 1. Auto-detect harnesses\n await autoDetectHarnesses(this.registry)\n\n // 2. Register in workboard\n const type = detectSessionType()\n const state = this.workboard.read()\n const existingIds = state.sessions.map((s) => s.id)\n this.sessionId = deriveSessionId(type, existingIds)\n\n const envLabels: Record<string, string> = {\n zed: 'Zed/ACP',\n cursor: 'Cursor',\n terminal: 'WSL/bash',\n }\n\n this.workboard.registerSession({\n id: this.sessionId,\n env: envLabels[type] ?? type,\n started: `${new Date().toISOString().slice(0, 16)}Z`,\n task: this.options.task ?? 'Harness coordination active',\n files: '',\n updated: `${new Date().toISOString().slice(0, 16)}Z`,\n })\n\n // 3. Start RPC server\n const socketPath =\n this.options.socketPath ??\n join(process.env.HOME ?? '/tmp', '.local', 'share', 'revealui', 'harness.sock')\n\n this.rpcServer = new RpcServer(this.registry, socketPath)\n await this.rpcServer.start()\n }\n\n async stop(): Promise<void> {\n // Unregister from workboard\n if (this.sessionId) {\n this.workboard.unregisterSession(this.sessionId)\n this.workboard.addRecentEntry({\n timestamp: new Date().toISOString().slice(0, 16).replace('T', ' '),\n sessionId: this.sessionId,\n description: `Session ended — ${this.options.task ?? 'harness coordination'}`,\n })\n }\n\n // Stop RPC server\n if (this.rpcServer) {\n await this.rpcServer.stop()\n this.rpcServer = null\n }\n\n // Dispose all adapters\n await this.registry.disposeAll()\n }\n\n /** The registry of detected harnesses. Available after start(). */\n getRegistry(): HarnessRegistry {\n return this.registry\n }\n\n /** The workboard manager. */\n getWorkboard(): WorkboardManager {\n return this.workboard\n }\n\n /** Register a custom adapter (must be called before start()). */\n registerAdapter(adapter: HarnessAdapter): void {\n this.registry.register(adapter)\n }\n}\n"],"mappings":"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","names":["execFile","join","promisify","execFileAsync","id","join","join"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-JUNNIQS3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|