@revealui/harnesses 0.1.8 → 0.1.9

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.
@@ -1,274 +1,20 @@
1
1
  import {
2
2
  DaemonStore
3
- } from "./chunk-DGQ5OB6L.js";
3
+ } from "./chunk-ANX4L2PF.js";
4
4
  import {
5
5
  WorkboardManager,
6
6
  deriveSessionId,
7
7
  detectSessionType
8
- } from "./chunk-4F4ANKIZ.js";
8
+ } from "./chunk-Y4FFO3TO.js";
9
9
  import {
10
10
  __require
11
- } from "./chunk-DGUM43GV.js";
11
+ } from "./chunk-3RG5ZIWI.js";
12
12
 
13
13
  // src/index.ts
14
14
  import { isFeatureEnabled } from "@revealui/core/features";
15
15
  import { initializeLicense } from "@revealui/core/license";
16
16
  import { logger } from "@revealui/core/observability/logger";
17
17
 
18
- // src/adapters/claude-code-adapter.ts
19
- import { execFile } from "child_process";
20
- import { promisify } from "util";
21
- var execFileAsync = promisify(execFile);
22
- var ClaudeCodeAdapter = class {
23
- id = "claude-code";
24
- name = "Claude Code";
25
- eventHandlers = /* @__PURE__ */ new Set();
26
- workboardPath;
27
- constructor(workboardPath) {
28
- this.workboardPath = workboardPath ?? process.env.REVEALUI_WORKBOARD_PATH;
29
- }
30
- getCapabilities() {
31
- return {
32
- generateCode: false,
33
- // interactive only — no headless CLI interface
34
- analyzeCode: false,
35
- // interactive only — no headless CLI interface
36
- applyEdit: false,
37
- // interactive only — edits are applied inside Claude Code sessions
38
- applyConfig: false,
39
- // config managed interactively via ~/.claude/settings.json
40
- readWorkboard: this.workboardPath !== void 0,
41
- writeWorkboard: this.workboardPath !== void 0
42
- };
43
- }
44
- async getInfo() {
45
- let version;
46
- try {
47
- const { stdout } = await execFileAsync("claude", ["--version"], {
48
- timeout: 5e3
49
- });
50
- version = stdout.trim().split("\n")[0];
51
- } catch {
52
- }
53
- return { id: this.id, name: this.name, version, capabilities: this.getCapabilities() };
54
- }
55
- async isAvailable() {
56
- try {
57
- await execFileAsync("claude", ["--version"], { timeout: 3e3 });
58
- return true;
59
- } catch {
60
- return false;
61
- }
62
- }
63
- notifyRegistered() {
64
- this.emit({ type: "harness-connected", harnessId: this.id });
65
- }
66
- notifyUnregistering() {
67
- this.emit({ type: "harness-disconnected", harnessId: this.id });
68
- }
69
- async execute(command) {
70
- try {
71
- return await this.executeInner(command);
72
- } catch (err) {
73
- const message = err instanceof Error ? err.message : String(err);
74
- this.emit({ type: "error", harnessId: this.id, message });
75
- return { success: false, command: command.type, message };
76
- }
77
- }
78
- async executeInner(command) {
79
- switch (command.type) {
80
- case "get-status": {
81
- const available = await this.isAvailable();
82
- return { success: true, command: command.type, data: { available } };
83
- }
84
- case "get-running-instances": {
85
- return { success: true, command: command.type, data: [] };
86
- }
87
- case "generate-code":
88
- case "analyze-code": {
89
- return {
90
- success: false,
91
- command: command.type,
92
- message: `${command.type} is not supported \u2014 Claude Code operates interactively`
93
- };
94
- }
95
- case "apply-config": {
96
- return {
97
- success: false,
98
- command: command.type,
99
- message: "Config is managed interactively via ~/.claude/settings.json"
100
- };
101
- }
102
- case "read-workboard": {
103
- if (!this.workboardPath) {
104
- return {
105
- success: false,
106
- command: command.type,
107
- message: "REVEALUI_WORKBOARD_PATH is not set"
108
- };
109
- }
110
- const manager = new WorkboardManager(this.workboardPath);
111
- const state = await manager.readAsync();
112
- return { success: true, command: command.type, data: state };
113
- }
114
- case "update-workboard": {
115
- if (!this.workboardPath) {
116
- return {
117
- success: false,
118
- command: command.type,
119
- message: "REVEALUI_WORKBOARD_PATH is not set"
120
- };
121
- }
122
- const manager = new WorkboardManager(this.workboardPath);
123
- manager.updateAgent(command.sessionId, {
124
- ...command.task !== void 0 && { task: command.task },
125
- ...command.files !== void 0 && { files: command.files.join(", ") },
126
- updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
127
- });
128
- return { success: true, command: command.type };
129
- }
130
- default: {
131
- return {
132
- success: false,
133
- command: command.type,
134
- message: `Command not supported by ${this.name}`
135
- };
136
- }
137
- }
138
- }
139
- onEvent(handler) {
140
- this.eventHandlers.add(handler);
141
- return () => this.eventHandlers.delete(handler);
142
- }
143
- async dispose() {
144
- this.eventHandlers.clear();
145
- }
146
- emit(event) {
147
- for (const handler of this.eventHandlers) {
148
- try {
149
- handler(event);
150
- } catch {
151
- }
152
- }
153
- }
154
- };
155
-
156
- // src/adapters/cursor-adapter.ts
157
- import { execFile as execFile2 } from "child_process";
158
- import { promisify as promisify2 } from "util";
159
- var execFileAsync2 = promisify2(execFile2);
160
- var CursorAdapter = class {
161
- id = "cursor";
162
- name = "Cursor";
163
- eventHandlers = /* @__PURE__ */ new Set();
164
- workboardPath;
165
- constructor(workboardPath) {
166
- this.workboardPath = workboardPath ?? process.env.REVEALUI_WORKBOARD_PATH;
167
- }
168
- getCapabilities() {
169
- return {
170
- generateCode: false,
171
- analyzeCode: false,
172
- applyEdit: false,
173
- applyConfig: false,
174
- readWorkboard: this.workboardPath !== void 0,
175
- writeWorkboard: this.workboardPath !== void 0
176
- };
177
- }
178
- async getInfo() {
179
- let version;
180
- try {
181
- const { stdout } = await execFileAsync2("cursor", ["--version"], {
182
- timeout: 5e3
183
- });
184
- version = stdout.trim().split("\n")[0];
185
- } catch {
186
- }
187
- return { id: this.id, name: this.name, version, capabilities: this.getCapabilities() };
188
- }
189
- async isAvailable() {
190
- try {
191
- await execFileAsync2("cursor", ["--version"], { timeout: 3e3 });
192
- return true;
193
- } catch {
194
- return false;
195
- }
196
- }
197
- notifyRegistered() {
198
- this.emit({ type: "harness-connected", harnessId: this.id });
199
- }
200
- notifyUnregistering() {
201
- this.emit({ type: "harness-disconnected", harnessId: this.id });
202
- }
203
- async execute(command) {
204
- try {
205
- return await this.executeInner(command);
206
- } catch (err) {
207
- const message = err instanceof Error ? err.message : String(err);
208
- this.emit({ type: "error", harnessId: this.id, message });
209
- return { success: false, command: command.type, message };
210
- }
211
- }
212
- async executeInner(command) {
213
- switch (command.type) {
214
- case "get-status": {
215
- const available = await this.isAvailable();
216
- return { success: true, command: command.type, data: { available } };
217
- }
218
- case "read-workboard": {
219
- if (!this.workboardPath) {
220
- return {
221
- success: false,
222
- command: command.type,
223
- message: "REVEALUI_WORKBOARD_PATH is not set"
224
- };
225
- }
226
- const manager = new WorkboardManager(this.workboardPath);
227
- const state = await manager.readAsync();
228
- return { success: true, command: command.type, data: state };
229
- }
230
- case "update-workboard": {
231
- if (!this.workboardPath) {
232
- return {
233
- success: false,
234
- command: command.type,
235
- message: "REVEALUI_WORKBOARD_PATH is not set"
236
- };
237
- }
238
- const manager = new WorkboardManager(this.workboardPath);
239
- manager.updateAgent(command.sessionId, {
240
- ...command.task !== void 0 && { task: command.task },
241
- ...command.files !== void 0 && { files: command.files.join(", ") },
242
- updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
243
- });
244
- return { success: true, command: command.type };
245
- }
246
- default: {
247
- return {
248
- success: false,
249
- command: command.type,
250
- message: `Command not supported by ${this.name}`
251
- };
252
- }
253
- }
254
- }
255
- onEvent(handler) {
256
- this.eventHandlers.add(handler);
257
- return () => this.eventHandlers.delete(handler);
258
- }
259
- async dispose() {
260
- this.eventHandlers.clear();
261
- }
262
- emit(event) {
263
- for (const handler of this.eventHandlers) {
264
- try {
265
- handler(event);
266
- } catch {
267
- }
268
- }
269
- }
270
- };
271
-
272
18
  // src/config/config-sync.ts
273
19
  import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs";
274
20
  import { dirname } from "path";
