@automagik/genie 0.260202.530 → 0.260202.1833
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/dist/claudio.js +44 -45
- package/dist/genie.js +58 -135
- package/dist/term.js +134 -67
- package/install.sh +43 -7
- package/package.json +1 -1
- package/src/claudio.ts +31 -21
- package/src/commands/launch.ts +12 -68
- package/src/genie-commands/doctor.ts +327 -0
- package/src/genie-commands/setup.ts +317 -199
- package/src/genie-commands/uninstall.ts +176 -0
- package/src/genie.ts +24 -44
- package/src/lib/claude-settings.ts +22 -64
- package/src/lib/genie-config.ts +169 -57
- 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/version.ts +1 -1
- package/src/lib/worker-registry.ts +229 -0
- package/src/term-commands/close.ts +221 -0
- package/src/term-commands/exec.ts +28 -6
- package/src/term-commands/kill.ts +143 -0
- package/src/term-commands/orchestrate.ts +844 -0
- package/src/term-commands/read.ts +6 -1
- package/src/term-commands/shortcuts.ts +14 -14
- package/src/term-commands/work.ts +415 -0
- package/src/term-commands/workers.ts +264 -0
- package/src/term.ts +201 -3
- package/src/types/genie-config.ts +49 -81
- package/src/genie-commands/hooks.ts +0 -317
- package/src/lib/hook-script.ts +0 -263
- package/src/lib/hooks/compose.ts +0 -72
- package/src/lib/hooks/index.ts +0 -163
- package/src/lib/hooks/presets/audited.ts +0 -191
- package/src/lib/hooks/presets/collaborative.ts +0 -143
- package/src/lib/hooks/presets/sandboxed.ts +0 -153
- package/src/lib/hooks/presets/supervised.ts +0 -66
- package/src/lib/hooks/utils/escape.ts +0 -46
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as logReader from '../lib/log-reader.js';
|
|
2
|
+
import { getTerminalConfig } from '../lib/genie-config.js';
|
|
2
3
|
|
|
3
4
|
export interface ReadOptions {
|
|
4
5
|
lines?: string;
|
|
@@ -15,9 +16,13 @@ export interface ReadOptions {
|
|
|
15
16
|
|
|
16
17
|
export async function readSessionLogs(sessionName: string, options: ReadOptions): Promise<void> {
|
|
17
18
|
try {
|
|
19
|
+
// Use config default if no lines specified
|
|
20
|
+
const termConfig = getTerminalConfig();
|
|
21
|
+
const defaultLines = termConfig.readLines;
|
|
22
|
+
|
|
18
23
|
// Parse options
|
|
19
24
|
const readOptions: logReader.ReadOptions = {
|
|
20
|
-
lines: options.lines ? parseInt(options.lines, 10) :
|
|
25
|
+
lines: options.lines ? parseInt(options.lines, 10) : defaultLines,
|
|
21
26
|
from: options.from ? parseInt(options.from, 10) : undefined,
|
|
22
27
|
to: options.to ? parseInt(options.to, 10) : undefined,
|
|
23
28
|
range: options.range,
|
|
@@ -15,13 +15,13 @@ export function generateTmuxConfig(): string {
|
|
|
15
15
|
# To use: add to ~/.tmux.conf or source this file
|
|
16
16
|
|
|
17
17
|
# Ctrl+T: New window (tab) in current session
|
|
18
|
-
bind-key -n C-t new-window
|
|
18
|
+
bind-key -n C-t new-window -c "#{pane_current_path}"
|
|
19
19
|
|
|
20
20
|
# Ctrl+S: Vertical split (requires stty -ixon in shell rc)
|
|
21
|
-
bind-key -n C-s split-window -v
|
|
21
|
+
bind-key -n C-s split-window -v -c "#{pane_current_path}"
|
|
22
22
|
|
|
23
|
-
# Ctrl+
|
|
24
|
-
bind-key -n C-
|
|
23
|
+
# Ctrl+Shift+S: Horizontal split
|
|
24
|
+
bind-key -n C-S split-window -h -c "#{pane_current_path}"
|
|
25
25
|
`;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -49,7 +49,7 @@ genie-new-tab() {
|
|
|
49
49
|
local session
|
|
50
50
|
session=$(tmux display-message -p '#S' 2>/dev/null)
|
|
51
51
|
if [ -n "$session" ]; then
|
|
52
|
-
tmux new-window
|
|
52
|
+
tmux new-window -c "#{pane_current_path}"
|
|
53
53
|
else
|
|
54
54
|
echo "Not in a tmux session"
|
|
55
55
|
fi
|
|
@@ -57,7 +57,7 @@ genie-new-tab() {
|
|
|
57
57
|
|
|
58
58
|
genie-vsplit() {
|
|
59
59
|
if tmux display-message -p '#S' >/dev/null 2>&1; then
|
|
60
|
-
tmux split-window -v
|
|
60
|
+
tmux split-window -v -c "#{pane_current_path}"
|
|
61
61
|
else
|
|
62
62
|
echo "Not in a tmux session"
|
|
63
63
|
fi
|
|
@@ -65,7 +65,7 @@ genie-vsplit() {
|
|
|
65
65
|
|
|
66
66
|
genie-hsplit() {
|
|
67
67
|
if tmux display-message -p '#S' >/dev/null 2>&1; then
|
|
68
|
-
tmux split-window -h
|
|
68
|
+
tmux split-window -h -c "#{pane_current_path}"
|
|
69
69
|
else
|
|
70
70
|
echo "Not in a tmux session"
|
|
71
71
|
fi
|
|
@@ -83,13 +83,13 @@ export function displayShortcuts(): void {
|
|
|
83
83
|
console.log(`
|
|
84
84
|
Warp-like Terminal Shortcuts for tmux + Termux
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
│ Shortcut
|
|
88
|
-
|
|
89
|
-
│ Ctrl+T
|
|
90
|
-
│ Ctrl+S
|
|
91
|
-
│ Ctrl+
|
|
92
|
-
|
|
86
|
+
┌──────────────────┬────────────────────────────────────────┐
|
|
87
|
+
│ Shortcut │ Action │
|
|
88
|
+
├──────────────────┼────────────────────────────────────────┤
|
|
89
|
+
│ Ctrl+T │ New tab (window) in current session │
|
|
90
|
+
│ Ctrl+S │ Vertical split in current session │
|
|
91
|
+
│ Ctrl+Shift+S │ Horizontal split in current session │
|
|
92
|
+
└──────────────────┴────────────────────────────────────────┘
|
|
93
93
|
|
|
94
94
|
Termux Extra Keys (F1-F3):
|
|
95
95
|
F1 → New tab F2 → Vertical split
|
|
@@ -0,0 +1,415 @@
|
|
|
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 { WorktreeManager } from '../lib/worktree.js';
|
|
19
|
+
import { EventMonitor, detectState } from '../lib/orchestrator/index.js';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import { homedir } from 'os';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
export interface WorkOptions {
|
|
28
|
+
noWorktree?: boolean;
|
|
29
|
+
session?: string;
|
|
30
|
+
focus?: boolean;
|
|
31
|
+
prompt?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface BeadsIssue {
|
|
35
|
+
id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
status: string;
|
|
38
|
+
blockedBy?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Configuration
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
const WORKTREE_BASE = join(homedir(), '.local', 'share', 'term', 'worktrees');
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Helper Functions
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Run bd command and parse output
|
|
53
|
+
*/
|
|
54
|
+
async function runBd(args: string[]): Promise<{ stdout: string; exitCode: number }> {
|
|
55
|
+
try {
|
|
56
|
+
const result = await $`bd ${args}`.quiet();
|
|
57
|
+
return { stdout: result.stdout.toString().trim(), exitCode: 0 };
|
|
58
|
+
} catch (error: any) {
|
|
59
|
+
return { stdout: error.stdout?.toString().trim() || '', exitCode: error.exitCode || 1 };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get a beads issue by ID
|
|
65
|
+
*/
|
|
66
|
+
async function getBeadsIssue(id: string): Promise<BeadsIssue | null> {
|
|
67
|
+
const { stdout, exitCode } = await runBd(['show', id, '--json']);
|
|
68
|
+
if (exitCode !== 0 || !stdout) return null;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const issue = JSON.parse(stdout);
|
|
72
|
+
return {
|
|
73
|
+
id: issue.id,
|
|
74
|
+
title: issue.title || issue.description?.substring(0, 50) || 'Untitled',
|
|
75
|
+
status: issue.status,
|
|
76
|
+
blockedBy: issue.blockedBy || [],
|
|
77
|
+
};
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get next ready beads issue
|
|
85
|
+
*/
|
|
86
|
+
async function getNextReadyIssue(): Promise<BeadsIssue | null> {
|
|
87
|
+
const { stdout, exitCode } = await runBd(['ready', '--json']);
|
|
88
|
+
if (exitCode !== 0 || !stdout) return null;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const issues = JSON.parse(stdout);
|
|
92
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
93
|
+
const issue = issues[0];
|
|
94
|
+
return {
|
|
95
|
+
id: issue.id,
|
|
96
|
+
title: issue.title || issue.description?.substring(0, 50) || 'Untitled',
|
|
97
|
+
status: issue.status,
|
|
98
|
+
blockedBy: issue.blockedBy || [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
} catch {
|
|
103
|
+
// Try parsing as single issue or line-based format
|
|
104
|
+
const lines = stdout.split('\n').filter(l => l.trim());
|
|
105
|
+
if (lines.length > 0) {
|
|
106
|
+
// Extract ID from first line (format: "bd-1: title" or just "bd-1")
|
|
107
|
+
const match = lines[0].match(/^(bd-\d+)/);
|
|
108
|
+
if (match) {
|
|
109
|
+
return getBeadsIssue(match[1]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Mark beads issue as in_progress
|
|
118
|
+
*/
|
|
119
|
+
async function claimIssue(id: string): Promise<boolean> {
|
|
120
|
+
const { exitCode } = await runBd(['update', id, '--status', 'in_progress']);
|
|
121
|
+
return exitCode === 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get current tmux session name
|
|
126
|
+
*/
|
|
127
|
+
async function getCurrentSession(): Promise<string | null> {
|
|
128
|
+
try {
|
|
129
|
+
const result = await tmux.executeTmux(`display-message -p '#{session_name}'`);
|
|
130
|
+
return result.trim() || null;
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create worktree for worker
|
|
138
|
+
*/
|
|
139
|
+
async function createWorktree(
|
|
140
|
+
taskId: string,
|
|
141
|
+
repoPath: string
|
|
142
|
+
): Promise<string | null> {
|
|
143
|
+
try {
|
|
144
|
+
const manager = new WorktreeManager({
|
|
145
|
+
baseDir: WORKTREE_BASE,
|
|
146
|
+
repoPath,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Check if worktree already exists
|
|
150
|
+
if (await manager.worktreeExists(taskId)) {
|
|
151
|
+
console.log(`ℹ️ Worktree for ${taskId} already exists`);
|
|
152
|
+
return manager.getWorktreePath(taskId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Create new worktree with branch
|
|
156
|
+
const info = await manager.createWorktree(taskId, true);
|
|
157
|
+
return info.path;
|
|
158
|
+
} catch (error: any) {
|
|
159
|
+
console.error(`⚠️ Failed to create worktree: ${error.message}`);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Spawn Claude worker in new pane
|
|
166
|
+
*/
|
|
167
|
+
async function spawnWorkerPane(
|
|
168
|
+
session: string,
|
|
169
|
+
workingDir: string
|
|
170
|
+
): Promise<{ paneId: string } | null> {
|
|
171
|
+
try {
|
|
172
|
+
// Find current window
|
|
173
|
+
const sessionObj = await tmux.findSessionByName(session);
|
|
174
|
+
if (!sessionObj) {
|
|
175
|
+
console.error(`❌ Session "${session}" not found`);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const windows = await tmux.listWindows(sessionObj.id);
|
|
180
|
+
if (!windows || windows.length === 0) {
|
|
181
|
+
console.error(`❌ No windows in session "${session}"`);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const panes = await tmux.listPanes(windows[0].id);
|
|
186
|
+
if (!panes || panes.length === 0) {
|
|
187
|
+
console.error(`❌ No panes in session "${session}"`);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Split current pane horizontally (side by side)
|
|
192
|
+
const newPane = await tmux.splitPane(
|
|
193
|
+
panes[0].id,
|
|
194
|
+
'horizontal',
|
|
195
|
+
50,
|
|
196
|
+
workingDir
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (!newPane) {
|
|
200
|
+
console.error(`❌ Failed to create new pane`);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { paneId: newPane.id };
|
|
205
|
+
} catch (error: any) {
|
|
206
|
+
console.error(`❌ Error spawning worker pane: ${error.message}`);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Start monitoring worker state and update registry
|
|
213
|
+
*/
|
|
214
|
+
function startWorkerMonitoring(
|
|
215
|
+
workerId: string,
|
|
216
|
+
session: string,
|
|
217
|
+
paneId: string
|
|
218
|
+
): void {
|
|
219
|
+
const monitor = new EventMonitor(session, {
|
|
220
|
+
pollIntervalMs: 1000,
|
|
221
|
+
paneId,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
monitor.on('state_change', async (event) => {
|
|
225
|
+
if (!event.state) return;
|
|
226
|
+
|
|
227
|
+
let newState: registry.WorkerState;
|
|
228
|
+
switch (event.state.type) {
|
|
229
|
+
case 'working':
|
|
230
|
+
case 'tool_use':
|
|
231
|
+
newState = 'working';
|
|
232
|
+
break;
|
|
233
|
+
case 'idle':
|
|
234
|
+
newState = 'idle';
|
|
235
|
+
break;
|
|
236
|
+
case 'permission':
|
|
237
|
+
newState = 'permission';
|
|
238
|
+
break;
|
|
239
|
+
case 'question':
|
|
240
|
+
newState = 'question';
|
|
241
|
+
break;
|
|
242
|
+
case 'error':
|
|
243
|
+
newState = 'error';
|
|
244
|
+
break;
|
|
245
|
+
case 'complete':
|
|
246
|
+
newState = 'done';
|
|
247
|
+
break;
|
|
248
|
+
default:
|
|
249
|
+
return; // Don't update for unknown states
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
await registry.updateState(workerId, newState);
|
|
254
|
+
} catch {
|
|
255
|
+
// Ignore errors in background monitoring
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
monitor.on('poll_error', () => {
|
|
260
|
+
// Pane may have been killed - unregister worker
|
|
261
|
+
registry.unregister(workerId).catch(() => {});
|
|
262
|
+
monitor.stop();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
monitor.start().catch(() => {
|
|
266
|
+
// Session/pane not found - ignore
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Store monitor reference for cleanup (could be enhanced)
|
|
270
|
+
// For now, monitoring is fire-and-forget
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ============================================================================
|
|
274
|
+
// Main Command
|
|
275
|
+
// ============================================================================
|
|
276
|
+
|
|
277
|
+
export async function workCommand(
|
|
278
|
+
target: string,
|
|
279
|
+
options: WorkOptions = {}
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
try {
|
|
282
|
+
// Get current working directory as repo path
|
|
283
|
+
const repoPath = process.cwd();
|
|
284
|
+
|
|
285
|
+
// 1. Resolve target
|
|
286
|
+
let issue: BeadsIssue | null = null;
|
|
287
|
+
|
|
288
|
+
if (target === 'next') {
|
|
289
|
+
console.log('🔍 Finding next ready issue...');
|
|
290
|
+
issue = await getNextReadyIssue();
|
|
291
|
+
if (!issue) {
|
|
292
|
+
console.log('ℹ️ No ready issues. Run `bd ready` to see the queue.');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
console.log(`📋 Found: ${issue.id} - "${issue.title}"`);
|
|
296
|
+
} else if (target === 'wish') {
|
|
297
|
+
console.error('❌ `term work wish` is not yet implemented. Coming in Phase 1.5.');
|
|
298
|
+
process.exit(1);
|
|
299
|
+
} else {
|
|
300
|
+
// Validate bd-id exists
|
|
301
|
+
issue = await getBeadsIssue(target);
|
|
302
|
+
if (!issue) {
|
|
303
|
+
console.error(`❌ Issue "${target}" not found. Run \`bd list\` to see issues.`);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const taskId = issue.id;
|
|
309
|
+
|
|
310
|
+
// 2. Check not already assigned
|
|
311
|
+
const existingWorker = await registry.findByTask(taskId);
|
|
312
|
+
if (existingWorker) {
|
|
313
|
+
console.error(`❌ ${taskId} already has a worker (pane ${existingWorker.paneId})`);
|
|
314
|
+
console.log(` Run \`term kill ${existingWorker.id}\` first, or work on a different issue.`);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 3. Get session
|
|
319
|
+
const session = options.session || await getCurrentSession();
|
|
320
|
+
if (!session) {
|
|
321
|
+
console.error('❌ Not in a tmux session. Attach to a session first or use --session.');
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 4. Claim in beads
|
|
326
|
+
console.log(`📝 Claiming ${taskId}...`);
|
|
327
|
+
const claimed = await claimIssue(taskId);
|
|
328
|
+
if (!claimed) {
|
|
329
|
+
console.error(`❌ Failed to claim ${taskId}. Check \`bd show ${taskId}\`.`);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 5. Create worktree (unless --no-worktree)
|
|
334
|
+
let workingDir = repoPath;
|
|
335
|
+
let worktreePath: string | null = null;
|
|
336
|
+
|
|
337
|
+
if (!options.noWorktree) {
|
|
338
|
+
console.log(`🌳 Creating worktree for ${taskId}...`);
|
|
339
|
+
worktreePath = await createWorktree(taskId, repoPath);
|
|
340
|
+
if (worktreePath) {
|
|
341
|
+
workingDir = worktreePath;
|
|
342
|
+
console.log(` Created: ${worktreePath}`);
|
|
343
|
+
} else {
|
|
344
|
+
console.log(`⚠️ Worktree creation failed. Using shared repo.`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 6. Spawn Claude pane
|
|
349
|
+
console.log(`🚀 Spawning worker pane...`);
|
|
350
|
+
const paneResult = await spawnWorkerPane(session, workingDir);
|
|
351
|
+
if (!paneResult) {
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const { paneId } = paneResult;
|
|
356
|
+
|
|
357
|
+
// 7. Register worker
|
|
358
|
+
const worker: registry.Worker = {
|
|
359
|
+
id: taskId,
|
|
360
|
+
paneId,
|
|
361
|
+
session,
|
|
362
|
+
worktree: worktreePath,
|
|
363
|
+
taskId,
|
|
364
|
+
taskTitle: issue.title,
|
|
365
|
+
startedAt: new Date().toISOString(),
|
|
366
|
+
state: 'spawning',
|
|
367
|
+
lastStateChange: new Date().toISOString(),
|
|
368
|
+
repoPath,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
await registry.register(worker);
|
|
372
|
+
|
|
373
|
+
// 8. Start Claude in pane
|
|
374
|
+
await tmux.executeCommand(paneId, 'claude', false, false);
|
|
375
|
+
|
|
376
|
+
// Wait a bit for Claude to start
|
|
377
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
378
|
+
|
|
379
|
+
// 9. Send initial prompt
|
|
380
|
+
const prompt = options.prompt || `Work on beads issue ${taskId}: "${issue.title}"
|
|
381
|
+
|
|
382
|
+
Read the issue details with: bd show ${taskId}
|
|
383
|
+
|
|
384
|
+
When you're done, commit your changes and let me know.`;
|
|
385
|
+
|
|
386
|
+
await tmux.executeCommand(paneId, prompt, false, false);
|
|
387
|
+
|
|
388
|
+
// 10. Update state to working
|
|
389
|
+
await registry.updateState(taskId, 'working');
|
|
390
|
+
|
|
391
|
+
// 11. Start monitoring
|
|
392
|
+
startWorkerMonitoring(taskId, session, paneId);
|
|
393
|
+
|
|
394
|
+
// 12. Focus pane (unless disabled)
|
|
395
|
+
if (options.focus !== false) {
|
|
396
|
+
await tmux.executeTmux(`select-pane -t '${paneId}'`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log(`\n✅ Worker started for ${taskId}`);
|
|
400
|
+
console.log(` Pane: ${paneId}`);
|
|
401
|
+
console.log(` Session: ${session}`);
|
|
402
|
+
if (worktreePath) {
|
|
403
|
+
console.log(` Worktree: ${worktreePath}`);
|
|
404
|
+
}
|
|
405
|
+
console.log(`\nCommands:`);
|
|
406
|
+
console.log(` term workers - Check worker status`);
|
|
407
|
+
console.log(` term approve ${taskId} - Approve permissions`);
|
|
408
|
+
console.log(` term close ${taskId} - Close issue when done`);
|
|
409
|
+
console.log(` term kill ${taskId} - Force kill worker`);
|
|
410
|
+
|
|
411
|
+
} catch (error: any) {
|
|
412
|
+
console.error(`❌ Error: ${error.message}`);
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
}
|