@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.
- package/README.md +6 -6
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +0 -1
- package/dist/{chunk-DGQ5OB6L.js → chunk-ANX4L2PF.js} +273 -2
- package/dist/{chunk-4F4ANKIZ.js → chunk-Y4FFO3TO.js} +27 -7
- package/dist/{chunk-6E2BKO6U.js → chunk-YYAYTCRM.js} +1330 -354
- package/dist/{chunk-PROC6EJC.js → chunk-ZNIQELKZ.js} +185 -341
- package/dist/cli.js +5 -6
- package/dist/content/index.d.ts +1 -1
- package/dist/content/index.js +2 -3
- package/dist/index-w5ashbfb.d.ts +266 -0
- package/dist/index.d.ts +539 -85
- package/dist/index.js +34 -11
- package/dist/storage/index.d.ts +1 -170
- package/dist/storage/index.js +2 -3
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +0 -1
- package/dist/workboard/index.d.ts +26 -14
- package/dist/workboard/index.js +2 -3
- package/package.json +23 -6
- package/dist/chunk-4F4ANKIZ.js.map +0 -1
- package/dist/chunk-6E2BKO6U.js.map +0 -1
- package/dist/chunk-DGQ5OB6L.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-PROC6EJC.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/content/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/workboard/index.js.map +0 -1
|
@@ -1,274 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DaemonStore
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ANX4L2PF.js";
|
|
4
4
|
import {
|
|
5
5
|
WorkboardManager,
|
|
6
6
|
deriveSessionId,
|
|
7
7
|
detectSessionType
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-Y4FFO3TO.js";
|
|
9
9
|
import {
|
|
10
10
|
__require
|
|
11
|
-
} from "./chunk-
|
|
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:
|
|
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
|
|
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
|
|
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 imports
|
|
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.
|
|
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/pair
|
|
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/pair
|
|
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/status
|
|
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 /rpc
|
|
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]
|
|
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
|
|
980
|
-
|
|
981
|
-
|
|
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
|
|
994
|
-
import { promisify as
|
|
995
|
-
var
|
|
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)
|
|
998
|
-
["gemma3", "General + vision
|
|
999
|
-
["deepseek-r1", "Reasoning
|
|
1000
|
-
["qwen-vl", "Vision-language
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1160
|
-
var
|
|
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
|
|
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,
|
|
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 = {
|
|
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
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
|
1793
|
-
|
|
1794
|
-
|
|
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")
|
|
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
|
-
|
|
2021
|
-
|
|
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
|