@@ -396,6 +142,764 @@ function backupIfExists(filePath) {
396
142
  import { mkdirSync as mkdirSync2 } from "fs";
397
143
  import { join as join4 } from "path";
398
144
 
145
+ // src/coordination/ci-feedback.ts
146
+ var MAX_RETRIES = 3;
147
+ var CIFeedback = class {
148
+ constructor(store) {
149
+ this.store = store;
150
+ }
151
+ store;
152
+ /**
153
+ * Handle a CI report. Called when GitHub Actions posts results
154
+ * to the daemon HTTP gateway.
155
+ */
156
+ async report(params) {
157
+ const mr = await this.findMergeRequest(params);
158
+ if (!mr) {
159
+ return { action: "no_merge_request" };
160
+ }
161
+ await this.store.logEvent({
162
+ agentId: mr.agent_id,
163
+ eventType: params.success ? "ci-passed" : "ci-failed",
164
+ payload: {
165
+ mergeId: mr.id,
166
+ prNumber: params.prNumber,
167
+ failedJob: params.failedJob,
168
+ runUrl: params.runUrl
169
+ }
170
+ });
171
+ if (params.success) {
172
+ return this.handleSuccess(mr);
173
+ }
174
+ return this.handleFailure(mr, params);
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Private - success path
178
+ // ---------------------------------------------------------------------------
179
+ async handleSuccess(mr) {
180
+ await this.store.updateMergeRequest(mr.id, { status: "merged" });
181
+ if (mr.task_id) {
182
+ await this.store.completeTask(mr.task_id, mr.agent_id);
183
+ }
184
+ await this.store.updateWorktreeStatus(mr.agent_id, "merged");
185
+ await this.store.sendMessage({
186
+ fromAgent: "ci-feedback",
187
+ toAgent: mr.agent_id,
188
+ subject: "CI passed - PR ready to merge",
189
+ body: `PR ${mr.pr_url ?? `#${mr.pr_number}`} passed CI. Merge request ${mr.id} is complete.`
190
+ });
191
+ await this.store.storeMemory({
192
+ agentId: mr.agent_id,
193
+ memoryType: "result",
194
+ content: `CI passed for ${mr.source_branch}. PR ${mr.pr_number ?? "(unknown)"} is ready.`,
195
+ metadata: { mergeId: mr.id, prNumber: mr.pr_number }
196
+ });
197
+ return { action: "merged", mergeRequestId: mr.id };
198
+ }
199
+ // ---------------------------------------------------------------------------
200
+ // Private - failure path
201
+ // ---------------------------------------------------------------------------
202
+ async handleFailure(mr, params) {
203
+ const retryCount = await this.store.incrementMergeRetry(mr.id);
204
+ await this.store.updateMergeRequest(mr.id, {
205
+ status: "ci_failed",
206
+ ciOutput: params.output?.slice(0, 1e4),
207
+ errorMessage: `CI failed (attempt ${retryCount}/${MAX_RETRIES}): ${params.failedJob ?? "unknown job"}`
208
+ });
209
+ if (retryCount >= MAX_RETRIES) {
210
+ return this.escalate(mr, retryCount, params);
211
+ }
212
+ const category = this.categorizeFailure(params);
213
+ const taskId = `fix-${mr.id}-${retryCount}`;
214
+ const description = this.buildFixTaskDescription(mr, category, params);
215
+ await this.store.createTask({ id: taskId, description });
216
+ await this.store.claimTask(taskId, mr.agent_id);
217
+ await this.store.sendMessage({
218
+ fromAgent: "ci-feedback",
219
+ toAgent: mr.agent_id,
220
+ subject: `CI failed \u2014 fix task created (attempt ${retryCount}/${MAX_RETRIES})`,
221
+ body: description
222
+ });
223
+ await this.store.storeMemory({
224
+ agentId: mr.agent_id,
225
+ memoryType: "action",
226
+ content: `CI failed on ${mr.source_branch}: ${category} error. Fix task ${taskId} created. Attempt ${retryCount}/${MAX_RETRIES}.`,
227
+ metadata: {
228
+ mergeId: mr.id,
229
+ category,
230
+ failedJob: params.failedJob,
231
+ retryCount
232
+ }
233
+ });
234
+ return {
235
+ action: "fix_task_created",
236
+ mergeRequestId: mr.id,
237
+ taskId,
238
+ retryCount
239
+ };
240
+ }
241
+ /** Escalate to human after max retries. */
242
+ async escalate(mr, retryCount, params) {
243
+ await this.store.updateMergeRequest(mr.id, { status: "escalated" });
244
+ await this.store.broadcastMessage({
245
+ fromAgent: "ci-feedback",
246
+ subject: `ESCALATION: ${mr.source_branch} failed CI ${retryCount} times`,
247
+ body: [
248
+ `Merge request ${mr.id} has failed CI ${retryCount} times and needs human intervention.`,
249
+ `Agent: ${mr.agent_id}`,
250
+ `Branch: ${mr.source_branch} \u2192 ${mr.base_branch}`,
251
+ `PR: ${mr.pr_url ?? `#${mr.pr_number}`}`,
252
+ `Last failure: ${params.failedJob ?? "unknown"}`,
253
+ `Run: ${params.runUrl ?? "no URL"}`
254
+ ].join("\n")
255
+ });
256
+ await this.store.logEvent({
257
+ agentId: mr.agent_id,
258
+ eventType: "merge-escalated",
259
+ payload: {
260
+ mergeId: mr.id,
261
+ retryCount,
262
+ failedJob: params.failedJob,
263
+ runUrl: params.runUrl
264
+ }
265
+ });
266
+ return {
267
+ action: "escalated",
268
+ mergeRequestId: mr.id,
269
+ retryCount
270
+ };
271
+ }
272
+ // ---------------------------------------------------------------------------
273
+ // Private - helpers
274
+ // ---------------------------------------------------------------------------
275
+ /** Find the merge request associated with this CI run. */
276
+ async findMergeRequest(params) {
277
+ if (params.prNumber) {
278
+ const mr = await this.store.getMergeRequestByPr(params.prNumber);
279
+ if (mr) return mr;
280
+ }
281
+ if (params.branch) {
282
+ return this.store.getMergeRequestByBranch(params.branch);
283
+ }
284
+ return null;
285
+ }
286
+ /** Categorize the CI failure from the job name or output. */
287
+ categorizeFailure(params) {
288
+ const job = params.failedJob?.toLowerCase() ?? "";
289
+ const output = params.output?.toLowerCase() ?? "";
290
+ if (job.includes("lint") || job.includes("quality")) return "lint";
291
+ if (job.includes("typecheck") || job.includes("type")) return "typecheck";
292
+ if (job.includes("test") && !job.includes("e2e")) return "test";
293
+ if (job.includes("build")) return "build";
294
+ if (job.includes("e2e") || job.includes("playwright")) return "e2e";
295
+ if (output.includes("biome") || output.includes("lint")) return "lint";
296
+ if (output.includes("ts2") || output.includes("type error")) return "typecheck";
297
+ if (output.includes("vitest") || output.includes("test failed")) return "test";
298
+ if (output.includes("build failed") || output.includes("esbuild")) return "build";
299
+ return "unknown";
300
+ }
301
+ /** Build a descriptive fix task for the agent. */
302
+ buildFixTaskDescription(mr, category, params) {
303
+ const lines = [`Fix CI ${category} failure on branch ${mr.source_branch}.`];
304
+ switch (category) {
305
+ case "lint":
306
+ lines.push("Run `pnpm lint:fix` and commit the changes.");
307
+ break;
308
+ case "typecheck":
309
+ lines.push("Run `pnpm typecheck:all` locally and fix type errors.");
310
+ break;
311
+ case "test":
312
+ lines.push("Run `pnpm test` locally and fix failing tests.");
313
+ break;
314
+ case "build":
315
+ lines.push("Run `pnpm build` locally and fix build errors.");
316
+ break;
317
+ case "e2e":
318
+ lines.push("Check Playwright E2E test output and fix the failing scenarios.");
319
+ break;
320
+ default:
321
+ lines.push("Review the CI output and fix the issue.");
322
+ }
323
+ if (params.runUrl) {
324
+ lines.push(`CI run: ${params.runUrl}`);
325
+ }
326
+ if (params.output) {
327
+ const truncated = params.output.slice(0, 2e3);
328
+ lines.push("", "CI output (truncated):", "```", truncated, "```");
329
+ }
330
+ return lines.join("\n");
331
+ }
332
+ };
333
+
334
+ // src/coordination/merge-pipeline.ts
335
+ import { execFile } from "child_process";
336
+ import { promisify } from "util";
337
+ var execFileAsync = promisify(execFile);
338
+ var GIT_TIMEOUT = 3e4;
339
+ var GH_TIMEOUT = 15e3;
340
+ async function getWorkspaceDependencyOrder(repoRoot) {
341
+ try {
342
+ const { stdout } = await execFileAsync("pnpm", ["ls", "--depth", "0", "--json", "-r"], {
343
+ cwd: repoRoot,
344
+ timeout: GIT_TIMEOUT
345
+ });
346
+ const packages = JSON.parse(stdout);
347
+ return packages.map((p) => p.name);
348
+ } catch {
349
+ return [];
350
+ }
351
+ }
352
+ var MergePipeline = class {
353
+ constructor(store, config) {
354
+ this.store = store;
355
+ this.repoRoot = config.repoRoot;
356
+ this.baseBranch = config.baseBranch ?? "test";
357
+ this.remote = config.remote ?? "origin";
358
+ }
359
+ store;
360
+ repoRoot;
361
+ baseBranch;
362
+ remote;
363
+ /**
364
+ * Request a merge for an agent's branch into the base branch.
365
+ * Creates a merge_request record and attempts the merge.
366
+ */
367
+ async requestMerge(params) {
368
+ const mergeId = `merge-${params.agentId}-${Date.now()}`;
369
+ const baseBranch = params.baseBranch ?? this.baseBranch;
370
+ await this.store.createMergeRequest({
371
+ id: mergeId,
372
+ agentId: params.agentId,
373
+ taskId: params.taskId,
374
+ sourceBranch: params.sourceBranch,
375
+ baseBranch
376
+ });
377
+ await this.store.logEvent({
378
+ agentId: params.agentId,
379
+ eventType: "merge-requested",
380
+ payload: { mergeId, sourceBranch: params.sourceBranch, baseBranch }
381
+ });
382
+ return this.processMerge(mergeId);
383
+ }
384
+ /**
385
+ * Process a pending merge request: fetch, check for conflicts,
386
+ * fast-forward merge, and create a PR.
387
+ */
388
+ async processMerge(mergeId) {
389
+ const mr = await this.store.getMergeRequest(mergeId);
390
+ if (!mr) {
391
+ return { mergeRequestId: mergeId, status: "pending", error: "Merge request not found" };
392
+ }
393
+ await this.store.updateMergeRequest(mergeId, { status: "merging" });
394
+ try {
395
+ await this.git(["fetch", this.remote, mr.base_branch, mr.source_branch]);
396
+ const conflictCheck = await this.checkConflicts(mr.source_branch, mr.base_branch);
397
+ if (!conflictCheck.clean) {
398
+ await this.handleConflict(mr, conflictCheck.conflictingFiles);
399
+ return {
400
+ mergeRequestId: mergeId,
401
+ status: "conflict",
402
+ conflictingFiles: conflictCheck.conflictingFiles,
403
+ error: `Merge conflict in: ${conflictCheck.conflictingFiles.join(", ")}`
404
+ };
405
+ }
406
+ const prResult = await this.createPR(mr);
407
+ await this.store.updateMergeRequest(mergeId, {
408
+ status: "pr_created",
409
+ prNumber: prResult.number,
410
+ prUrl: prResult.url
411
+ });
412
+ await this.store.logEvent({
413
+ agentId: mr.agent_id,
414
+ eventType: "pr-created",
415
+ payload: { mergeId, prNumber: prResult.number, prUrl: prResult.url }
416
+ });
417
+ return {
418
+ mergeRequestId: mergeId,
419
+ status: "pr_created",
420
+ prUrl: prResult.url,
421
+ prNumber: prResult.number
422
+ };
423
+ } catch (err) {
424
+ const message = err instanceof Error ? err.message : String(err);
425
+ await this.store.updateMergeRequest(mergeId, {
426
+ status: "ci_failed",
427
+ errorMessage: message
428
+ });
429
+ return { mergeRequestId: mergeId, status: "ci_failed", error: message };
430
+ }
431
+ }
432
+ /** Get the current status of a merge request. */
433
+ async getStatus(mergeId) {
434
+ const mr = await this.store.getMergeRequest(mergeId);
435
+ if (!mr) {
436
+ return { mergeRequestId: mergeId, status: "pending", error: "Not found" };
437
+ }
438
+ return {
439
+ mergeRequestId: mergeId,
440
+ status: mr.status,
441
+ prUrl: mr.pr_url ?? void 0,
442
+ prNumber: mr.pr_number ?? void 0,
443
+ error: mr.error_message ?? void 0
444
+ };
445
+ }
446
+ /** Retry a failed or conflicted merge request. */
447
+ async resolve(mergeId) {
448
+ const mr = await this.store.getMergeRequest(mergeId);
449
+ if (!mr) {
450
+ return { mergeRequestId: mergeId, status: "pending", error: "Not found" };
451
+ }
452
+ if (mr.status !== "conflict" && mr.status !== "ci_failed") {
453
+ return {
454
+ mergeRequestId: mergeId,
455
+ status: mr.status,
456
+ error: `Cannot resolve: status is ${mr.status}`
457
+ };
458
+ }
459
+ await this.store.updateMergeRequest(mergeId, { status: "pending", errorMessage: "" });
460
+ return this.processMerge(mergeId);
461
+ }
462
+ /** List all active (non-terminal) merge requests. */
463
+ async listActive() {
464
+ const all = await this.store.listMergeRequests();
465
+ return all.filter((mr) => mr.status !== "merged" && mr.status !== "escalated");
466
+ }
467
+ /**
468
+ * Get the workspace dependency order for merge sequencing.
469
+ * Packages that are dependencies of others should merge first.
470
+ */
471
+ async getDependencyOrder() {
472
+ return getWorkspaceDependencyOrder(this.repoRoot);
473
+ }
474
+ // ---------------------------------------------------------------------------
475
+ // Private helpers
476
+ // ---------------------------------------------------------------------------
477
+ /** Run a git command in the repo root. */
478
+ async git(args) {
479
+ const { stdout } = await execFileAsync("git", args, {
480
+ cwd: this.repoRoot,
481
+ timeout: GIT_TIMEOUT
482
+ });
483
+ return stdout.trim();
484
+ }
485
+ /**
486
+ * Check whether source branch merges cleanly into base branch.
487
+ * Uses `git merge-tree` (available in Git 2.38+) for a tree-only check
488
+ * that doesn't touch the working directory.
489
+ */
490
+ async checkConflicts(sourceBranch, baseBranch) {
491
+ try {
492
+ await execFileAsync(
493
+ "git",
494
+ [
495
+ "merge-tree",
496
+ "--write-tree",
497
+ `${this.remote}/${baseBranch}`,
498
+ `${this.remote}/${sourceBranch}`
499
+ ],
500
+ { cwd: this.repoRoot, timeout: GIT_TIMEOUT }
501
+ );
502
+ return { clean: true, conflictingFiles: [] };
503
+ } catch (err) {
504
+ const output = err instanceof Error && "stdout" in err ? String(err.stdout) : "";
505
+ const conflicting = [];
506
+ for (const line of output.split("\n")) {
507
+ if (line.startsWith("CONFLICT")) {
508
+ const parts = line.split(" ");
509
+ const lastPart = parts[parts.length - 1];
510
+ if (lastPart) conflicting.push(lastPart);
511
+ }
512
+ }
513
+ return { clean: false, conflictingFiles: conflicting };
514
+ }
515
+ }
516
+ /** Notify conflicting agents via the messaging system. */
517
+ async handleConflict(mr, conflictingFiles) {
518
+ await this.store.updateMergeRequest(mr.id, {
519
+ status: "conflict",
520
+ errorMessage: `Conflict in: ${conflictingFiles.join(", ")}`
521
+ });
522
+ const notifiedAgents = /* @__PURE__ */ new Set();
523
+ for (const file of conflictingFiles) {
524
+ const reservation = await this.store.checkReservation(file);
525
+ if (reservation && reservation.agent_id !== mr.agent_id) {
526
+ notifiedAgents.add(reservation.agent_id);
527
+ }
528
+ }
529
+ for (const agentId of notifiedAgents) {
530
+ await this.store.sendMessage({
531
+ fromAgent: "merge-pipeline",
532
+ toAgent: agentId,
533
+ subject: `Merge conflict with ${mr.agent_id}`,
534
+ body: `Branch ${mr.source_branch} conflicts with your work on: ${conflictingFiles.join(", ")}. Please coordinate resolution.`
535
+ });
536
+ }
537
+ await this.store.sendMessage({
538
+ fromAgent: "merge-pipeline",
539
+ toAgent: mr.agent_id,
540
+ subject: "Merge conflict detected",
541
+ body: `Your branch ${mr.source_branch} has conflicts with ${mr.base_branch} in: ${conflictingFiles.join(", ")}. Resolve and call merge.resolve to retry.`
542
+ });
543
+ await this.store.logEvent({
544
+ agentId: mr.agent_id,
545
+ eventType: "merge-conflict",
546
+ payload: {
547
+ mergeId: mr.id,
548
+ conflictingFiles,
549
+ notifiedAgents: [...notifiedAgents]
550
+ }
551
+ });
552
+ }
553
+ /** Create a GitHub PR using the `gh` CLI. */
554
+ async createPR(mr) {
555
+ const title = `agent/${mr.agent_id}: ${mr.task_id ?? mr.source_branch}`;
556
+ const body = [
557
+ "## Automated Agent PR",
558
+ "",
559
+ `**Agent**: ${mr.agent_id}`,
560
+ `**Branch**: ${mr.source_branch} \u2192 ${mr.base_branch}`,
561
+ mr.task_id ? `**Task**: ${mr.task_id}` : "",
562
+ "",
563
+ "This PR was created automatically by the merge pipeline.",
564
+ "CI must pass before merge."
565
+ ].filter(Boolean).join("\n");
566
+ const { stdout } = await execFileAsync(
567
+ "gh",
568
+ [
569
+ "pr",
570
+ "create",
571
+ "--base",
572
+ mr.base_branch,
573
+ "--head",
574
+ mr.source_branch,
575
+ "--title",
576
+ title,
577
+ "--body",
578
+ body
579
+ ],
580
+ { cwd: this.repoRoot, timeout: GH_TIMEOUT }
581
+ );
582
+ const url = stdout.trim();
583
+ const parts = url.split("/");
584
+ const number = Number.parseInt(parts[parts.length - 1] ?? "0", 10);
585
+ return { number, url };
586
+ }
587
+ };
588
+
589
+ // src/vaughn/capabilities.ts
590
+ function createDefaultCapabilities() {
591
+ return {
592
+ dispatch: {
593
+ generateCode: false,
594
+ analyzeCode: false,
595
+ applyEdit: false,
596
+ executeCommand: false
597
+ },
598
+ readWorkboard: false,
599
+ writeWorkboard: false,
600
+ claimTasks: false,
601
+ reportConflicts: false,
602
+ headless: false,
603
+ resumable: false,
604
+ forkable: false,
605
+ backgroundable: false,
606
+ hooks: { supported: false, granularity: "none", canBlock: false },
607
+ sandbox: { supported: false, modes: [] },
608
+ supportsWorktrees: false,
609
+ supportsSkills: false,
610
+ supportsMcp: false,
611
+ memory: { supported: false, backend: "none" },
612
+ maxContextTokens: 0,
613
+ lifecycleEvents: []
614
+ };
615
+ }
616
+ var TOOL_PROFILES = {
617
+ "claude-code": {
618
+ dispatch: {
619
+ generateCode: false,
620
+ analyzeCode: false,
621
+ applyEdit: false,
622
+ executeCommand: false
623
+ },
624
+ readWorkboard: true,
625
+ writeWorkboard: true,
626
+ claimTasks: true,
627
+ reportConflicts: true,
628
+ headless: true,
629
+ resumable: false,
630
+ forkable: false,
631
+ backgroundable: true,
632
+ hooks: { supported: true, granularity: "all-tools", canBlock: true },
633
+ sandbox: { supported: false, modes: [] },
634
+ supportsWorktrees: true,
635
+ supportsSkills: true,
636
+ supportsMcp: true,
637
+ memory: { supported: false, backend: "none" },
638
+ maxContextTokens: 2e5,
639
+ lifecycleEvents: [
640
+ "session.start",
641
+ "session.stop",
642
+ "prompt.submit",
643
+ "tool.before",
644
+ "tool.after",
645
+ "tool.blocked"
646
+ ]
647
+ },
648
+ codex: {
649
+ dispatch: {
650
+ generateCode: false,
651
+ analyzeCode: false,
652
+ applyEdit: false,
653
+ executeCommand: false
654
+ },
655
+ readWorkboard: true,
656
+ writeWorkboard: true,
657
+ claimTasks: true,
658
+ reportConflicts: false,
659
+ headless: true,
660
+ resumable: true,
661
+ forkable: true,
662
+ backgroundable: true,
663
+ hooks: { supported: true, granularity: "bash-only", canBlock: true },
664
+ sandbox: { supported: true, modes: ["read-only", "workspace-write", "full-access"] },
665
+ supportsWorktrees: false,
666
+ supportsSkills: true,
667
+ supportsMcp: true,
668
+ memory: { supported: true, backend: "sqlite" },
669
+ maxContextTokens: 2e5,
670
+ lifecycleEvents: [
671
+ "session.start",
672
+ "session.stop",
673
+ "prompt.submit",
674
+ "tool.before",
675
+ "tool.after",
676
+ "tool.blocked"
677
+ ]
678
+ },
679
+ cursor: {
680
+ dispatch: {
681
+ generateCode: false,
682
+ analyzeCode: false,
683
+ applyEdit: false,
684
+ executeCommand: false
685
+ },
686
+ readWorkboard: false,
687
+ writeWorkboard: false,
688
+ claimTasks: false,
689
+ reportConflicts: false,
690
+ headless: false,
691
+ resumable: false,
692
+ forkable: false,
693
+ backgroundable: false,
694
+ hooks: { supported: false, granularity: "none", canBlock: false },
695
+ sandbox: { supported: false, modes: [] },
696
+ supportsWorktrees: false,
697
+ supportsSkills: false,
698
+ supportsMcp: false,
699
+ memory: { supported: false, backend: "none" },
700
+ maxContextTokens: 128e3,
701
+ lifecycleEvents: []
702
+ },
703
+ "revealui-agent": {
704
+ dispatch: {
705
+ generateCode: true,
706
+ analyzeCode: true,
707
+ applyEdit: true,
708
+ executeCommand: true
709
+ },
710
+ readWorkboard: true,
711
+ writeWorkboard: true,
712
+ claimTasks: true,
713
+ reportConflicts: true,
714
+ headless: true,
715
+ resumable: false,
716
+ forkable: false,
717
+ backgroundable: true,
718
+ hooks: { supported: true, granularity: "all-tools", canBlock: true },
719
+ sandbox: { supported: false, modes: [] },
720
+ supportsWorktrees: true,
721
+ supportsSkills: true,
722
+ supportsMcp: true,
723
+ memory: { supported: true, backend: "crdt" },
724
+ maxContextTokens: 2e5,
725
+ lifecycleEvents: [
726
+ "session.start",
727
+ "session.stop",
728
+ "session.crash",
729
+ "prompt.submit",
730
+ "tool.before",
731
+ "tool.after",
732
+ "tool.blocked",
733
+ "task.claimed",
734
+ "task.completed",
735
+ "agent.heartbeat"
736
+ ]
737
+ }
738
+ };
739
+
740
+ // src/vaughn/degradation-strategies.ts
741
+ var DEGRADATION_TABLE = {
742
+ "claude-code": {
743
+ "session.crash": "polyfill",
744
+ "task.claimed": "polyfill",
745
+ "task.completed": "polyfill",
746
+ "agent.heartbeat": "polyfill"
747
+ },
748
+ codex: {
749
+ "session.crash": "polyfill",
750
+ "task.claimed": "polyfill",
751
+ "task.completed": "polyfill",
752
+ "agent.heartbeat": "polyfill"
753
+ },
754
+ cursor: {
755
+ "session.start": "absent",
756
+ "session.stop": "absent",
757
+ "session.crash": "polyfill",
758
+ "prompt.submit": "absent",
759
+ "tool.before": "absent",
760
+ "tool.after": "absent",
761
+ "tool.blocked": "absent",
762
+ "task.claimed": "absent",
763
+ "task.completed": "absent",
764
+ "agent.heartbeat": "polyfill"
765
+ },
766
+ "revealui-agent": {
767
+ // RevealUI Agent natively supports all 10 events; no degradation needed.
768
+ }
769
+ };
770
+ function getDegradationStrategy(toolName, event) {
771
+ const toolDegradations = DEGRADATION_TABLE[toolName];
772
+ if (!toolDegradations) return "absent";
773
+ return toolDegradations[event];
774
+ }
775
+
776
+ // src/vaughn/event-envelope.ts
777
+ import { z } from "zod";
778
+ var VAUGHN_VERSION = "0.1.0";
779
+ var VAUGHN_EVENTS = [
780
+ "session.start",
781
+ "session.stop",
782
+ "session.crash",
783
+ "prompt.submit",
784
+ "tool.before",
785
+ "tool.after",
786
+ "tool.blocked",
787
+ "task.claimed",
788
+ "task.completed",
789
+ "agent.heartbeat"
790
+ ];
791
+ var vaughnEventSchema = z.enum([
792
+ "session.start",
793
+ "session.stop",
794
+ "session.crash",
795
+ "prompt.submit",
796
+ "tool.before",
797
+ "tool.after",
798
+ "tool.blocked",
799
+ "task.claimed",
800
+ "task.completed",
801
+ "agent.heartbeat"
802
+ ]);
803
+ var vaughnEventEnvelopeSchema = z.object({
804
+ version: z.literal(VAUGHN_VERSION),
805
+ event: vaughnEventSchema,
806
+ timestamp: z.string().min(1),
807
+ agentId: z.string().min(1),
808
+ toolName: z.string().min(1),
809
+ sessionId: z.string().min(1),
810
+ payload: z.record(z.string(), z.unknown())
811
+ });
812
+ function createEventEnvelope(event, agentId, toolName, sessionId, payload = {}) {
813
+ return {
814
+ version: VAUGHN_VERSION,
815
+ event,
816
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
817
+ agentId,
818
+ toolName,
819
+ sessionId,
820
+ payload
821
+ };
822
+ }
823
+
824
+ // src/vaughn/event-normalizer.ts
825
+ var VaughnEventNormalizer = class {
826
+ constructor(toolName, agentId, sessionId) {
827
+ this.toolName = toolName;
828
+ this.agentId = agentId;
829
+ this.sessionId = sessionId;
830
+ }
831
+ toolName;
832
+ agentId;
833
+ sessionId;
834
+ /**
835
+ * Map a HarnessEvent type to its canonical VAUGHN event.
836
+ * Returns null if the event has no VAUGHN mapping.
837
+ */
838
+ mapEventType(event) {
839
+ switch (event.type) {
840
+ case "harness-connected":
841
+ return "session.start";
842
+ case "harness-disconnected":
843
+ return "session.stop";
844
+ case "generation-started":
845
+ return "tool.before";
846
+ case "generation-completed":
847
+ return "tool.after";
848
+ case "error":
849
+ return "session.crash";
850
+ default:
851
+ return null;
852
+ }
853
+ }
854
+ /** Extract event-specific payload fields. */
855
+ extractPayload(event) {
856
+ switch (event.type) {
857
+ case "harness-connected":
858
+ return { harnessId: event.harnessId };
859
+ case "harness-disconnected":
860
+ return { harnessId: event.harnessId };
861
+ case "generation-started":
862
+ return { taskId: event.taskId };
863
+ case "generation-completed":
864
+ return { taskId: event.taskId, output: event.output };
865
+ case "error":
866
+ return { harnessId: event.harnessId, message: event.message };
867
+ default:
868
+ return {};
869
+ }
870
+ }
871
+ /**
872
+ * Normalize a HarnessEvent into a VaughnEventEnvelope.
873
+ *
874
+ * Returns null if:
875
+ * - The event has no VAUGHN mapping
876
+ * - The degradation strategy for this tool/event is 'absent'
877
+ */
878
+ normalize(event) {
879
+ const vaughnEvent = this.mapEventType(event);
880
+ if (!vaughnEvent) return null;
881
+ const degradation = getDegradationStrategy(this.toolName, vaughnEvent);
882
+ if (degradation === "absent") return null;
883
+ const payload = this.extractPayload(event);
884
+ if (degradation) {
885
+ payload.degraded = true;
886
+ payload.degradationStrategy = degradation;
887
+ }
888
+ const envelope = createEventEnvelope(
889
+ vaughnEvent,
890
+ this.agentId,
891
+ this.toolName,
892
+ this.sessionId,
893
+ payload
894
+ );
895
+ return { envelope, degradation };
896
+ }
897
+ /** Convenience: normalize and return just the envelope (or null). */
898
+ normalizeToEnvelope(event) {
899
+ return this.normalize(event)?.envelope ?? null;
900
+ }
901
+ };
902
+
399
903
  // src/adapters/revealui-agent-adapter.ts
400
904
  var DEFAULT_CONFIG = {
401
905
  projectRoot: process.cwd(),
@@ -407,6 +911,8 @@ var RevealUIAgentAdapter = class {
407
911
  name = "RevealUI Agent";
408
912
  config;
409
913
  eventHandlers = /* @__PURE__ */ new Set();
914
+ vaughnEventHandlers = /* @__PURE__ */ new Set();
915
+ vaughnNormalizer = null;
410
916
  constructor(config) {
411
917
  this.config = { ...DEFAULT_CONFIG, ...config };
412
918
  }
@@ -428,19 +934,35 @@ var RevealUIAgentAdapter = class {
428
934
  capabilities: this.getCapabilities()
429
935
  };
430
936
  }
937
+ /** Get the VAUGHN capability profile for this adapter. */
938
+ getVaughnCapabilities() {
939
+ return TOOL_PROFILES["revealui-agent"];
940
+ }
941
+ /** Subscribe to VAUGHN-normalized events. */
942
+ onVaughnEvent(handler) {
943
+ this.vaughnEventHandlers.add(handler);
944
+ if (!this.vaughnNormalizer) {
945
+ this.vaughnNormalizer = new VaughnEventNormalizer(
946
+ "revealui-agent",
947
+ this.id,
948
+ `session-${Date.now()}`
949
+ );
950
+ }
951
+ return () => this.vaughnEventHandlers.delete(handler);
952
+ }
953
+ /** Generate tool-native config files from canonical VAUGHN config (stub). */
954
+ async generateConfig(_config) {
955
+ return { files: /* @__PURE__ */ new Map() };
956
+ }
957
+ /** Read tool-native config into canonical form (stub). */
958
+ async readConfig() {
959
+ return {};
960
+ }
431
961
  /**
432
962
  * The RevealUI agent is available if at least one LLM provider is reachable.
433
- * Checks in order: BitNet (localhost), Ollama (localhost), Groq (API key).
963
+ * Checks in order: Ollama (localhost), Groq (API key).
434
964
  */
435
965
  async isAvailable() {
436
- if (process.env.BITNET_BASE_URL) {
437
- try {
438
- const url = `${process.env.BITNET_BASE_URL}/v1/models`;
439
- const res = await fetch(url, { signal: AbortSignal.timeout(2e3) });
440
- if (res.ok) return true;
441
- } catch {
442
- }
443
- }
444
966
  const ollamaUrl = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434";
445
967
  try {
446
968
  const res = await fetch(`${ollamaUrl}/api/tags`, {
@@ -512,7 +1034,7 @@ ${command.diff}`
512
1034
  return {
513
1035
  success: false,
514
1036
  command: command.type,
515
- message: "Config sync is not applicable \u2014 RevealUI agent uses the content layer directly"
1037
+ message: "Config sync is not applicable - RevealUI agent uses the content layer directly"
516
1038
  };
517
1039
  }
518
1040
  case "read-workboard":
@@ -520,7 +1042,7 @@ ${command.diff}`
520
1042
  return {
521
1043
  success: false,
522
1044
  command: command.type,
523
- message: "Workboard support not yet wired \u2014 use WorkboardManager directly"
1045
+ message: "Workboard support not yet wired - use WorkboardManager directly"
524
1046
  };
525
1047
  }
526
1048
  default: {
@@ -535,7 +1057,7 @@ ${command.diff}`
535
1057
  /**
536
1058
  * Run a headless prompt through the coding agent.
537
1059
  * Lazy-imports @revealui/ai to avoid hard dependency at module load time.
538
- * Types are inferred from the dynamic importsno compile-time @revealui/ai dependency.
1060
+ * Types are inferred from the dynamic imports - no compile-time @revealui/ai dependency.
539
1061
  */
540
1062
  async runHeadlessPrompt(prompt, maxTurns, timeoutMs) {
541
1063
  const aiRuntimePath = "@revealui/ai/orchestration/streaming-runtime";
@@ -567,7 +1089,7 @@ ${command.diff}`
567
1089
  llmClient = new LLMClient({
568
1090
  provider: this.config.provider,
569
1091
  model: this.config.model,
570
- baseURL: process.env.BITNET_BASE_URL ?? process.env.OLLAMA_BASE_URL,
1092
+ baseURL: process.env.INFERENCE_SNAPS_BASE_URL ?? process.env.OLLAMA_BASE_URL,
571
1093
  apiKey: process.env.GROQ_API_KEY ?? "not-needed"
572
1094
  });
573
1095
  } else {
@@ -658,6 +1180,8 @@ ${command.diff}`
658
1180
  }
659
1181
  async dispose() {
660
1182
  this.eventHandlers.clear();
1183
+ this.vaughnEventHandlers.clear();
1184
+ this.vaughnNormalizer = null;
661
1185
  }
662
1186
  emit(event) {
663
1187
  for (const handler of this.eventHandlers) {
@@ -666,17 +1190,23 @@ ${command.diff}`
666
1190
  } catch {
667
1191
  }
668
1192
  }
1193
+ if (this.vaughnNormalizer && this.vaughnEventHandlers.size > 0) {
1194
+ const envelope = this.vaughnNormalizer.normalizeToEnvelope(event);
1195
+ if (envelope) {
1196
+ for (const handler of this.vaughnEventHandlers) {
1197
+ try {
1198
+ handler(envelope);
1199
+ } catch {
1200
+ }
1201
+ }
1202
+ }
1203
+ }
669
1204
  }
670
1205
  };
671
1206
 
672
1207
  // src/detection/auto-detector.ts
673
1208
  async function autoDetectHarnesses(registry) {
674
- const candidates = [
675
- new RevealUIAgentAdapter(),
676
- new ClaudeCodeAdapter(),
677
- new CursorAdapter()
678
- // Copilot adapter excluded — stub only, no standalone CLI available
679
- ];
1209
+ const candidates = [new RevealUIAgentAdapter()];
680
1210
  const registered = [];
681
1211
  await Promise.all(
682
1212
  candidates.map(async (adapter) => {
@@ -848,7 +1378,7 @@ var HttpGateway = class {
848
1378
  }
849
1379
  return true;
850
1380
  }
851
- /** POST /api/pairsubmit pairing code, receive session token */
1381
+ /** POST /api/pair - submit pairing code, receive session token */
852
1382
  handlePair(req, res) {
853
1383
  let body = "";
854
1384
  req.on("data", (chunk) => {
@@ -876,14 +1406,14 @@ var HttpGateway = class {
876
1406
  }
877
1407
  });
878
1408
  }
879
- /** GET /api/paircheck pairing status */
1409
+ /** GET /api/pair - check pairing status */
880
1410
  handlePairStatus(res) {
881
1411
  jsonResponse(res, 200, {
882
1412
  paired: this.paired,
883
1413
  activeSessions: this.sessionTokens.size
884
1414
  });
885
1415
  }
886
- /** GET /api/statusdaemon status summary */
1416
+ /** GET /api/status - daemon status summary */
887
1417
  handleStatus(res) {
888
1418
  jsonResponse(res, 200, {
889
1419
  daemon: "revdev-harness",
@@ -893,7 +1423,7 @@ var HttpGateway = class {
893
1423
  activeSessions: this.sessionTokens.size
894
1424
  });
895
1425
  }
896
- /** POST /rpcproxy JSON-RPC to the daemon's dispatch */
1426
+ /** POST /rpc - proxy JSON-RPC to the daemon's dispatch */
897
1427
  handleRpc(req, res) {
898
1428
  let body = "";
899
1429
  req.on("data", (chunk) => {
@@ -910,7 +1440,7 @@ var HttpGateway = class {
910
1440
  });
911
1441
  });
912
1442
  }
913
- /** GET /api/stream[/:sessionId]SSE for agent output and exit events */
1443
+ /** GET /api/stream[/:sessionId] - SSE for agent output and exit events */
914
1444
  handleStream(req, res, sessionFilter) {
915
1445
  const spawner = this.config.spawner;
916
1446
  if (!spawner) {
@@ -920,8 +1450,7 @@ var HttpGateway = class {
920
1450
  res.writeHead(200, {
921
1451
  "Content-Type": "text/event-stream",
922
1452
  "Cache-Control": "no-cache",
923
- Connection: "keep-alive",
924
- "Access-Control-Allow-Origin": "*"
1453
+ Connection: "keep-alive"
925
1454
  });
926
1455
  res.write(": connected\n\n");
927
1456
  const onOutput = (evt) => {
@@ -976,9 +1505,13 @@ data: ${JSON.stringify(evt)}
976
1505
  }
977
1506
  };
978
1507
  function generatePairingCode() {
979
- const bytes = randomBytes(4);
980
- const num = bytes.readUInt32BE(0) % 1e6;
981
- return num.toString().padStart(6, "0");
1508
+ const max = Math.floor(4294967296 / 1e6) * 1e6;
1509
+ let num;
1510
+ do {
1511
+ const bytes = randomBytes(4);
1512
+ num = bytes.readUInt32BE(0);
1513
+ } while (num >= max);
1514
+ return (num % 1e6).toString().padStart(6, "0");
982
1515
  }
983
1516
  function jsonResponse(res, status, body) {
984
1517
  const json = JSON.stringify(body);
@@ -990,29 +1523,29 @@ function jsonResponse(res, status, body) {
990
1523
  }
991
1524
 
992
1525
  // src/server/inference-service.ts
993
- import { execFile as execFile3 } from "child_process";
994
- import { promisify as promisify3 } from "util";
995
- var execFileAsync3 = promisify3(execFile3);
1526
+ import { execFile as execFile2 } from "child_process";
1527
+ import { promisify as promisify2 } from "util";
1528
+ var execFileAsync2 = promisify2(execFile2);
996
1529
  var KNOWN_SNAPS = [
997
- ["nemotron-3-nano", "General (reasoning + non-reasoning) \u2014 free tier default"],
998
- ["gemma3", "General + vision \u2014 image understanding, multimodal"],
999
- ["deepseek-r1", "Reasoning \u2014 complex analysis, chain-of-thought"],
1000
- ["qwen-vl", "Vision-language \u2014 document parsing, visual Q&A"]
1001
- ];
1002
- var BITNET_MODEL_PATHS = [
1003
- `${process.env.HOME ?? "/root"}/models/bitnet`,
1004
- "/mnt/forge/models/bitnet"
1530
+ ["nemotron-3-nano", "General (reasoning + non-reasoning) - free tier default"],
1531
+ ["gemma3", "General + vision - image understanding, multimodal"],
1532
+ ["deepseek-r1", "Reasoning - complex analysis, chain-of-thought"],
1533
+ ["qwen-vl", "Vision-language - document parsing, visual Q&A"]
1005
1534
  ];
1006
1535
  async function commandExists(cmd) {
1007
1536
  try {
1008
- await execFileAsync3("which", [cmd]);
1537
+ await execFileAsync2("which", [cmd]);
1009
1538
  return true;
1010
1539
  } catch {
1011
1540
  return false;
1012
1541
  }
1013
1542
  }
1543
+ var ALLOWED_COMMANDS = /* @__PURE__ */ new Set(["ollama", "snap", "pkill", "which"]);
1014
1544
  async function run(cmd, args) {
1015
- return execFileAsync3(cmd, args, { timeout: 3e4 });
1545
+ if (!ALLOWED_COMMANDS.has(cmd)) {
1546
+ throw new Error(`Command not allowed: ${cmd}`);
1547
+ }
1548
+ return execFileAsync2(cmd, args, { timeout: 3e4 });
1016
1549
  }
1017
1550
  var InferenceService = class {
1018
1551
  // ── Ollama ──────────────────────────────────────────────────────
@@ -1052,8 +1585,11 @@ var InferenceService = class {
1052
1585
  return models;
1053
1586
  }
1054
1587
  async ollamaPull(modelName) {
1588
+ if (!/^[\w./:@-]+$/.test(modelName)) {
1589
+ return { success: false, message: `Invalid model name: ${modelName}` };
1590
+ }
1055
1591
  try {
1056
- const { stdout, stderr } = await execFileAsync3("ollama", ["pull", modelName], {
1592
+ const { stdout, stderr } = await execFileAsync2("ollama", ["pull", modelName], {
1057
1593
  timeout: 6e5
1058
1594
  // 10 min for large models
1059
1595
  });
@@ -1063,6 +1599,9 @@ var InferenceService = class {
1063
1599
  }
1064
1600
  }
1065
1601
  async ollamaDelete(modelName) {
1602
+ if (!/^[\w./:@-]+$/.test(modelName)) {
1603
+ throw new Error(`Invalid model name: ${modelName}`);
1604
+ }
1066
1605
  await run("ollama", ["rm", modelName]);
1067
1606
  }
1068
1607
  async ollamaStart() {
@@ -1079,21 +1618,6 @@ var InferenceService = class {
1079
1618
  } catch {
1080
1619
  }
1081
1620
  }
1082
- // ── BitNet ──────────────────────────────────────────────────────
1083
- async bitnetStatus() {
1084
- const installed = await commandExists("bitnet");
1085
- let modelPath = null;
1086
- if (installed) {
1087
- const { existsSync: existsSync4 } = await import("fs");
1088
- for (const p of BITNET_MODEL_PATHS) {
1089
- if (existsSync4(p)) {
1090
- modelPath = p;
1091
- break;
1092
- }
1093
- }
1094
- }
1095
- return { installed, modelPath };
1096
- }
1097
1621
  // ── Inference Snaps ─────────────────────────────────────────────
1098
1622
  async snapList() {
1099
1623
  const results = [];
@@ -1109,6 +1633,8 @@ var InferenceService = class {
1109
1633
  return results;
1110
1634
  }
1111
1635
  async snapStatus(snapName) {
1636
+ const known = KNOWN_SNAPS.some(([name]) => name === snapName);
1637
+ if (!known) throw new Error(`Unknown inference snap: ${snapName}`);
1112
1638
  let installed = false;
1113
1639
  let version = null;
1114
1640
  try {
@@ -1134,7 +1660,7 @@ var InferenceService = class {
1134
1660
  const known = KNOWN_SNAPS.some(([name]) => name === snapName);
1135
1661
  if (!known) throw new Error(`Unknown inference snap: ${snapName}`);
1136
1662
  try {
1137
- const { stdout, stderr } = await execFileAsync3("sudo", ["snap", "install", snapName], {
1663
+ const { stdout, stderr } = await execFileAsync2("sudo", ["snap", "install", snapName], {
1138
1664
  timeout: 3e5
1139
1665
  // 5 min for large snaps
1140
1666
  });
@@ -1144,7 +1670,7 @@ var InferenceService = class {
1144
1670
  }
1145
1671
  }
1146
1672
  async snapRemove(snapName) {
1147
- await execFileAsync3("sudo", ["snap", "remove", snapName], { timeout: 6e4 });
1673
+ await execFileAsync2("sudo", ["snap", "remove", snapName], { timeout: 6e4 });
1148
1674
  }
1149
1675
  };
1150
1676
 
@@ -1153,14 +1679,14 @@ import { existsSync as existsSync3, unlinkSync } from "fs";
1153
1679
  import { createServer as createServer2 } from "net";
1154
1680
 
1155
1681
  // src/detection/process-detector.ts
1156
- import { execFile as execFile4 } from "child_process";
1682
+ import { execFile as execFile3 } from "child_process";
1157
1683
  import { readdir } from "fs/promises";
1158
1684
  import { join as join3 } from "path";
1159
- import { promisify as promisify4 } from "util";
1160
- var execFileAsync4 = promisify4(execFile4);
1685
+ import { promisify as promisify3 } from "util";
1686
+ var execFileAsync3 = promisify3(execFile3);
1161
1687
  async function findProcesses(pattern) {
1162
1688
  try {
1163
- const { stdout } = await execFileAsync4("pgrep", ["-a", pattern], { timeout: 3e3 });
1689
+ const { stdout } = await execFileAsync3("pgrep", ["-a", pattern], { timeout: 3e3 });
1164
1690
  return stdout.trim().split("\n").filter(Boolean).map((line) => {
1165
1691
  const spaceIdx = line.indexOf(" ");
1166
1692
  const pid = parseInt(line.slice(0, spaceIdx), 10);
@@ -1209,12 +1735,198 @@ async function findClaudeCodeSockets() {
1209
1735
  return sockets;
1210
1736
  }
1211
1737
 
1738
+ // src/vaughn/config-normalizer.ts
1739
+ var MCP_SERVER_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
1740
+ var FORBIDDEN_MCP_SERVER_NAMES = /* @__PURE__ */ new Set([
1741
+ "__proto__",
1742
+ "constructor",
1743
+ "prototype",
1744
+ "hasOwnProperty",
1745
+ "isPrototypeOf",
1746
+ "propertyIsEnumerable",
1747
+ "toLocaleString",
1748
+ "toString",
1749
+ "valueOf"
1750
+ ]);
1751
+ function isSafeMcpServerName(name) {
1752
+ return typeof name === "string" && MCP_SERVER_NAME_PATTERN.test(name) && !FORBIDDEN_MCP_SERVER_NAMES.has(name);
1753
+ }
1754
+ function vaughnConfigToClaudeSettings(config) {
1755
+ const settings = {};
1756
+ if (config.permissions.autoApprove.length > 0 || config.permissions.deny.length > 0) {
1757
+ settings.permissions = {};
1758
+ if (config.permissions.autoApprove.length > 0) {
1759
+ settings.permissions.allow = config.permissions.autoApprove;
1760
+ }
1761
+ if (config.permissions.deny.length > 0) {
1762
+ settings.permissions.deny = config.permissions.deny;
1763
+ }
1764
+ }
1765
+ if (Object.keys(config.environment.variables).length > 0) {
1766
+ settings.env = { ...config.environment.variables };
1767
+ }
1768
+ if (config.environment.mcpServers.length > 0) {
1769
+ const servers = {};
1770
+ for (const server of config.environment.mcpServers) {
1771
+ if (!isSafeMcpServerName(server.name)) continue;
1772
+ servers[server.name] = {
1773
+ command: server.command,
1774
+ ...server.args && { args: server.args },
1775
+ ...server.env && { env: server.env }
1776
+ };
1777
+ }
1778
+ if (Object.keys(servers).length > 0) {
1779
+ settings.mcpServers = servers;
1780
+ }
1781
+ }
1782
+ return settings;
1783
+ }
1784
+ function claudeSettingsToVaughnConfig(settings) {
1785
+ const config = {};
1786
+ if (settings.permissions) {
1787
+ config.permissions = {
1788
+ autoApprove: settings.permissions.allow ?? [],
1789
+ deny: settings.permissions.deny ?? []
1790
+ };
1791
+ }
1792
+ const mcpServers = settings.mcpServers ? Object.entries(settings.mcpServers).filter(([name]) => isSafeMcpServerName(name)).map(([name, server]) => ({
1793
+ name,
1794
+ command: server.command,
1795
+ ...server.args && { args: server.args },
1796
+ ...server.env && { env: server.env }
1797
+ })) : [];
1798
+ config.environment = {
1799
+ variables: settings.env ?? {},
1800
+ mcpServers
1801
+ };
1802
+ return config;
1803
+ }
1804
+ function vaughnConfigToCursorrules(config) {
1805
+ const lines = [];
1806
+ lines.push("# Project Rules");
1807
+ lines.push("");
1808
+ lines.push("## Identity");
1809
+ lines.push(`- Name: ${config.identity.name}`);
1810
+ if (config.identity.role) {
1811
+ lines.push(`- Role: ${config.identity.role}`);
1812
+ }
1813
+ lines.push("");
1814
+ if (config.rules.length > 0) {
1815
+ lines.push("## Rules");
1816
+ lines.push("");
1817
+ for (const rule of config.rules) {
1818
+ lines.push(`### ${rule.id}`);
1819
+ lines.push("");
1820
+ lines.push(rule.description);
1821
+ lines.push("");
1822
+ lines.push(renderRuleContent(rule));
1823
+ lines.push("");
1824
+ }
1825
+ }
1826
+ if (config.skills.length > 0) {
1827
+ lines.push("## Skills");
1828
+ lines.push("");
1829
+ for (const skill of config.skills) {
1830
+ lines.push(`### ${skill.name}`);
1831
+ lines.push("");
1832
+ lines.push(skill.description);
1833
+ lines.push("");
1834
+ lines.push(skill.instructions);
1835
+ lines.push("");
1836
+ }
1837
+ }
1838
+ return lines.join("\n");
1839
+ }
1840
+ function vaughnConfigToAgentsMd(config) {
1841
+ const lines = [];
1842
+ lines.push("# AGENTS.md");
1843
+ lines.push("");
1844
+ lines.push(`> Generated by VAUGHN protocol v${VAUGHN_VERSION}`);
1845
+ lines.push("");
1846
+ lines.push("## Identity");
1847
+ lines.push("");
1848
+ lines.push(`- Name: ${config.identity.name}`);
1849
+ lines.push(`- Email: ${config.identity.email}`);
1850
+ if (config.identity.role) {
1851
+ lines.push(`- Role: ${config.identity.role}`);
1852
+ }
1853
+ lines.push("");
1854
+ if (config.permissions.deny.length > 0) {
1855
+ lines.push("## Denied Operations");
1856
+ lines.push("");
1857
+ for (const d of config.permissions.deny) {
1858
+ lines.push(`- ${d}`);
1859
+ }
1860
+ lines.push("");
1861
+ }
1862
+ if (config.rules.length > 0) {
1863
+ lines.push("## Rules");
1864
+ lines.push("");
1865
+ for (const rule of config.rules) {
1866
+ lines.push(`### ${rule.id}`);
1867
+ lines.push("");
1868
+ lines.push(rule.description);
1869
+ lines.push("");
1870
+ if (rule.appliesTo.length > 0) {
1871
+ lines.push(`Applies to: ${rule.appliesTo.join(", ")}`);
1872
+ lines.push("");
1873
+ }
1874
+ lines.push(renderRuleContent(rule));
1875
+ lines.push("");
1876
+ }
1877
+ }
1878
+ if (config.commands.length > 0) {
1879
+ lines.push("## Commands");
1880
+ lines.push("");
1881
+ for (const cmd of config.commands) {
1882
+ lines.push(`### /${cmd.id}`);
1883
+ lines.push("");
1884
+ lines.push(cmd.description);
1885
+ lines.push("");
1886
+ if (cmd.steps.length > 0) {
1887
+ lines.push("Steps:");
1888
+ for (let i = 0; i < cmd.steps.length; i++) {
1889
+ lines.push(`${i + 1}. ${cmd.steps[i]}`);
1890
+ }
1891
+ lines.push("");
1892
+ }
1893
+ }
1894
+ }
1895
+ return lines.join("\n");
1896
+ }
1897
+ function generateAllConfigs(config) {
1898
+ const files = /* @__PURE__ */ new Map();
1899
+ files.set(".claude/settings.json", JSON.stringify(vaughnConfigToClaudeSettings(config), null, 2));
1900
+ files.set(".cursorrules", vaughnConfigToCursorrules(config));
1901
+ files.set("AGENTS.md", vaughnConfigToAgentsMd(config));
1902
+ for (const rule of config.rules) {
1903
+ const frontmatter = [
1904
+ "---",
1905
+ `description: ${rule.description}`,
1906
+ ...rule.appliesTo.length > 0 ? [`globs: ${rule.appliesTo.join(", ")}`] : [],
1907
+ "---"
1908
+ ].join("\n");
1909
+ files.set(`.claude/rules/${rule.id}.md`, `${frontmatter}
1910
+
1911
+ ${renderRuleContent(rule)}
1912
+ `);
1913
+ }
1914
+ return { files };
1915
+ }
1916
+ function renderRuleContent(rule) {
1917
+ let content = rule.content;
1918
+ for (const [key, value] of Object.entries(rule.variables)) {
1919
+ content = content.split(`{{${key}}}`).join(value);
1920
+ }
1921
+ return content;
1922
+ }
1923
+
1212
1924
  // src/server/rpc-server.ts
1213
1925
  var ERR_PARSE = -32700;
1214
1926
  var ERR_INVALID_PARAMS = -32602;
1215
1927
  var ERR_METHOD_NOT_FOUND = -32601;
1216
1928
  var ERR_INTERNAL = -32603;
1217
- var RpcServer = class {
1929
+ var RpcServer = class _RpcServer {
1218
1930
  constructor(registry, socketPath, store) {
1219
1931
  this.registry = registry;
1220
1932
  this.socketPath = socketPath;
@@ -1234,10 +1946,18 @@ var RpcServer = class {
1234
1946
  });
1235
1947
  });
1236
1948
  }
1949
+ registry;
1950
+ socketPath;
1951
+ store;
1237
1952
  server = createServer2();
1238
1953
  healthCheckFn = null;
1239
1954
  spawner = null;
1240
1955
  inference = null;
1956
+ mergePipeline = null;
1957
+ ciFeedback = null;
1958
+ vaughnDispatchFn = null;
1959
+ vaughnEventQueue = [];
1960
+ static MAX_VAUGHN_EVENTS = 100;
1241
1961
  handleLine(line, reply) {
1242
1962
  let req;
1243
1963
  try {
@@ -1553,6 +2273,87 @@ var RpcServer = class {
1553
2273
  return { jsonrpc: "2.0", id, result: events };
1554
2274
  }
1555
2275
  // -----------------------------------------------------------------------
2276
+ // Worktrees (PGlite-backed)
2277
+ // -----------------------------------------------------------------------
2278
+ case "worktree.create": {
2279
+ if (!this.store) return this.noStore(id);
2280
+ const agentId = p.agentId;
2281
+ const branch = p.branch;
2282
+ const worktreePath = p.worktreePath;
2283
+ if (!(agentId && branch && worktreePath))
2284
+ return this.missingParam(id, "agentId, branch, worktreePath");
2285
+ const wt = await this.store.registerWorktree({
2286
+ agentId,
2287
+ branch,
2288
+ worktreePath,
2289
+ baseBranch: p.baseBranch
2290
+ });
2291
+ return { jsonrpc: "2.0", id, result: wt };
2292
+ }
2293
+ case "worktree.get": {
2294
+ if (!this.store) return this.noStore(id);
2295
+ const agentId = p.agentId;
2296
+ if (!agentId) return this.missingParam(id, "agentId");
2297
+ const wt = await this.store.getWorktree(agentId);
2298
+ return { jsonrpc: "2.0", id, result: wt };
2299
+ }
2300
+ case "worktree.list": {
2301
+ if (!this.store) return this.noStore(id);
2302
+ const worktrees = await this.store.getActiveWorktrees();
2303
+ return { jsonrpc: "2.0", id, result: worktrees };
2304
+ }
2305
+ case "worktree.status": {
2306
+ if (!this.store) return this.noStore(id);
2307
+ const agentId = p.agentId;
2308
+ const status = p.status;
2309
+ if (!(agentId && status)) return this.missingParam(id, "agentId, status");
2310
+ const ok = await this.store.updateWorktreeStatus(agentId, status);
2311
+ return { jsonrpc: "2.0", id, result: { success: ok } };
2312
+ }
2313
+ case "worktree.remove": {
2314
+ if (!this.store) return this.noStore(id);
2315
+ const agentId = p.agentId;
2316
+ if (!agentId) return this.missingParam(id, "agentId");
2317
+ const ok = await this.store.removeWorktree(agentId);
2318
+ return { jsonrpc: "2.0", id, result: { success: ok } };
2319
+ }
2320
+ // -----------------------------------------------------------------------
2321
+ // Agent Memory (PGlite-backed)
2322
+ // -----------------------------------------------------------------------
2323
+ case "memory.store": {
2324
+ if (!this.store) return this.noStore(id);
2325
+ const agentId = p.agentId;
2326
+ const memoryType = p.memoryType;
2327
+ const content = p.content;
2328
+ if (!(agentId && memoryType && content))
2329
+ return this.missingParam(id, "agentId, memoryType, content");
2330
+ const entry = await this.store.storeMemory({
2331
+ agentId,
2332
+ memoryType,
2333
+ content,
2334
+ metadata: p.metadata
2335
+ });
2336
+ return { jsonrpc: "2.0", id, result: entry };
2337
+ }
2338
+ case "memory.recall": {
2339
+ if (!this.store) return this.noStore(id);
2340
+ const entries = await this.store.recallMemory({
2341
+ agentId: p.agentId,
2342
+ memoryType: p.memoryType,
2343
+ keyword: p.keyword,
2344
+ limit: p.limit
2345
+ });
2346
+ return { jsonrpc: "2.0", id, result: entries };
2347
+ }
2348
+ case "memory.summarize": {
2349
+ if (!this.store) return this.noStore(id);
2350
+ const agentId = p.agentId;
2351
+ if (!agentId) return this.missingParam(id, "agentId");
2352
+ const perType = p.perType ?? 5;
2353
+ const entries = await this.store.summarizeMemory(agentId, perType);
2354
+ return { jsonrpc: "2.0", id, result: entries };
2355
+ }
2356
+ // -----------------------------------------------------------------------
1556
2357
  // Agent spawner (process management)
1557
2358
  // -----------------------------------------------------------------------
1558
2359
  case "agent.spawn": {
@@ -1564,12 +2365,7 @@ var RpcServer = class {
1564
2365
  if (!(name && backend && model && prompt)) {
1565
2366
  return this.missingParam(id, "name, backend, model, prompt");
1566
2367
  }
1567
- const sessionId = this.spawner.spawn(
1568
- name,
1569
- backend,
1570
- model,
1571
- prompt
1572
- );
2368
+ const sessionId = this.spawner.spawn(name, backend, model, prompt);
1573
2369
  return { jsonrpc: "2.0", id, result: { sessionId } };
1574
2370
  }
1575
2371
  case "agent.stop": {
@@ -1590,8 +2386,16 @@ var RpcServer = class {
1590
2386
  this.spawner.remove(sessionId);
1591
2387
  return { jsonrpc: "2.0", id, result: { ok: true } };
1592
2388
  }
2389
+ case "agent.input":
2390
+ case "agent.resize": {
2391
+ return {
2392
+ jsonrpc: "2.0",
2393
+ id,
2394
+ error: { code: -32601, message: "PTY interaction not supported for current backends" }
2395
+ };
2396
+ }
1593
2397
  // -----------------------------------------------------------------------
1594
- // Inference management (Ollama, BitNet, Snaps)
2398
+ // Inference management (Ollama, Snaps)
1595
2399
  // -----------------------------------------------------------------------
1596
2400
  case "inference.ollama.status": {
1597
2401
  if (!this.inference) return this.noService(id, "inference");
@@ -1624,10 +2428,6 @@ var RpcServer = class {
1624
2428
  await this.inference.ollamaStop();
1625
2429
  return { jsonrpc: "2.0", id, result: { ok: true } };
1626
2430
  }
1627
- case "inference.bitnet.status": {
1628
- if (!this.inference) return this.noService(id, "inference");
1629
- return { jsonrpc: "2.0", id, result: await this.inference.bitnetStatus() };
1630
- }
1631
2431
  case "inference.snap.list": {
1632
2432
  if (!this.inference) return this.noService(id, "inference");
1633
2433
  return { jsonrpc: "2.0", id, result: await this.inference.snapList() };
@@ -1651,6 +2451,90 @@ var RpcServer = class {
1651
2451
  await this.inference.snapRemove(snapName);
1652
2452
  return { jsonrpc: "2.0", id, result: { ok: true } };
1653
2453
  }
2454
+ // -----------------------------------------------------------------------
2455
+ // Merge Pipeline
2456
+ // -----------------------------------------------------------------------
2457
+ case "merge.request": {
2458
+ if (!this.mergePipeline) return this.noService(id, "merge-pipeline");
2459
+ const agentId = p.agentId;
2460
+ const sourceBranch = p.sourceBranch;
2461
+ if (!(agentId && sourceBranch)) return this.missingParam(id, "agentId, sourceBranch");
2462
+ const result = await this.mergePipeline.requestMerge({
2463
+ agentId,
2464
+ sourceBranch,
2465
+ taskId: p.taskId,
2466
+ baseBranch: p.baseBranch
2467
+ });
2468
+ return { jsonrpc: "2.0", id, result };
2469
+ }
2470
+ case "merge.status": {
2471
+ if (!this.mergePipeline) return this.noService(id, "merge-pipeline");
2472
+ const mergeId = p.mergeId;
2473
+ if (!mergeId) return this.missingParam(id, "mergeId");
2474
+ const result = await this.mergePipeline.getStatus(mergeId);
2475
+ return { jsonrpc: "2.0", id, result };
2476
+ }
2477
+ case "merge.resolve": {
2478
+ if (!this.mergePipeline) return this.noService(id, "merge-pipeline");
2479
+ const mergeId = p.mergeId;
2480
+ if (!mergeId) return this.missingParam(id, "mergeId");
2481
+ const result = await this.mergePipeline.resolve(mergeId);
2482
+ return { jsonrpc: "2.0", id, result };
2483
+ }
2484
+ case "merge.list": {
2485
+ if (!this.mergePipeline) return this.noService(id, "merge-pipeline");
2486
+ const result = await this.mergePipeline.listActive();
2487
+ return { jsonrpc: "2.0", id, result };
2488
+ }
2489
+ // -----------------------------------------------------------------------
2490
+ // CI Feedback
2491
+ // -----------------------------------------------------------------------
2492
+ case "ci.report": {
2493
+ if (!this.ciFeedback) return this.noService(id, "ci-feedback");
2494
+ const result = await this.ciFeedback.report({
2495
+ prNumber: p.prNumber,
2496
+ branch: p.branch,
2497
+ success: p.success,
2498
+ output: p.output,
2499
+ runUrl: p.runUrl,
2500
+ failedJob: p.failedJob
2501
+ });
2502
+ return { jsonrpc: "2.0", id, result };
2503
+ }
2504
+ // -----------------------------------------------------------------------
2505
+ // VAUGHN Protocol
2506
+ // -----------------------------------------------------------------------
2507
+ case "vaughn.capabilities": {
2508
+ const result = [];
2509
+ for (const adapterId of this.registry.listAll()) {
2510
+ const caps = TOOL_PROFILES[adapterId];
2511
+ if (caps) result.push({ id: adapterId, capabilities: caps });
2512
+ }
2513
+ return { jsonrpc: "2.0", id, result };
2514
+ }
2515
+ case "vaughn.dispatch": {
2516
+ if (!this.vaughnDispatchFn) return this.noService(id, "vaughn-dispatch");
2517
+ const description = p.description;
2518
+ if (!description) return this.missingParam(id, "description");
2519
+ const requirements = p.requirements ?? {};
2520
+ const adapterId = this.vaughnDispatchFn(requirements, description);
2521
+ return { jsonrpc: "2.0", id, result: { adapterId } };
2522
+ }
2523
+ case "vaughn.events": {
2524
+ const limit = p.limit ?? 50;
2525
+ const events = this.vaughnEventQueue.slice(-limit);
2526
+ return { jsonrpc: "2.0", id, result: events };
2527
+ }
2528
+ case "vaughn.config.sync": {
2529
+ const config = p.config;
2530
+ if (!config) return this.missingParam(id, "config");
2531
+ const generated = generateAllConfigs(config);
2532
+ const files = {};
2533
+ for (const [path, content] of generated.files) {
2534
+ files[path] = content;
2535
+ }
2536
+ return { jsonrpc: "2.0", id, result: { files } };
2537
+ }
1654
2538
  default:
1655
2539
  return {
1656
2540
  jsonrpc: "2.0",
@@ -1702,6 +2586,25 @@ var RpcServer = class {
1702
2586
  setInference(inference) {
1703
2587
  this.inference = inference;
1704
2588
  }
2589
+ /** Attach the merge pipeline (called by coordinator after construction). */
2590
+ setMergePipeline(pipeline) {
2591
+ this.mergePipeline = pipeline;
2592
+ }
2593
+ /** Attach the CI feedback handler (called by coordinator after construction). */
2594
+ setCIFeedback(feedback) {
2595
+ this.ciFeedback = feedback;
2596
+ }
2597
+ /** Attach the VAUGHN dispatch function (called by coordinator after construction). */
2598
+ setVaughnDispatch(fn) {
2599
+ this.vaughnDispatchFn = fn;
2600
+ }
2601
+ /** Push a VAUGHN event into the recent event queue (capped at 100). */
2602
+ pushVaughnEvent(event) {
2603
+ this.vaughnEventQueue.push(event);
2604
+ if (this.vaughnEventQueue.length > _RpcServer.MAX_VAUGHN_EVENTS) {
2605
+ this.vaughnEventQueue.shift();
2606
+ }
2607
+ }
1705
2608
  /** Get the spawner service (used by HTTP gateway for SSE). */
1706
2609
  getSpawner() {
1707
2610
  return this.spawner;
@@ -1771,29 +2674,26 @@ var SpawnerService = class extends EventEmitter {
1771
2674
  });
1772
2675
  break;
1773
2676
  }
1774
- case "BitNet": {
1775
- child = nodeSpawn("bitnet", ["run", "--model", model, "--prompt", prompt], {
1776
- stdio: ["ignore", "pipe", "pipe"]
1777
- });
1778
- break;
1779
- }
1780
2677
  }
1781
- const proc = { name, model, backend, prompt, child, status: "running" };
2678
+ const proc = {
2679
+ name,
2680
+ model,
2681
+ backend,
2682
+ prompt,
2683
+ child,
2684
+ status: "running"
2685
+ };
1782
2686
  this.sessions.set(sessionId, proc);
1783
2687
  child.stdout?.on("data", (chunk) => {
1784
- const lines = chunk.toString().split("\n");
1785
- for (const line of lines) {
1786
- if (line.length > 0) {
1787
- this.emit("output", { sessionId, stream: "stdout", line });
1788
- }
2688
+ const data = chunk.toString();
2689
+ if (data.length > 0) {
2690
+ this.emit("output", { sessionId, stream: "stdout", data });
1789
2691
  }
1790
2692
  });
1791
2693
  child.stderr?.on("data", (chunk) => {
1792
- const lines = chunk.toString().split("\n");
1793
- for (const line of lines) {
1794
- if (line.length > 0) {
1795
- this.emit("output", { sessionId, stream: "stderr", line });
1796
- }
2694
+ const data = chunk.toString();
2695
+ if (data.length > 0) {
2696
+ this.emit("output", { sessionId, stream: "stderr", data });
1797
2697
  }
1798
2698
  });
1799
2699
  child.on("close", (code) => {
@@ -1834,7 +2734,8 @@ var SpawnerService = class extends EventEmitter {
1834
2734
  remove(sessionId) {
1835
2735
  const proc = this.sessions.get(sessionId);
1836
2736
  if (!proc) throw new Error(`No agent session: ${sessionId}`);
1837
- if (proc.status === "running") throw new Error("Cannot remove a running agent \u2014 stop it first");
2737
+ if (proc.status === "running")
2738
+ throw new Error("Cannot remove a running agent - stop it first");
1838
2739
  this.sessions.delete(sessionId);
1839
2740
  }
1840
2741
  /** Kill all running agents (called on daemon shutdown). */
@@ -1855,12 +2756,16 @@ var HarnessCoordinator = class {
1855
2756
  const workboardPath = join4(options.projectRoot, ".claude", "workboard.md");
1856
2757
  this.workboard = new WorkboardManager(workboardPath);
1857
2758
  }
2759
+ options;
1858
2760
  registry = new HarnessRegistry();
2761
+ vaughnCapabilities = /* @__PURE__ */ new Map();
1859
2762
  rpcServer = null;
1860
2763
  httpGateway = null;
1861
2764
  store = null;
1862
2765
  spawner = null;
1863
2766
  inference = null;
2767
+ mergePipeline = null;
2768
+ ciFeedback = null;
1864
2769
  sessionId = null;
1865
2770
  workboard;
1866
2771
  async start() {
@@ -1889,10 +2794,17 @@ var HarnessCoordinator = class {
1889
2794
  const socketPath = this.options.socketPath ?? join4(process.env.HOME ?? "/tmp", ".local", "share", "revealui", "harness.sock");
1890
2795
  this.rpcServer = new RpcServer(this.registry, socketPath, this.store);
1891
2796
  this.rpcServer.setHealthCheck(() => this.healthCheck());
2797
+ this.rpcServer.setVaughnDispatch((req, desc) => this.dispatchTask(req, desc));
1892
2798
  this.spawner = new SpawnerService();
1893
2799
  this.inference = new InferenceService();
1894
2800
  this.rpcServer.setSpawner(this.spawner);
1895
2801
  this.rpcServer.setInference(this.inference);
2802
+ this.mergePipeline = new MergePipeline(this.store, {
2803
+ repoRoot: this.options.projectRoot
2804
+ });
2805
+ this.ciFeedback = new CIFeedback(this.store);
2806
+ this.rpcServer.setMergePipeline(this.mergePipeline);
2807
+ this.rpcServer.setCIFeedback(this.ciFeedback);
1896
2808
  await this.rpcServer.start();
1897
2809
  if (this.options.httpPort) {
1898
2810
  this.httpGateway = new HttpGateway({
@@ -1922,6 +2834,8 @@ var HarnessCoordinator = class {
1922
2834
  this.spawner = null;
1923
2835
  }
1924
2836
  this.inference = null;
2837
+ this.mergePipeline = null;
2838
+ this.ciFeedback = null;
1925
2839
  if (this.rpcServer) {
1926
2840
  await this.rpcServer.stop();
1927
2841
  this.rpcServer = null;
@@ -1948,6 +2862,57 @@ var HarnessCoordinator = class {
1948
2862
  registerAdapter(adapter) {
1949
2863
  this.registry.register(adapter);
1950
2864
  }
2865
+ /** Register explicit VAUGHN capabilities for an adapter. */
2866
+ registerVaughnCapabilities(adapterId, capabilities) {
2867
+ this.vaughnCapabilities.set(adapterId, capabilities);
2868
+ }
2869
+ /**
2870
+ * Dispatch a task to the best-matching adapter based on VAUGHN capability requirements.
2871
+ *
2872
+ * Returns the selected adapter ID, or null if no adapter matches.
2873
+ * Prefers adapters with hooks.canBlock for safety-critical dispatch.
2874
+ */
2875
+ dispatchTask(requirements, _description) {
2876
+ const candidates = [];
2877
+ for (const id of this.registry.listAll()) {
2878
+ const caps = this.vaughnCapabilities.get(id) ?? TOOL_PROFILES[id];
2879
+ if (!caps) continue;
2880
+ if (this.matchesRequirements(caps, requirements)) {
2881
+ candidates.push({ id, caps });
2882
+ }
2883
+ }
2884
+ if (candidates.length === 0) return null;
2885
+ const blocking = candidates.filter((c) => c.caps.hooks.canBlock);
2886
+ const best = blocking.length > 0 ? blocking[0] : candidates[0];
2887
+ return best?.id ?? null;
2888
+ }
2889
+ /** Check whether capabilities satisfy requirements. */
2890
+ matchesRequirements(caps, req) {
2891
+ if (req.dispatch) {
2892
+ if (req.dispatch.generateCode && !caps.dispatch.generateCode) return false;
2893
+ if (req.dispatch.analyzeCode && !caps.dispatch.analyzeCode) return false;
2894
+ if (req.dispatch.applyEdit && !caps.dispatch.applyEdit) return false;
2895
+ if (req.dispatch.executeCommand && !caps.dispatch.executeCommand) return false;
2896
+ }
2897
+ if (req.headless && !caps.headless) return false;
2898
+ if (req.resumable && !caps.resumable) return false;
2899
+ if (req.forkable && !caps.forkable) return false;
2900
+ if (req.backgroundable && !caps.backgroundable) return false;
2901
+ if (req.readWorkboard && !caps.readWorkboard) return false;
2902
+ if (req.writeWorkboard && !caps.writeWorkboard) return false;
2903
+ if (req.claimTasks && !caps.claimTasks) return false;
2904
+ if (req.reportConflicts && !caps.reportConflicts) return false;
2905
+ if (req.supportsWorktrees && !caps.supportsWorktrees) return false;
2906
+ if (req.supportsSkills && !caps.supportsSkills) return false;
2907
+ if (req.supportsMcp && !caps.supportsMcp) return false;
2908
+ if (req.hooks) {
2909
+ if (req.hooks.supported && !caps.hooks.supported) return false;
2910
+ if (req.hooks.canBlock && !caps.hooks.canBlock) return false;
2911
+ }
2912
+ if (req.sandbox?.supported && !caps.sandbox.supported) return false;
2913
+ if (req.memory?.supported && !caps.memory.supported) return false;
2914
+ return true;
2915
+ }
1951
2916
  /** The HTTP gateway (available after start() if httpPort was set). */
1952
2917
  getHttpGateway() {
1953
2918
  return this.httpGateway;
@@ -2017,8 +2982,15 @@ async function checkHarnessesLicense() {
2017
2982
  }
2018
2983
 
2019
2984
  export {
2020
- ClaudeCodeAdapter,
2021
- CursorAdapter,
2985
+ createDefaultCapabilities,
2986
+ TOOL_PROFILES,
2987
+ getDegradationStrategy,
2988
+ VAUGHN_VERSION,
2989
+ VAUGHN_EVENTS,
2990
+ vaughnEventSchema,
2991
+ vaughnEventEnvelopeSchema,
2992
+ createEventEnvelope,
2993
+ VaughnEventNormalizer,
2022
2994
  autoDetectHarnesses,
2023
2995
  HarnessRegistry,
2024
2996
  getLocalConfigPath,
@@ -2033,8 +3005,12 @@ export {
2033
3005
  findHarnessProcesses,
2034
3006
  findAllHarnessProcesses,
2035
3007
  findClaudeCodeSockets,
3008
+ vaughnConfigToClaudeSettings,
3009
+ claudeSettingsToVaughnConfig,
3010
+ vaughnConfigToCursorrules,
3011
+ vaughnConfigToAgentsMd,
3012
+ generateAllConfigs,
2036
3013
  RpcServer,
2037
3014
  HarnessCoordinator,
2038
3015
  checkHarnessesLicense
2039
3016
  };
2040
- //# sourceMappingURL=chunk-6E2BKO6U.js.map