@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.
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Kill command - Force kill a worker
3
+ *
4
+ * Usage:
5
+ * term kill <worker> - Kill worker by ID or pane
6
+ *
7
+ * Options:
8
+ * -y, --yes - Skip confirmation
9
+ * --keep-worktree - Don't remove the worktree
10
+ */
11
+
12
+ import { confirm } from '@inquirer/prompts';
13
+ import * as tmux from '../lib/tmux.js';
14
+ import * as registry from '../lib/worker-registry.js';
15
+ import * as beadsRegistry from '../lib/beads-registry.js';
16
+ import { WorktreeManager } from '../lib/worktree.js';
17
+ import { join } from 'path';
18
+ import { homedir } from 'os';
19
+
20
+ // Use beads registry when enabled
21
+ const useBeads = beadsRegistry.isBeadsRegistryEnabled();
22
+
23
+ // ============================================================================
24
+ // Types
25
+ // ============================================================================
26
+
27
+ export interface KillOptions {
28
+ yes?: boolean;
29
+ keepWorktree?: boolean;
30
+ }
31
+
32
+ // ============================================================================
33
+ // Configuration
34
+ // ============================================================================
35
+
36
+ const WORKTREE_BASE = join(homedir(), '.local', 'share', 'term', 'worktrees');
37
+
38
+ // ============================================================================
39
+ // Helper Functions
40
+ // ============================================================================
41
+
42
+ /**
43
+ * Kill worker pane
44
+ */
45
+ async function killWorkerPane(paneId: string): Promise<boolean> {
46
+ try {
47
+ await tmux.killPane(paneId);
48
+ return true;
49
+ } catch {
50
+ return false; // Pane may already be gone
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Remove worktree
56
+ * Uses bd worktree when beads registry is enabled
57
+ * Falls back to WorktreeManager otherwise
58
+ */
59
+ async function removeWorktree(taskId: string, repoPath: string): Promise<boolean> {
60
+ // Try bd worktree first when beads is enabled
61
+ if (useBeads) {
62
+ try {
63
+ const removed = await beadsRegistry.removeWorktree(taskId);
64
+ if (removed) return true;
65
+ // Fall through to WorktreeManager if bd worktree fails
66
+ } catch {
67
+ // Fall through
68
+ }
69
+ }
70
+
71
+ // Fallback to WorktreeManager
72
+ try {
73
+ const manager = new WorktreeManager({
74
+ baseDir: WORKTREE_BASE,
75
+ repoPath,
76
+ });
77
+
78
+ if (await manager.worktreeExists(taskId)) {
79
+ await manager.removeWorktree(taskId);
80
+ return true;
81
+ }
82
+ return true; // Already doesn't exist
83
+ } catch (error: any) {
84
+ console.error(`⚠️ Failed to remove worktree: ${error.message}`);
85
+ return false;
86
+ }
87
+ }
88
+
89
+ // ============================================================================
90
+ // Main Command
91
+ // ============================================================================
92
+
93
+ export async function killCommand(
94
+ target: string,
95
+ options: KillOptions = {}
96
+ ): Promise<void> {
97
+ try {
98
+ // Find worker by ID or pane (check both registries during transition)
99
+ let worker = await registry.get(target);
100
+
101
+ if (!worker && useBeads) {
102
+ // Try beads registry
103
+ worker = await beadsRegistry.getWorker(target);
104
+ }
105
+
106
+ if (!worker) {
107
+ // Try finding by pane ID
108
+ worker = await registry.findByPane(target);
109
+ }
110
+
111
+ if (!worker && useBeads) {
112
+ worker = await beadsRegistry.findByPane(target);
113
+ }
114
+
115
+ if (!worker) {
116
+ // Try finding by task ID
117
+ worker = await registry.findByTask(target);
118
+ }
119
+
120
+ if (!worker && useBeads) {
121
+ worker = await beadsRegistry.findByTask(target);
122
+ }
123
+
124
+ if (!worker) {
125
+ console.error(`❌ Worker "${target}" not found.`);
126
+ console.log(` Run \`term workers\` to see active workers.`);
127
+ process.exit(1);
128
+ }
129
+
130
+ // Confirm with user
131
+ if (!options.yes) {
132
+ const confirmed = await confirm({
133
+ message: `Kill worker ${worker.id} (pane ${worker.paneId})?`,
134
+ default: true,
135
+ });
136
+
137
+ if (!confirmed) {
138
+ console.log('Cancelled.');
139
+ return;
140
+ }
141
+ }
142
+
143
+ // 1. Kill worker pane
144
+ console.log(`💀 Killing worker pane ${worker.paneId}...`);
145
+ const killed = await killWorkerPane(worker.paneId);
146
+ if (killed) {
147
+ console.log(` ✅ Pane killed`);
148
+ } else {
149
+ console.log(` ℹ️ Pane already gone`);
150
+ }
151
+
152
+ // 2. Remove worktree (unless --keep-worktree)
153
+ if (worker.worktree && !options.keepWorktree) {
154
+ console.log(`🌳 Removing worktree...`);
155
+ const removed = await removeWorktree(worker.taskId, worker.repoPath);
156
+ if (removed) {
157
+ console.log(` ✅ Worktree removed`);
158
+ }
159
+ }
160
+
161
+ // 3. Unregister worker from both registries
162
+ if (useBeads) {
163
+ try {
164
+ // Unbind work from agent
165
+ await beadsRegistry.unbindWork(worker.id);
166
+ // Set agent state to error (killed, not done)
167
+ await beadsRegistry.setAgentState(worker.id, 'error');
168
+ // Delete agent bead
169
+ await beadsRegistry.deleteAgent(worker.id);
170
+ } catch {
171
+ // Non-fatal if beads cleanup fails
172
+ }
173
+ }
174
+ await registry.unregister(worker.id);
175
+ console.log(` ✅ Worker unregistered`);
176
+
177
+ // 4. Note about task status
178
+ console.log(`\n⚠️ Task ${worker.taskId} is still in_progress in beads.`);
179
+ console.log(` Run \`bd update ${worker.taskId} --status open\` to reopen,`);
180
+ console.log(` or \`term work ${worker.taskId}\` to start a new worker.`);
181
+
182
+ } catch (error: any) {
183
+ console.error(`❌ Error: ${error.message}`);
184
+ process.exit(1);
185
+ }
186
+ }