gbos 1.3.22 → 1.4.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 +2 -1
- package/package.json +1 -1
- package/src/cli.js +36 -1
- package/src/commands/orchestrator.js +329 -0
- package/src/lib/api.js +6 -1
- package/src/orchestrator/adapters/base-adapter.js +204 -0
- package/src/orchestrator/adapters/claude-adapter.js +197 -0
- package/src/orchestrator/adapters/codex-adapter.js +133 -0
- package/src/orchestrator/adapters/gemini-adapter.js +144 -0
- package/src/orchestrator/adapters/index.js +70 -0
- package/src/orchestrator/managers/git-manager.js +358 -0
- package/src/orchestrator/managers/verification-manager.js +398 -0
- package/src/orchestrator/managers/workspace-manager.js +327 -0
- package/src/orchestrator/orchestrator.js +587 -0
- package/src/orchestrator/runners/session-runner.js +379 -0
- package/src/orchestrator/state-machine.js +257 -0
package/README.md
CHANGED
|
@@ -155,6 +155,7 @@ Stored at `~/.gbos/session.json`:
|
|
|
155
155
|
| Variable | Description |
|
|
156
156
|
|----------|-------------|
|
|
157
157
|
| `DEBUG=1` | Enable debug output |
|
|
158
|
+
| `GBOS_API_URL` | Override API endpoint (default: `https://api.gbos.io/api/v1`) |
|
|
158
159
|
|
|
159
160
|
## API Endpoints
|
|
160
161
|
|
|
@@ -260,7 +261,7 @@ gbos auth --force
|
|
|
260
261
|
## Links
|
|
261
262
|
|
|
262
263
|
- **GBOS Platform**: [https://gbos.io](https://gbos.io)
|
|
263
|
-
- **API Documentation**: [https://
|
|
264
|
+
- **API Documentation**: [https://api.gbos.io/docs](https://api.gbos.io/docs)
|
|
264
265
|
- **Issues**: [https://github.com/mystroanalytics/gbos-node-local/issues](https://github.com/mystroanalytics/gbos-node-local/issues)
|
|
265
266
|
|
|
266
267
|
## License
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ const logoutCommand = require('./commands/logout');
|
|
|
9
9
|
const { tasksCommand, nextTaskCommand, continueCommand, fallbackCommand, addTaskCommand, completedCommand } = require('./commands/tasks');
|
|
10
10
|
const { syncStartCommand, syncStopCommand, syncStatusCommand, syncNowCommand, repoCreateCommand, repoListCommand, repoCloneCommand } = require('./commands/gitlab');
|
|
11
11
|
const { registryLoginCommand, registryImagesCommand, registryPushCommand, registryPullCommand } = require('./commands/registry');
|
|
12
|
+
const { startCommand, resumeCommand, stopCommand, runsCommand } = require('./commands/orchestrator');
|
|
12
13
|
const config = require('./lib/config');
|
|
13
14
|
const { displayStatus, printBanner } = require('./lib/display');
|
|
14
15
|
|
|
@@ -133,6 +134,40 @@ program
|
|
|
133
134
|
.description('Create a new task interactively')
|
|
134
135
|
.action(addTaskCommand);
|
|
135
136
|
|
|
137
|
+
// ==================== Orchestrator Commands ====================
|
|
138
|
+
|
|
139
|
+
program
|
|
140
|
+
.command('start')
|
|
141
|
+
.description('Start the GBOS orchestrator to automatically process tasks')
|
|
142
|
+
.option('-a, --agent <agent>', 'Agent to use (claude-code, codex, gemini)', 'claude-code')
|
|
143
|
+
.option('-d, --dir <directory>', 'Working directory')
|
|
144
|
+
.option('--auto-approve', 'Auto-approve agent actions')
|
|
145
|
+
.option('--no-mr', 'Skip merge request creation')
|
|
146
|
+
.option('-c, --continuous', 'Continuously process tasks')
|
|
147
|
+
.option('-n, --max-tasks <number>', 'Maximum tasks to process', '1')
|
|
148
|
+
.option('--show-prompt', 'Show the generated prompt')
|
|
149
|
+
.action(startCommand);
|
|
150
|
+
|
|
151
|
+
program
|
|
152
|
+
.command('resume')
|
|
153
|
+
.description('Resume a paused orchestrator run')
|
|
154
|
+
.option('-r, --run-id <runId>', 'Specific run ID to resume')
|
|
155
|
+
.option('--no-mr', 'Skip merge request creation')
|
|
156
|
+
.action(resumeCommand);
|
|
157
|
+
|
|
158
|
+
program
|
|
159
|
+
.command('stop')
|
|
160
|
+
.description('Stop an active orchestrator run')
|
|
161
|
+
.option('-r, --run-id <runId>', 'Specific run ID to stop')
|
|
162
|
+
.option('-f, --force', 'Force stop and mark as failed')
|
|
163
|
+
.action(stopCommand);
|
|
164
|
+
|
|
165
|
+
program
|
|
166
|
+
.command('runs')
|
|
167
|
+
.description('List recent orchestrator runs')
|
|
168
|
+
.option('-l, --limit <number>', 'Number of runs to show', '10')
|
|
169
|
+
.action(runsCommand);
|
|
170
|
+
|
|
136
171
|
program
|
|
137
172
|
.command('logout')
|
|
138
173
|
.description('Log out from GBOS services and clear credentials')
|
|
@@ -244,7 +279,7 @@ program
|
|
|
244
279
|
cmd.outputHelp();
|
|
245
280
|
} else {
|
|
246
281
|
console.log(`Unknown command: ${command}`);
|
|
247
|
-
console.log('Available commands: auth, connect, disconnect, status, tasks, next, continue, completed, fallback, add_task, logout, gitlab, registry, help');
|
|
282
|
+
console.log('Available commands: auth, connect, disconnect, status, tasks, next, continue, completed, fallback, add_task, start, resume, stop, runs, logout, gitlab, registry, help');
|
|
248
283
|
}
|
|
249
284
|
} else {
|
|
250
285
|
program.outputHelp();
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator Commands
|
|
3
|
+
* CLI commands for start, resume, and stop
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const config = require('../lib/config');
|
|
7
|
+
const { displayMessageBox, fg, LOGO_PURPLE, RESET, BOLD, DIM, getTerminalWidth } = require('../lib/display');
|
|
8
|
+
const Orchestrator = require('../orchestrator/orchestrator');
|
|
9
|
+
const { StateMachine, STATES, RUNS_DIR } = require('../orchestrator/state-machine');
|
|
10
|
+
const { checkInstalledAdapters } = require('../orchestrator/adapters');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// Colors
|
|
15
|
+
const GREEN = '\x1b[32m';
|
|
16
|
+
const YELLOW = '\x1b[33m';
|
|
17
|
+
const RED = '\x1b[31m';
|
|
18
|
+
const CYAN = '\x1b[36m';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* gbos start - Start the orchestrator
|
|
22
|
+
*/
|
|
23
|
+
async function startCommand(options) {
|
|
24
|
+
// Check authentication
|
|
25
|
+
if (!config.isAuthenticated()) {
|
|
26
|
+
displayMessageBox('Not Authenticated', 'Please run "gbos auth" first.', 'warning');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check connection
|
|
31
|
+
const connection = config.getConnection();
|
|
32
|
+
if (!connection) {
|
|
33
|
+
displayMessageBox('Not Connected', 'Please run "gbos connect" first.', 'warning');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const termWidth = getTerminalWidth();
|
|
38
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
39
|
+
|
|
40
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
41
|
+
console.log(`${BOLD} GBOS Orchestrator${RESET}`);
|
|
42
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
43
|
+
|
|
44
|
+
// Check for existing active run
|
|
45
|
+
const activeRun = StateMachine.getActiveRun();
|
|
46
|
+
if (activeRun) {
|
|
47
|
+
console.log(` ${YELLOW}!${RESET} An active run exists: ${activeRun.runId}`);
|
|
48
|
+
console.log(` ${DIM}State: ${activeRun.state}${RESET}`);
|
|
49
|
+
console.log(` ${DIM}Use "gbos resume" to continue or "gbos stop" to cancel.${RESET}\n`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check agent availability
|
|
54
|
+
console.log(` ${DIM}Checking agent availability...${RESET}`);
|
|
55
|
+
const adapters = await checkInstalledAdapters();
|
|
56
|
+
const agentName = options.agent || 'claude-code';
|
|
57
|
+
const agentInfo = adapters[agentName] || adapters['claude-code'];
|
|
58
|
+
|
|
59
|
+
if (!agentInfo?.available) {
|
|
60
|
+
displayMessageBox('Agent Not Found', `Agent "${agentName}" is not installed.\n\nAvailable agents: ${Object.entries(adapters).filter(([_, v]) => v.available).map(([k]) => k).join(', ') || 'none'}`, 'error');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(` ${GREEN}✓${RESET} Agent: ${agentName} (${agentInfo.version || 'installed'})`);
|
|
65
|
+
console.log(` ${DIM}Application: ${connection.application?.name || 'N/A'}${RESET}`);
|
|
66
|
+
console.log(` ${DIM}Node: ${connection.node?.name || 'N/A'}${RESET}`);
|
|
67
|
+
console.log('');
|
|
68
|
+
|
|
69
|
+
// Create orchestrator
|
|
70
|
+
const orchestrator = new Orchestrator({
|
|
71
|
+
agent: agentName,
|
|
72
|
+
autoApprove: options.autoApprove || false,
|
|
73
|
+
createMR: options.mr !== false,
|
|
74
|
+
continuous: options.continuous || false,
|
|
75
|
+
maxTasks: options.maxTasks ? parseInt(options.maxTasks) : 1,
|
|
76
|
+
workingDir: options.dir ? path.resolve(options.dir) : null,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Set up event handlers
|
|
80
|
+
orchestrator.on('started', ({ runId }) => {
|
|
81
|
+
console.log(` ${GREEN}✓${RESET} Run started: ${runId}\n`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
orchestrator.on('stage', ({ stage }) => {
|
|
85
|
+
console.log(` ${CYAN}▸${RESET} ${stage.replace(/_/g, ' ')}`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
orchestrator.on('log', ({ message }) => {
|
|
89
|
+
console.log(` ${DIM}${message}${RESET}`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
orchestrator.on('prompt', ({ prompt }) => {
|
|
93
|
+
if (options.showPrompt) {
|
|
94
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
95
|
+
console.log(`${BOLD} Task Prompt${RESET}`);
|
|
96
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
97
|
+
console.log(prompt);
|
|
98
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
orchestrator.on('committed', (result) => {
|
|
103
|
+
if (result.commit) {
|
|
104
|
+
console.log(` ${GREEN}✓${RESET} Committed: ${result.commit.shortHash}`);
|
|
105
|
+
}
|
|
106
|
+
if (result.mergeRequest) {
|
|
107
|
+
console.log(` ${GREEN}✓${RESET} MR: ${result.mergeRequest.url}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
orchestrator.on('completed', ({ tasksCompleted }) => {
|
|
112
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
113
|
+
console.log(`${GREEN}✓${RESET} ${BOLD}Orchestrator completed${RESET}`);
|
|
114
|
+
console.log(` ${DIM}Tasks completed: ${tasksCompleted}${RESET}`);
|
|
115
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
orchestrator.on('failed', ({ error }) => {
|
|
119
|
+
console.log(`\n${RED}✗${RESET} ${BOLD}Orchestrator failed${RESET}`);
|
|
120
|
+
console.log(` ${DIM}Error: ${error.message}${RESET}\n`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Handle interrupts
|
|
124
|
+
process.on('SIGINT', async () => {
|
|
125
|
+
console.log(`\n\n ${YELLOW}!${RESET} Stopping orchestrator...`);
|
|
126
|
+
await orchestrator.stop();
|
|
127
|
+
console.log(` ${DIM}Run paused. Use "gbos resume" to continue.${RESET}\n`);
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Start the orchestrator
|
|
132
|
+
try {
|
|
133
|
+
await orchestrator.start();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.log(`\n${RED}✗${RESET} ${error.message}\n`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* gbos resume - Resume a paused run
|
|
142
|
+
*/
|
|
143
|
+
async function resumeCommand(options) {
|
|
144
|
+
const termWidth = getTerminalWidth();
|
|
145
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
146
|
+
|
|
147
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
148
|
+
console.log(`${BOLD} Resume Orchestrator${RESET}`);
|
|
149
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
150
|
+
|
|
151
|
+
// Find run to resume
|
|
152
|
+
let runId = options.runId;
|
|
153
|
+
let run;
|
|
154
|
+
|
|
155
|
+
if (runId) {
|
|
156
|
+
try {
|
|
157
|
+
run = StateMachine.loadRun(runId);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
displayMessageBox('Run Not Found', `Run "${runId}" not found.`, 'error');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
run = StateMachine.getActiveRun();
|
|
164
|
+
if (!run) {
|
|
165
|
+
console.log(` ${DIM}No active runs to resume.${RESET}`);
|
|
166
|
+
console.log(` ${DIM}Use "gbos start" to begin a new run.${RESET}\n`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
runId = run.runId;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!run.isResumable()) {
|
|
173
|
+
console.log(` ${YELLOW}!${RESET} Run ${runId} is not resumable (state: ${run.state})`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(` ${DIM}Resuming run: ${runId}${RESET}`);
|
|
178
|
+
console.log(` ${DIM}State: ${run.state}${RESET}`);
|
|
179
|
+
console.log(` ${DIM}Task: ${run.context.taskKey || run.context.taskId || 'N/A'}${RESET}\n`);
|
|
180
|
+
|
|
181
|
+
// Create orchestrator and resume
|
|
182
|
+
const orchestrator = new Orchestrator({
|
|
183
|
+
agent: run.context.agentVendor || 'claude-code',
|
|
184
|
+
createMR: options.mr !== false,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Set up event handlers (same as start)
|
|
188
|
+
orchestrator.on('stage', ({ stage }) => {
|
|
189
|
+
console.log(` ${CYAN}▸${RESET} ${stage.replace(/_/g, ' ')}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
orchestrator.on('log', ({ message }) => {
|
|
193
|
+
console.log(` ${DIM}${message}${RESET}`);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
orchestrator.on('completed', ({ tasksCompleted }) => {
|
|
197
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
198
|
+
console.log(`${GREEN}✓${RESET} ${BOLD}Orchestrator completed${RESET}`);
|
|
199
|
+
console.log(` ${DIM}Tasks completed: ${tasksCompleted}${RESET}`);
|
|
200
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
orchestrator.on('failed', ({ error }) => {
|
|
204
|
+
console.log(`\n${RED}✗${RESET} ${BOLD}Orchestrator failed${RESET}`);
|
|
205
|
+
console.log(` ${DIM}Error: ${error.message}${RESET}\n`);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
process.on('SIGINT', async () => {
|
|
209
|
+
console.log(`\n\n ${YELLOW}!${RESET} Stopping orchestrator...`);
|
|
210
|
+
await orchestrator.stop();
|
|
211
|
+
process.exit(0);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
await orchestrator.resume(runId);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.log(`\n${RED}✗${RESET} ${error.message}\n`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* gbos stop - Stop an active run
|
|
224
|
+
*/
|
|
225
|
+
async function stopCommand(options) {
|
|
226
|
+
const termWidth = getTerminalWidth();
|
|
227
|
+
const tableWidth = Math.min(80, termWidth - 4);
|
|
228
|
+
|
|
229
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
230
|
+
console.log(`${BOLD} Stop Orchestrator${RESET}`);
|
|
231
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
232
|
+
|
|
233
|
+
// Find active run
|
|
234
|
+
let runId = options.runId;
|
|
235
|
+
let run;
|
|
236
|
+
|
|
237
|
+
if (runId) {
|
|
238
|
+
try {
|
|
239
|
+
run = StateMachine.loadRun(runId);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
displayMessageBox('Run Not Found', `Run "${runId}" not found.`, 'error');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
run = StateMachine.getActiveRun();
|
|
246
|
+
if (!run) {
|
|
247
|
+
console.log(` ${DIM}No active runs to stop.${RESET}\n`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
runId = run.runId;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (run.state === STATES.COMPLETED || run.state === STATES.FAILED) {
|
|
254
|
+
console.log(` ${DIM}Run ${runId} is already ${run.state}.${RESET}\n`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Transition to paused or failed
|
|
259
|
+
if (options.force) {
|
|
260
|
+
run.transition(STATES.FAILED, { reason: 'Forcefully stopped by user' });
|
|
261
|
+
console.log(` ${RED}✗${RESET} Run ${runId} forcefully stopped.\n`);
|
|
262
|
+
} else {
|
|
263
|
+
run.transition(STATES.PAUSED, { reason: 'Stopped by user' });
|
|
264
|
+
console.log(` ${YELLOW}!${RESET} Run ${runId} paused.`);
|
|
265
|
+
console.log(` ${DIM}Use "gbos resume" to continue.${RESET}\n`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* gbos runs - List recent runs
|
|
271
|
+
*/
|
|
272
|
+
async function runsCommand(options) {
|
|
273
|
+
const termWidth = getTerminalWidth();
|
|
274
|
+
const tableWidth = Math.min(100, termWidth - 4);
|
|
275
|
+
|
|
276
|
+
console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
|
|
277
|
+
console.log(`${BOLD} Orchestrator Runs${RESET}`);
|
|
278
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
279
|
+
|
|
280
|
+
StateMachine.ensureRunsDir();
|
|
281
|
+
|
|
282
|
+
const files = fs.readdirSync(RUNS_DIR)
|
|
283
|
+
.filter(f => f.endsWith('.json'))
|
|
284
|
+
.sort()
|
|
285
|
+
.reverse()
|
|
286
|
+
.slice(0, options.limit ? parseInt(options.limit) : 10);
|
|
287
|
+
|
|
288
|
+
if (files.length === 0) {
|
|
289
|
+
console.log(` ${DIM}No runs found.${RESET}`);
|
|
290
|
+
console.log(` ${DIM}Use "gbos start" to begin a new run.${RESET}\n`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const file of files) {
|
|
295
|
+
try {
|
|
296
|
+
const runId = file.replace('.json', '');
|
|
297
|
+
const run = StateMachine.loadRun(runId);
|
|
298
|
+
const summary = run.getSummary();
|
|
299
|
+
|
|
300
|
+
const stateColors = {
|
|
301
|
+
[STATES.COMPLETED]: GREEN,
|
|
302
|
+
[STATES.FAILED]: RED,
|
|
303
|
+
[STATES.PAUSED]: YELLOW,
|
|
304
|
+
};
|
|
305
|
+
const stateColor = stateColors[summary.state] || CYAN;
|
|
306
|
+
|
|
307
|
+
console.log(` ${stateColor}●${RESET} ${BOLD}${summary.runId}${RESET}`);
|
|
308
|
+
console.log(` ${DIM}State: ${summary.state} | Agent: ${summary.agent}${RESET}`);
|
|
309
|
+
if (summary.taskKey) {
|
|
310
|
+
console.log(` ${DIM}Task: ${summary.taskKey}${RESET}`);
|
|
311
|
+
}
|
|
312
|
+
if (summary.startTime) {
|
|
313
|
+
console.log(` ${DIM}Started: ${new Date(summary.startTime).toLocaleString()}${RESET}`);
|
|
314
|
+
}
|
|
315
|
+
console.log('');
|
|
316
|
+
} catch (e) {
|
|
317
|
+
// Skip invalid files
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = {
|
|
325
|
+
startCommand,
|
|
326
|
+
resumeCommand,
|
|
327
|
+
stopCommand,
|
|
328
|
+
runsCommand,
|
|
329
|
+
};
|
package/src/lib/api.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const config = require('./config');
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Default API endpoint (new domain)
|
|
4
|
+
const DEFAULT_API_URL = 'https://api.gbos.io/api/v1';
|
|
5
|
+
|
|
6
|
+
// Support GBOS_API_URL env var for backwards compatibility or custom endpoints
|
|
7
|
+
// e.g., GBOS_API_URL=https://gbos-api-579767694933.us-south1.run.app/api/v1
|
|
8
|
+
const API_BASE_URL = process.env.GBOS_API_URL || DEFAULT_API_URL;
|
|
4
9
|
|
|
5
10
|
class GbosApiClient {
|
|
6
11
|
constructor() {
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Agent Adapter
|
|
3
|
+
* Defines the interface for all agent adapters
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { EventEmitter } = require('events');
|
|
7
|
+
|
|
8
|
+
class BaseAdapter extends EventEmitter {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super();
|
|
11
|
+
this.name = 'base';
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.process = null;
|
|
14
|
+
this.isRunning = false;
|
|
15
|
+
this.supportsNonInteractive = false;
|
|
16
|
+
this.supportsInteractive = true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if the agent CLI is available
|
|
21
|
+
* @returns {Promise<boolean>}
|
|
22
|
+
*/
|
|
23
|
+
async isAvailable() {
|
|
24
|
+
throw new Error('isAvailable() must be implemented');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the agent version
|
|
29
|
+
* @returns {Promise<string>}
|
|
30
|
+
*/
|
|
31
|
+
async getVersion() {
|
|
32
|
+
throw new Error('getVersion() must be implemented');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate the command to run the agent
|
|
37
|
+
* @param {Object} options - Run options
|
|
38
|
+
* @returns {Object} { command, args, env }
|
|
39
|
+
*/
|
|
40
|
+
getCommand(options = {}) {
|
|
41
|
+
throw new Error('getCommand() must be implemented');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format a prompt for this specific agent
|
|
46
|
+
* @param {Object} task - Task object from GBOS API
|
|
47
|
+
* @param {Object} context - Additional context (repo, cloudRunUrl, etc.)
|
|
48
|
+
* @returns {string} Formatted prompt
|
|
49
|
+
*/
|
|
50
|
+
formatPrompt(task, context = {}) {
|
|
51
|
+
throw new Error('formatPrompt() must be implemented');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect if the agent has completed its work
|
|
56
|
+
* Used for interactive mode
|
|
57
|
+
* @param {string} output - Recent output from the agent
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
detectCompletion(output) {
|
|
61
|
+
// Default: look for common completion patterns
|
|
62
|
+
const completionPatterns = [
|
|
63
|
+
/task.*completed?/i,
|
|
64
|
+
/done.*with.*changes/i,
|
|
65
|
+
/all.*changes.*committed/i,
|
|
66
|
+
/finished.*working/i,
|
|
67
|
+
/ready.*for.*review/i,
|
|
68
|
+
];
|
|
69
|
+
return completionPatterns.some(p => p.test(output));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Detect if the agent is waiting for input
|
|
74
|
+
* @param {string} output - Recent output
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
detectWaitingForInput(output) {
|
|
78
|
+
const waitPatterns = [
|
|
79
|
+
/\?.*$/m,
|
|
80
|
+
/\(y\/n\)/i,
|
|
81
|
+
/press.*enter/i,
|
|
82
|
+
/continue\?/i,
|
|
83
|
+
];
|
|
84
|
+
return waitPatterns.some(p => p.test(output));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect if the agent encountered an error
|
|
89
|
+
* @param {string} output - Recent output
|
|
90
|
+
* @returns {boolean}
|
|
91
|
+
*/
|
|
92
|
+
detectError(output) {
|
|
93
|
+
const errorPatterns = [
|
|
94
|
+
/error:/i,
|
|
95
|
+
/failed:/i,
|
|
96
|
+
/exception:/i,
|
|
97
|
+
/fatal:/i,
|
|
98
|
+
];
|
|
99
|
+
return errorPatterns.some(p => p.test(output));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse the output to extract useful information
|
|
104
|
+
* @param {string} output - Full output from the agent
|
|
105
|
+
* @returns {Object} Parsed information
|
|
106
|
+
*/
|
|
107
|
+
parseOutput(output) {
|
|
108
|
+
return {
|
|
109
|
+
raw: output,
|
|
110
|
+
filesModified: this.extractFilesModified(output),
|
|
111
|
+
testsRun: this.extractTestResults(output),
|
|
112
|
+
errors: this.extractErrors(output),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Extract files modified from output
|
|
118
|
+
* @param {string} output
|
|
119
|
+
* @returns {string[]}
|
|
120
|
+
*/
|
|
121
|
+
extractFilesModified(output) {
|
|
122
|
+
const files = new Set();
|
|
123
|
+
// Common patterns for file modifications
|
|
124
|
+
const patterns = [
|
|
125
|
+
/(?:created?|modified?|updated?|wrote?|edited?)\s+[`']?([^\s`']+\.[a-z]+)/gi,
|
|
126
|
+
/(?:file|path):\s*[`']?([^\s`']+\.[a-z]+)/gi,
|
|
127
|
+
];
|
|
128
|
+
patterns.forEach(pattern => {
|
|
129
|
+
let match;
|
|
130
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
131
|
+
files.add(match[1]);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return Array.from(files);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Extract test results from output
|
|
139
|
+
* @param {string} output
|
|
140
|
+
* @returns {Object|null}
|
|
141
|
+
*/
|
|
142
|
+
extractTestResults(output) {
|
|
143
|
+
// Look for common test output patterns
|
|
144
|
+
const jestMatch = output.match(/Tests:\s*(\d+)\s*passed,?\s*(\d+)?\s*failed?/i);
|
|
145
|
+
if (jestMatch) {
|
|
146
|
+
return {
|
|
147
|
+
framework: 'jest',
|
|
148
|
+
passed: parseInt(jestMatch[1]) || 0,
|
|
149
|
+
failed: parseInt(jestMatch[2]) || 0,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const pytestMatch = output.match(/(\d+)\s*passed.*?(\d+)?\s*failed?/i);
|
|
154
|
+
if (pytestMatch) {
|
|
155
|
+
return {
|
|
156
|
+
framework: 'pytest',
|
|
157
|
+
passed: parseInt(pytestMatch[1]) || 0,
|
|
158
|
+
failed: parseInt(pytestMatch[2]) || 0,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Extract errors from output
|
|
167
|
+
* @param {string} output
|
|
168
|
+
* @returns {string[]}
|
|
169
|
+
*/
|
|
170
|
+
extractErrors(output) {
|
|
171
|
+
const errors = [];
|
|
172
|
+
const lines = output.split('\n');
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
if (/error:|failed:|exception:|fatal:/i.test(line)) {
|
|
175
|
+
errors.push(line.trim());
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return errors;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Stop the running agent
|
|
183
|
+
*/
|
|
184
|
+
async stop() {
|
|
185
|
+
if (this.process) {
|
|
186
|
+
this.process.kill('SIGTERM');
|
|
187
|
+
this.isRunning = false;
|
|
188
|
+
this.emit('stopped');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Force kill the agent
|
|
194
|
+
*/
|
|
195
|
+
async kill() {
|
|
196
|
+
if (this.process) {
|
|
197
|
+
this.process.kill('SIGKILL');
|
|
198
|
+
this.isRunning = false;
|
|
199
|
+
this.emit('killed');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = BaseAdapter;
|