@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.
- package/LICENSE +1 -1
- package/README.md +8 -465
- package/cli/index.js +101 -5231
- package/package.json +16 -130
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -149
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -275
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -581
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -208
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -220
- package/lib/git-remote-utils.js +0 -165
- package/lib/id-detector.js +0 -56
- package/lib/provider-defaults.js +0 -62
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -57
- package/lib/settings/claude-auth.js +0 -78
- package/lib/settings.js +0 -503
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/setup-merge-queue.sh +0 -170
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -246
- package/src/agent/agent-context-builder.js +0 -444
- package/src/agent/agent-hook-executor.js +0 -599
- package/src/agent/agent-lifecycle.js +0 -743
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1690
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -217
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -146
- package/src/agent-wrapper.js +0 -579
- package/src/agents/git-pusher-template.js +0 -285
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -574
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1925
- package/src/copy-worker.js +0 -43
- package/src/input-helpers.js +0 -65
- package/src/isolation-manager.js +0 -1471
- package/src/issue-providers/README.md +0 -305
- package/src/issue-providers/azure-devops-provider.js +0 -273
- package/src/issue-providers/base-provider.js +0 -232
- package/src/issue-providers/github-provider.js +0 -179
- package/src/issue-providers/gitlab-provider.js +0 -241
- package/src/issue-providers/index.js +0 -196
- package/src/issue-providers/jira-provider.js +0 -239
- package/src/ledger.js +0 -631
- package/src/lib/safe-exec.js +0 -88
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2815
- package/src/preflight.js +0 -639
- package/src/process-metrics.js +0 -608
- package/src/providers/anthropic/cli-builder.js +0 -73
- package/src/providers/anthropic/index.js +0 -204
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -177
- package/src/providers/base-provider.js +0 -251
- package/src/providers/capabilities.js +0 -60
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -101
- package/src/providers/index.js +0 -91
- package/src/providers/openai/cli-builder.js +0 -133
- package/src/providers/openai/index.js +0 -136
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -143
- package/src/providers/opencode/cli-builder.js +0 -42
- package/src/providers/opencode/index.js +0 -103
- package/src/providers/opencode/models.js +0 -55
- package/src/providers/opencode/output-parser.js +0 -122
- package/src/schemas/sub-cluster.js +0 -185
- package/src/status-footer.js +0 -1026
- package/src/sub-cluster-wrapper.js +0 -601
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -372
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -349
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -129
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -324
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -184
- package/task-lib/attachable-watcher.js +0 -309
- package/task-lib/claude-recovery.js +0 -156
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/episodes.js +0 -105
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -453
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -148
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -8
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -201
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -529
- package/task-lib/tui/formatters.js +0 -215
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -129
- package/task-lib/tui.js +0 -425
- 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;
|