@automagik/genie 0.260202.1607 → 0.260202.1901
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/.beads/README.md +81 -0
- package/.beads/config.yaml +67 -0
- package/.beads/interactions.jsonl +0 -0
- package/.beads/issues.jsonl +0 -0
- package/.beads/metadata.json +4 -0
- package/.gitattributes +3 -0
- package/AGENTS.md +40 -0
- package/dist/claudio.js +5 -5
- package/dist/genie.js +6 -6
- package/dist/term.js +116 -53
- package/package.json +1 -1
- package/src/lib/beads-registry.ts +546 -0
- package/src/lib/orchestrator/completion.ts +392 -0
- package/src/lib/orchestrator/event-monitor.ts +442 -0
- package/src/lib/orchestrator/index.ts +12 -0
- package/src/lib/orchestrator/patterns.ts +277 -0
- package/src/lib/orchestrator/state-detector.ts +339 -0
- package/src/lib/tmux.ts +15 -1
- package/src/lib/version.ts +1 -1
- package/src/lib/worker-registry.ts +229 -0
- package/src/term-commands/close.ts +256 -0
- package/src/term-commands/daemon.ts +176 -0
- package/src/term-commands/kill.ts +186 -0
- package/src/term-commands/orchestrate.ts +844 -0
- package/src/term-commands/split.ts +8 -7
- package/src/term-commands/work.ts +497 -0
- package/src/term-commands/workers.ts +298 -0
- package/src/term.ts +227 -1
package/package.json
CHANGED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beads Registry - Worker state management via beads native commands
|
|
3
|
+
*
|
|
4
|
+
* Replaces JSON file-based worker-registry.ts with beads agent/slot system.
|
|
5
|
+
* Provides unified state tracking with Witness support and built-in cardinality.
|
|
6
|
+
*
|
|
7
|
+
* Environment:
|
|
8
|
+
* TERM_USE_BEADS_REGISTRY=false - Disable beads registry (fallback to JSON)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { $ } from 'bun';
|
|
12
|
+
import type { Worker, WorkerState } from './worker-registry.js';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Configuration
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
const AGENT_LABEL = 'gt:agent';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if beads registry is enabled (default: true)
|
|
22
|
+
*/
|
|
23
|
+
export function isBeadsRegistryEnabled(): boolean {
|
|
24
|
+
return process.env.TERM_USE_BEADS_REGISTRY !== 'false';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Helper Functions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Run bd command and parse output
|
|
33
|
+
*/
|
|
34
|
+
async function runBd(args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
35
|
+
try {
|
|
36
|
+
const result = await $`bd ${args}`.quiet();
|
|
37
|
+
return {
|
|
38
|
+
stdout: result.stdout.toString().trim(),
|
|
39
|
+
stderr: result.stderr.toString().trim(),
|
|
40
|
+
exitCode: 0,
|
|
41
|
+
};
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
return {
|
|
44
|
+
stdout: error.stdout?.toString().trim() || '',
|
|
45
|
+
stderr: error.stderr?.toString().trim() || '',
|
|
46
|
+
exitCode: error.exitCode || 1,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse JSON from bd command, handling potential errors
|
|
53
|
+
*/
|
|
54
|
+
function parseJson<T>(output: string): T | null {
|
|
55
|
+
if (!output) return null;
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(output);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Agent Bead Management
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
interface AgentBead {
|
|
68
|
+
id: string;
|
|
69
|
+
title: string;
|
|
70
|
+
type: string;
|
|
71
|
+
labels: string[];
|
|
72
|
+
state?: string;
|
|
73
|
+
metadata?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a new agent bead for a worker
|
|
78
|
+
*/
|
|
79
|
+
export async function createAgent(
|
|
80
|
+
workerId: string,
|
|
81
|
+
metadata: {
|
|
82
|
+
paneId: string;
|
|
83
|
+
session: string;
|
|
84
|
+
worktree: string | null;
|
|
85
|
+
repoPath: string;
|
|
86
|
+
taskId: string;
|
|
87
|
+
taskTitle?: string;
|
|
88
|
+
}
|
|
89
|
+
): Promise<string> {
|
|
90
|
+
// Create agent bead with metadata
|
|
91
|
+
const title = `Worker: ${workerId}`;
|
|
92
|
+
const { stdout, exitCode } = await runBd([
|
|
93
|
+
'create',
|
|
94
|
+
`--title=${title}`,
|
|
95
|
+
'--type=agent',
|
|
96
|
+
`--label=${AGENT_LABEL}`,
|
|
97
|
+
`--label=worker:${workerId}`,
|
|
98
|
+
'--json',
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
if (exitCode !== 0) {
|
|
102
|
+
throw new Error(`Failed to create agent bead: ${stdout}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const created = parseJson<{ id: string }>(stdout);
|
|
106
|
+
if (!created?.id) {
|
|
107
|
+
throw new Error('Failed to parse created agent bead');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Store worker metadata in agent
|
|
111
|
+
const metadataJson = JSON.stringify({
|
|
112
|
+
paneId: metadata.paneId,
|
|
113
|
+
session: metadata.session,
|
|
114
|
+
worktree: metadata.worktree,
|
|
115
|
+
repoPath: metadata.repoPath,
|
|
116
|
+
taskId: metadata.taskId,
|
|
117
|
+
taskTitle: metadata.taskTitle,
|
|
118
|
+
startedAt: new Date().toISOString(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await runBd(['update', created.id, `--metadata=${metadataJson}`]);
|
|
122
|
+
|
|
123
|
+
return created.id;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Ensure an agent bead exists for a worker, creating if needed
|
|
128
|
+
*/
|
|
129
|
+
export async function ensureAgent(
|
|
130
|
+
workerId: string,
|
|
131
|
+
metadata: {
|
|
132
|
+
paneId: string;
|
|
133
|
+
session: string;
|
|
134
|
+
worktree: string | null;
|
|
135
|
+
repoPath: string;
|
|
136
|
+
taskId: string;
|
|
137
|
+
taskTitle?: string;
|
|
138
|
+
}
|
|
139
|
+
): Promise<string> {
|
|
140
|
+
// Check if agent already exists
|
|
141
|
+
const existing = await findAgentByWorkerId(workerId);
|
|
142
|
+
if (existing) {
|
|
143
|
+
return existing.id;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return createAgent(workerId, metadata);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Find agent bead by worker ID
|
|
151
|
+
*/
|
|
152
|
+
async function findAgentByWorkerId(workerId: string): Promise<AgentBead | null> {
|
|
153
|
+
const { stdout, exitCode } = await runBd([
|
|
154
|
+
'list',
|
|
155
|
+
`--label=${AGENT_LABEL}`,
|
|
156
|
+
`--label=worker:${workerId}`,
|
|
157
|
+
'--json',
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
if (exitCode !== 0 || !stdout) return null;
|
|
161
|
+
|
|
162
|
+
const agents = parseJson<AgentBead[]>(stdout);
|
|
163
|
+
return agents?.[0] || null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Delete an agent bead
|
|
168
|
+
*/
|
|
169
|
+
export async function deleteAgent(agentIdOrWorkerId: string): Promise<boolean> {
|
|
170
|
+
// Try direct ID first
|
|
171
|
+
let { exitCode } = await runBd(['delete', agentIdOrWorkerId]);
|
|
172
|
+
if (exitCode === 0) return true;
|
|
173
|
+
|
|
174
|
+
// Try finding by worker ID
|
|
175
|
+
const agent = await findAgentByWorkerId(agentIdOrWorkerId);
|
|
176
|
+
if (agent) {
|
|
177
|
+
({ exitCode } = await runBd(['delete', agent.id]));
|
|
178
|
+
return exitCode === 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// Agent State Management
|
|
186
|
+
// ============================================================================
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Map our WorkerState to beads agent states
|
|
190
|
+
*/
|
|
191
|
+
function mapToBeadsState(state: WorkerState): string {
|
|
192
|
+
switch (state) {
|
|
193
|
+
case 'spawning':
|
|
194
|
+
return 'spawning';
|
|
195
|
+
case 'working':
|
|
196
|
+
return 'working';
|
|
197
|
+
case 'idle':
|
|
198
|
+
return 'idle';
|
|
199
|
+
case 'permission':
|
|
200
|
+
return 'blocked';
|
|
201
|
+
case 'question':
|
|
202
|
+
return 'blocked';
|
|
203
|
+
case 'done':
|
|
204
|
+
return 'done';
|
|
205
|
+
case 'error':
|
|
206
|
+
return 'error';
|
|
207
|
+
default:
|
|
208
|
+
return 'unknown';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Map beads agent state to WorkerState
|
|
214
|
+
*/
|
|
215
|
+
function mapFromBeadsState(beadsState: string): WorkerState {
|
|
216
|
+
switch (beadsState) {
|
|
217
|
+
case 'spawning':
|
|
218
|
+
return 'spawning';
|
|
219
|
+
case 'working':
|
|
220
|
+
return 'working';
|
|
221
|
+
case 'idle':
|
|
222
|
+
return 'idle';
|
|
223
|
+
case 'blocked':
|
|
224
|
+
return 'permission'; // Could be permission or question
|
|
225
|
+
case 'done':
|
|
226
|
+
return 'done';
|
|
227
|
+
case 'error':
|
|
228
|
+
return 'error';
|
|
229
|
+
default:
|
|
230
|
+
return 'idle';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Set agent state
|
|
236
|
+
*/
|
|
237
|
+
export async function setAgentState(workerId: string, state: WorkerState): Promise<void> {
|
|
238
|
+
const agent = await findAgentByWorkerId(workerId);
|
|
239
|
+
if (!agent) {
|
|
240
|
+
throw new Error(`Agent not found for worker ${workerId}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const beadsState = mapToBeadsState(state);
|
|
244
|
+
const { exitCode, stderr } = await runBd(['agent', 'state', agent.id, beadsState]);
|
|
245
|
+
|
|
246
|
+
if (exitCode !== 0) {
|
|
247
|
+
throw new Error(`Failed to set agent state: ${stderr}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Send heartbeat for agent
|
|
253
|
+
*/
|
|
254
|
+
export async function heartbeat(workerId: string): Promise<void> {
|
|
255
|
+
const agent = await findAgentByWorkerId(workerId);
|
|
256
|
+
if (!agent) return; // Silently ignore if agent doesn't exist
|
|
257
|
+
|
|
258
|
+
await runBd(['agent', 'heartbeat', agent.id]);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// Work Binding (Slots)
|
|
263
|
+
// ============================================================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Bind work (task) to an agent
|
|
267
|
+
*/
|
|
268
|
+
export async function bindWork(workerId: string, taskId: string): Promise<void> {
|
|
269
|
+
const agent = await findAgentByWorkerId(workerId);
|
|
270
|
+
if (!agent) {
|
|
271
|
+
throw new Error(`Agent not found for worker ${workerId}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const { exitCode, stderr } = await runBd(['slot', 'set', agent.id, 'hook', taskId]);
|
|
275
|
+
|
|
276
|
+
if (exitCode !== 0) {
|
|
277
|
+
throw new Error(`Failed to bind work: ${stderr}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Unbind work from an agent
|
|
283
|
+
*/
|
|
284
|
+
export async function unbindWork(workerId: string): Promise<void> {
|
|
285
|
+
const agent = await findAgentByWorkerId(workerId);
|
|
286
|
+
if (!agent) return; // Silently ignore if agent doesn't exist
|
|
287
|
+
|
|
288
|
+
await runBd(['slot', 'clear', agent.id, 'hook']);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Worker Query Functions
|
|
293
|
+
// ============================================================================
|
|
294
|
+
|
|
295
|
+
interface AgentMetadata {
|
|
296
|
+
paneId: string;
|
|
297
|
+
session: string;
|
|
298
|
+
worktree: string | null;
|
|
299
|
+
repoPath: string;
|
|
300
|
+
taskId: string;
|
|
301
|
+
taskTitle?: string;
|
|
302
|
+
startedAt: string;
|
|
303
|
+
wishSlug?: string;
|
|
304
|
+
groupNumber?: number;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Convert agent bead to Worker interface
|
|
309
|
+
*/
|
|
310
|
+
function agentToWorker(agent: AgentBead, metadata: AgentMetadata): Worker {
|
|
311
|
+
return {
|
|
312
|
+
id: metadata.taskId, // Worker ID matches task ID
|
|
313
|
+
paneId: metadata.paneId,
|
|
314
|
+
session: metadata.session,
|
|
315
|
+
worktree: metadata.worktree,
|
|
316
|
+
taskId: metadata.taskId,
|
|
317
|
+
taskTitle: metadata.taskTitle,
|
|
318
|
+
wishSlug: metadata.wishSlug,
|
|
319
|
+
groupNumber: metadata.groupNumber,
|
|
320
|
+
startedAt: metadata.startedAt,
|
|
321
|
+
state: mapFromBeadsState(agent.state || 'idle'),
|
|
322
|
+
lastStateChange: new Date().toISOString(), // Would need to track this in beads
|
|
323
|
+
repoPath: metadata.repoPath,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get a worker by ID
|
|
329
|
+
*/
|
|
330
|
+
export async function getWorker(workerId: string): Promise<Worker | null> {
|
|
331
|
+
const agent = await findAgentByWorkerId(workerId);
|
|
332
|
+
if (!agent) return null;
|
|
333
|
+
|
|
334
|
+
// Get full agent details with metadata
|
|
335
|
+
const { stdout, exitCode } = await runBd(['show', agent.id, '--json']);
|
|
336
|
+
if (exitCode !== 0 || !stdout) return null;
|
|
337
|
+
|
|
338
|
+
const fullAgent = parseJson<AgentBead & { metadata?: AgentMetadata }>(stdout);
|
|
339
|
+
if (!fullAgent?.metadata) return null;
|
|
340
|
+
|
|
341
|
+
return agentToWorker(fullAgent, fullAgent.metadata);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* List all workers
|
|
346
|
+
*/
|
|
347
|
+
export async function listWorkers(): Promise<Worker[]> {
|
|
348
|
+
const { stdout, exitCode } = await runBd([
|
|
349
|
+
'list',
|
|
350
|
+
`--label=${AGENT_LABEL}`,
|
|
351
|
+
'--json',
|
|
352
|
+
]);
|
|
353
|
+
|
|
354
|
+
if (exitCode !== 0 || !stdout) return [];
|
|
355
|
+
|
|
356
|
+
const agents = parseJson<Array<AgentBead & { metadata?: AgentMetadata }>>(stdout);
|
|
357
|
+
if (!agents) return [];
|
|
358
|
+
|
|
359
|
+
const workers: Worker[] = [];
|
|
360
|
+
for (const agent of agents) {
|
|
361
|
+
if (agent.metadata) {
|
|
362
|
+
workers.push(agentToWorker(agent, agent.metadata));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return workers;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Find worker by pane ID
|
|
371
|
+
*/
|
|
372
|
+
export async function findByPane(paneId: string): Promise<Worker | null> {
|
|
373
|
+
const workers = await listWorkers();
|
|
374
|
+
const normalizedPaneId = paneId.startsWith('%') ? paneId : `%${paneId}`;
|
|
375
|
+
return workers.find(w => w.paneId === normalizedPaneId) || null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Find worker by task ID
|
|
380
|
+
*/
|
|
381
|
+
export async function findByTask(taskId: string): Promise<Worker | null> {
|
|
382
|
+
// Worker ID typically matches task ID
|
|
383
|
+
return getWorker(taskId);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Check if a worker exists for a task
|
|
388
|
+
*/
|
|
389
|
+
export async function hasWorkerForTask(taskId: string): Promise<boolean> {
|
|
390
|
+
const worker = await findByTask(taskId);
|
|
391
|
+
return worker !== null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// Daemon Management
|
|
396
|
+
// ============================================================================
|
|
397
|
+
|
|
398
|
+
interface DaemonStatus {
|
|
399
|
+
running: boolean;
|
|
400
|
+
pid?: number;
|
|
401
|
+
lastSync?: string;
|
|
402
|
+
autoCommit?: boolean;
|
|
403
|
+
autoPush?: boolean;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Check if beads daemon is running
|
|
408
|
+
*/
|
|
409
|
+
export async function checkDaemonStatus(): Promise<DaemonStatus> {
|
|
410
|
+
const { stdout, exitCode } = await runBd(['daemon', 'status', '--json']);
|
|
411
|
+
|
|
412
|
+
if (exitCode !== 0) {
|
|
413
|
+
return { running: false };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const status = parseJson<DaemonStatus>(stdout);
|
|
417
|
+
return status || { running: false };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Start beads daemon
|
|
422
|
+
*/
|
|
423
|
+
export async function startDaemon(options?: { autoCommit?: boolean; autoPush?: boolean }): Promise<boolean> {
|
|
424
|
+
const args = ['daemon', 'start'];
|
|
425
|
+
if (options?.autoCommit) args.push('--auto-commit');
|
|
426
|
+
if (options?.autoPush) args.push('--auto-push');
|
|
427
|
+
|
|
428
|
+
const { exitCode } = await runBd(args);
|
|
429
|
+
return exitCode === 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Stop beads daemon
|
|
434
|
+
*/
|
|
435
|
+
export async function stopDaemon(): Promise<boolean> {
|
|
436
|
+
const { exitCode } = await runBd(['daemon', 'stop']);
|
|
437
|
+
return exitCode === 0;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Ensure daemon is running, start if not
|
|
442
|
+
*/
|
|
443
|
+
export async function ensureDaemon(options?: { autoCommit?: boolean }): Promise<boolean> {
|
|
444
|
+
const status = await checkDaemonStatus();
|
|
445
|
+
if (status.running) return true;
|
|
446
|
+
|
|
447
|
+
return startDaemon(options);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ============================================================================
|
|
451
|
+
// Worktree Management (via bd worktree)
|
|
452
|
+
// ============================================================================
|
|
453
|
+
|
|
454
|
+
export interface BeadsWorktreeInfo {
|
|
455
|
+
path: string;
|
|
456
|
+
branch: string;
|
|
457
|
+
name: string;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Create worktree via beads
|
|
462
|
+
*/
|
|
463
|
+
export async function createWorktree(name: string): Promise<BeadsWorktreeInfo | null> {
|
|
464
|
+
const { stdout, exitCode, stderr } = await runBd(['worktree', 'create', name, '--json']);
|
|
465
|
+
|
|
466
|
+
if (exitCode !== 0) {
|
|
467
|
+
console.error(`bd worktree create failed: ${stderr}`);
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const info = parseJson<BeadsWorktreeInfo>(stdout);
|
|
472
|
+
return info;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Remove worktree via beads
|
|
477
|
+
*/
|
|
478
|
+
export async function removeWorktree(name: string): Promise<boolean> {
|
|
479
|
+
const { exitCode } = await runBd(['worktree', 'remove', name]);
|
|
480
|
+
return exitCode === 0;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* List worktrees via beads
|
|
485
|
+
*/
|
|
486
|
+
export async function listWorktrees(): Promise<BeadsWorktreeInfo[]> {
|
|
487
|
+
const { stdout, exitCode } = await runBd(['worktree', 'list', '--json']);
|
|
488
|
+
|
|
489
|
+
if (exitCode !== 0 || !stdout) return [];
|
|
490
|
+
|
|
491
|
+
const worktrees = parseJson<BeadsWorktreeInfo[]>(stdout);
|
|
492
|
+
return worktrees || [];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Check if worktree exists via beads
|
|
497
|
+
*/
|
|
498
|
+
export async function worktreeExists(name: string): Promise<boolean> {
|
|
499
|
+
const worktrees = await listWorktrees();
|
|
500
|
+
return worktrees.some(wt => wt.name === name || wt.branch === name);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Get worktree info via beads
|
|
505
|
+
*/
|
|
506
|
+
export async function getWorktree(name: string): Promise<BeadsWorktreeInfo | null> {
|
|
507
|
+
const worktrees = await listWorktrees();
|
|
508
|
+
return worktrees.find(wt => wt.name === name || wt.branch === name) || null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ============================================================================
|
|
512
|
+
// Migration/Compatibility
|
|
513
|
+
// ============================================================================
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Register a worker (compatibility with worker-registry interface)
|
|
517
|
+
* Writes to both beads and JSON registry during migration
|
|
518
|
+
*/
|
|
519
|
+
export async function register(worker: Worker): Promise<void> {
|
|
520
|
+
await ensureAgent(worker.id, {
|
|
521
|
+
paneId: worker.paneId,
|
|
522
|
+
session: worker.session,
|
|
523
|
+
worktree: worker.worktree,
|
|
524
|
+
repoPath: worker.repoPath,
|
|
525
|
+
taskId: worker.taskId,
|
|
526
|
+
taskTitle: worker.taskTitle,
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
await setAgentState(worker.id, worker.state);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Unregister a worker (compatibility with worker-registry interface)
|
|
534
|
+
*/
|
|
535
|
+
export async function unregister(workerId: string): Promise<void> {
|
|
536
|
+
await unbindWork(workerId);
|
|
537
|
+
await deleteAgent(workerId);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Update worker state (compatibility with worker-registry interface)
|
|
542
|
+
*/
|
|
543
|
+
export async function updateState(workerId: string, state: WorkerState): Promise<void> {
|
|
544
|
+
await setAgentState(workerId, state);
|
|
545
|
+
await heartbeat(workerId);
|
|
546
|
+
}
|