@itz4blitz/agentful 1.2.0 → 1.3.0
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/README.md +28 -1
- package/bin/cli.js +11 -1055
- package/bin/hooks/block-file-creation.js +271 -0
- package/bin/hooks/product-spec-watcher.js +151 -0
- package/lib/index.js +0 -11
- package/lib/init.js +2 -21
- package/lib/parallel-execution.js +235 -0
- package/lib/presets.js +26 -4
- package/package.json +4 -7
- package/template/.claude/agents/architect.md +2 -2
- package/template/.claude/agents/backend.md +17 -30
- package/template/.claude/agents/frontend.md +17 -39
- package/template/.claude/agents/orchestrator.md +63 -4
- package/template/.claude/agents/product-analyzer.md +1 -1
- package/template/.claude/agents/tester.md +16 -29
- package/template/.claude/commands/agentful-generate.md +221 -14
- package/template/.claude/commands/agentful-init.md +621 -0
- package/template/.claude/commands/agentful-product.md +1 -1
- package/template/.claude/commands/agentful-start.md +99 -1
- package/template/.claude/product/EXAMPLES.md +2 -2
- package/template/.claude/product/index.md +1 -1
- package/template/.claude/settings.json +22 -0
- package/template/.claude/skills/research/SKILL.md +432 -0
- package/template/CLAUDE.md +5 -6
- package/template/bin/hooks/architect-drift-detector.js +242 -0
- package/template/bin/hooks/product-spec-watcher.js +151 -0
- package/version.json +1 -1
- package/bin/hooks/post-agent.js +0 -101
- package/bin/hooks/post-feature.js +0 -227
- package/bin/hooks/pre-agent.js +0 -118
- package/bin/hooks/pre-feature.js +0 -138
- package/lib/VALIDATION_README.md +0 -455
- package/lib/ci/claude-action-integration.js +0 -641
- package/lib/ci/index.js +0 -10
- package/lib/core/analyzer.js +0 -497
- package/lib/core/cli.js +0 -141
- package/lib/core/detectors/conventions.js +0 -342
- package/lib/core/detectors/framework.js +0 -276
- package/lib/core/detectors/index.js +0 -15
- package/lib/core/detectors/language.js +0 -199
- package/lib/core/detectors/patterns.js +0 -356
- package/lib/core/generator.js +0 -626
- package/lib/core/index.js +0 -9
- package/lib/core/output-parser.js +0 -458
- package/lib/core/storage.js +0 -515
- package/lib/core/templates.js +0 -556
- package/lib/pipeline/cli.js +0 -423
- package/lib/pipeline/engine.js +0 -928
- package/lib/pipeline/executor.js +0 -440
- package/lib/pipeline/index.js +0 -33
- package/lib/pipeline/integrations.js +0 -559
- package/lib/pipeline/schemas.js +0 -288
- package/lib/remote/client.js +0 -361
- package/lib/server/auth.js +0 -270
- package/lib/server/client-example.js +0 -190
- package/lib/server/executor.js +0 -477
- package/lib/server/index.js +0 -494
- package/lib/update-helpers.js +0 -505
- package/lib/validation.js +0 -460
package/lib/pipeline/schemas.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSON Schemas for Pipeline Definitions
|
|
3
|
-
*
|
|
4
|
-
* Provides validation schemas for pipeline YAML files
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export const pipelineSchema = {
|
|
8
|
-
type: 'object',
|
|
9
|
-
required: ['name', 'jobs'],
|
|
10
|
-
properties: {
|
|
11
|
-
name: {
|
|
12
|
-
type: 'string',
|
|
13
|
-
description: 'Pipeline name',
|
|
14
|
-
pattern: '^[a-z0-9-]+$'
|
|
15
|
-
},
|
|
16
|
-
version: {
|
|
17
|
-
type: 'string',
|
|
18
|
-
description: 'Pipeline version',
|
|
19
|
-
pattern: '^\\d+\\.\\d+(\\.\\d+)?$',
|
|
20
|
-
default: '1.0'
|
|
21
|
-
},
|
|
22
|
-
description: {
|
|
23
|
-
type: 'string',
|
|
24
|
-
description: 'Pipeline description'
|
|
25
|
-
},
|
|
26
|
-
triggers: {
|
|
27
|
-
type: 'array',
|
|
28
|
-
description: 'Pipeline triggers',
|
|
29
|
-
items: {
|
|
30
|
-
$ref: '#/definitions/trigger'
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
env: {
|
|
34
|
-
type: 'object',
|
|
35
|
-
description: 'Environment variables',
|
|
36
|
-
additionalProperties: { type: 'string' }
|
|
37
|
-
},
|
|
38
|
-
jobs: {
|
|
39
|
-
type: 'array',
|
|
40
|
-
description: 'Pipeline jobs',
|
|
41
|
-
minItems: 1,
|
|
42
|
-
items: {
|
|
43
|
-
$ref: '#/definitions/job'
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
concurrency: {
|
|
47
|
-
type: 'object',
|
|
48
|
-
description: 'Concurrency settings',
|
|
49
|
-
properties: {
|
|
50
|
-
maxConcurrentJobs: {
|
|
51
|
-
type: 'integer',
|
|
52
|
-
minimum: 1,
|
|
53
|
-
maximum: 10,
|
|
54
|
-
default: 3
|
|
55
|
-
},
|
|
56
|
-
cancelInProgress: {
|
|
57
|
-
type: 'boolean',
|
|
58
|
-
default: false
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
timeout: {
|
|
63
|
-
type: 'integer',
|
|
64
|
-
description: 'Default job timeout in milliseconds',
|
|
65
|
-
minimum: 1000,
|
|
66
|
-
default: 1800000 // 30 minutes
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
definitions: {
|
|
70
|
-
trigger: {
|
|
71
|
-
type: 'object',
|
|
72
|
-
required: ['type'],
|
|
73
|
-
properties: {
|
|
74
|
-
type: {
|
|
75
|
-
type: 'string',
|
|
76
|
-
enum: ['push', 'pull_request', 'schedule', 'manual', 'webhook']
|
|
77
|
-
},
|
|
78
|
-
branches: {
|
|
79
|
-
type: 'array',
|
|
80
|
-
items: { type: 'string' }
|
|
81
|
-
},
|
|
82
|
-
cron: {
|
|
83
|
-
type: 'string',
|
|
84
|
-
description: 'Cron expression for scheduled triggers'
|
|
85
|
-
},
|
|
86
|
-
webhook: {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
secret: { type: 'string' },
|
|
90
|
-
filters: { type: 'object' }
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
job: {
|
|
96
|
-
type: 'object',
|
|
97
|
-
required: ['id', 'agent'],
|
|
98
|
-
properties: {
|
|
99
|
-
id: {
|
|
100
|
-
type: 'string',
|
|
101
|
-
description: 'Unique job identifier',
|
|
102
|
-
pattern: '^[a-z0-9-]+$'
|
|
103
|
-
},
|
|
104
|
-
name: {
|
|
105
|
-
type: 'string',
|
|
106
|
-
description: 'Human-readable job name'
|
|
107
|
-
},
|
|
108
|
-
agent: {
|
|
109
|
-
type: 'string',
|
|
110
|
-
description: 'Agent to execute (name or path)'
|
|
111
|
-
},
|
|
112
|
-
task: {
|
|
113
|
-
type: 'string',
|
|
114
|
-
description: 'Task description for the agent'
|
|
115
|
-
},
|
|
116
|
-
prompt: {
|
|
117
|
-
type: 'string',
|
|
118
|
-
description: 'Additional prompt instructions'
|
|
119
|
-
},
|
|
120
|
-
dependsOn: {
|
|
121
|
-
oneOf: [
|
|
122
|
-
{ type: 'string' },
|
|
123
|
-
{ type: 'array', items: { type: 'string' } }
|
|
124
|
-
],
|
|
125
|
-
description: 'Job dependencies'
|
|
126
|
-
},
|
|
127
|
-
when: {
|
|
128
|
-
type: 'string',
|
|
129
|
-
description: 'Conditional execution expression'
|
|
130
|
-
},
|
|
131
|
-
inputs: {
|
|
132
|
-
type: 'object',
|
|
133
|
-
description: 'Job-specific inputs',
|
|
134
|
-
additionalProperties: true
|
|
135
|
-
},
|
|
136
|
-
timeout: {
|
|
137
|
-
type: 'integer',
|
|
138
|
-
description: 'Job timeout in milliseconds',
|
|
139
|
-
minimum: 1000
|
|
140
|
-
},
|
|
141
|
-
retry: {
|
|
142
|
-
$ref: '#/definitions/retry'
|
|
143
|
-
},
|
|
144
|
-
continueOnError: {
|
|
145
|
-
type: 'boolean',
|
|
146
|
-
description: 'Continue pipeline even if this job fails',
|
|
147
|
-
default: false
|
|
148
|
-
},
|
|
149
|
-
execution: {
|
|
150
|
-
type: 'object',
|
|
151
|
-
description: 'Execution configuration',
|
|
152
|
-
properties: {
|
|
153
|
-
method: {
|
|
154
|
-
type: 'string',
|
|
155
|
-
enum: ['subprocess', 'api'],
|
|
156
|
-
default: 'subprocess'
|
|
157
|
-
},
|
|
158
|
-
isolation: {
|
|
159
|
-
type: 'string',
|
|
160
|
-
enum: ['shared', 'isolated'],
|
|
161
|
-
default: 'shared'
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
runsOn: {
|
|
166
|
-
type: 'string',
|
|
167
|
-
description: 'Runner environment (for CI/CD integrations)',
|
|
168
|
-
default: 'ubuntu-latest'
|
|
169
|
-
},
|
|
170
|
-
stage: {
|
|
171
|
-
type: 'string',
|
|
172
|
-
description: 'Pipeline stage (for GitLab CI)',
|
|
173
|
-
default: 'test'
|
|
174
|
-
},
|
|
175
|
-
setup: {
|
|
176
|
-
type: 'array',
|
|
177
|
-
description: 'Setup steps before job execution',
|
|
178
|
-
items: {
|
|
179
|
-
type: 'object',
|
|
180
|
-
required: ['command'],
|
|
181
|
-
properties: {
|
|
182
|
-
name: { type: 'string' },
|
|
183
|
-
command: { type: 'string' }
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
retry: {
|
|
190
|
-
type: 'object',
|
|
191
|
-
description: 'Retry policy',
|
|
192
|
-
properties: {
|
|
193
|
-
maxAttempts: {
|
|
194
|
-
type: 'integer',
|
|
195
|
-
minimum: 0,
|
|
196
|
-
maximum: 5,
|
|
197
|
-
default: 0
|
|
198
|
-
},
|
|
199
|
-
backoff: {
|
|
200
|
-
type: 'string',
|
|
201
|
-
enum: ['exponential', 'linear', 'fixed'],
|
|
202
|
-
default: 'exponential'
|
|
203
|
-
},
|
|
204
|
-
delayMs: {
|
|
205
|
-
type: 'integer',
|
|
206
|
-
minimum: 0,
|
|
207
|
-
default: 2000
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Validate pipeline definition
|
|
216
|
-
*
|
|
217
|
-
* @param {Object} pipeline - Pipeline definition
|
|
218
|
-
* @returns {Object} Validation result { valid: boolean, errors: array }
|
|
219
|
-
*/
|
|
220
|
-
export function validatePipeline(pipeline) {
|
|
221
|
-
// Basic validation
|
|
222
|
-
const errors = [];
|
|
223
|
-
|
|
224
|
-
if (!pipeline || typeof pipeline !== 'object') {
|
|
225
|
-
errors.push('Pipeline must be a valid object');
|
|
226
|
-
return { valid: false, errors };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (!pipeline.name) {
|
|
230
|
-
errors.push('Pipeline must have a name');
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (!pipeline.jobs || !Array.isArray(pipeline.jobs) || pipeline.jobs.length === 0) {
|
|
234
|
-
errors.push('Pipeline must have at least one job');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Collect all job IDs first
|
|
238
|
-
const jobIds = new Set();
|
|
239
|
-
for (const job of pipeline.jobs || []) {
|
|
240
|
-
if (job.id) {
|
|
241
|
-
if (jobIds.has(job.id)) {
|
|
242
|
-
errors.push(`Duplicate job id: ${job.id}`);
|
|
243
|
-
} else {
|
|
244
|
-
jobIds.add(job.id);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Validate jobs
|
|
250
|
-
for (const job of pipeline.jobs || []) {
|
|
251
|
-
// Check if job is a valid object
|
|
252
|
-
if (!job || typeof job !== 'object') {
|
|
253
|
-
errors.push('Each job must be a valid object');
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (!job.id) {
|
|
258
|
-
errors.push('Each job must have an id');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (!job.agent) {
|
|
262
|
-
errors.push(`Job ${job.id} must specify an agent`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Validate dependencies
|
|
266
|
-
if ('dependsOn' in job) {
|
|
267
|
-
const deps = Array.isArray(job.dependsOn) ? job.dependsOn : [job.dependsOn];
|
|
268
|
-
for (const depId of deps) {
|
|
269
|
-
// Check for empty string or invalid values
|
|
270
|
-
if (typeof depId !== 'string' || depId === '') {
|
|
271
|
-
errors.push(`Job ${job.id} depends on unknown job: ${depId}`);
|
|
272
|
-
} else if (!jobIds.has(depId)) {
|
|
273
|
-
errors.push(`Job ${job.id} depends on unknown job: ${depId}`);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return {
|
|
280
|
-
valid: errors.length === 0,
|
|
281
|
-
errors
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export default {
|
|
286
|
-
pipelineSchema,
|
|
287
|
-
validatePipeline
|
|
288
|
-
};
|
package/lib/remote/client.js
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agentful Remote Client
|
|
3
|
-
*
|
|
4
|
-
* HTTP client for triggering agent execution on remote agentful servers.
|
|
5
|
-
* Supports Tailscale, HMAC, and SSH tunnel authentication modes.
|
|
6
|
-
*
|
|
7
|
-
* @module remote/client
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import crypto from 'crypto';
|
|
11
|
-
import fs from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import os from 'os';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Remote configuration storage
|
|
17
|
-
*/
|
|
18
|
-
const CONFIG_DIR = path.join(os.homedir(), '.agentful');
|
|
19
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'remotes.json');
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Ensure config directory exists
|
|
23
|
-
*/
|
|
24
|
-
function ensureConfigDir() {
|
|
25
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
26
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Load remote configurations
|
|
32
|
-
* @returns {Object} Remote configurations
|
|
33
|
-
*/
|
|
34
|
-
export function loadRemotes() {
|
|
35
|
-
ensureConfigDir();
|
|
36
|
-
|
|
37
|
-
if (!fs.existsSync(CONFIG_FILE)) {
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error('Failed to load remotes.json:', error.message);
|
|
45
|
-
return {};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Save remote configurations
|
|
51
|
-
* @param {Object} remotes - Remote configurations
|
|
52
|
-
*/
|
|
53
|
-
export function saveRemotes(remotes) {
|
|
54
|
-
ensureConfigDir();
|
|
55
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(remotes, null, 2), 'utf-8');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Add a remote configuration
|
|
60
|
-
* @param {string} name - Remote name
|
|
61
|
-
* @param {string} url - Server URL
|
|
62
|
-
* @param {Object} options - Remote options
|
|
63
|
-
* @param {string} [options.auth='tailscale'] - Auth mode
|
|
64
|
-
* @param {string} [options.secret] - HMAC secret
|
|
65
|
-
*/
|
|
66
|
-
export function addRemote(name, url, options = {}) {
|
|
67
|
-
const { auth = 'tailscale', secret } = options;
|
|
68
|
-
|
|
69
|
-
// Validate auth mode
|
|
70
|
-
if (auth === 'hmac' && !secret) {
|
|
71
|
-
throw new Error('HMAC auth requires --secret');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Parse and validate URL
|
|
75
|
-
let parsedUrl;
|
|
76
|
-
try {
|
|
77
|
-
parsedUrl = new URL(url);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
throw new Error(`Invalid URL: ${url}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const remotes = loadRemotes();
|
|
83
|
-
|
|
84
|
-
remotes[name] = {
|
|
85
|
-
url: parsedUrl.toString(),
|
|
86
|
-
auth,
|
|
87
|
-
secret: secret || null,
|
|
88
|
-
added: new Date().toISOString(),
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
saveRemotes(remotes);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Remove a remote configuration
|
|
96
|
-
* @param {string} name - Remote name
|
|
97
|
-
*/
|
|
98
|
-
export function removeRemote(name) {
|
|
99
|
-
const remotes = loadRemotes();
|
|
100
|
-
|
|
101
|
-
if (!remotes[name]) {
|
|
102
|
-
throw new Error(`Remote not found: ${name}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
delete remotes[name];
|
|
106
|
-
saveRemotes(remotes);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get a remote configuration
|
|
111
|
-
* @param {string} name - Remote name
|
|
112
|
-
* @returns {Object} Remote configuration
|
|
113
|
-
*/
|
|
114
|
-
export function getRemote(name) {
|
|
115
|
-
const remotes = loadRemotes();
|
|
116
|
-
|
|
117
|
-
if (!remotes[name]) {
|
|
118
|
-
throw new Error(`Remote not found: ${name}. Run: agentful remote add ${name} <url>`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return remotes[name];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* List all remote configurations
|
|
126
|
-
* @returns {Object} Remote configurations
|
|
127
|
-
*/
|
|
128
|
-
export function listRemotes() {
|
|
129
|
-
return loadRemotes();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Generate HMAC signature for request
|
|
134
|
-
* @param {string} body - Request body
|
|
135
|
-
* @param {string} secret - HMAC secret
|
|
136
|
-
* @returns {Object} Headers with signature
|
|
137
|
-
*/
|
|
138
|
-
function generateHMACHeaders(body, secret) {
|
|
139
|
-
const timestamp = Date.now().toString();
|
|
140
|
-
const signature = crypto
|
|
141
|
-
.createHmac('sha256', secret)
|
|
142
|
-
.update(timestamp + body)
|
|
143
|
-
.digest('hex');
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
'X-Agentful-Signature': signature,
|
|
147
|
-
'X-Agentful-Timestamp': timestamp,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Execute an agent on a remote server
|
|
153
|
-
* @param {string} remoteName - Remote name
|
|
154
|
-
* @param {string} agent - Agent name
|
|
155
|
-
* @param {string} task - Task description
|
|
156
|
-
* @param {Object} options - Execution options
|
|
157
|
-
* @param {number} [options.timeout] - Execution timeout in ms
|
|
158
|
-
* @param {Object} [options.env] - Environment variables
|
|
159
|
-
* @returns {Promise<Object>} Execution result
|
|
160
|
-
*/
|
|
161
|
-
export async function executeRemoteAgent(remoteName, agent, task, options = {}) {
|
|
162
|
-
const remote = getRemote(remoteName);
|
|
163
|
-
const { timeout, env } = options;
|
|
164
|
-
|
|
165
|
-
// Build request body
|
|
166
|
-
const body = JSON.stringify({
|
|
167
|
-
agent,
|
|
168
|
-
task,
|
|
169
|
-
timeout,
|
|
170
|
-
env,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Build headers
|
|
174
|
-
const headers = {
|
|
175
|
-
'Content-Type': 'application/json',
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Add HMAC signature if needed
|
|
179
|
-
if (remote.auth === 'hmac') {
|
|
180
|
-
Object.assign(headers, generateHMACHeaders(body, remote.secret));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Make request
|
|
184
|
-
const response = await fetch(`${remote.url}/trigger`, {
|
|
185
|
-
method: 'POST',
|
|
186
|
-
headers,
|
|
187
|
-
body,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
if (!response.ok) {
|
|
191
|
-
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
192
|
-
throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return await response.json();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Get execution status from remote server
|
|
200
|
-
* @param {string} remoteName - Remote name
|
|
201
|
-
* @param {string} executionId - Execution ID
|
|
202
|
-
* @returns {Promise<Object>} Execution status
|
|
203
|
-
*/
|
|
204
|
-
export async function getRemoteExecutionStatus(remoteName, executionId) {
|
|
205
|
-
const remote = getRemote(remoteName);
|
|
206
|
-
|
|
207
|
-
// Build headers
|
|
208
|
-
const headers = {};
|
|
209
|
-
|
|
210
|
-
// Add HMAC signature if needed
|
|
211
|
-
if (remote.auth === 'hmac') {
|
|
212
|
-
Object.assign(headers, generateHMACHeaders('', remote.secret));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Make request
|
|
216
|
-
const response = await fetch(`${remote.url}/status/${executionId}`, {
|
|
217
|
-
method: 'GET',
|
|
218
|
-
headers,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
if (!response.ok) {
|
|
222
|
-
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
223
|
-
throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return await response.json();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* List executions on remote server
|
|
231
|
-
* @param {string} remoteName - Remote name
|
|
232
|
-
* @param {Object} filters - Filter options
|
|
233
|
-
* @param {string} [filters.agent] - Filter by agent name
|
|
234
|
-
* @param {string} [filters.state] - Filter by state
|
|
235
|
-
* @param {number} [filters.limit] - Maximum number of results
|
|
236
|
-
* @returns {Promise<Object>} Executions list
|
|
237
|
-
*/
|
|
238
|
-
export async function listRemoteExecutions(remoteName, filters = {}) {
|
|
239
|
-
const remote = getRemote(remoteName);
|
|
240
|
-
|
|
241
|
-
// Build query string
|
|
242
|
-
const params = new URLSearchParams();
|
|
243
|
-
if (filters.agent) params.append('agent', filters.agent);
|
|
244
|
-
if (filters.state) params.append('state', filters.state);
|
|
245
|
-
if (filters.limit) params.append('limit', filters.limit.toString());
|
|
246
|
-
|
|
247
|
-
// Build headers
|
|
248
|
-
const headers = {};
|
|
249
|
-
|
|
250
|
-
// Add HMAC signature if needed
|
|
251
|
-
if (remote.auth === 'hmac') {
|
|
252
|
-
Object.assign(headers, generateHMACHeaders('', remote.secret));
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Make request
|
|
256
|
-
const url = `${remote.url}/executions${params.toString() ? '?' + params.toString() : ''}`;
|
|
257
|
-
const response = await fetch(url, {
|
|
258
|
-
method: 'GET',
|
|
259
|
-
headers,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (!response.ok) {
|
|
263
|
-
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
264
|
-
throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return await response.json();
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* List available agents on remote server
|
|
272
|
-
* @param {string} remoteName - Remote name
|
|
273
|
-
* @returns {Promise<Object>} Agents list
|
|
274
|
-
*/
|
|
275
|
-
export async function listRemoteAgents(remoteName) {
|
|
276
|
-
const remote = getRemote(remoteName);
|
|
277
|
-
|
|
278
|
-
// Build headers
|
|
279
|
-
const headers = {};
|
|
280
|
-
|
|
281
|
-
// Add HMAC signature if needed
|
|
282
|
-
if (remote.auth === 'hmac') {
|
|
283
|
-
Object.assign(headers, generateHMACHeaders('', remote.secret));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Make request
|
|
287
|
-
const response = await fetch(`${remote.url}/agents`, {
|
|
288
|
-
method: 'GET',
|
|
289
|
-
headers,
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
if (!response.ok) {
|
|
293
|
-
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
294
|
-
throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return await response.json();
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Check remote server health
|
|
302
|
-
* @param {string} remoteName - Remote name
|
|
303
|
-
* @returns {Promise<Object>} Health status
|
|
304
|
-
*/
|
|
305
|
-
export async function checkRemoteHealth(remoteName) {
|
|
306
|
-
const remote = getRemote(remoteName);
|
|
307
|
-
|
|
308
|
-
// No auth required for /health endpoint
|
|
309
|
-
const response = await fetch(`${remote.url}/health`, {
|
|
310
|
-
method: 'GET',
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
if (!response.ok) {
|
|
314
|
-
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
315
|
-
throw new Error(`Health check failed (${response.status}): ${error.message || error.error}`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return await response.json();
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Poll execution until completion
|
|
323
|
-
* @param {string} remoteName - Remote name
|
|
324
|
-
* @param {string} executionId - Execution ID
|
|
325
|
-
* @param {Object} options - Polling options
|
|
326
|
-
* @param {number} [options.interval=5000] - Polling interval in ms
|
|
327
|
-
* @param {Function} [options.onUpdate] - Callback for status updates
|
|
328
|
-
* @returns {Promise<Object>} Final execution status
|
|
329
|
-
*/
|
|
330
|
-
export async function pollExecution(remoteName, executionId, options = {}) {
|
|
331
|
-
const { interval = 5000, onUpdate } = options;
|
|
332
|
-
|
|
333
|
-
while (true) {
|
|
334
|
-
const status = await getRemoteExecutionStatus(remoteName, executionId);
|
|
335
|
-
|
|
336
|
-
if (onUpdate) {
|
|
337
|
-
onUpdate(status);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Check if completed
|
|
341
|
-
if (status.state === 'completed' || status.state === 'failed') {
|
|
342
|
-
return status;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Wait before next poll
|
|
346
|
-
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export default {
|
|
351
|
-
addRemote,
|
|
352
|
-
removeRemote,
|
|
353
|
-
getRemote,
|
|
354
|
-
listRemotes,
|
|
355
|
-
executeRemoteAgent,
|
|
356
|
-
getRemoteExecutionStatus,
|
|
357
|
-
listRemoteExecutions,
|
|
358
|
-
listRemoteAgents,
|
|
359
|
-
checkRemoteHealth,
|
|
360
|
-
pollExecution,
|
|
361
|
-
};
|