@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
|
@@ -38,6 +38,9 @@ export async function splitSessionPane(
|
|
|
38
38
|
// Determine direction
|
|
39
39
|
const splitDirection = direction === 'h' ? 'horizontal' : 'vertical';
|
|
40
40
|
|
|
41
|
+
// Get source pane's current working directory
|
|
42
|
+
const sourcePath = await tmux.executeTmux(`display-message -p -t '${paneId}' '#{pane_current_path}'`);
|
|
43
|
+
|
|
41
44
|
// Handle workspace and worktree options
|
|
42
45
|
let workingDir: string | undefined;
|
|
43
46
|
|
|
@@ -62,20 +65,18 @@ export async function splitSessionPane(
|
|
|
62
65
|
workingDir = manager.getWorktreePath(options.worktree);
|
|
63
66
|
} else if (options.workspace) {
|
|
64
67
|
workingDir = options.workspace;
|
|
68
|
+
} else {
|
|
69
|
+
// Default to source pane's current directory
|
|
70
|
+
workingDir = sourcePath.trim() || undefined;
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
// Split pane
|
|
68
|
-
const newPane = await tmux.splitPane(paneId, splitDirection);
|
|
73
|
+
// Split pane with working directory (tmux -c flag handles this natively)
|
|
74
|
+
const newPane = await tmux.splitPane(paneId, splitDirection, undefined, workingDir);
|
|
69
75
|
if (!newPane) {
|
|
70
76
|
console.error('❌ Failed to split pane');
|
|
71
77
|
process.exit(1);
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
// Change to working directory if specified
|
|
75
|
-
if (workingDir && newPane) {
|
|
76
|
-
await tmux.executeTmux(`send-keys -t '${newPane.id}' 'cd ${workingDir}' Enter`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
80
|
console.log(`✅ Pane split ${splitDirection}ly in session "${sessionName}"`);
|
|
80
81
|
if (workingDir) {
|
|
81
82
|
console.log(` Working directory: ${workingDir}`);
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work command - Spawn worker bound to beads issue
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* term work <bd-id> - Work on specific beads issue
|
|
6
|
+
* term work next - Work on next ready issue
|
|
7
|
+
* term work wish - Create a new wish (deferred)
|
|
8
|
+
*
|
|
9
|
+
* Options:
|
|
10
|
+
* --no-worktree - Use shared repo instead of worktree
|
|
11
|
+
* -s, --session <name> - Target tmux session
|
|
12
|
+
* --focus - Focus the worker pane (default: true)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { $ } from 'bun';
|
|
16
|
+
import * as tmux from '../lib/tmux.js';
|
|
17
|
+
import * as registry from '../lib/worker-registry.js';
|
|
18
|
+
import * as beadsRegistry from '../lib/beads-registry.js';
|
|
19
|
+
import { WorktreeManager } from '../lib/worktree.js';
|
|
20
|
+
import { EventMonitor, detectState } from '../lib/orchestrator/index.js';
|
|
21
|
+
import { join } from 'path';
|
|
22
|
+
import { homedir } from 'os';
|
|
23
|
+
|
|
24
|
+
// Use beads registry when enabled
|
|
25
|
+
const useBeads = beadsRegistry.isBeadsRegistryEnabled();
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Types
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
export interface WorkOptions {
|
|
32
|
+
noWorktree?: boolean;
|
|
33
|
+
session?: string;
|
|
34
|
+
focus?: boolean;
|
|
35
|
+
prompt?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface BeadsIssue {
|
|
39
|
+
id: string;
|
|
40
|
+
title: string;
|
|
41
|
+
status: string;
|
|
42
|
+
blockedBy?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Configuration
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
const WORKTREE_BASE = join(homedir(), '.local', 'share', 'term', 'worktrees');
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Helper Functions
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run bd command and parse output
|
|
57
|
+
*/
|
|
58
|
+
async function runBd(args: string[]): Promise<{ stdout: string; exitCode: number }> {
|
|
59
|
+
try {
|
|
60
|
+
const result = await $`bd ${args}`.quiet();
|
|
61
|
+
return { stdout: result.stdout.toString().trim(), exitCode: 0 };
|
|
62
|
+
} catch (error: any) {
|
|
63
|
+
return { stdout: error.stdout?.toString().trim() || '', exitCode: error.exitCode || 1 };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get a beads issue by ID
|
|
69
|
+
*/
|
|
70
|
+
async function getBeadsIssue(id: string): Promise<BeadsIssue | null> {
|
|
71
|
+
const { stdout, exitCode } = await runBd(['show', id, '--json']);
|
|
72
|
+
if (exitCode !== 0 || !stdout) return null;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const issue = JSON.parse(stdout);
|
|
76
|
+
return {
|
|
77
|
+
id: issue.id,
|
|
78
|
+
title: issue.title || issue.description?.substring(0, 50) || 'Untitled',
|
|
79
|
+
status: issue.status,
|
|
80
|
+
blockedBy: issue.blockedBy || [],
|
|
81
|
+
};
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get next ready beads issue
|
|
89
|
+
*/
|
|
90
|
+
async function getNextReadyIssue(): Promise<BeadsIssue | null> {
|
|
91
|
+
const { stdout, exitCode } = await runBd(['ready', '--json']);
|
|
92
|
+
if (exitCode !== 0 || !stdout) return null;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const issues = JSON.parse(stdout);
|
|
96
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
97
|
+
const issue = issues[0];
|
|
98
|
+
return {
|
|
99
|
+
id: issue.id,
|
|
100
|
+
title: issue.title || issue.description?.substring(0, 50) || 'Untitled',
|
|
101
|
+
status: issue.status,
|
|
102
|
+
blockedBy: issue.blockedBy || [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
} catch {
|
|
107
|
+
// Try parsing as single issue or line-based format
|
|
108
|
+
const lines = stdout.split('\n').filter(l => l.trim());
|
|
109
|
+
if (lines.length > 0) {
|
|
110
|
+
// Extract ID from first line (format: "bd-1: title" or just "bd-1")
|
|
111
|
+
const match = lines[0].match(/^(bd-\d+)/);
|
|
112
|
+
if (match) {
|
|
113
|
+
return getBeadsIssue(match[1]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Mark beads issue as in_progress
|
|
122
|
+
*/
|
|
123
|
+
async function claimIssue(id: string): Promise<boolean> {
|
|
124
|
+
const { exitCode } = await runBd(['update', id, '--status', 'in_progress']);
|
|
125
|
+
return exitCode === 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get current tmux session name
|
|
130
|
+
*/
|
|
131
|
+
async function getCurrentSession(): Promise<string | null> {
|
|
132
|
+
try {
|
|
133
|
+
const result = await tmux.executeTmux(`display-message -p '#{session_name}'`);
|
|
134
|
+
return result.trim() || null;
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Create worktree for worker
|
|
142
|
+
* Uses bd worktree when beads registry is enabled (auto .beads redirect)
|
|
143
|
+
* Falls back to WorktreeManager otherwise
|
|
144
|
+
*/
|
|
145
|
+
async function createWorktree(
|
|
146
|
+
taskId: string,
|
|
147
|
+
repoPath: string
|
|
148
|
+
): Promise<string | null> {
|
|
149
|
+
// Try bd worktree first when beads is enabled
|
|
150
|
+
if (useBeads) {
|
|
151
|
+
try {
|
|
152
|
+
// Check if worktree exists via beads
|
|
153
|
+
const existing = await beadsRegistry.getWorktree(taskId);
|
|
154
|
+
if (existing) {
|
|
155
|
+
console.log(`ℹ️ Worktree for ${taskId} already exists`);
|
|
156
|
+
return existing.path;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create via bd worktree (includes .beads redirect)
|
|
160
|
+
const info = await beadsRegistry.createWorktree(taskId);
|
|
161
|
+
if (info) {
|
|
162
|
+
return info.path;
|
|
163
|
+
}
|
|
164
|
+
// Fall through to WorktreeManager if bd worktree fails
|
|
165
|
+
console.log(`⚠️ bd worktree failed, falling back to git worktree`);
|
|
166
|
+
} catch (error: any) {
|
|
167
|
+
console.log(`⚠️ bd worktree error: ${error.message}, falling back`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Fallback to WorktreeManager
|
|
172
|
+
try {
|
|
173
|
+
const manager = new WorktreeManager({
|
|
174
|
+
baseDir: WORKTREE_BASE,
|
|
175
|
+
repoPath,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Check if worktree already exists
|
|
179
|
+
if (await manager.worktreeExists(taskId)) {
|
|
180
|
+
console.log(`ℹ️ Worktree for ${taskId} already exists`);
|
|
181
|
+
return manager.getWorktreePath(taskId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Create new worktree with branch
|
|
185
|
+
const info = await manager.createWorktree(taskId, true);
|
|
186
|
+
return info.path;
|
|
187
|
+
} catch (error: any) {
|
|
188
|
+
console.error(`⚠️ Failed to create worktree: ${error.message}`);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Spawn Claude worker in new pane
|
|
195
|
+
*/
|
|
196
|
+
async function spawnWorkerPane(
|
|
197
|
+
session: string,
|
|
198
|
+
workingDir: string
|
|
199
|
+
): Promise<{ paneId: string } | null> {
|
|
200
|
+
try {
|
|
201
|
+
// Find current window
|
|
202
|
+
const sessionObj = await tmux.findSessionByName(session);
|
|
203
|
+
if (!sessionObj) {
|
|
204
|
+
console.error(`❌ Session "${session}" not found`);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const windows = await tmux.listWindows(sessionObj.id);
|
|
209
|
+
if (!windows || windows.length === 0) {
|
|
210
|
+
console.error(`❌ No windows in session "${session}"`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const panes = await tmux.listPanes(windows[0].id);
|
|
215
|
+
if (!panes || panes.length === 0) {
|
|
216
|
+
console.error(`❌ No panes in session "${session}"`);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Split current pane horizontally (side by side)
|
|
221
|
+
const newPane = await tmux.splitPane(
|
|
222
|
+
panes[0].id,
|
|
223
|
+
'horizontal',
|
|
224
|
+
50,
|
|
225
|
+
workingDir
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (!newPane) {
|
|
229
|
+
console.error(`❌ Failed to create new pane`);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { paneId: newPane.id };
|
|
234
|
+
} catch (error: any) {
|
|
235
|
+
console.error(`❌ Error spawning worker pane: ${error.message}`);
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Start monitoring worker state and update registry
|
|
242
|
+
* Updates both beads and JSON registry during transition
|
|
243
|
+
*/
|
|
244
|
+
function startWorkerMonitoring(
|
|
245
|
+
workerId: string,
|
|
246
|
+
session: string,
|
|
247
|
+
paneId: string
|
|
248
|
+
): void {
|
|
249
|
+
const monitor = new EventMonitor(session, {
|
|
250
|
+
pollIntervalMs: 1000,
|
|
251
|
+
paneId,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
monitor.on('state_change', async (event) => {
|
|
255
|
+
if (!event.state) return;
|
|
256
|
+
|
|
257
|
+
let newState: registry.WorkerState;
|
|
258
|
+
switch (event.state.type) {
|
|
259
|
+
case 'working':
|
|
260
|
+
case 'tool_use':
|
|
261
|
+
newState = 'working';
|
|
262
|
+
break;
|
|
263
|
+
case 'idle':
|
|
264
|
+
newState = 'idle';
|
|
265
|
+
break;
|
|
266
|
+
case 'permission':
|
|
267
|
+
newState = 'permission';
|
|
268
|
+
break;
|
|
269
|
+
case 'question':
|
|
270
|
+
newState = 'question';
|
|
271
|
+
break;
|
|
272
|
+
case 'error':
|
|
273
|
+
newState = 'error';
|
|
274
|
+
break;
|
|
275
|
+
case 'complete':
|
|
276
|
+
newState = 'done';
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
return; // Don't update for unknown states
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
// Update both registries during transition
|
|
284
|
+
if (useBeads) {
|
|
285
|
+
await beadsRegistry.updateState(workerId, newState);
|
|
286
|
+
}
|
|
287
|
+
await registry.updateState(workerId, newState);
|
|
288
|
+
} catch {
|
|
289
|
+
// Ignore errors in background monitoring
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
monitor.on('poll_error', () => {
|
|
294
|
+
// Pane may have been killed - unregister worker
|
|
295
|
+
if (useBeads) {
|
|
296
|
+
beadsRegistry.unregister(workerId).catch(() => {});
|
|
297
|
+
}
|
|
298
|
+
registry.unregister(workerId).catch(() => {});
|
|
299
|
+
monitor.stop();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
monitor.start().catch(() => {
|
|
303
|
+
// Session/pane not found - ignore
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Store monitor reference for cleanup (could be enhanced)
|
|
307
|
+
// For now, monitoring is fire-and-forget
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// Main Command
|
|
312
|
+
// ============================================================================
|
|
313
|
+
|
|
314
|
+
export async function workCommand(
|
|
315
|
+
target: string,
|
|
316
|
+
options: WorkOptions = {}
|
|
317
|
+
): Promise<void> {
|
|
318
|
+
try {
|
|
319
|
+
// Get current working directory as repo path
|
|
320
|
+
const repoPath = process.cwd();
|
|
321
|
+
|
|
322
|
+
// Ensure beads daemon is running for auto-sync
|
|
323
|
+
if (useBeads) {
|
|
324
|
+
const daemonStatus = await beadsRegistry.checkDaemonStatus();
|
|
325
|
+
if (!daemonStatus.running) {
|
|
326
|
+
console.log('🔄 Starting beads daemon for auto-sync...');
|
|
327
|
+
const started = await beadsRegistry.startDaemon({ autoCommit: true });
|
|
328
|
+
if (started) {
|
|
329
|
+
console.log(' ✅ Daemon started');
|
|
330
|
+
} else {
|
|
331
|
+
console.log(' ⚠️ Daemon failed to start (non-fatal)');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 1. Resolve target
|
|
337
|
+
let issue: BeadsIssue | null = null;
|
|
338
|
+
|
|
339
|
+
if (target === 'next') {
|
|
340
|
+
console.log('🔍 Finding next ready issue...');
|
|
341
|
+
issue = await getNextReadyIssue();
|
|
342
|
+
if (!issue) {
|
|
343
|
+
console.log('ℹ️ No ready issues. Run `bd ready` to see the queue.');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
console.log(`📋 Found: ${issue.id} - "${issue.title}"`);
|
|
347
|
+
} else if (target === 'wish') {
|
|
348
|
+
console.error('❌ `term work wish` is not yet implemented. Coming in Phase 1.5.');
|
|
349
|
+
process.exit(1);
|
|
350
|
+
} else {
|
|
351
|
+
// Validate bd-id exists
|
|
352
|
+
issue = await getBeadsIssue(target);
|
|
353
|
+
if (!issue) {
|
|
354
|
+
console.error(`❌ Issue "${target}" not found. Run \`bd list\` to see issues.`);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const taskId = issue.id;
|
|
360
|
+
|
|
361
|
+
// 2. Check not already assigned (check both registries)
|
|
362
|
+
let existingWorker = useBeads
|
|
363
|
+
? await beadsRegistry.findByTask(taskId)
|
|
364
|
+
: null;
|
|
365
|
+
if (!existingWorker) {
|
|
366
|
+
existingWorker = await registry.findByTask(taskId);
|
|
367
|
+
}
|
|
368
|
+
if (existingWorker) {
|
|
369
|
+
console.error(`❌ ${taskId} already has a worker (pane ${existingWorker.paneId})`);
|
|
370
|
+
console.log(` Run \`term kill ${existingWorker.id}\` first, or work on a different issue.`);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 3. Get session
|
|
375
|
+
const session = options.session || await getCurrentSession();
|
|
376
|
+
if (!session) {
|
|
377
|
+
console.error('❌ Not in a tmux session. Attach to a session first or use --session.');
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 4. Claim in beads
|
|
382
|
+
console.log(`📝 Claiming ${taskId}...`);
|
|
383
|
+
const claimed = await claimIssue(taskId);
|
|
384
|
+
if (!claimed) {
|
|
385
|
+
console.error(`❌ Failed to claim ${taskId}. Check \`bd show ${taskId}\`.`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 5. Create worktree (unless --no-worktree)
|
|
390
|
+
let workingDir = repoPath;
|
|
391
|
+
let worktreePath: string | null = null;
|
|
392
|
+
|
|
393
|
+
if (!options.noWorktree) {
|
|
394
|
+
console.log(`🌳 Creating worktree for ${taskId}...`);
|
|
395
|
+
worktreePath = await createWorktree(taskId, repoPath);
|
|
396
|
+
if (worktreePath) {
|
|
397
|
+
workingDir = worktreePath;
|
|
398
|
+
console.log(` Created: ${worktreePath}`);
|
|
399
|
+
} else {
|
|
400
|
+
console.log(`⚠️ Worktree creation failed. Using shared repo.`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 6. Spawn Claude pane
|
|
405
|
+
console.log(`🚀 Spawning worker pane...`);
|
|
406
|
+
const paneResult = await spawnWorkerPane(session, workingDir);
|
|
407
|
+
if (!paneResult) {
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const { paneId } = paneResult;
|
|
412
|
+
|
|
413
|
+
// 7. Register worker (write to both registries during transition)
|
|
414
|
+
const worker: registry.Worker = {
|
|
415
|
+
id: taskId,
|
|
416
|
+
paneId,
|
|
417
|
+
session,
|
|
418
|
+
worktree: worktreePath,
|
|
419
|
+
taskId,
|
|
420
|
+
taskTitle: issue.title,
|
|
421
|
+
startedAt: new Date().toISOString(),
|
|
422
|
+
state: 'spawning',
|
|
423
|
+
lastStateChange: new Date().toISOString(),
|
|
424
|
+
repoPath,
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// Register in beads (creates agent bead)
|
|
428
|
+
if (useBeads) {
|
|
429
|
+
try {
|
|
430
|
+
const agentId = await beadsRegistry.ensureAgent(taskId, {
|
|
431
|
+
paneId,
|
|
432
|
+
session,
|
|
433
|
+
worktree: worktreePath,
|
|
434
|
+
repoPath,
|
|
435
|
+
taskId,
|
|
436
|
+
taskTitle: issue.title,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Bind work to agent
|
|
440
|
+
await beadsRegistry.bindWork(taskId, taskId);
|
|
441
|
+
|
|
442
|
+
// Set initial state
|
|
443
|
+
await beadsRegistry.setAgentState(taskId, 'spawning');
|
|
444
|
+
} catch (error: any) {
|
|
445
|
+
console.log(`⚠️ Beads registration failed: ${error.message} (non-fatal)`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Also register in JSON registry (parallel operation during transition)
|
|
450
|
+
await registry.register(worker);
|
|
451
|
+
|
|
452
|
+
// 8. Start Claude in pane
|
|
453
|
+
await tmux.executeCommand(paneId, 'claude', false, false);
|
|
454
|
+
|
|
455
|
+
// Wait a bit for Claude to start
|
|
456
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
457
|
+
|
|
458
|
+
// 9. Send initial prompt
|
|
459
|
+
const prompt = options.prompt || `Work on beads issue ${taskId}: "${issue.title}"
|
|
460
|
+
|
|
461
|
+
Read the issue details with: bd show ${taskId}
|
|
462
|
+
|
|
463
|
+
When you're done, commit your changes and let me know.`;
|
|
464
|
+
|
|
465
|
+
await tmux.executeCommand(paneId, prompt, false, false);
|
|
466
|
+
|
|
467
|
+
// 10. Update state to working (both registries)
|
|
468
|
+
if (useBeads) {
|
|
469
|
+
await beadsRegistry.setAgentState(taskId, 'working').catch(() => {});
|
|
470
|
+
}
|
|
471
|
+
await registry.updateState(taskId, 'working');
|
|
472
|
+
|
|
473
|
+
// 11. Start monitoring
|
|
474
|
+
startWorkerMonitoring(taskId, session, paneId);
|
|
475
|
+
|
|
476
|
+
// 12. Focus pane (unless disabled)
|
|
477
|
+
if (options.focus !== false) {
|
|
478
|
+
await tmux.executeTmux(`select-pane -t '${paneId}'`);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
console.log(`\n✅ Worker started for ${taskId}`);
|
|
482
|
+
console.log(` Pane: ${paneId}`);
|
|
483
|
+
console.log(` Session: ${session}`);
|
|
484
|
+
if (worktreePath) {
|
|
485
|
+
console.log(` Worktree: ${worktreePath}`);
|
|
486
|
+
}
|
|
487
|
+
console.log(`\nCommands:`);
|
|
488
|
+
console.log(` term workers - Check worker status`);
|
|
489
|
+
console.log(` term approve ${taskId} - Approve permissions`);
|
|
490
|
+
console.log(` term close ${taskId} - Close issue when done`);
|
|
491
|
+
console.log(` term kill ${taskId} - Force kill worker`);
|
|
492
|
+
|
|
493
|
+
} catch (error: any) {
|
|
494
|
+
console.error(`❌ Error: ${error.message}`);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|