@covibes/zeroshot 1.0.1
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/CHANGELOG.md +167 -0
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/cli/index.js +3990 -0
- package/cluster-templates/base-templates/debug-workflow.json +181 -0
- package/cluster-templates/base-templates/full-workflow.json +455 -0
- package/cluster-templates/base-templates/single-worker.json +48 -0
- package/cluster-templates/base-templates/worker-validator.json +131 -0
- package/cluster-templates/conductor-bootstrap.json +122 -0
- package/cluster-templates/conductor-junior-bootstrap.json +69 -0
- package/docker/zeroshot-cluster/Dockerfile +132 -0
- package/lib/completion.js +174 -0
- package/lib/id-detector.js +53 -0
- package/lib/settings.js +97 -0
- package/lib/stream-json-parser.js +236 -0
- package/package.json +121 -0
- package/src/agent/agent-config.js +121 -0
- package/src/agent/agent-context-builder.js +241 -0
- package/src/agent/agent-hook-executor.js +329 -0
- package/src/agent/agent-lifecycle.js +555 -0
- package/src/agent/agent-stuck-detector.js +256 -0
- package/src/agent/agent-task-executor.js +1034 -0
- package/src/agent/agent-trigger-evaluator.js +67 -0
- package/src/agent-wrapper.js +459 -0
- package/src/agents/git-pusher-agent.json +20 -0
- package/src/attach/attach-client.js +438 -0
- package/src/attach/attach-server.js +543 -0
- package/src/attach/index.js +35 -0
- package/src/attach/protocol.js +220 -0
- package/src/attach/ring-buffer.js +121 -0
- package/src/attach/socket-discovery.js +242 -0
- package/src/claude-task-runner.js +468 -0
- package/src/config-router.js +80 -0
- package/src/config-validator.js +598 -0
- package/src/github.js +103 -0
- package/src/isolation-manager.js +1042 -0
- package/src/ledger.js +429 -0
- package/src/logic-engine.js +223 -0
- package/src/message-bus-bridge.js +139 -0
- package/src/message-bus.js +202 -0
- package/src/name-generator.js +232 -0
- package/src/orchestrator.js +1938 -0
- package/src/schemas/sub-cluster.js +156 -0
- package/src/sub-cluster-wrapper.js +545 -0
- package/src/task-runner.js +28 -0
- package/src/template-resolver.js +347 -0
- package/src/tui/CHANGES.txt +133 -0
- package/src/tui/LAYOUT.md +261 -0
- package/src/tui/README.txt +192 -0
- package/src/tui/TWO-LEVEL-NAVIGATION.md +186 -0
- package/src/tui/data-poller.js +325 -0
- package/src/tui/demo.js +208 -0
- package/src/tui/formatters.js +123 -0
- package/src/tui/index.js +193 -0
- package/src/tui/keybindings.js +383 -0
- package/src/tui/layout.js +317 -0
- package/src/tui/renderer.js +194 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AttachClient - Terminal client for attaching to running tasks/agents
|
|
3
|
+
*
|
|
4
|
+
* Key sequences:
|
|
5
|
+
* - Ctrl+C: Clean detach (return to shell, task continues running)
|
|
6
|
+
* - Ctrl+B d: Also detach (for tmux muscle memory)
|
|
7
|
+
* - Ctrl+B ?: Show help
|
|
8
|
+
* - Ctrl+B c: Send SIGINT to process (interrupt agent - USE WITH CAUTION)
|
|
9
|
+
* - Ctrl+Z: Forward SIGTSTP to process
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Raw terminal mode (passes through all input)
|
|
13
|
+
* - Output history replay on attach
|
|
14
|
+
* - Terminal resize forwarding
|
|
15
|
+
* - Graceful cleanup on exit
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const net = require('net');
|
|
19
|
+
const EventEmitter = require('events');
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
|
|
22
|
+
const protocol = require('./protocol');
|
|
23
|
+
|
|
24
|
+
// Key codes
|
|
25
|
+
const CTRL_B = '\x02';
|
|
26
|
+
const CTRL_C = '\x03';
|
|
27
|
+
const CTRL_Z = '\x1a';
|
|
28
|
+
|
|
29
|
+
// Detach timeout (ms) - how long to wait for second key after Ctrl+B
|
|
30
|
+
const DETACH_TIMEOUT = 500;
|
|
31
|
+
|
|
32
|
+
class AttachClient extends EventEmitter {
|
|
33
|
+
/**
|
|
34
|
+
* @param {object} options
|
|
35
|
+
* @param {string} options.socketPath - Unix socket path to connect to
|
|
36
|
+
* @param {object} [options.stdin] - Input stream (default process.stdin)
|
|
37
|
+
* @param {object} [options.stdout] - Output stream (default process.stdout)
|
|
38
|
+
*/
|
|
39
|
+
constructor(options) {
|
|
40
|
+
super();
|
|
41
|
+
|
|
42
|
+
if (!options.socketPath) {
|
|
43
|
+
throw new Error('AttachClient: socketPath is required');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.socketPath = options.socketPath;
|
|
47
|
+
this.stdin = options.stdin || process.stdin;
|
|
48
|
+
this.stdout = options.stdout || process.stdout;
|
|
49
|
+
|
|
50
|
+
this.clientId = crypto.randomUUID();
|
|
51
|
+
this.socket = null;
|
|
52
|
+
this.decoder = new protocol.MessageDecoder();
|
|
53
|
+
this.connected = false;
|
|
54
|
+
this.wasRawMode = null;
|
|
55
|
+
|
|
56
|
+
// Ctrl+B sequence detection
|
|
57
|
+
this.ctrlBPressed = false;
|
|
58
|
+
this.ctrlBTimeout = null;
|
|
59
|
+
|
|
60
|
+
// Bind handlers
|
|
61
|
+
this._onSocketData = this._onSocketData.bind(this);
|
|
62
|
+
this._onSocketClose = this._onSocketClose.bind(this);
|
|
63
|
+
this._onSocketError = this._onSocketError.bind(this);
|
|
64
|
+
this._onStdinData = this._onStdinData.bind(this);
|
|
65
|
+
this._onResize = this._onResize.bind(this);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Connect to the attach server
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
connect() {
|
|
73
|
+
if (this.connected) {
|
|
74
|
+
throw new Error('AttachClient: Already connected');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
this.socket = net.createConnection(this.socketPath);
|
|
79
|
+
|
|
80
|
+
this.socket.on('connect', () => {
|
|
81
|
+
this.connected = true;
|
|
82
|
+
|
|
83
|
+
// Send attach message with terminal dimensions
|
|
84
|
+
const cols = this.stdout.columns || 80;
|
|
85
|
+
const rows = this.stdout.rows || 24;
|
|
86
|
+
|
|
87
|
+
this.socket.write(protocol.encode(protocol.createAttachMessage(this.clientId, cols, rows)));
|
|
88
|
+
|
|
89
|
+
// Set up terminal
|
|
90
|
+
this._setupTerminal();
|
|
91
|
+
|
|
92
|
+
// Set up socket handlers
|
|
93
|
+
this.socket.on('data', this._onSocketData);
|
|
94
|
+
this.socket.on('close', this._onSocketClose);
|
|
95
|
+
this.socket.on('error', this._onSocketError);
|
|
96
|
+
|
|
97
|
+
resolve();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.socket.on('error', (err) => {
|
|
101
|
+
if (!this.connected) {
|
|
102
|
+
reject(err);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Connection timeout
|
|
107
|
+
const timeout = setTimeout(() => {
|
|
108
|
+
if (!this.connected) {
|
|
109
|
+
this.socket.destroy();
|
|
110
|
+
reject(new Error('Connection timeout'));
|
|
111
|
+
}
|
|
112
|
+
}, 5000);
|
|
113
|
+
|
|
114
|
+
this.socket.on('connect', () => clearTimeout(timeout));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Disconnect from the server
|
|
120
|
+
*/
|
|
121
|
+
disconnect() {
|
|
122
|
+
if (!this.connected) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Send detach message
|
|
127
|
+
try {
|
|
128
|
+
this.socket.write(protocol.encode(protocol.createDetachMessage(this.clientId)));
|
|
129
|
+
} catch {
|
|
130
|
+
// Ignore
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this._cleanup();
|
|
134
|
+
this.emit('detach');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Send a signal to the remote process
|
|
139
|
+
* @param {string} signal - Signal name
|
|
140
|
+
*/
|
|
141
|
+
sendSignal(signal) {
|
|
142
|
+
if (!this.connected) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
this.socket.write(protocol.encode(protocol.createSignalMessage(signal)));
|
|
148
|
+
} catch {
|
|
149
|
+
// Ignore
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────
|
|
154
|
+
// Private methods
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Set up terminal for raw mode
|
|
159
|
+
* @private
|
|
160
|
+
*/
|
|
161
|
+
_setupTerminal() {
|
|
162
|
+
// Enable raw mode if stdin is a TTY
|
|
163
|
+
if (this.stdin.isTTY && this.stdin.setRawMode) {
|
|
164
|
+
this.wasRawMode = this.stdin.isRaw;
|
|
165
|
+
this.stdin.setRawMode(true);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Resume stdin (may be paused)
|
|
169
|
+
this.stdin.resume();
|
|
170
|
+
|
|
171
|
+
// Listen for input
|
|
172
|
+
this.stdin.on('data', this._onStdinData);
|
|
173
|
+
|
|
174
|
+
// Listen for resize events
|
|
175
|
+
if (this.stdout.isTTY) {
|
|
176
|
+
this.stdout.on('resize', this._onResize);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Handle process signals for cleanup
|
|
180
|
+
process.on('SIGINT', () => {
|
|
181
|
+
// Clean detach on Ctrl+C - task continues running
|
|
182
|
+
this.disconnect();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
process.on('SIGTERM', () => {
|
|
186
|
+
this._cleanup();
|
|
187
|
+
process.exit(0);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Restore terminal state
|
|
193
|
+
* @private
|
|
194
|
+
*/
|
|
195
|
+
_restoreTerminal() {
|
|
196
|
+
// Restore raw mode
|
|
197
|
+
if (this.stdin.isTTY && this.stdin.setRawMode && this.wasRawMode !== null) {
|
|
198
|
+
this.stdin.setRawMode(this.wasRawMode);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Remove listeners
|
|
202
|
+
this.stdin.removeListener('data', this._onStdinData);
|
|
203
|
+
if (this.stdout.isTTY) {
|
|
204
|
+
this.stdout.removeListener('resize', this._onResize);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Pause stdin
|
|
208
|
+
this.stdin.pause();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Handle data from socket
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
_onSocketData(data) {
|
|
216
|
+
try {
|
|
217
|
+
const messages = this.decoder.feed(data);
|
|
218
|
+
for (const msg of messages) {
|
|
219
|
+
this._handleMessage(msg);
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
this.emit('error', new Error(`Protocol error: ${e.message}`));
|
|
223
|
+
this._cleanup();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Handle message from server
|
|
229
|
+
* @private
|
|
230
|
+
*/
|
|
231
|
+
_handleMessage(message) {
|
|
232
|
+
switch (message.type) {
|
|
233
|
+
case protocol.MessageType.OUTPUT: {
|
|
234
|
+
const data = protocol.decodeData(message);
|
|
235
|
+
if (data) {
|
|
236
|
+
this.stdout.write(data);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
case protocol.MessageType.HISTORY: {
|
|
242
|
+
const data = protocol.decodeData(message);
|
|
243
|
+
if (data) {
|
|
244
|
+
this.stdout.write(data);
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case protocol.MessageType.STATE: {
|
|
250
|
+
this.emit('state', message);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case protocol.MessageType.EXIT: {
|
|
255
|
+
const { code, signal } = message;
|
|
256
|
+
this.emit('exit', { code, signal });
|
|
257
|
+
this._cleanup();
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case protocol.MessageType.ERROR: {
|
|
262
|
+
this.emit('error', new Error(message.message));
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Handle stdin data
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
_onStdinData(data) {
|
|
273
|
+
const str = data.toString();
|
|
274
|
+
|
|
275
|
+
// Handle Ctrl+B sequence
|
|
276
|
+
if (this.ctrlBPressed) {
|
|
277
|
+
this.ctrlBPressed = false;
|
|
278
|
+
if (this.ctrlBTimeout) {
|
|
279
|
+
clearTimeout(this.ctrlBTimeout);
|
|
280
|
+
this.ctrlBTimeout = null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check for command keys
|
|
284
|
+
if (str === 'd' || str === 'D') {
|
|
285
|
+
// Detach
|
|
286
|
+
this.disconnect();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (str === 'c' || str === 'C') {
|
|
291
|
+
// Send SIGINT to process (interrupt agent - USE WITH CAUTION)
|
|
292
|
+
this.stdout.write('\r\n⚠️ Sending SIGINT to agent (interrupting task)...\r\n');
|
|
293
|
+
this.sendSignal('SIGINT');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (str === '?') {
|
|
298
|
+
// Show help
|
|
299
|
+
this._showHelp();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Not a recognized command, forward Ctrl+B + this key
|
|
304
|
+
this._forwardInput(Buffer.from([0x02]));
|
|
305
|
+
this._forwardInput(data);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check for Ctrl+B
|
|
310
|
+
if (str === CTRL_B) {
|
|
311
|
+
this.ctrlBPressed = true;
|
|
312
|
+
|
|
313
|
+
// Set timeout to forward if no follow-up key
|
|
314
|
+
this.ctrlBTimeout = setTimeout(() => {
|
|
315
|
+
if (this.ctrlBPressed) {
|
|
316
|
+
this.ctrlBPressed = false;
|
|
317
|
+
this._forwardInput(data);
|
|
318
|
+
}
|
|
319
|
+
}, DETACH_TIMEOUT);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check for Ctrl+C - clean detach (task continues running)
|
|
324
|
+
if (str === CTRL_C) {
|
|
325
|
+
this.disconnect();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check for Ctrl+Z
|
|
330
|
+
if (str === CTRL_Z) {
|
|
331
|
+
this.sendSignal('SIGTSTP');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Forward other input (future interactive mode)
|
|
336
|
+
this._forwardInput(data);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Forward input to remote process
|
|
341
|
+
* @private
|
|
342
|
+
*/
|
|
343
|
+
_forwardInput(data) {
|
|
344
|
+
if (!this.connected) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
this.socket.write(protocol.encode(protocol.createStdinMessage(data)));
|
|
350
|
+
} catch {
|
|
351
|
+
// Ignore
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Handle terminal resize
|
|
357
|
+
* @private
|
|
358
|
+
*/
|
|
359
|
+
_onResize() {
|
|
360
|
+
if (!this.connected) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const cols = this.stdout.columns;
|
|
365
|
+
const rows = this.stdout.rows;
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
this.socket.write(protocol.encode(protocol.createResizeMessage(cols, rows)));
|
|
369
|
+
} catch {
|
|
370
|
+
// Ignore
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Handle socket close
|
|
376
|
+
* @private
|
|
377
|
+
*/
|
|
378
|
+
_onSocketClose() {
|
|
379
|
+
if (this.connected) {
|
|
380
|
+
this.emit('close');
|
|
381
|
+
this._cleanup();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Handle socket error
|
|
387
|
+
* @private
|
|
388
|
+
*/
|
|
389
|
+
_onSocketError(err) {
|
|
390
|
+
this.emit('error', err);
|
|
391
|
+
this._cleanup();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Show help message
|
|
396
|
+
* @private
|
|
397
|
+
*/
|
|
398
|
+
_showHelp() {
|
|
399
|
+
const help = `
|
|
400
|
+
\r\n╭──────────────────────────────────────────────────────────╮
|
|
401
|
+
\r\n│ Vibe Attach - Key Bindings │
|
|
402
|
+
\r\n├──────────────────────────────────────────────────────────┤
|
|
403
|
+
\r\n│ Ctrl+C Detach (task continues running) │
|
|
404
|
+
\r\n│ Ctrl+B d Also detach (for tmux muscle memory) │
|
|
405
|
+
\r\n│ Ctrl+B ? Show this help │
|
|
406
|
+
\r\n│ Ctrl+B c ⚠️ Interrupt agent (sends SIGINT) │
|
|
407
|
+
\r\n│ Ctrl+Z Suspend process (sends SIGTSTP) │
|
|
408
|
+
\r\n╰──────────────────────────────────────────────────────────╯
|
|
409
|
+
\r\n`;
|
|
410
|
+
this.stdout.write(help);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Clean up resources
|
|
415
|
+
* @private
|
|
416
|
+
*/
|
|
417
|
+
_cleanup() {
|
|
418
|
+
this.connected = false;
|
|
419
|
+
|
|
420
|
+
// Clear Ctrl+B timeout
|
|
421
|
+
if (this.ctrlBTimeout) {
|
|
422
|
+
clearTimeout(this.ctrlBTimeout);
|
|
423
|
+
this.ctrlBTimeout = null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Restore terminal
|
|
427
|
+
this._restoreTerminal();
|
|
428
|
+
|
|
429
|
+
// Close socket
|
|
430
|
+
if (this.socket) {
|
|
431
|
+
this.socket.removeAllListeners();
|
|
432
|
+
this.socket.destroy();
|
|
433
|
+
this.socket = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
module.exports = AttachClient;
|