@revealui/harnesses 0.1.0 → 0.1.1
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 +3 -0
- package/dist/{chunk-PG4RAOWS.js → chunk-6WO7SSCX.js} +198 -66
- package/dist/chunk-6WO7SSCX.js.map +1 -0
- package/dist/{chunk-BDA7D725.js → chunk-MO2EXBUG.js} +363 -83
- package/dist/chunk-MO2EXBUG.js.map +1 -0
- package/dist/cli.js +48 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +63 -136
- package/dist/index.js +28 -24
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +151 -0
- package/dist/types/index.js +1 -0
- package/dist/workboard/index.d.ts +44 -3
- package/dist/workboard/index.js +15 -4
- package/package.json +14 -4
- package/dist/chunk-BDA7D725.js.map +0 -1
- package/dist/chunk-JUNNIQS3.js +0 -1
- package/dist/chunk-PG4RAOWS.js.map +0 -1
- /package/dist/{chunk-JUNNIQS3.js.map → types/index.js.map} +0 -0
|
@@ -2,7 +2,12 @@ import {
|
|
|
2
2
|
WorkboardManager,
|
|
3
3
|
deriveSessionId,
|
|
4
4
|
detectSessionType
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6WO7SSCX.js";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { isFeatureEnabled } from "@revealui/core/features";
|
|
9
|
+
import { initializeLicense } from "@revealui/core/license";
|
|
10
|
+
import { logger } from "@revealui/core/observability/logger";
|
|
6
11
|
|
|
7
12
|
// src/adapters/claude-code-adapter.ts
|
|
8
13
|
import { execFile } from "child_process";
|
|
@@ -49,7 +54,22 @@ var ClaudeCodeAdapter = class {
|
|
|
49
54
|
return false;
|
|
50
55
|
}
|
|
51
56
|
}
|
|
57
|
+
notifyRegistered() {
|
|
58
|
+
this.emit({ type: "harness-connected", harnessId: this.id });
|
|
59
|
+
}
|
|
60
|
+
notifyUnregistering() {
|
|
61
|
+
this.emit({ type: "harness-disconnected", harnessId: this.id });
|
|
62
|
+
}
|
|
52
63
|
async execute(command) {
|
|
64
|
+
try {
|
|
65
|
+
return await this.executeInner(command);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
this.emit({ type: "error", harnessId: this.id, message });
|
|
69
|
+
return { success: false, command: command.type, message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async executeInner(command) {
|
|
53
73
|
switch (command.type) {
|
|
54
74
|
case "get-status": {
|
|
55
75
|
const available = await this.isAvailable();
|
|
@@ -117,6 +137,14 @@ var ClaudeCodeAdapter = class {
|
|
|
117
137
|
async dispose() {
|
|
118
138
|
this.eventHandlers.clear();
|
|
119
139
|
}
|
|
140
|
+
emit(event) {
|
|
141
|
+
for (const handler of this.eventHandlers) {
|
|
142
|
+
try {
|
|
143
|
+
handler(event);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
120
148
|
};
|
|
121
149
|
|
|
122
150
|
// src/adapters/copilot-adapter.ts
|
|
@@ -141,6 +169,12 @@ var CopilotAdapter = class {
|
|
|
141
169
|
async isAvailable() {
|
|
142
170
|
return false;
|
|
143
171
|
}
|
|
172
|
+
notifyRegistered() {
|
|
173
|
+
this.emit({ type: "harness-connected", harnessId: this.id });
|
|
174
|
+
}
|
|
175
|
+
notifyUnregistering() {
|
|
176
|
+
this.emit({ type: "harness-disconnected", harnessId: this.id });
|
|
177
|
+
}
|
|
144
178
|
async execute(command) {
|
|
145
179
|
return {
|
|
146
180
|
success: false,
|
|
@@ -155,83 +189,147 @@ var CopilotAdapter = class {
|
|
|
155
189
|
async dispose() {
|
|
156
190
|
this.eventHandlers.clear();
|
|
157
191
|
}
|
|
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) => {
|
|
192
|
+
emit(event) {
|
|
193
|
+
for (const handler of this.eventHandlers) {
|
|
166
194
|
try {
|
|
167
|
-
|
|
168
|
-
registry.register(adapter);
|
|
169
|
-
registered.push(adapter.id);
|
|
170
|
-
} else {
|
|
171
|
-
await adapter.dispose();
|
|
172
|
-
}
|
|
195
|
+
handler(event);
|
|
173
196
|
} catch {
|
|
174
|
-
await adapter.dispose();
|
|
175
197
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
180
201
|
|
|
181
|
-
// src/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
202
|
+
// src/adapters/cursor-adapter.ts
|
|
203
|
+
import { execFile as execFile2 } from "child_process";
|
|
204
|
+
import { promisify as promisify2 } from "util";
|
|
205
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
206
|
+
var CursorAdapter = class {
|
|
207
|
+
id = "cursor";
|
|
208
|
+
name = "Cursor";
|
|
209
|
+
eventHandlers = /* @__PURE__ */ new Set();
|
|
210
|
+
workboardPath;
|
|
211
|
+
constructor(workboardPath) {
|
|
212
|
+
this.workboardPath = workboardPath ?? process.env.REVEALUI_WORKBOARD_PATH;
|
|
213
|
+
}
|
|
214
|
+
getCapabilities() {
|
|
215
|
+
return {
|
|
216
|
+
generateCode: false,
|
|
217
|
+
analyzeCode: false,
|
|
218
|
+
applyEdit: false,
|
|
219
|
+
applyConfig: false,
|
|
220
|
+
readWorkboard: this.workboardPath !== void 0,
|
|
221
|
+
writeWorkboard: this.workboardPath !== void 0
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async getInfo() {
|
|
225
|
+
let version;
|
|
226
|
+
try {
|
|
227
|
+
const { stdout } = await execFileAsync2("cursor", ["--version"], {
|
|
228
|
+
timeout: 5e3
|
|
229
|
+
});
|
|
230
|
+
version = stdout.trim().split("\n")[0];
|
|
231
|
+
} catch {
|
|
188
232
|
}
|
|
189
|
-
this.
|
|
233
|
+
return { id: this.id, name: this.name, version, capabilities: this.getCapabilities() };
|
|
190
234
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
235
|
+
async isAvailable() {
|
|
236
|
+
try {
|
|
237
|
+
await execFileAsync2("cursor", ["--version"], { timeout: 3e3 });
|
|
238
|
+
return true;
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
197
241
|
}
|
|
198
242
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return this.adapters.get(id);
|
|
243
|
+
notifyRegistered() {
|
|
244
|
+
this.emit({ type: "harness-connected", harnessId: this.id });
|
|
202
245
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return Array.from(this.adapters.keys());
|
|
246
|
+
notifyUnregistering() {
|
|
247
|
+
this.emit({ type: "harness-disconnected", harnessId: this.id });
|
|
206
248
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return results.filter((r) => r.available).map((r) => r.id);
|
|
249
|
+
async execute(command) {
|
|
250
|
+
try {
|
|
251
|
+
return await this.executeInner(command);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
254
|
+
this.emit({ type: "error", harnessId: this.id, message });
|
|
255
|
+
return { success: false, command: command.type, message };
|
|
256
|
+
}
|
|
216
257
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
258
|
+
async executeInner(command) {
|
|
259
|
+
switch (command.type) {
|
|
260
|
+
case "get-status": {
|
|
261
|
+
const available = await this.isAvailable();
|
|
262
|
+
return { success: true, command: command.type, data: { available } };
|
|
263
|
+
}
|
|
264
|
+
case "read-workboard": {
|
|
265
|
+
if (!this.workboardPath) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
command: command.type,
|
|
269
|
+
message: "REVEALUI_WORKBOARD_PATH is not set"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const manager = new WorkboardManager(this.workboardPath);
|
|
273
|
+
const state = await manager.readAsync();
|
|
274
|
+
return { success: true, command: command.type, data: state };
|
|
275
|
+
}
|
|
276
|
+
case "update-workboard": {
|
|
277
|
+
if (!this.workboardPath) {
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
command: command.type,
|
|
281
|
+
message: "REVEALUI_WORKBOARD_PATH is not set"
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const manager = new WorkboardManager(this.workboardPath);
|
|
285
|
+
manager.updateSession(command.sessionId, {
|
|
286
|
+
...command.task !== void 0 && { task: command.task },
|
|
287
|
+
...command.files !== void 0 && { files: command.files.join(", ") },
|
|
288
|
+
updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
|
|
289
|
+
});
|
|
290
|
+
return { success: true, command: command.type };
|
|
291
|
+
}
|
|
292
|
+
default: {
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
command: command.type,
|
|
296
|
+
message: `Command not supported by ${this.name}`
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
onEvent(handler) {
|
|
302
|
+
this.eventHandlers.add(handler);
|
|
303
|
+
return () => this.eventHandlers.delete(handler);
|
|
304
|
+
}
|
|
305
|
+
async dispose() {
|
|
306
|
+
this.eventHandlers.clear();
|
|
307
|
+
}
|
|
308
|
+
emit(event) {
|
|
309
|
+
for (const handler of this.eventHandlers) {
|
|
310
|
+
try {
|
|
311
|
+
handler(event);
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
}
|
|
221
315
|
}
|
|
222
316
|
};
|
|
223
317
|
|
|
318
|
+
// src/config/config-sync.ts
|
|
319
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
320
|
+
import { dirname } from "path";
|
|
321
|
+
|
|
224
322
|
// src/config/harness-config-paths.ts
|
|
225
323
|
import { homedir } from "os";
|
|
226
324
|
import { join } from "path";
|
|
227
325
|
var HOME = homedir();
|
|
228
|
-
var
|
|
326
|
+
var REVEALUI_ROOT = process.env.REVEALUI_ROOT ?? join(HOME, ".revealui");
|
|
229
327
|
var LOCAL_CONFIG_PATHS = {
|
|
230
328
|
"claude-code": join(HOME, ".claude", "settings.json"),
|
|
231
329
|
cursor: join(HOME, ".cursor", "settings.json"),
|
|
232
330
|
copilot: join(HOME, ".config", "github-copilot", "hosts.json")
|
|
233
331
|
};
|
|
234
|
-
var
|
|
332
|
+
var ROOT_CONFIG_FILES = {
|
|
235
333
|
"claude-code": "settings.json",
|
|
236
334
|
cursor: "settings.json",
|
|
237
335
|
copilot: "hosts.json"
|
|
@@ -239,22 +337,20 @@ var SSD_CONFIG_FILES = {
|
|
|
239
337
|
function getLocalConfigPath(harnessId) {
|
|
240
338
|
return LOCAL_CONFIG_PATHS[harnessId];
|
|
241
339
|
}
|
|
242
|
-
function
|
|
243
|
-
const file =
|
|
340
|
+
function getRootConfigPath(harnessId, root = REVEALUI_ROOT) {
|
|
341
|
+
const file = ROOT_CONFIG_FILES[harnessId];
|
|
244
342
|
if (!file) return void 0;
|
|
245
|
-
return join(
|
|
343
|
+
return join(root, "harness-configs", harnessId, file);
|
|
246
344
|
}
|
|
247
345
|
function getConfigurableHarnesses() {
|
|
248
346
|
return Object.keys(LOCAL_CONFIG_PATHS);
|
|
249
347
|
}
|
|
250
348
|
|
|
251
349
|
// src/config/config-sync.ts
|
|
252
|
-
|
|
253
|
-
import { dirname } from "path";
|
|
254
|
-
function syncConfig(harnessId, direction, ssdBase) {
|
|
350
|
+
function syncConfig(harnessId, direction, root) {
|
|
255
351
|
const localPath = getLocalConfigPath(harnessId);
|
|
256
|
-
const
|
|
257
|
-
if (!(localPath &&
|
|
352
|
+
const rootPath = getRootConfigPath(harnessId, root);
|
|
353
|
+
if (!(localPath && rootPath)) {
|
|
258
354
|
return {
|
|
259
355
|
success: false,
|
|
260
356
|
harnessId,
|
|
@@ -264,12 +360,13 @@ function syncConfig(harnessId, direction, ssdBase) {
|
|
|
264
360
|
}
|
|
265
361
|
try {
|
|
266
362
|
if (direction === "pull") {
|
|
267
|
-
if (!existsSync(
|
|
268
|
-
return { success: false, harnessId, direction, message: `
|
|
363
|
+
if (!existsSync(rootPath)) {
|
|
364
|
+
return { success: false, harnessId, direction, message: `Root config not found: ${rootPath}` };
|
|
269
365
|
}
|
|
270
366
|
mkdirSync(dirname(localPath), { recursive: true });
|
|
271
|
-
|
|
272
|
-
|
|
367
|
+
backupIfExists(localPath);
|
|
368
|
+
copyFileSync(rootPath, localPath);
|
|
369
|
+
return { success: true, harnessId, direction, message: `Pulled ${rootPath} \u2192 ${localPath}` };
|
|
273
370
|
} else {
|
|
274
371
|
if (!existsSync(localPath)) {
|
|
275
372
|
return {
|
|
@@ -279,9 +376,10 @@ function syncConfig(harnessId, direction, ssdBase) {
|
|
|
279
376
|
message: `Local config not found: ${localPath}`
|
|
280
377
|
};
|
|
281
378
|
}
|
|
282
|
-
mkdirSync(dirname(
|
|
283
|
-
|
|
284
|
-
|
|
379
|
+
mkdirSync(dirname(rootPath), { recursive: true });
|
|
380
|
+
backupIfExists(rootPath);
|
|
381
|
+
copyFileSync(localPath, rootPath);
|
|
382
|
+
return { success: true, harnessId, direction, message: `Pushed ${localPath} \u2192 ${rootPath}` };
|
|
285
383
|
}
|
|
286
384
|
} catch (err) {
|
|
287
385
|
return {
|
|
@@ -292,32 +390,130 @@ function syncConfig(harnessId, direction, ssdBase) {
|
|
|
292
390
|
};
|
|
293
391
|
}
|
|
294
392
|
}
|
|
295
|
-
function diffConfig(harnessId,
|
|
393
|
+
function diffConfig(harnessId, root) {
|
|
296
394
|
const localPath = getLocalConfigPath(harnessId);
|
|
297
|
-
const
|
|
395
|
+
const rootPath = getRootConfigPath(harnessId, root);
|
|
298
396
|
const localExists = !!localPath && existsSync(localPath);
|
|
299
|
-
const ssdExists = !!
|
|
397
|
+
const ssdExists = !!rootPath && existsSync(rootPath);
|
|
300
398
|
if (!(localExists && ssdExists)) {
|
|
301
399
|
return { harnessId, localExists, ssdExists, identical: false };
|
|
302
400
|
}
|
|
303
401
|
try {
|
|
304
402
|
const localContent = readFileSync(localPath, "utf8");
|
|
305
|
-
const ssdContent = readFileSync(
|
|
403
|
+
const ssdContent = readFileSync(rootPath, "utf8");
|
|
306
404
|
return { harnessId, localExists, ssdExists, identical: localContent === ssdContent };
|
|
307
405
|
} catch {
|
|
308
406
|
return { harnessId, localExists, ssdExists, identical: false };
|
|
309
407
|
}
|
|
310
408
|
}
|
|
409
|
+
function syncAllConfigs(direction, root) {
|
|
410
|
+
return getConfigurableHarnesses().map((id) => syncConfig(id, direction, root));
|
|
411
|
+
}
|
|
412
|
+
function diffAllConfigs(root) {
|
|
413
|
+
return getConfigurableHarnesses().map((id) => diffConfig(id, root));
|
|
414
|
+
}
|
|
415
|
+
function validateConfigJson(harnessId) {
|
|
416
|
+
const localPath = getLocalConfigPath(harnessId);
|
|
417
|
+
if (!localPath) return `No config path known for harness: ${harnessId}`;
|
|
418
|
+
if (!existsSync(localPath)) return `Config file not found: ${localPath}`;
|
|
419
|
+
try {
|
|
420
|
+
const content = readFileSync(localPath, "utf8");
|
|
421
|
+
JSON.parse(content);
|
|
422
|
+
return null;
|
|
423
|
+
} catch (err) {
|
|
424
|
+
return err instanceof Error ? err.message : String(err);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function backupIfExists(filePath) {
|
|
428
|
+
try {
|
|
429
|
+
if (existsSync(filePath)) {
|
|
430
|
+
copyFileSync(filePath, `${filePath}.bak`);
|
|
431
|
+
}
|
|
432
|
+
} catch {
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/coordinator.ts
|
|
437
|
+
import { join as join3 } from "path";
|
|
438
|
+
|
|
439
|
+
// src/detection/auto-detector.ts
|
|
440
|
+
async function autoDetectHarnesses(registry) {
|
|
441
|
+
const candidates = [new ClaudeCodeAdapter(), new CursorAdapter(), new CopilotAdapter()];
|
|
442
|
+
const registered = [];
|
|
443
|
+
await Promise.all(
|
|
444
|
+
candidates.map(async (adapter) => {
|
|
445
|
+
try {
|
|
446
|
+
if (await adapter.isAvailable()) {
|
|
447
|
+
registry.register(adapter);
|
|
448
|
+
registered.push(adapter.id);
|
|
449
|
+
} else {
|
|
450
|
+
await adapter.dispose();
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
await adapter.dispose();
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
);
|
|
457
|
+
return registered;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/registry/harness-registry.ts
|
|
461
|
+
var HarnessRegistry = class {
|
|
462
|
+
adapters = /* @__PURE__ */ new Map();
|
|
463
|
+
/** Register an adapter. Throws if an adapter with the same id already exists. */
|
|
464
|
+
register(adapter) {
|
|
465
|
+
if (this.adapters.has(adapter.id)) {
|
|
466
|
+
throw new Error(`Harness adapter already registered: ${adapter.id}`);
|
|
467
|
+
}
|
|
468
|
+
this.adapters.set(adapter.id, adapter);
|
|
469
|
+
adapter.notifyRegistered?.();
|
|
470
|
+
}
|
|
471
|
+
/** Unregister an adapter, disposing it in the process. */
|
|
472
|
+
async unregister(id) {
|
|
473
|
+
const adapter = this.adapters.get(id);
|
|
474
|
+
if (adapter) {
|
|
475
|
+
adapter.notifyUnregistering?.();
|
|
476
|
+
await adapter.dispose();
|
|
477
|
+
this.adapters.delete(id);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/** Retrieve an adapter by id. */
|
|
481
|
+
get(id) {
|
|
482
|
+
return this.adapters.get(id);
|
|
483
|
+
}
|
|
484
|
+
/** List all registered adapter ids. */
|
|
485
|
+
listAll() {
|
|
486
|
+
return Array.from(this.adapters.keys());
|
|
487
|
+
}
|
|
488
|
+
/** List ids of adapters that report isAvailable() === true. */
|
|
489
|
+
async listAvailable() {
|
|
490
|
+
const results = await Promise.all(
|
|
491
|
+
Array.from(this.adapters.entries()).map(async ([id, adapter]) => ({
|
|
492
|
+
id,
|
|
493
|
+
available: await adapter.isAvailable()
|
|
494
|
+
}))
|
|
495
|
+
);
|
|
496
|
+
return results.filter((r) => r.available).map((r) => r.id);
|
|
497
|
+
}
|
|
498
|
+
/** Dispose all adapters and clear the registry. */
|
|
499
|
+
async disposeAll() {
|
|
500
|
+
await Promise.all(Array.from(this.adapters.values()).map((a) => a.dispose()));
|
|
501
|
+
this.adapters.clear();
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/server/rpc-server.ts
|
|
506
|
+
import { createServer } from "net";
|
|
311
507
|
|
|
312
508
|
// src/detection/process-detector.ts
|
|
313
|
-
import { execFile as
|
|
509
|
+
import { execFile as execFile3 } from "child_process";
|
|
314
510
|
import { readdir } from "fs/promises";
|
|
315
511
|
import { join as join2 } from "path";
|
|
316
|
-
import { promisify as
|
|
317
|
-
var
|
|
512
|
+
import { promisify as promisify3 } from "util";
|
|
513
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
318
514
|
async function findProcesses(pattern) {
|
|
319
515
|
try {
|
|
320
|
-
const { stdout } = await
|
|
516
|
+
const { stdout } = await execFileAsync3("pgrep", ["-a", pattern], { timeout: 3e3 });
|
|
321
517
|
return stdout.trim().split("\n").filter(Boolean).map((line) => {
|
|
322
518
|
const spaceIdx = line.indexOf(" ");
|
|
323
519
|
const pid = parseInt(line.slice(0, spaceIdx), 10);
|
|
@@ -367,7 +563,6 @@ async function findClaudeCodeSockets() {
|
|
|
367
563
|
}
|
|
368
564
|
|
|
369
565
|
// src/server/rpc-server.ts
|
|
370
|
-
import { createServer } from "net";
|
|
371
566
|
var ERR_PARSE = -32700;
|
|
372
567
|
var ERR_INVALID_PARAMS = -32602;
|
|
373
568
|
var ERR_METHOD_NOT_FOUND = -32601;
|
|
@@ -392,6 +587,7 @@ var RpcServer = class {
|
|
|
392
587
|
});
|
|
393
588
|
}
|
|
394
589
|
server = createServer();
|
|
590
|
+
healthCheckFn = null;
|
|
395
591
|
handleLine(line, reply) {
|
|
396
592
|
let req;
|
|
397
593
|
try {
|
|
@@ -492,6 +688,17 @@ var RpcServer = class {
|
|
|
492
688
|
const processes = await findHarnessProcesses(harnessId);
|
|
493
689
|
return { jsonrpc: "2.0", id, result: processes };
|
|
494
690
|
}
|
|
691
|
+
case "harness.health": {
|
|
692
|
+
if (!this.healthCheckFn) {
|
|
693
|
+
return {
|
|
694
|
+
jsonrpc: "2.0",
|
|
695
|
+
id,
|
|
696
|
+
error: { code: ERR_INTERNAL, message: "Health check not configured" }
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const health = await this.healthCheckFn();
|
|
700
|
+
return { jsonrpc: "2.0", id, result: health };
|
|
701
|
+
}
|
|
495
702
|
default:
|
|
496
703
|
return {
|
|
497
704
|
jsonrpc: "2.0",
|
|
@@ -500,6 +707,10 @@ var RpcServer = class {
|
|
|
500
707
|
};
|
|
501
708
|
}
|
|
502
709
|
}
|
|
710
|
+
/** Set the health check function (called by coordinator after construction). */
|
|
711
|
+
setHealthCheck(fn) {
|
|
712
|
+
this.healthCheckFn = fn;
|
|
713
|
+
}
|
|
503
714
|
start() {
|
|
504
715
|
return new Promise((resolve, reject) => {
|
|
505
716
|
this.server.listen(this.socketPath, () => resolve());
|
|
@@ -512,7 +723,6 @@ var RpcServer = class {
|
|
|
512
723
|
};
|
|
513
724
|
|
|
514
725
|
// src/coordinator.ts
|
|
515
|
-
import { join as join3 } from "path";
|
|
516
726
|
var HarnessCoordinator = class {
|
|
517
727
|
constructor(options) {
|
|
518
728
|
this.options = options;
|
|
@@ -544,6 +754,7 @@ var HarnessCoordinator = class {
|
|
|
544
754
|
});
|
|
545
755
|
const socketPath = this.options.socketPath ?? join3(process.env.HOME ?? "/tmp", ".local", "share", "revealui", "harness.sock");
|
|
546
756
|
this.rpcServer = new RpcServer(this.registry, socketPath);
|
|
757
|
+
this.rpcServer.setHealthCheck(() => this.healthCheck());
|
|
547
758
|
await this.rpcServer.start();
|
|
548
759
|
}
|
|
549
760
|
async stop() {
|
|
@@ -573,23 +784,92 @@ var HarnessCoordinator = class {
|
|
|
573
784
|
registerAdapter(adapter) {
|
|
574
785
|
this.registry.register(adapter);
|
|
575
786
|
}
|
|
787
|
+
/** Run a health check across all registered harnesses and the workboard. */
|
|
788
|
+
async healthCheck() {
|
|
789
|
+
const diagnostics = [];
|
|
790
|
+
const STALE_MS = 4 * 60 * 60 * 1e3;
|
|
791
|
+
const allIds = this.registry.listAll();
|
|
792
|
+
const registeredHarnesses = await Promise.all(
|
|
793
|
+
allIds.map(async (harnessId) => {
|
|
794
|
+
const adapter = this.registry.get(harnessId);
|
|
795
|
+
let available = false;
|
|
796
|
+
try {
|
|
797
|
+
available = adapter ? await adapter.isAvailable() : false;
|
|
798
|
+
} catch (err) {
|
|
799
|
+
diagnostics.push(
|
|
800
|
+
`${harnessId}: availability check failed \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (!available) diagnostics.push(`${harnessId}: not available`);
|
|
804
|
+
return { harnessId, available };
|
|
805
|
+
})
|
|
806
|
+
);
|
|
807
|
+
let readable = false;
|
|
808
|
+
let sessionCount = 0;
|
|
809
|
+
const staleSessionIds = [];
|
|
810
|
+
try {
|
|
811
|
+
const state = this.workboard.read();
|
|
812
|
+
readable = true;
|
|
813
|
+
sessionCount = state.sessions.length;
|
|
814
|
+
const now = Date.now();
|
|
815
|
+
for (const s of state.sessions) {
|
|
816
|
+
const ts = Date.parse(s.updated);
|
|
817
|
+
if (!Number.isNaN(ts) && now - ts > STALE_MS) {
|
|
818
|
+
staleSessionIds.push(s.id);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (staleSessionIds.length > 0) {
|
|
822
|
+
diagnostics.push(`Stale sessions: ${staleSessionIds.join(", ")}`);
|
|
823
|
+
}
|
|
824
|
+
} catch (err) {
|
|
825
|
+
diagnostics.push(
|
|
826
|
+
`Workboard unreadable: ${err instanceof Error ? err.message : String(err)}`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
const healthy = registeredHarnesses.some((h) => h.available) && readable && staleSessionIds.length === 0;
|
|
830
|
+
return {
|
|
831
|
+
healthy,
|
|
832
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
833
|
+
registeredHarnesses,
|
|
834
|
+
workboard: { readable, sessionCount, staleSessionIds },
|
|
835
|
+
diagnostics
|
|
836
|
+
};
|
|
837
|
+
}
|
|
576
838
|
};
|
|
577
839
|
|
|
840
|
+
// src/index.ts
|
|
841
|
+
async function checkHarnessesLicense() {
|
|
842
|
+
await initializeLicense();
|
|
843
|
+
if (!isFeatureEnabled("ai")) {
|
|
844
|
+
logger.warn(
|
|
845
|
+
"[@revealui/harnesses] AI harness integration requires a Pro or Enterprise license. Visit https://revealui.com/pricing for details.",
|
|
846
|
+
{ feature: "ai" }
|
|
847
|
+
);
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
|
|
578
853
|
export {
|
|
579
854
|
ClaudeCodeAdapter,
|
|
580
855
|
CopilotAdapter,
|
|
856
|
+
CursorAdapter,
|
|
581
857
|
autoDetectHarnesses,
|
|
582
858
|
HarnessRegistry,
|
|
583
859
|
getLocalConfigPath,
|
|
584
|
-
|
|
860
|
+
getRootConfigPath,
|
|
585
861
|
getConfigurableHarnesses,
|
|
586
862
|
syncConfig,
|
|
587
863
|
diffConfig,
|
|
864
|
+
syncAllConfigs,
|
|
865
|
+
diffAllConfigs,
|
|
866
|
+
validateConfigJson,
|
|
588
867
|
findProcesses,
|
|
589
868
|
findHarnessProcesses,
|
|
590
869
|
findAllHarnessProcesses,
|
|
591
870
|
findClaudeCodeSockets,
|
|
592
871
|
RpcServer,
|
|
593
|
-
HarnessCoordinator
|
|
872
|
+
HarnessCoordinator,
|
|
873
|
+
checkHarnessesLicense
|
|
594
874
|
};
|
|
595
|
-
//# sourceMappingURL=chunk-
|
|
875
|
+
//# sourceMappingURL=chunk-MO2EXBUG.js.map
|