@revealui/harnesses 0.1.1 → 0.1.3

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.
@@ -9,9 +9,8 @@ TERMS AND CONDITIONS
9
9
 
10
10
  "Software" means the RevealUI source code, documentation, and associated
11
11
  files contained in directories and packages designated as commercial,
12
- including but not limited to: packages/ai, packages/mcp, packages/editors,
13
- packages/services, packages/harnesses, and any directory named "ee" within
14
- the repository.
12
+ including but not limited to: packages/ai, packages/harnesses, and any
13
+ directory named "ee" within the repository.
15
14
 
16
15
  "License Key" means a valid RevealUI license key obtained through an active
17
16
  paid subscription at https://revealui.com.
@@ -0,0 +1,11 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
11
+ //# sourceMappingURL=chunk-DGUM43GV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -30,7 +30,7 @@ function acquireLock(lockPath, timeoutMs = DEFAULT_TIMEOUT_MS) {
30
30
  if (err.code !== "EEXIST") return false;
31
31
  try {
32
32
  const holderPid = Number.parseInt(readFileSync(lockPath, "utf8").trim(), 10);
33
- if (!Number.isNaN(holderPid) && !isPidAlive(holderPid)) {
33
+ if (!(Number.isNaN(holderPid) || isPidAlive(holderPid))) {
34
34
  unlinkSync(lockPath);
35
35
  continue;
36
36
  }
@@ -53,7 +53,9 @@ function releaseLock(lockPath) {
53
53
  function withLock(lockPath, fn, timeoutMs) {
54
54
  const acquired = acquireLock(lockPath, timeoutMs);
55
55
  if (!acquired) {
56
- throw new Error(`Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`);
56
+ throw new Error(
57
+ `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`
58
+ );
57
59
  }
58
60
  try {
59
61
  return fn();
@@ -64,7 +66,9 @@ function withLock(lockPath, fn, timeoutMs) {
64
66
  async function withLockAsync(lockPath, fn, timeoutMs) {
65
67
  const acquired = acquireLock(lockPath, timeoutMs);
66
68
  if (!acquired) {
67
- throw new Error(`Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`);
69
+ throw new Error(
70
+ `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`
71
+ );
68
72
  }
69
73
  try {
70
74
  return await fn();
@@ -380,4 +384,4 @@ export {
380
384
  detectSessionType,
381
385
  deriveSessionId
382
386
  };
383
- //# sourceMappingURL=chunk-6WO7SSCX.js.map
387
+ //# sourceMappingURL=chunk-FJGN6DTH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workboard/file-lock.ts","../src/workboard/session-identity.ts","../src/workboard/workboard-manager.ts"],"sourcesContent":["import {\n closeSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n writeSync,\n} from 'node:fs';\n\nconst DEFAULT_TIMEOUT_MS = 2000;\nconst RETRY_MS = 50;\n\n/**\n * Check whether a process is still running.\n * Uses signal 0 which doesn't actually send a signal — just checks existence.\n */\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Acquire an exclusive file lock using O_EXCL (kernel-level atomic create).\n * Writes the current PID to the lock file for dead-holder detection.\n *\n * Spins with a 50ms interval until timeout. If the current lock holder\n * is dead (process no longer running), the lock is stolen.\n *\n * @returns true if the lock was acquired, false on timeout\n */\nexport function acquireLock(lockPath: string, timeoutMs = DEFAULT_TIMEOUT_MS): boolean {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n const fd = openSync(lockPath, 'wx');\n writeSync(fd, String(process.pid));\n closeSync(fd);\n return true;\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') return false;\n // Lock exists — check if holder is alive\n try {\n const holderPid = Number.parseInt(readFileSync(lockPath, 'utf8').trim(), 10);\n if (!(Number.isNaN(holderPid) || isPidAlive(holderPid))) {\n // Holder crashed — steal the lock\n unlinkSync(lockPath);\n continue;\n }\n } catch {\n // Lock file disappeared between checks — retry immediately\n continue;\n }\n // Busy-wait\n const spinUntil = Date.now() + RETRY_MS;\n while (Date.now() < spinUntil) {\n /* spin */\n }\n }\n }\n return false;\n}\n\n/**\n * Release a file lock. Swallows ENOENT (already released).\n */\nexport function releaseLock(lockPath: string): void {\n try {\n unlinkSync(lockPath);\n } catch {\n // Already released or never acquired — non-fatal\n }\n}\n\n/**\n * Execute a synchronous function while holding the file lock.\n * The lock is always released, even if fn throws.\n */\nexport function withLock<T>(lockPath: string, fn: () => T, timeoutMs?: number): T {\n const acquired = acquireLock(lockPath, timeoutMs);\n if (!acquired) {\n throw new Error(\n `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`,\n );\n }\n try {\n return fn();\n } finally {\n releaseLock(lockPath);\n }\n}\n\n/**\n * Execute an async function while holding the file lock.\n * The lock is always released, even if fn throws.\n */\nexport async function withLockAsync<T>(\n lockPath: string,\n fn: () => Promise<T>,\n timeoutMs?: number,\n): Promise<T> {\n const acquired = acquireLock(lockPath, timeoutMs);\n if (!acquired) {\n throw new Error(\n `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`,\n );\n }\n try {\n return await fn();\n } finally {\n releaseLock(lockPath);\n }\n}\n\n/**\n * Write a file atomically: write to a temporary file, then rename.\n * rename() on the same filesystem is atomic at the kernel level.\n */\nexport function atomicWriteSync(filePath: string, content: string): void {\n const tmpPath = `${filePath}.tmp.${process.pid}`;\n writeFileSync(tmpPath, content, 'utf8');\n renameSync(tmpPath, filePath);\n}\n\n/**\n * Derive a lock path from a workboard path (.md → .lock).\n */\nexport function lockPathFor(workboardPath: string): string {\n return workboardPath.replace(/\\.md$/, '.lock');\n}\n","import { readFileSync } from 'node:fs';\n\n/** Type of session detected from the runtime environment. */\nexport type SessionType = 'zed' | 'cursor' | 'terminal';\n\n/**\n * Detects whether the current process is running inside an AI tool (Zed, Cursor)\n * or a plain terminal session by walking the parent process chain.\n *\n * Uses /proc/<pid>/cmdline on Linux/WSL. Falls back to TERM_PROGRAM env var.\n */\nexport function detectSessionType(): SessionType {\n // Walk parent process chain looking for known AI tool process names.\n try {\n let pid = process.ppid;\n for (let depth = 0; depth < 8; depth++) {\n if (!pid || pid <= 1) break;\n const cmdline = readFileSync(`/proc/${pid}/cmdline`, 'utf8')\n .replace(/\\0/g, ' ')\n .toLowerCase();\n if (cmdline.includes('zed')) return 'zed';\n if (cmdline.includes('cursor')) return 'cursor';\n const status = readFileSync(`/proc/${pid}/status`, 'utf8');\n const m = status.match(/PPid:\\s+(\\d+)/);\n if (!m) break;\n pid = parseInt(m[1] ?? '0', 10);\n }\n } catch {\n // /proc not available (macOS, Windows non-WSL).\n }\n\n // Fallback: TERM_PROGRAM env var (set by some terminal emulators and IDEs).\n const termProgram = (process.env.TERM_PROGRAM ?? '').toLowerCase();\n if (termProgram.includes('zed')) return 'zed';\n if (termProgram.includes('cursor')) return 'cursor';\n\n return 'terminal';\n}\n\n/**\n * Derives a session ID (e.g. \"zed-1\", \"terminal-2\") given a type and a list\n * of existing session IDs already in the workboard.\n *\n * Picks the next available numeric suffix to avoid collisions.\n */\nexport function deriveSessionId(type: SessionType, existingIds: string[]): string {\n const matching = existingIds\n .filter((id) => id.startsWith(`${type}-`))\n .map((id) => parseInt(id.split('-')[1] ?? '0', 10))\n .filter((n) => !Number.isNaN(n));\n\n const maxN = matching.length > 0 ? Math.max(...matching) : 0;\n return `${type}-${maxN + 1}`;\n}\n","import { readFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { atomicWriteSync, lockPathFor, withLock, withLockAsync } from './file-lock.js';\nimport type {\n ConflictResult,\n WorkboardEntry,\n WorkboardSession,\n WorkboardState,\n} from './workboard-protocol.js';\n\nconst STALE_THRESHOLD_MS = 4 * 60 * 60 * 1000; // 4 hours\n\n/**\n * WorkboardManager — reads, parses, and writes .claude/workboard.md.\n *\n * The workboard is a markdown file with a specific structure:\n * ## Sessions — markdown table\n * ## Plans — freeform markdown\n * ## Recent — bullet list\n * ## Context — freeform markdown\n * ## Plan Reference — freeform markdown\n *\n * This class provides programmatic access to the Sessions table and Recent list.\n * Plans, Context, and Plan Reference are treated as opaque strings.\n *\n * All mutating methods use file locking (O_EXCL) to prevent race conditions\n * when multiple harness instances write concurrently. Writes are atomic\n * (tmp file + rename).\n */\nexport class WorkboardManager {\n private readonly lockPath: string;\n\n constructor(private readonly workboardPath: string) {\n const resolved = resolve(workboardPath);\n // Workboard paths must be markdown files. Resolving before checking\n // means path traversal sequences (../../etc) have already been collapsed.\n if (!resolved.endsWith('.md')) {\n throw new Error(`Invalid workboard path: must be a .md file`);\n }\n this.lockPath = lockPathFor(resolved);\n }\n\n /** Read and parse the workboard. */\n read(): WorkboardState {\n let content: string;\n try {\n content = readFileSync(this.workboardPath, 'utf8');\n } catch {\n return { sessions: [], recent: [], plans: '', context: '', planReference: '' };\n }\n return parseWorkboard(content);\n }\n\n /** Write a workboard state back to disk (locked + atomic). */\n write(state: WorkboardState): void {\n withLock(this.lockPath, () => {\n atomicWriteSync(this.workboardPath, serializeWorkboard(state));\n });\n }\n\n /** Read and parse the workboard asynchronously. */\n async readAsync(): Promise<WorkboardState> {\n try {\n const content = await readFile(this.workboardPath, 'utf8');\n return parseWorkboard(content);\n } catch {\n return { sessions: [], recent: [], plans: '', context: '', planReference: '' };\n }\n }\n\n /** Write a workboard state back to disk asynchronously (locked + atomic). */\n async writeAsync(state: WorkboardState): Promise<void> {\n await withLockAsync(this.lockPath, async () => {\n atomicWriteSync(this.workboardPath, serializeWorkboard(state));\n });\n }\n\n /** Register a new session, replacing any existing row with the same id. */\n registerSession(session: WorkboardSession): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const existing = state.sessions.findIndex((s) => s.id === session.id);\n if (existing >= 0) {\n state.sessions[existing] = session;\n } else {\n state.sessions.push(session);\n }\n this.writeUnlocked(state);\n });\n }\n\n /** Remove a session row by id. */\n unregisterSession(id: string): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n state.sessions = state.sessions.filter((s) => s.id !== id);\n this.writeUnlocked(state);\n });\n }\n\n /** Update specific fields of an existing session row. */\n updateSession(id: string, updates: Partial<WorkboardSession>): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const idx = state.sessions.findIndex((s) => s.id === id);\n if (idx < 0) return;\n const current = state.sessions[idx];\n if (!current) return;\n state.sessions[idx] = { ...current, ...updates };\n this.writeUnlocked(state);\n });\n }\n\n /** Update a session's files list and timestamp. */\n claimFiles(id: string, files: string[]): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const idx = state.sessions.findIndex((s) => s.id === id);\n if (idx < 0) return;\n const current = state.sessions[idx];\n if (!current) return;\n state.sessions[idx] = {\n ...current,\n files: files.join(', '),\n updated: `${new Date().toISOString().slice(0, 16)}Z`,\n };\n this.writeUnlocked(state);\n });\n }\n\n /** Clear a session's file reservations. */\n releaseFiles(id: string): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const idx = state.sessions.findIndex((s) => s.id === id);\n if (idx < 0) return;\n const current = state.sessions[idx];\n if (!current) return;\n state.sessions[idx] = {\n ...current,\n files: '',\n updated: `${new Date().toISOString().slice(0, 16)}Z`,\n };\n this.writeUnlocked(state);\n });\n }\n\n /** Prepend a timestamped entry to the ## Recent section (keeps last 20). */\n addRecentEntry(entry: WorkboardEntry): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n state.recent.unshift({ ...entry });\n if (state.recent.length > 20) state.recent.splice(20);\n this.writeUnlocked(state);\n });\n }\n\n /** Returns sessions whose `updated` timestamp is older than 4 hours. */\n detectStale(): WorkboardSession[] {\n const state = this.read();\n const now = Date.now();\n return state.sessions.filter((s) => {\n try {\n return now - new Date(s.updated).getTime() > STALE_THRESHOLD_MS;\n } catch {\n return false;\n }\n });\n }\n\n /**\n * Check whether the given files conflict with any other active session's reservations.\n * Returns a ConflictResult describing any overlaps found.\n */\n checkConflicts(mySessionId: string, files: string[]): ConflictResult {\n const state = this.read();\n const conflicts: ConflictResult['conflicts'] = [];\n\n for (const session of state.sessions) {\n if (session.id === mySessionId) continue;\n const theirFiles = session.files\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean);\n\n const overlapping = files.filter((myFile) =>\n theirFiles.some(\n (theirFile) =>\n myFile === theirFile ||\n myFile.startsWith(theirFile.replace('**', '')) ||\n theirFile.startsWith(myFile.replace('**', '')),\n ),\n );\n\n if (overlapping.length > 0) {\n conflicts.push({\n thisSession: mySessionId,\n otherSession: session.id,\n overlappingFiles: overlapping,\n });\n }\n }\n\n return { clean: conflicts.length === 0, conflicts };\n }\n\n // ---- Internal: unlocked read/write (caller must hold the lock) ----\n\n private readUnlocked(): WorkboardState {\n let content: string;\n try {\n content = readFileSync(this.workboardPath, 'utf8');\n } catch {\n return { sessions: [], recent: [], plans: '', context: '', planReference: '' };\n }\n return parseWorkboard(content);\n }\n\n private writeUnlocked(state: WorkboardState): void {\n atomicWriteSync(this.workboardPath, serializeWorkboard(state));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal parser / serializer\n// ---------------------------------------------------------------------------\n\nfunction parseWorkboard(content: string): WorkboardState {\n const state: WorkboardState = {\n sessions: [],\n recent: [],\n plans: '',\n context: '',\n planReference: '',\n };\n\n // Split into top-level sections at ## headings.\n const sectionRe = /^## (.+)$/gm;\n const sections: Array<{ title: string; start: number }> = [];\n for (const m of content.matchAll(sectionRe)) {\n sections.push({ title: (m[1] ?? '').trim(), start: m.index });\n }\n\n for (let i = 0; i < sections.length; i++) {\n const section = sections[i];\n if (!section) continue;\n const { title, start } = section;\n const nextSection = sections[i + 1];\n const end = nextSection !== undefined ? nextSection.start : content.length;\n const body = content.slice(start, end);\n\n if (title === 'Sessions') {\n state.sessions = parseSessionsTable(body);\n } else if (title === 'Recent') {\n state.recent = parseRecentList(body);\n } else if (title === 'Plans') {\n state.plans = body.replace(/^## Plans\\n/, '');\n } else if (title === 'Context') {\n state.context = body.replace(/^## Context\\n/, '');\n } else if (title === 'Plan Reference') {\n state.planReference = body.replace(/^## Plan Reference\\n/, '');\n }\n }\n\n return state;\n}\n\nfunction parseSessionsTable(section: string): WorkboardSession[] {\n const sessions: WorkboardSession[] = [];\n const lines = section.split('\\n');\n for (const line of lines) {\n if (!line.startsWith('|')) continue;\n const parts = line\n .split('|')\n .slice(1, -1)\n .map((c) => c.trim());\n if (parts.length < 6) continue;\n // Skip header and separator rows\n if (parts[0] === 'id' || /^-+$/.test(parts[0] ?? '')) continue;\n sessions.push({\n id: parts[0] ?? '',\n env: parts[1] ?? '',\n started: parts[2] ?? '',\n task: parts[3] ?? '',\n files: parts[4] ?? '',\n updated: parts[5] ?? '',\n });\n }\n return sessions;\n}\n\nfunction parseRecentList(section: string): WorkboardEntry[] {\n const entries: WorkboardEntry[] = [];\n const lineRe = /^- \\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})\\] ([\\w-]+): (.+)$/;\n for (const line of section.split('\\n')) {\n const m = lineRe.exec(line);\n if (!m) continue;\n entries.push({ timestamp: m[1] ?? '', sessionId: m[2] ?? '', description: m[3] ?? '' });\n }\n return entries;\n}\n\nfunction serializeWorkboard(state: WorkboardState): string {\n const lines: string[] = ['# Workboard', ''];\n\n // Sessions table\n lines.push('## Sessions', '');\n lines.push('| id | env | started | task | files | updated |');\n lines.push('| --- | --- | ------- | ---- | ----- | ------- |');\n for (const s of state.sessions) {\n lines.push(`| ${s.id} | ${s.env} | ${s.started} | ${s.task} | ${s.files} | ${s.updated} |`);\n }\n lines.push('');\n\n // Plans\n lines.push('## Plans');\n lines.push(state.plans.trimEnd());\n lines.push('');\n\n // Recent\n lines.push('## Recent', '');\n for (const e of state.recent) {\n lines.push(`- [${e.timestamp}] ${e.sessionId}: ${e.description}`);\n }\n lines.push('');\n\n // Context\n lines.push('## Context');\n lines.push(state.context.trimEnd());\n lines.push('');\n\n // Plan Reference\n lines.push('## Plan Reference');\n lines.push(state.planReference.trimEnd());\n lines.push('');\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["readFileSync","readFileSync","readFileSync"]}
@@ -2,7 +2,10 @@ import {
2
2
  WorkboardManager,
3
3
  deriveSessionId,
4
4
  detectSessionType
5
- } from "./chunk-6WO7SSCX.js";
5
+ } from "./chunk-FJGN6DTH.js";
6
+ import {
7
+ __require
8
+ } from "./chunk-DGUM43GV.js";
6
9
 
7
10
  // src/index.ts
8
11
  import { isFeatureEnabled } from "@revealui/core/features";
@@ -147,58 +150,6 @@ var ClaudeCodeAdapter = class {
147
150
  }
148
151
  };
149
152
 
150
- // src/adapters/copilot-adapter.ts
151
- var CopilotAdapter = class {
152
- id = "copilot";
153
- name = "GitHub Copilot";
154
- eventHandlers = /* @__PURE__ */ new Set();
155
- getCapabilities() {
156
- return {
157
- generateCode: false,
158
- // stub — no CLI available
159
- analyzeCode: false,
160
- applyEdit: false,
161
- applyConfig: false,
162
- readWorkboard: false,
163
- writeWorkboard: false
164
- };
165
- }
166
- async getInfo() {
167
- return { id: this.id, name: this.name, capabilities: this.getCapabilities() };
168
- }
169
- async isAvailable() {
170
- return false;
171
- }
172
- notifyRegistered() {
173
- this.emit({ type: "harness-connected", harnessId: this.id });
174
- }
175
- notifyUnregistering() {
176
- this.emit({ type: "harness-disconnected", harnessId: this.id });
177
- }
178
- async execute(command) {
179
- return {
180
- success: false,
181
- command: command.type,
182
- message: "Copilot adapter is a stub \u2014 no standalone CLI available"
183
- };
184
- }
185
- onEvent(handler) {
186
- this.eventHandlers.add(handler);
187
- return () => this.eventHandlers.delete(handler);
188
- }
189
- async dispose() {
190
- this.eventHandlers.clear();
191
- }
192
- emit(event) {
193
- for (const handler of this.eventHandlers) {
194
- try {
195
- handler(event);
196
- } catch {
197
- }
198
- }
199
- }
200
- };
201
-
202
153
  // src/adapters/cursor-adapter.ts
203
154
  import { execFile as execFile2 } from "child_process";
204
155
  import { promisify as promisify2 } from "util";
@@ -361,7 +312,12 @@ function syncConfig(harnessId, direction, root) {
361
312
  try {
362
313
  if (direction === "pull") {
363
314
  if (!existsSync(rootPath)) {
364
- return { success: false, harnessId, direction, message: `Root config not found: ${rootPath}` };
315
+ return {
316
+ success: false,
317
+ harnessId,
318
+ direction,
319
+ message: `Root config not found: ${rootPath}`
320
+ };
365
321
  }
366
322
  mkdirSync(dirname(localPath), { recursive: true });
367
323
  backupIfExists(localPath);
@@ -436,9 +392,339 @@ function backupIfExists(filePath) {
436
392
  // src/coordinator.ts
437
393
  import { join as join3 } from "path";
438
394
 
395
+ // src/adapters/copilot-adapter.ts
396
+ var CopilotAdapter = class {
397
+ id = "copilot";
398
+ name = "GitHub Copilot";
399
+ eventHandlers = /* @__PURE__ */ new Set();
400
+ getCapabilities() {
401
+ return {
402
+ generateCode: false,
403
+ // stub — no CLI available
404
+ analyzeCode: false,
405
+ applyEdit: false,
406
+ applyConfig: false,
407
+ readWorkboard: false,
408
+ writeWorkboard: false
409
+ };
410
+ }
411
+ async getInfo() {
412
+ return { id: this.id, name: this.name, capabilities: this.getCapabilities() };
413
+ }
414
+ async isAvailable() {
415
+ return false;
416
+ }
417
+ notifyRegistered() {
418
+ this.emit({ type: "harness-connected", harnessId: this.id });
419
+ }
420
+ notifyUnregistering() {
421
+ this.emit({ type: "harness-disconnected", harnessId: this.id });
422
+ }
423
+ async execute(command) {
424
+ return {
425
+ success: false,
426
+ command: command.type,
427
+ message: "Copilot adapter is a stub \u2014 no standalone CLI available"
428
+ };
429
+ }
430
+ onEvent(handler) {
431
+ this.eventHandlers.add(handler);
432
+ return () => this.eventHandlers.delete(handler);
433
+ }
434
+ async dispose() {
435
+ this.eventHandlers.clear();
436
+ }
437
+ emit(event) {
438
+ for (const handler of this.eventHandlers) {
439
+ try {
440
+ handler(event);
441
+ } catch {
442
+ }
443
+ }
444
+ }
445
+ };
446
+
447
+ // src/adapters/revealui-agent-adapter.ts
448
+ var DEFAULT_CONFIG = {
449
+ projectRoot: process.cwd(),
450
+ maxIterations: 10,
451
+ timeoutMs: 12e4
452
+ };
453
+ var RevealUIAgentAdapter = class {
454
+ id = "revealui-agent";
455
+ name = "RevealUI Agent";
456
+ config;
457
+ eventHandlers = /* @__PURE__ */ new Set();
458
+ constructor(config) {
459
+ this.config = { ...DEFAULT_CONFIG, ...config };
460
+ }
461
+ getCapabilities() {
462
+ return {
463
+ generateCode: true,
464
+ analyzeCode: true,
465
+ applyEdit: true,
466
+ applyConfig: false,
467
+ readWorkboard: (this.config.workboardPath ?? process.env.REVEALUI_WORKBOARD_PATH) !== void 0,
468
+ writeWorkboard: (this.config.workboardPath ?? process.env.REVEALUI_WORKBOARD_PATH) !== void 0
469
+ };
470
+ }
471
+ async getInfo() {
472
+ return {
473
+ id: this.id,
474
+ name: this.name,
475
+ version: "0.1.0",
476
+ capabilities: this.getCapabilities()
477
+ };
478
+ }
479
+ /**
480
+ * The RevealUI agent is available if at least one LLM provider is reachable.
481
+ * Checks in order: BitNet (localhost), Ollama (localhost), Groq (API key).
482
+ */
483
+ async isAvailable() {
484
+ if (process.env.BITNET_BASE_URL) {
485
+ try {
486
+ const url = `${process.env.BITNET_BASE_URL}/v1/models`;
487
+ const res = await fetch(url, { signal: AbortSignal.timeout(2e3) });
488
+ if (res.ok) return true;
489
+ } catch {
490
+ }
491
+ }
492
+ const ollamaUrl = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434";
493
+ try {
494
+ const res = await fetch(`${ollamaUrl}/api/tags`, {
495
+ signal: AbortSignal.timeout(2e3)
496
+ });
497
+ if (res.ok) return true;
498
+ } catch {
499
+ }
500
+ if (process.env.GROQ_API_KEY) return true;
501
+ return false;
502
+ }
503
+ notifyRegistered() {
504
+ this.emit({ type: "harness-connected", harnessId: this.id });
505
+ }
506
+ notifyUnregistering() {
507
+ this.emit({ type: "harness-disconnected", harnessId: this.id });
508
+ }
509
+ async execute(command) {
510
+ try {
511
+ return await this.executeInner(command);
512
+ } catch (err) {
513
+ const message = err instanceof Error ? err.message : String(err);
514
+ this.emit({ type: "error", harnessId: this.id, message });
515
+ return { success: false, command: command.type, message };
516
+ }
517
+ }
518
+ async executeInner(command) {
519
+ switch (command.type) {
520
+ case "get-status": {
521
+ const available = await this.isAvailable();
522
+ return {
523
+ success: true,
524
+ command: command.type,
525
+ data: {
526
+ available,
527
+ provider: this.config.provider ?? "auto",
528
+ model: this.config.model ?? "auto",
529
+ projectRoot: this.config.projectRoot
530
+ }
531
+ };
532
+ }
533
+ case "headless-prompt": {
534
+ return this.runHeadlessPrompt(command.prompt, command.maxTurns, command.timeoutMs);
535
+ }
536
+ case "generate-code": {
537
+ return this.runHeadlessPrompt(
538
+ `Generate code: ${command.prompt}${command.language ? ` (language: ${command.language})` : ""}${command.context ? `
539
+
540
+ Context:
541
+ ${command.context}` : ""}`
542
+ );
543
+ }
544
+ case "analyze-code": {
545
+ const question = command.question ?? "Analyze this file and explain what it does.";
546
+ return this.runHeadlessPrompt(
547
+ `Read the file at ${command.filePath} and answer: ${question}`
548
+ );
549
+ }
550
+ case "apply-edit": {
551
+ return this.runHeadlessPrompt(
552
+ `Apply the following diff to ${command.filePath}:
553
+
554
+ ${command.diff}`
555
+ );
556
+ }
557
+ case "apply-config":
558
+ case "sync-config":
559
+ case "diff-config": {
560
+ return {
561
+ success: false,
562
+ command: command.type,
563
+ message: "Config sync is not applicable \u2014 RevealUI agent uses the content layer directly"
564
+ };
565
+ }
566
+ case "read-workboard":
567
+ case "update-workboard": {
568
+ return {
569
+ success: false,
570
+ command: command.type,
571
+ message: "Workboard support not yet wired \u2014 use WorkboardManager directly"
572
+ };
573
+ }
574
+ default: {
575
+ return {
576
+ success: false,
577
+ command: command.type,
578
+ message: `Command not supported by ${this.name}`
579
+ };
580
+ }
581
+ }
582
+ }
583
+ /**
584
+ * Run a headless prompt through the coding agent.
585
+ * Lazy-imports @revealui/ai to avoid hard dependency at module load time.
586
+ * Types are inferred from the dynamic imports — no compile-time @revealui/ai dependency.
587
+ */
588
+ async runHeadlessPrompt(prompt, maxTurns, timeoutMs) {
589
+ const aiRuntimePath = "@revealui/ai/orchestration/streaming-runtime";
590
+ const aiClientPath = "@revealui/ai/llm/client";
591
+ const aiToolsPath = "@revealui/ai/tools/coding";
592
+ let runtimeMod;
593
+ let clientMod;
594
+ let toolsMod;
595
+ try {
596
+ [runtimeMod, clientMod, toolsMod] = await Promise.all([
597
+ import(aiRuntimePath),
598
+ import(aiClientPath),
599
+ import(aiToolsPath)
600
+ ]);
601
+ } catch {
602
+ return {
603
+ success: false,
604
+ command: "headless-prompt",
605
+ message: "@revealui/ai is not installed. Install it to use the RevealUI agent: npm install @revealui/ai"
606
+ };
607
+ }
608
+ const StreamingAgentRuntime = runtimeMod.StreamingAgentRuntime;
609
+ const createCodingTools = toolsMod.createCodingTools;
610
+ const projectRoot = this.config.projectRoot ?? process.cwd();
611
+ const tools = createCodingTools({ projectRoot });
612
+ let llmClient;
613
+ if (this.config.provider) {
614
+ const LLMClient = clientMod.LLMClient;
615
+ llmClient = new LLMClient({
616
+ provider: this.config.provider,
617
+ model: this.config.model,
618
+ baseURL: process.env.BITNET_BASE_URL ?? process.env.OLLAMA_BASE_URL,
619
+ apiKey: process.env.GROQ_API_KEY ?? "not-needed"
620
+ });
621
+ } else {
622
+ const createLLMClientFromEnv = clientMod.createLLMClientFromEnv;
623
+ llmClient = createLLMClientFromEnv();
624
+ }
625
+ const agent = {
626
+ id: "revealui-coding-agent",
627
+ name: "RevealUI Coding Agent",
628
+ instructions: this.buildInstructions(),
629
+ tools,
630
+ config: {},
631
+ getContext: () => ({ projectRoot, workingDirectory: projectRoot })
632
+ };
633
+ const task = {
634
+ id: `task-${Date.now()}`,
635
+ type: "headless-prompt",
636
+ description: prompt
637
+ };
638
+ const runtime = new StreamingAgentRuntime({
639
+ maxIterations: maxTurns ?? this.config.maxIterations ?? DEFAULT_CONFIG.maxIterations,
640
+ timeout: timeoutMs ?? this.config.timeoutMs ?? DEFAULT_CONFIG.timeoutMs
641
+ });
642
+ const taskId = task.id;
643
+ this.emit({ type: "generation-started", taskId });
644
+ const outputParts = [];
645
+ try {
646
+ for await (const chunk of runtime.streamTask(agent, task, llmClient)) {
647
+ switch (chunk.type) {
648
+ case "text":
649
+ if (chunk.content) outputParts.push(chunk.content);
650
+ break;
651
+ case "tool_call_result":
652
+ if (chunk.toolResult?.content) {
653
+ outputParts.push(`[tool: ${chunk.toolCall?.name}] ${chunk.toolResult.content}`);
654
+ }
655
+ break;
656
+ case "error":
657
+ if (chunk.error) outputParts.push(`[error] ${chunk.error}`);
658
+ break;
659
+ case "done":
660
+ break;
661
+ }
662
+ }
663
+ } finally {
664
+ await runtime.cleanup();
665
+ }
666
+ const output = outputParts.join("\n");
667
+ this.emit({ type: "generation-completed", taskId, output });
668
+ return {
669
+ success: true,
670
+ command: "headless-prompt",
671
+ message: output,
672
+ data: { taskId, output }
673
+ };
674
+ }
675
+ /**
676
+ * Build system instructions from the content layer.
677
+ * Loads rules from .claude/rules/ as a baseline (they are the canonical content).
678
+ */
679
+ buildInstructions() {
680
+ const lines = [
681
+ "You are the RevealUI coding agent. You help with software development tasks in this project.",
682
+ "Use the available tools to read, write, edit, search, and execute commands.",
683
+ "Always read files before modifying them. Prefer surgical edits over full rewrites.",
684
+ "Follow project conventions discovered via the project_context tool.",
685
+ ""
686
+ ];
687
+ try {
688
+ const { readdirSync, readFileSync: readFileSync2 } = __require("fs");
689
+ const { join: join4 } = __require("path");
690
+ const projectRoot = this.config.projectRoot ?? process.cwd();
691
+ const rulesDir = join4(projectRoot, ".claude", "rules");
692
+ const ruleFiles = readdirSync(rulesDir);
693
+ for (const file of ruleFiles) {
694
+ if (file.endsWith(".md")) {
695
+ const content = readFileSync2(join4(rulesDir, file), "utf8");
696
+ lines.push(`## ${file.replace(".md", "")}`, content, "");
697
+ }
698
+ }
699
+ } catch {
700
+ }
701
+ return lines.join("\n");
702
+ }
703
+ onEvent(handler) {
704
+ this.eventHandlers.add(handler);
705
+ return () => this.eventHandlers.delete(handler);
706
+ }
707
+ async dispose() {
708
+ this.eventHandlers.clear();
709
+ }
710
+ emit(event) {
711
+ for (const handler of this.eventHandlers) {
712
+ try {
713
+ handler(event);
714
+ } catch {
715
+ }
716
+ }
717
+ }
718
+ };
719
+
439
720
  // src/detection/auto-detector.ts
440
721
  async function autoDetectHarnesses(registry) {
441
- const candidates = [new ClaudeCodeAdapter(), new CursorAdapter(), new CopilotAdapter()];
722
+ const candidates = [
723
+ new RevealUIAgentAdapter(),
724
+ new ClaudeCodeAdapter(),
725
+ new CursorAdapter(),
726
+ new CopilotAdapter()
727
+ ];
442
728
  const registered = [];
443
729
  await Promise.all(
444
730
  candidates.map(async (adapter) => {
@@ -787,7 +1073,7 @@ var HarnessCoordinator = class {
787
1073
  /** Run a health check across all registered harnesses and the workboard. */
788
1074
  async healthCheck() {
789
1075
  const diagnostics = [];
790
- const STALE_MS = 4 * 60 * 60 * 1e3;
1076
+ const StaleMs = 4 * 60 * 60 * 1e3;
791
1077
  const allIds = this.registry.listAll();
792
1078
  const registeredHarnesses = await Promise.all(
793
1079
  allIds.map(async (harnessId) => {
@@ -814,7 +1100,7 @@ var HarnessCoordinator = class {
814
1100
  const now = Date.now();
815
1101
  for (const s of state.sessions) {
816
1102
  const ts = Date.parse(s.updated);
817
- if (!Number.isNaN(ts) && now - ts > STALE_MS) {
1103
+ if (!Number.isNaN(ts) && now - ts > StaleMs) {
818
1104
  staleSessionIds.push(s.id);
819
1105
  }
820
1106
  }
@@ -822,9 +1108,7 @@ var HarnessCoordinator = class {
822
1108
  diagnostics.push(`Stale sessions: ${staleSessionIds.join(", ")}`);
823
1109
  }
824
1110
  } catch (err) {
825
- diagnostics.push(
826
- `Workboard unreadable: ${err instanceof Error ? err.message : String(err)}`
827
- );
1111
+ diagnostics.push(`Workboard unreadable: ${err instanceof Error ? err.message : String(err)}`);
828
1112
  }
829
1113
  const healthy = registeredHarnesses.some((h) => h.available) && readable && staleSessionIds.length === 0;
830
1114
  return {
@@ -852,7 +1136,6 @@ async function checkHarnessesLicense() {
852
1136
 
853
1137
  export {
854
1138
  ClaudeCodeAdapter,
855
- CopilotAdapter,
856
1139
  CursorAdapter,
857
1140
  autoDetectHarnesses,
858
1141
  HarnessRegistry,
@@ -872,4 +1155,4 @@ export {
872
1155
  HarnessCoordinator,
873
1156
  checkHarnessesLicense
874
1157
  };
875
- //# sourceMappingURL=chunk-MO2EXBUG.js.map
1158
+ //# sourceMappingURL=chunk-HH2PJYQN.js.map