@covibes/zeroshot 5.4.0 → 5.4.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.
Files changed (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
@@ -1,285 +0,0 @@
1
- /**
2
- * Git Pusher Agent Template
3
- *
4
- * Generates platform-specific git-pusher agent configurations.
5
- * Eliminates duplication across github/gitlab/azure JSON files.
6
- *
7
- * Single source of truth for:
8
- * - Trigger logic (validation consensus detection)
9
- * - Agent structure (id, role, modelLevel, output)
10
- * - Prompt template with platform-specific commands
11
- */
12
-
13
- /**
14
- * Shared trigger logic for detecting when all validators have approved.
15
- * This is the SINGLE source of truth - no more duplicating across 3 JSON files.
16
- */
17
- const SHARED_TRIGGER_SCRIPT = `const validators = cluster.getAgentsByRole('validator');
18
- const lastPush = ledger.findLast({ topic: 'IMPLEMENTATION_READY' });
19
- if (!lastPush) return false;
20
- if (validators.length === 0) return true;
21
- const results = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp });
22
- if (results.length < validators.length) return false;
23
- const allApproved = results.every(r => r.content?.data?.approved === 'true' || r.content?.data?.approved === true);
24
- if (!allApproved) return false;
25
- const hasRealEvidence = results.every(r => {
26
- const criteria = r.content?.data?.criteriaResults || [];
27
- return criteria.every(c => {
28
- return c.evidence?.command && typeof c.evidence?.exitCode === 'number' && c.evidence?.output?.length > 10;
29
- });
30
- });
31
- return hasRealEvidence;`;
32
-
33
- /**
34
- * Platform-specific CLI commands and terminology
35
- */
36
- const PLATFORM_CONFIGS = {
37
- github: {
38
- prName: 'PR',
39
- prNameLower: 'pull request',
40
- createCmd: 'gh pr create --title "feat: {{issue_title}}" --body "Closes #{{issue_number}}"',
41
- mergeCmd: 'gh pr merge --merge --auto',
42
- mergeFallbackCmd: 'gh pr merge --merge',
43
- prUrlExample: 'https://github.com/owner/repo/pull/123',
44
- outputFields: { urlField: 'pr_url', numberField: 'pr_number', mergedField: 'merged' },
45
- },
46
- gitlab: {
47
- prName: 'MR',
48
- prNameLower: 'merge request',
49
- createCmd:
50
- 'glab mr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
51
- mergeCmd: 'glab mr merge --auto-merge',
52
- mergeFallbackCmd: 'glab mr merge',
53
- prUrlExample: 'https://gitlab.com/owner/repo/-/merge_requests/123',
54
- outputFields: { urlField: 'mr_url', numberField: 'mr_number', mergedField: 'merged' },
55
- },
56
- 'azure-devops': {
57
- prName: 'PR',
58
- prNameLower: 'pull request',
59
- createCmd:
60
- 'az repos pr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
61
- mergeCmd: 'az repos pr update --id <PR_ID> --auto-complete true',
62
- mergeFallbackCmd: 'az repos pr update --id <PR_ID> --status completed',
63
- prUrlExample: 'https://dev.azure.com/org/project/_git/repo/pullrequest/123',
64
- outputFields: {
65
- urlField: 'pr_url',
66
- numberField: 'pr_number',
67
- mergedField: 'merged',
68
- autoCompleteField: 'auto_complete',
69
- },
70
- // Azure requires extracting PR ID from create output
71
- requiresPrIdExtraction: true,
72
- },
73
- };
74
-
75
- /**
76
- * Generate the prompt for a specific platform
77
- * @param {Object} config - Platform configuration from PLATFORM_CONFIGS
78
- * @returns {string} The complete prompt with platform-specific commands
79
- */
80
- function generatePrompt(config) {
81
- const {
82
- prName,
83
- prNameLower,
84
- createCmd,
85
- mergeCmd,
86
- mergeFallbackCmd,
87
- prUrlExample,
88
- outputFields,
89
- requiresPrIdExtraction,
90
- } = config;
91
-
92
- // Azure-specific instructions for PR ID extraction
93
- const azurePrIdNote = requiresPrIdExtraction
94
- ? `\n\n💡 IMPORTANT: The output will contain the PR ID. You MUST extract it for the next step.
95
- Look for output like: "Created PR 123" or parse the URL for the PR number.
96
- Save the PR ID to a variable for step 6.`
97
- : '';
98
-
99
- // Azure uses different merge terminology
100
- const mergeDescription = requiresPrIdExtraction
101
- ? 'SET AUTO-COMPLETE (MANDATORY - THIS IS NOT OPTIONAL)'
102
- : `MERGE THE ${prName} (MANDATORY - THIS IS NOT OPTIONAL)`;
103
-
104
- const mergeExplanation = requiresPrIdExtraction
105
- ? `Replace <PR_ID> with the actual PR number from step 5.
106
- This enables auto-complete (auto-merge when CI passes).
107
-
108
- If auto-complete is not available or you need to merge immediately:`
109
- : `This sets auto-merge. If it fails (e.g., no auto-merge enabled), try:`;
110
-
111
- const postMergeStatus = requiresPrIdExtraction
112
- ? 'PR IS CREATED AND AUTO-COMPLETE IS SET'
113
- : `${prName} IS MERGED`;
114
-
115
- const finalOutputNote = requiresPrIdExtraction
116
- ? `ONLY after the PR is created and auto-complete is set, output:
117
- \`\`\`json
118
- {"${outputFields.urlField}": "${prUrlExample}", "${outputFields.numberField}": 123, "merged": false, "auto_complete": true}
119
- \`\`\`
120
-
121
- If truly no changes exist, output:
122
- \`\`\`json
123
- {"${outputFields.urlField}": null, "${outputFields.numberField}": null, "merged": false, "auto_complete": false}
124
- \`\`\``
125
- : `ONLY after the ${prName} is MERGED, output:
126
- \`\`\`json
127
- {"${outputFields.urlField}": "${prUrlExample}", "${outputFields.numberField}": 123, "merged": true}
128
- \`\`\`
129
-
130
- If truly no changes exist, output:
131
- \`\`\`json
132
- {"${outputFields.urlField}": null, "${outputFields.numberField}": null, "merged": false}
133
- \`\`\``;
134
-
135
- return `🚨 CRITICAL: ALL VALIDATORS APPROVED. YOU MUST CREATE A ${prName} AND GET IT MERGED. DO NOT STOP UNTIL THE ${prName} IS MERGED. 🚨
136
-
137
- ## MANDATORY STEPS - EXECUTE EACH ONE IN ORDER - DO NOT SKIP ANY STEP
138
-
139
- ### STEP 1: Stage ALL changes (MANDATORY)
140
- \`\`\`bash
141
- git add -A
142
- \`\`\`
143
- Run this command. Do not skip it.
144
-
145
- ### STEP 2: Check what's staged
146
- \`\`\`bash
147
- git status
148
- \`\`\`
149
- Run this. If nothing to commit, output JSON with ${outputFields.urlField}: null and stop.
150
-
151
- ### STEP 3: Commit the changes (MANDATORY if there are changes)
152
- \`\`\`bash
153
- git commit -m "feat: implement #{{issue_number}} - {{issue_title}}"
154
- \`\`\`
155
- Run this command. Do not skip it.
156
-
157
- ### STEP 4: Push to origin (MANDATORY)
158
- \`\`\`bash
159
- git push -u origin HEAD
160
- \`\`\`
161
- Run this. If it fails, check the error and fix it.
162
-
163
- ⚠️ AFTER PUSH YOU ARE NOT DONE! CONTINUE TO STEP 5! ⚠️
164
-
165
- ### STEP 5: CREATE THE ${prName.toUpperCase()} (MANDATORY - YOU MUST RUN THIS COMMAND)
166
- \`\`\`bash
167
- ${createCmd}
168
- \`\`\`
169
- 🚨 YOU MUST RUN \`${createCmd.split(' ').slice(0, 3).join(' ')}\`! Outputting a link is NOT creating a ${prName}! 🚨
170
- The push output shows a "Create a ${prNameLower}" link - IGNORE IT.
171
- You MUST run the \`${createCmd.split(' ').slice(0, 3).join(' ')}\` command above.${requiresPrIdExtraction ? '' : ` Save the actual ${prName} URL from the output.`}${azurePrIdNote}
172
-
173
- ⚠️ AFTER ${prName} CREATION YOU ARE NOT DONE! CONTINUE TO STEP 6! ⚠️
174
-
175
- ### STEP 6: ${mergeDescription}
176
- \`\`\`bash
177
- ${mergeCmd}
178
- \`\`\`
179
- ${mergeExplanation}
180
- \`\`\`bash
181
- ${mergeFallbackCmd}
182
- \`\`\`
183
-
184
- 🚨 IF MERGE FAILS DUE TO CONFLICTS - YOU MUST RESOLVE THEM:
185
- a) Pull latest main and rebase:
186
- \`\`\`bash
187
- git fetch origin main
188
- git rebase origin/main
189
- \`\`\`
190
- b) If conflicts appear - RESOLVE THEM IMMEDIATELY:
191
- - Read the conflicting files
192
- - Make intelligent decisions about what code to keep
193
- - Edit the files to resolve conflicts
194
- - \`git add <resolved-files>\`
195
- - \`git rebase --continue\`
196
- c) Force push the resolved branch:
197
- \`\`\`bash
198
- git push --force-with-lease
199
- \`\`\`
200
- d) Retry merge:
201
- \`\`\`bash
202
- ${mergeFallbackCmd}
203
- \`\`\`
204
-
205
- REPEAT UNTIL MERGED. DO NOT GIVE UP. DO NOT SKIP. THE ${prName} MUST BE ${requiresPrIdExtraction ? 'SET TO AUTO-COMPLETE' : 'MERGED'}.
206
- If merge is blocked by CI, wait and retry. ${requiresPrIdExtraction ? 'The auto-complete will merge when CI passes.' : 'If blocked by reviews, set auto-merge.'}
207
-
208
- ## CRITICAL RULES
209
- - Execute EVERY step in order (1, 2, 3, 4, 5, 6)
210
- - Do NOT skip git add -A
211
- - Do NOT skip git commit
212
- - Do NOT skip ${createCmd.split(' ').slice(0, 3).join(' ')} - THE TASK IS NOT DONE UNTIL ${prName} EXISTS
213
- - Do NOT skip ${mergeCmd.split(' ').slice(0, 4).join(' ')} - THE TASK IS NOT DONE UNTIL ${postMergeStatus}${requiresPrIdExtraction ? '\n- MUST extract PR ID from step 5 output to use in step 6' : ''}
214
- - If push fails, debug and fix it
215
- - If ${prName} creation fails, debug and fix it
216
- - If ${requiresPrIdExtraction ? 'auto-complete' : 'merge'} fails, debug and fix it
217
- - DO NOT OUTPUT JSON UNTIL ${postMergeStatus}
218
- - A link from git push is NOT a ${prName} - you must run ${createCmd.split(' ').slice(0, 3).join(' ')}
219
-
220
- ## Final Output
221
- ${finalOutputNote}`;
222
- }
223
-
224
- /**
225
- * Generate a git-pusher agent configuration for a specific platform
226
- *
227
- * @param {string} platform - Platform ID ('github', 'gitlab', 'azure-devops')
228
- * @returns {Object} Agent configuration object
229
- * @throws {Error} If platform is not supported
230
- */
231
- function generateGitPusherAgent(platform) {
232
- const config = PLATFORM_CONFIGS[platform];
233
-
234
- if (!config) {
235
- const supported = Object.keys(PLATFORM_CONFIGS).join(', ');
236
- throw new Error(`Unsupported platform '${platform}'. Supported: ${supported}`);
237
- }
238
-
239
- return {
240
- id: 'git-pusher',
241
- role: 'completion-detector',
242
- modelLevel: 'level2',
243
- triggers: [
244
- {
245
- topic: 'VALIDATION_RESULT',
246
- logic: {
247
- engine: 'javascript',
248
- script: SHARED_TRIGGER_SCRIPT,
249
- },
250
- action: 'execute_task',
251
- },
252
- ],
253
- prompt: generatePrompt(config),
254
- output: {
255
- topic: 'PR_CREATED',
256
- publishAfter: 'CLUSTER_COMPLETE',
257
- },
258
- };
259
- }
260
-
261
- /**
262
- * Get list of supported platforms for git-pusher
263
- * @returns {string[]} Array of platform IDs
264
- */
265
- function getSupportedPlatforms() {
266
- return Object.keys(PLATFORM_CONFIGS);
267
- }
268
-
269
- /**
270
- * Check if a platform supports git-pusher (PR/MR creation)
271
- * @param {string} platform - Platform ID
272
- * @returns {boolean}
273
- */
274
- function isPlatformSupported(platform) {
275
- return platform in PLATFORM_CONFIGS;
276
- }
277
-
278
- module.exports = {
279
- generateGitPusherAgent,
280
- getSupportedPlatforms,
281
- isPlatformSupported,
282
- // Export for testing
283
- SHARED_TRIGGER_SCRIPT,
284
- PLATFORM_CONFIGS,
285
- };
@@ -1,438 +0,0 @@
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;