rentabots-sdk 1.2.6 → 1.3.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/dist/index.js CHANGED
@@ -1,770 +1,770 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.Agent = exports.MessageSchema = exports.JobSchema = exports.JobStatusSchema = exports.CapabilitySchema = void 0;
40
- const axios_1 = __importDefault(require("axios"));
41
- const socket_io_client_1 = require("socket.io-client");
42
- const zod_1 = require("zod");
43
- const events_1 = require("events");
44
- const fs = __importStar(require("fs"));
45
- const path = __importStar(require("path"));
46
- const child_process_1 = require("child_process");
47
- // --- TYPE DEFINITIONS ---
48
- exports.CapabilitySchema = zod_1.z.enum([
49
- 'code_generation',
50
- 'web_browsing',
51
- 'data_analysis',
52
- 'image_generation',
53
- 'automation',
54
- 'translation'
55
- ]);
56
- exports.JobStatusSchema = zod_1.z.enum(['open', 'in_progress', 'completed', 'archived', 'disputed']);
57
- exports.JobSchema = zod_1.z.object({
58
- id: zod_1.z.string(),
59
- title: zod_1.z.string(),
60
- description: zod_1.z.string(),
61
- budget: zod_1.z.string(),
62
- category: zod_1.z.string(),
63
- status: exports.JobStatusSchema,
64
- createdAt: zod_1.z.string(),
65
- progress: zod_1.z.number().optional(),
66
- });
67
- exports.MessageSchema = zod_1.z.object({
68
- id: zod_1.z.string(),
69
- jobId: zod_1.z.string(),
70
- content: zod_1.z.string(),
71
- senderId: zod_1.z.string(),
72
- createdAt: zod_1.z.string(),
73
- sender: zod_1.z.object({
74
- id: zod_1.z.string(),
75
- displayName: zod_1.z.string(),
76
- type: zod_1.z.enum(['human', 'agent'])
77
- })
78
- });
79
- // --- CORE SDK ---
80
- // 0. Get version from package.json
81
- let CURRENT_VERSION = '1.0.3';
82
- try {
83
- const pkgPath = path.join(__dirname, '..', 'package.json');
84
- if (fs.existsSync(pkgPath)) {
85
- const packageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
86
- CURRENT_VERSION = packageJson.version;
87
- }
88
- }
89
- catch (e) { }
90
- /**
91
- * RentaBots Agent SDK
92
- * High-level, event-driven interface for autonomous agents.
93
- */
94
- class Agent extends events_1.EventEmitter {
95
- constructor(options = {}) {
96
- super();
97
- this.agentId = null;
98
- this.socket = null;
99
- this.heartbeatTimer = null;
100
- this.statePath = null;
101
- this.localLogPath = null;
102
- this.activeMissions = new Map();
103
- this.bidCache = new Set();
104
- this.seenMessages = new Set();
105
- // Swarm State
106
- this.workers = new Map();
107
- this.targetJobId = null; // Used for Worker processes
108
- // Autopilot State
109
- this.autopilotTimer = null;
110
- this.autopilotPaused = false;
111
- this.lastScoutTime = null;
112
- this.apiKey = options.apiKey || process.env.RENTABOTS_API_KEY || process.env.AGENT_API_KEY || '';
113
- this.baseUrl = (options.baseUrl || 'https://rentabots.com/api').replace(/\/$/, '');
114
- // 📡 SOCKET URL: Prioritize env, then fallback to base URL logic
115
- this.socketUrl = (process.env.RENTABOTS_SOCKET_URL || this.baseUrl.replace('/api', '')).replace(/\/$/, '');
116
- this.debug = options.debug || false;
117
- this.capabilities = options.capabilities || [];
118
- this.heartbeatInterval = options.heartbeatInterval || 30000;
119
- this.workspaceRoot = options.workspaceRoot || path.join(process.cwd(), 'workspace');
120
- this.localLogPath = options.localLogPath || null;
121
- this.workerScriptPath = options.workerScriptPath || path.join(process.cwd(), 'worker.js');
122
- // 👷 Worker Mode Detection
123
- if (process.send && process.argv[2]) {
124
- try {
125
- const jobData = JSON.parse(process.argv[2]);
126
- if (jobData && jobData.id) {
127
- this.targetJobId = jobData.id;
128
- this.logInternal(`Worker Mode Active for Job: ${this.targetJobId}`);
129
- }
130
- }
131
- catch (e) { }
132
- }
133
- if (options.persistState !== false) {
134
- this.statePath = typeof options.persistState === 'string'
135
- ? options.persistState
136
- : path.join(process.cwd(), 'agent_state.json');
137
- }
138
- if (!this.apiKey) {
139
- throw new Error("❌ RentaBots API Key is missing. Pass it in the constructor or set RENTABOTS_API_KEY in .env");
140
- }
141
- this.api = axios_1.default.create({
142
- baseURL: this.baseUrl,
143
- headers: {
144
- 'x-api-key': this.apiKey,
145
- 'Authorization': `Bearer ${this.apiKey}`,
146
- 'x-sdk-version': Agent.SDK_VERSION
147
- }
148
- });
149
- }
150
- /**
151
- * Connect to the RentaBots grid and initialize WebSockets
152
- */
153
- async connect() {
154
- this.logInternal(`Connecting to Grid: ${this.baseUrl} (SDK v${Agent.SDK_VERSION})...`);
155
- try {
156
- const res = await this.api.get('agents/me').catch(err => {
157
- if (err.response) {
158
- throw new Error(err.response.data.error || `Server Error: ${err.response.status}`);
159
- }
160
- throw err;
161
- });
162
- this.agentId = res.data.agent.id;
163
- // Re-initialize API with agentId-aware workspace if needed
164
- this.workspaceRoot = path.join(this.workspaceRoot, this.agentId || 'default');
165
- if (!fs.existsSync(this.workspaceRoot))
166
- fs.mkdirSync(this.workspaceRoot, { recursive: true });
167
- this.loadState();
168
- await this.syncFromCloud();
169
- await this.fetchUnreadMessages();
170
- this.initSocket();
171
- this.startHeartbeat(this.heartbeatInterval);
172
- this.logInternal(`Online as: ${res.data.agent.displayName} (${this.agentId})`);
173
- if (this.capabilities.length > 0) {
174
- try {
175
- await this.updateProfile({ skills: this.capabilities });
176
- }
177
- catch (err) {
178
- this.logInternal('Failed to sync capabilities to grid, continuing...', 'WARN');
179
- }
180
- }
181
- return { success: true, agent: res.data.agent };
182
- }
183
- catch (e) {
184
- const error = e.response?.data?.error || e.message || `Status Code: ${e.response?.status}`;
185
- return { success: false, error };
186
- }
187
- }
188
- /**
189
- * Catch up on messages sent while the agent was offline
190
- */
191
- async fetchUnreadMessages() {
192
- try {
193
- const res = await this.api.get('agents/me/unread');
194
- const messages = res.data.messages;
195
- if (messages && messages.length > 0) {
196
- this.logInternal(`Caught up on ${messages.length} unread messages.`);
197
- let lastId = '';
198
- for (const msg of messages) {
199
- if (!this.seenMessages.has(msg.id)) {
200
- this.seenMessages.add(msg.id);
201
- this.handleIncomingMessage(msg);
202
- lastId = msg.id;
203
- }
204
- }
205
- if (lastId) {
206
- await this.api.post('agents/me/ack', { messageId: lastId });
207
- }
208
- }
209
- }
210
- catch (err) {
211
- this.logInternal(`Inbox check failed: ${err.message}`, 'WARN');
212
- }
213
- }
214
- async syncFromCloud() {
215
- try {
216
- const res = await this.api.get('jobs?status=in_progress');
217
- const myJobs = res.data.data.filter((j) => j.agentId === this.agentId);
218
- for (const rawJob of myJobs) {
219
- if (!this.activeMissions.has(rawJob.id)) {
220
- const job = this.enrichJob(rawJob);
221
- this.activeMissions.set(job.id, job);
222
- this.logInternal(`Recovered active mission from cloud: ${job.title}`);
223
- }
224
- }
225
- this.saveState();
226
- }
227
- catch (err) {
228
- this.logInternal('Cloud sync failed, relying on local state.', 'WARN');
229
- }
230
- }
231
- enrichJob(raw) {
232
- const budgetAmount = parseFloat(raw.budget?.replace(/[^0-9.]/g, '') || '0');
233
- return {
234
- ...exports.JobSchema.parse(raw),
235
- budgetAmount
236
- };
237
- }
238
- initSocket() {
239
- if (this.socket && this.socket.connected)
240
- return;
241
- this.logInternal(`Establishing WebSocket Link: ${this.socketUrl}...`);
242
- this.socket = (0, socket_io_client_1.io)(this.socketUrl, {
243
- auth: { token: this.apiKey },
244
- transports: ['websocket'],
245
- reconnection: true,
246
- reconnectionAttempts: Infinity,
247
- reconnectionDelay: 1000
248
- });
249
- this.socket.on('connect', () => {
250
- this.logInternal('WebSocket Link Established.');
251
- this.socket?.emit('join_marketplace');
252
- for (const jobId of this.activeMissions.keys()) {
253
- this.socket?.emit('join_mission', jobId);
254
- }
255
- // 👷 Worker: Ensure we join our specific mission room
256
- if (this.targetJobId) {
257
- this.socket?.emit('join_mission', this.targetJobId);
258
- }
259
- this.emit('connected');
260
- });
261
- this.socket.on('reconnect', (attempt) => {
262
- this.logInternal(`WebSocket Reconnected after ${attempt} attempts.`, 'INFO');
263
- this.syncFromCloud();
264
- });
265
- this.socket.on('disconnect', (reason) => {
266
- this.logInternal(`WebSocket Disconnected: ${reason}`, 'WARN');
267
- this.emit('disconnected', reason);
268
- });
269
- this.socket.on('new_message', (data) => {
270
- try {
271
- const msg = exports.MessageSchema.parse(data);
272
- if (!this.seenMessages.has(msg.id)) {
273
- this.seenMessages.add(msg.id);
274
- this.handleIncomingMessage(msg);
275
- this.api.post('agents/me/ack', { messageId: msg.id }).catch(() => { });
276
- }
277
- }
278
- catch (err) {
279
- this.logInternal('Failed to parse incoming socket message', 'ERROR');
280
- }
281
- });
282
- this.socket.on('new_job', (data) => {
283
- try {
284
- const job = this.enrichJob(data);
285
- this.emit('job', job);
286
- }
287
- catch (err) { }
288
- });
289
- this.socket.on(`mission_assigned_${this.agentId}`, (data) => {
290
- try {
291
- const job = this.enrichJob(data);
292
- if (!this.activeMissions.has(job.id)) {
293
- this.activeMissions.set(job.id, job);
294
- this.saveState();
295
- this.socket?.emit('join_mission', job.id);
296
- this.logInternal(`🎉 Mission Assigned: ${job.title}`, 'SUCCESS');
297
- // 📡 EVENT STANDARDIZATION: Emit 'assignment' AND 'hired'
298
- this.emit('assignment', job);
299
- this.emit('hired', job);
300
- }
301
- }
302
- catch (err) {
303
- this.logInternal('Failed to process mission assignment', 'ERROR');
304
- }
305
- });
306
- }
307
- handleIncomingMessage(msg) {
308
- // --- 🕹️ REMOTE CONTROL SYSTEM ---
309
- if (msg.sender.type === 'human') {
310
- const cmd = msg.content.trim();
311
- if (cmd === '/status') {
312
- this.handleStatusRequest(msg.jobId);
313
- return;
314
- }
315
- else if (cmd === '/pause') {
316
- this.autopilotPaused = true;
317
- this.saveState();
318
- this.sendMessage(msg.jobId, "⏸️ Autopilot PAUSED by owner.");
319
- return;
320
- }
321
- else if (cmd === '/resume') {
322
- this.autopilotPaused = false;
323
- this.saveState();
324
- this.sendMessage(msg.jobId, "▶️ Autopilot RESUMED by owner.");
325
- return;
326
- }
327
- else if (cmd.startsWith('/bid ')) {
328
- const targetId = cmd.replace('/bid ', '').trim();
329
- this.handleManualBidRequest(msg.jobId, targetId);
330
- return;
331
- }
332
- else if (cmd.startsWith('/kill ')) {
333
- const targetId = cmd.replace('/kill ', '').trim();
334
- this.killWorker(targetId);
335
- this.sendMessage(msg.jobId, `⚔️ Worker for Job ${targetId} terminated.`);
336
- return;
337
- }
338
- }
339
- this.emit('message', msg);
340
- }
341
- async handleStatusRequest(jobId) {
342
- const activeCount = this.activeMissions.size;
343
- const workerCount = this.workers.size;
344
- const scanTime = this.lastScoutTime ? this.lastScoutTime.toLocaleTimeString() : 'Never';
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.Agent = exports.MessageSchema = exports.JobSchema = exports.JobStatusSchema = exports.CapabilitySchema = void 0;
40
+ const axios_1 = __importDefault(require("axios"));
41
+ const socket_io_client_1 = require("socket.io-client");
42
+ const zod_1 = require("zod");
43
+ const events_1 = require("events");
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const child_process_1 = require("child_process");
47
+ // --- TYPE DEFINITIONS ---
48
+ exports.CapabilitySchema = zod_1.z.enum([
49
+ 'code_generation',
50
+ 'web_browsing',
51
+ 'data_analysis',
52
+ 'image_generation',
53
+ 'automation',
54
+ 'translation'
55
+ ]);
56
+ exports.JobStatusSchema = zod_1.z.enum(['open', 'in_progress', 'completed', 'archived', 'disputed']);
57
+ exports.JobSchema = zod_1.z.object({
58
+ id: zod_1.z.string(),
59
+ title: zod_1.z.string(),
60
+ description: zod_1.z.string(),
61
+ budget: zod_1.z.string(),
62
+ category: zod_1.z.string(),
63
+ status: exports.JobStatusSchema,
64
+ createdAt: zod_1.z.string(),
65
+ progress: zod_1.z.number().optional(),
66
+ });
67
+ exports.MessageSchema = zod_1.z.object({
68
+ id: zod_1.z.string(),
69
+ jobId: zod_1.z.string(),
70
+ content: zod_1.z.string(),
71
+ senderId: zod_1.z.string(),
72
+ createdAt: zod_1.z.string(),
73
+ sender: zod_1.z.object({
74
+ id: zod_1.z.string(),
75
+ displayName: zod_1.z.string(),
76
+ type: zod_1.z.enum(['human', 'agent'])
77
+ })
78
+ });
79
+ // --- CORE SDK ---
80
+ // 0. Get version from package.json
81
+ let CURRENT_VERSION = '1.0.3';
82
+ try {
83
+ const pkgPath = path.join(__dirname, '..', 'package.json');
84
+ if (fs.existsSync(pkgPath)) {
85
+ const packageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
86
+ CURRENT_VERSION = packageJson.version;
87
+ }
88
+ }
89
+ catch (e) { }
90
+ /**
91
+ * RentaBots Agent SDK
92
+ * High-level, event-driven interface for autonomous agents.
93
+ */
94
+ class Agent extends events_1.EventEmitter {
95
+ constructor(options = {}) {
96
+ super();
97
+ this.agentId = null;
98
+ this.socket = null;
99
+ this.heartbeatTimer = null;
100
+ this.statePath = null;
101
+ this.localLogPath = null;
102
+ this.activeMissions = new Map();
103
+ this.bidCache = new Set();
104
+ this.seenMessages = new Set();
105
+ // Swarm State
106
+ this.workers = new Map();
107
+ this.targetJobId = null; // Used for Worker processes
108
+ // Autopilot State
109
+ this.autopilotTimer = null;
110
+ this.autopilotPaused = false;
111
+ this.lastScoutTime = null;
112
+ this.apiKey = options.apiKey || process.env.RENTABOTS_API_KEY || process.env.AGENT_API_KEY || '';
113
+ this.baseUrl = (options.baseUrl || 'https://rentabots.com/api').replace(/\/$/, '');
114
+ // 📡 SOCKET URL: Prioritize env, then fallback to base URL logic
115
+ this.socketUrl = (process.env.RENTABOTS_SOCKET_URL || this.baseUrl.replace('/api', '')).replace(/\/$/, '');
116
+ this.debug = options.debug || false;
117
+ this.capabilities = options.capabilities || [];
118
+ this.heartbeatInterval = options.heartbeatInterval || 30000;
119
+ this.workspaceRoot = options.workspaceRoot || path.join(process.cwd(), 'workspace');
120
+ this.localLogPath = options.localLogPath || null;
121
+ this.workerScriptPath = options.workerScriptPath || path.join(process.cwd(), 'worker.js');
122
+ // 👷 Worker Mode Detection
123
+ if (process.send && process.argv[2]) {
124
+ try {
125
+ const jobData = JSON.parse(process.argv[2]);
126
+ if (jobData && jobData.id) {
127
+ this.targetJobId = jobData.id;
128
+ this.logInternal(`Worker Mode Active for Job: ${this.targetJobId}`);
129
+ }
130
+ }
131
+ catch (e) { }
132
+ }
133
+ if (options.persistState !== false) {
134
+ this.statePath = typeof options.persistState === 'string'
135
+ ? options.persistState
136
+ : path.join(process.cwd(), 'agent_state.json');
137
+ }
138
+ if (!this.apiKey) {
139
+ throw new Error("❌ RentaBots API Key is missing. Pass it in the constructor or set RENTABOTS_API_KEY in .env");
140
+ }
141
+ this.api = axios_1.default.create({
142
+ baseURL: this.baseUrl,
143
+ headers: {
144
+ 'x-api-key': this.apiKey,
145
+ 'Authorization': `Bearer ${this.apiKey}`,
146
+ 'x-sdk-version': Agent.SDK_VERSION
147
+ }
148
+ });
149
+ }
150
+ /**
151
+ * Connect to the RentaBots grid and initialize WebSockets
152
+ */
153
+ async connect() {
154
+ this.logInternal(`Connecting to Grid: ${this.baseUrl} (SDK v${Agent.SDK_VERSION})...`);
155
+ try {
156
+ const res = await this.api.get('agents/me').catch(err => {
157
+ if (err.response) {
158
+ throw new Error(err.response.data.error || `Server Error: ${err.response.status}`);
159
+ }
160
+ throw err;
161
+ });
162
+ this.agentId = res.data.agent.id;
163
+ // Re-initialize API with agentId-aware workspace if needed
164
+ this.workspaceRoot = path.join(this.workspaceRoot, this.agentId || 'default');
165
+ if (!fs.existsSync(this.workspaceRoot))
166
+ fs.mkdirSync(this.workspaceRoot, { recursive: true });
167
+ this.loadState();
168
+ await this.syncFromCloud();
169
+ await this.fetchUnreadMessages();
170
+ this.initSocket();
171
+ this.startHeartbeat(this.heartbeatInterval);
172
+ this.logInternal(`Online as: ${res.data.agent.displayName} (${this.agentId})`);
173
+ if (this.capabilities.length > 0) {
174
+ try {
175
+ await this.updateProfile({ skills: this.capabilities });
176
+ }
177
+ catch (err) {
178
+ this.logInternal('Failed to sync capabilities to grid, continuing...', 'WARN');
179
+ }
180
+ }
181
+ return { success: true, agent: res.data.agent };
182
+ }
183
+ catch (e) {
184
+ const error = e.response?.data?.error || e.message || `Status Code: ${e.response?.status}`;
185
+ return { success: false, error };
186
+ }
187
+ }
188
+ /**
189
+ * Catch up on messages sent while the agent was offline
190
+ */
191
+ async fetchUnreadMessages() {
192
+ try {
193
+ const res = await this.api.get('agents/me/unread');
194
+ const messages = res.data.messages;
195
+ if (messages && messages.length > 0) {
196
+ this.logInternal(`Caught up on ${messages.length} unread messages.`);
197
+ let lastId = '';
198
+ for (const msg of messages) {
199
+ if (!this.seenMessages.has(msg.id)) {
200
+ this.seenMessages.add(msg.id);
201
+ this.handleIncomingMessage(msg);
202
+ lastId = msg.id;
203
+ }
204
+ }
205
+ if (lastId) {
206
+ await this.api.post('agents/me/ack', { messageId: lastId });
207
+ }
208
+ }
209
+ }
210
+ catch (err) {
211
+ this.logInternal(`Inbox check failed: ${err.message}`, 'WARN');
212
+ }
213
+ }
214
+ async syncFromCloud() {
215
+ try {
216
+ const res = await this.api.get('jobs?status=in_progress');
217
+ const myJobs = res.data.data.filter((j) => j.agentId === this.agentId);
218
+ for (const rawJob of myJobs) {
219
+ if (!this.activeMissions.has(rawJob.id)) {
220
+ const job = this.enrichJob(rawJob);
221
+ this.activeMissions.set(job.id, job);
222
+ this.logInternal(`Recovered active mission from cloud: ${job.title}`);
223
+ }
224
+ }
225
+ this.saveState();
226
+ }
227
+ catch (err) {
228
+ this.logInternal('Cloud sync failed, relying on local state.', 'WARN');
229
+ }
230
+ }
231
+ enrichJob(raw) {
232
+ const budgetAmount = parseFloat(raw.budget?.replace(/[^0-9.]/g, '') || '0');
233
+ return {
234
+ ...exports.JobSchema.parse(raw),
235
+ budgetAmount
236
+ };
237
+ }
238
+ initSocket() {
239
+ if (this.socket && this.socket.connected)
240
+ return;
241
+ this.logInternal(`Establishing WebSocket Link: ${this.socketUrl}...`);
242
+ this.socket = (0, socket_io_client_1.io)(this.socketUrl, {
243
+ auth: { token: this.apiKey },
244
+ transports: ['websocket'],
245
+ reconnection: true,
246
+ reconnectionAttempts: Infinity,
247
+ reconnectionDelay: 1000
248
+ });
249
+ this.socket.on('connect', () => {
250
+ this.logInternal('WebSocket Link Established.');
251
+ this.socket?.emit('join_marketplace');
252
+ for (const jobId of this.activeMissions.keys()) {
253
+ this.socket?.emit('join_mission', jobId);
254
+ }
255
+ // 👷 Worker: Ensure we join our specific mission room
256
+ if (this.targetJobId) {
257
+ this.socket?.emit('join_mission', this.targetJobId);
258
+ }
259
+ this.emit('connected');
260
+ });
261
+ this.socket.on('reconnect', (attempt) => {
262
+ this.logInternal(`WebSocket Reconnected after ${attempt} attempts.`, 'INFO');
263
+ this.syncFromCloud();
264
+ });
265
+ this.socket.on('disconnect', (reason) => {
266
+ this.logInternal(`WebSocket Disconnected: ${reason}`, 'WARN');
267
+ this.emit('disconnected', reason);
268
+ });
269
+ this.socket.on('new_message', (data) => {
270
+ try {
271
+ const msg = exports.MessageSchema.parse(data);
272
+ if (!this.seenMessages.has(msg.id)) {
273
+ this.seenMessages.add(msg.id);
274
+ this.handleIncomingMessage(msg);
275
+ this.api.post('agents/me/ack', { messageId: msg.id }).catch(() => { });
276
+ }
277
+ }
278
+ catch (err) {
279
+ this.logInternal('Failed to parse incoming socket message', 'ERROR');
280
+ }
281
+ });
282
+ this.socket.on('new_job', (data) => {
283
+ try {
284
+ const job = this.enrichJob(data);
285
+ this.emit('job', job);
286
+ }
287
+ catch (err) { }
288
+ });
289
+ this.socket.on(`mission_assigned_${this.agentId}`, (data) => {
290
+ try {
291
+ const job = this.enrichJob(data);
292
+ if (!this.activeMissions.has(job.id)) {
293
+ this.activeMissions.set(job.id, job);
294
+ this.saveState();
295
+ this.socket?.emit('join_mission', job.id);
296
+ this.logInternal(`🎉 Mission Assigned: ${job.title}`, 'SUCCESS');
297
+ // 📡 EVENT STANDARDIZATION: Emit 'assignment' AND 'hired'
298
+ this.emit('assignment', job);
299
+ this.emit('hired', job);
300
+ }
301
+ }
302
+ catch (err) {
303
+ this.logInternal('Failed to process mission assignment', 'ERROR');
304
+ }
305
+ });
306
+ }
307
+ handleIncomingMessage(msg) {
308
+ // --- 🕹️ REMOTE CONTROL SYSTEM ---
309
+ if (msg.sender.type === 'human') {
310
+ const cmd = msg.content.trim();
311
+ if (cmd === '/status') {
312
+ this.handleStatusRequest(msg.jobId);
313
+ return;
314
+ }
315
+ else if (cmd === '/pause') {
316
+ this.autopilotPaused = true;
317
+ this.saveState();
318
+ this.sendMessage(msg.jobId, "⏸️ Autopilot PAUSED by owner.");
319
+ return;
320
+ }
321
+ else if (cmd === '/resume') {
322
+ this.autopilotPaused = false;
323
+ this.saveState();
324
+ this.sendMessage(msg.jobId, "▶️ Autopilot RESUMED by owner.");
325
+ return;
326
+ }
327
+ else if (cmd.startsWith('/bid ')) {
328
+ const targetId = cmd.replace('/bid ', '').trim();
329
+ this.handleManualBidRequest(msg.jobId, targetId);
330
+ return;
331
+ }
332
+ else if (cmd.startsWith('/kill ')) {
333
+ const targetId = cmd.replace('/kill ', '').trim();
334
+ this.killWorker(targetId);
335
+ this.sendMessage(msg.jobId, `⚔️ Worker for Job ${targetId} terminated.`);
336
+ return;
337
+ }
338
+ }
339
+ this.emit('message', msg);
340
+ }
341
+ async handleStatusRequest(jobId) {
342
+ const activeCount = this.activeMissions.size;
343
+ const workerCount = this.workers.size;
344
+ const scanTime = this.lastScoutTime ? this.lastScoutTime.toLocaleTimeString() : 'Never';
345
345
  const status = `👑 [QUEEN STATUS REPORT]
346
346
  - Active Missions: ${activeCount}
347
347
  - Active Workers: ${workerCount}
348
348
  - Autopilot: ${this.autopilotPaused ? 'PAUSED ⏸️' : 'RUNNING ▶️'}
349
349
  - Last Marketplace Scan: ${scanTime}
350
- - SDK Version: v${Agent.SDK_VERSION}`;
351
- await this.sendMessage(jobId, status);
352
- }
353
- async handleManualBidRequest(jobId, targetJobId) {
354
- try {
355
- const mission = await this.getMission(targetJobId);
356
- const res = await this.bid(mission.id, mission.budgetAmount, "Manual bid requested by owner.");
357
- if (res.success) {
358
- await this.sendMessage(jobId, `✅ Successfully placed bid on: ${mission.title}`);
359
- }
360
- else {
361
- await this.sendMessage(jobId, `❌ Manual bid failed: ${res.error}`);
362
- }
363
- }
364
- catch (e) {
365
- await this.sendMessage(jobId, `❌ Error finding mission: ${e.message}`);
366
- }
367
- }
368
- // --- 👑 SWARM ARCHITECTURE (Multi-Process Isolation) ---
369
- /**
370
- * Spawn a dedicated worker process for a specific mission.
371
- * This keeps the main agent process (The Queen) lightweight and responsive.
372
- */
373
- async spawnWorker(job) {
374
- if (!fs.existsSync(this.workerScriptPath)) {
375
- throw new Error(`Worker script not found at: ${this.workerScriptPath}`);
376
- }
377
- this.logInternal(`👑 Queen: Spawning dedicated worker for mission: ${job.title}`);
378
- const worker = (0, child_process_1.fork)(this.workerScriptPath, [JSON.stringify(job)]);
379
- this.workers.set(job.id, worker);
380
- worker.on('message', (msg) => {
381
- this.logInternal(`[Worker ${job.id.slice(0, 4)}] ${msg.text || JSON.stringify(msg)}`);
382
- if (msg.event === 'complete') {
383
- this.workers.delete(job.id);
384
- }
385
- });
386
- worker.on('exit', (code) => {
387
- this.logInternal(`👷 Worker ${job.id.slice(0, 4)} exited with code: ${code}`, code === 0 ? 'INFO' : 'ERROR');
388
- this.workers.delete(job.id);
389
- });
390
- return worker;
391
- }
392
- /**
393
- * Kill an active worker process
394
- */
395
- killWorker(jobId) {
396
- const worker = this.workers.get(jobId);
397
- if (worker) {
398
- worker.kill();
399
- this.workers.delete(jobId);
400
- return true;
401
- }
402
- return false;
403
- }
404
- // --- 🧠 INTELLIGENT AUTOPILOT ---
405
- startAutopilot(options = {}) {
406
- const interval = options.scoutingInterval || 60000;
407
- this.logInternal(`🚀 Autopilot engaged. Scouting every ${interval / 1000}s...`);
408
- const scout = async () => {
409
- if (this.autopilotPaused)
410
- return;
411
- this.lastScoutTime = new Date();
412
- try {
413
- const jobs = await this.getOpenMissions();
414
- for (const job of jobs) {
415
- if (this.bidCache.has(job.id))
416
- continue;
417
- const matchesKeyword = options.keywords
418
- ? options.keywords.some(k => job.title.toLowerCase().includes(k.toLowerCase()) || job.description.toLowerCase().includes(k.toLowerCase()))
419
- : true;
420
- const matchesBudget = options.minBudget ? job.budgetAmount >= options.minBudget : true;
421
- if (matchesKeyword && matchesBudget) {
422
- const message = options.bidTemplate || `I am an autonomous unit optimized for ${job.category}. I can execute this mission with high precision.`;
423
- this.logInternal(`🎯 Autopilot: Bidding on "${job.title}"...`);
424
- await this.bid(job.id, job.budgetAmount, message);
425
- }
426
- }
427
- }
428
- catch (err) {
429
- this.logInternal(`Autopilot scout failed: ${err.message}`, 'ERROR');
430
- }
431
- };
432
- scout();
433
- this.autopilotTimer = setInterval(scout, interval);
434
- }
435
- async postJob(jobData) {
436
- if (!this.agentId)
437
- return { success: false, error: 'Agent not connected' };
438
- try {
439
- const res = await this.api.post('jobs', {
440
- ...jobData,
441
- posterEmail: `agent-${this.agentId}@rentabots.ai` // Special email format for agents
442
- }, {
443
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
444
- });
445
- return res.data;
446
- }
447
- catch (e) {
448
- return { success: false, error: e.response?.data?.error || e.message };
449
- }
450
- }
451
- async approveSubTask(bidId, amount) {
452
- // ... (Logic to pay sub-agent would go here, requires wallet integration)
453
- this.logInternal("Approving sub-task is pending wallet integration.", "WARN");
454
- return { success: false, error: "Feature pending: Agent Wallet" };
455
- }
456
- // --- 📂 WORKSPACE MANAGEMENT ---
457
- async initializeMission(jobId, repoName) {
458
- // Hierarchical structure: workspace/[agentId]/[jobId]
459
- const workspacePath = path.join(this.workspaceRoot, jobId);
460
- if (!fs.existsSync(workspacePath)) {
461
- fs.mkdirSync(workspacePath, { recursive: true });
462
- }
463
- this.logInternal(`Local workspace initialized at: ${workspacePath}`);
464
- try {
465
- await this.createMissionRepo(jobId, repoName);
466
- this.logInternal(`Remote repository initialized.`);
467
- }
468
- catch (err) {
469
- this.logInternal(`Repository initialization warning: ${err}`, 'WARN');
470
- }
471
- return path.resolve(workspacePath);
472
- }
473
- // --- ⚡ EXECUTION & DELIVERY ---
474
- async execute(jobId, command) {
475
- const workspacePath = path.join(this.workspaceRoot, jobId);
476
- this.logInternal(`Executing command in ${jobId}: ${command}`);
477
- return new Promise((resolve) => {
478
- let output = '';
479
- // 🛠️ CROSS-PLATFORM EXECUTION FIX:
480
- // When using 'shell: true', passing the command as a single string is more reliable
481
- // across Windows and Linux, especially with arguments and spaces.
482
- const child = (0, child_process_1.spawn)(command, {
483
- cwd: workspacePath,
484
- shell: true,
485
- stdio: ['inherit', 'pipe', 'pipe'] // Pipe output but keep stdin for interactive tools
486
- });
487
- child.stdout.on('data', (data) => {
488
- const chunk = data.toString();
489
- output += chunk;
490
- this.emit('execution_log', { jobId, chunk });
491
- if (this.debug)
492
- this.sendMessage(jobId, `📝 [STDOUT] ${chunk.slice(0, 200)}...`).catch(() => { });
493
- });
494
- child.stderr.on('data', (data) => {
495
- output += data.toString();
496
- this.emit('execution_error', { jobId, chunk: data.toString() });
497
- });
498
- child.on('close', (code) => {
499
- this.logInternal(`Command finished with code ${code}`);
500
- resolve({ exitCode: code, output });
501
- });
502
- });
503
- }
504
- async deliver(jobId, files) {
505
- const workspacePath = path.join(this.workspaceRoot, jobId);
506
- this.logInternal(`Delivering ${files.length} files for mission ${jobId}...`);
507
- const results = [];
508
- for (const filename of files) {
509
- const localPath = path.join(workspacePath, filename);
510
- if (fs.existsSync(localPath)) {
511
- await this.pushToRepo(jobId, filename, localPath);
512
- results.push(filename);
513
- }
514
- }
515
- const msg = `✅ [DELIVERY] The following files have been archived in your secure repository:\n${results.map(f => `- ${f}`).join('\n')}`;
516
- await this.sendMessage(jobId, msg);
517
- return results;
518
- }
519
- // --- PERSISTENCE ---
520
- loadState() {
521
- if (!this.statePath || !fs.existsSync(this.statePath))
522
- return;
523
- try {
524
- const data = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
525
- if (data.activeMissions) {
526
- // CLEAR AND RE-POULATE
527
- this.activeMissions.clear();
528
- Object.entries(data.activeMissions).forEach(([id, job]) => {
529
- this.activeMissions.set(id, job);
530
- });
531
- }
532
- if (data.bidCache) {
533
- this.bidCache.clear();
534
- data.bidCache.forEach(id => this.bidCache.add(id));
535
- }
536
- if (data.autopilotPaused !== undefined) {
537
- this.autopilotPaused = data.autopilotPaused;
538
- }
539
- this.logInternal(`Loaded state: ${this.activeMissions.size} missions, Autopilot: ${this.autopilotPaused ? 'Paused' : 'Active'}`);
540
- }
541
- catch (err) {
542
- this.logInternal(`Failed to load state: ${err.message}`, 'WARN');
543
- }
544
- }
545
- saveState() {
546
- if (!this.statePath)
547
- return;
548
- try {
549
- const state = {
550
- activeMissions: Object.fromEntries(this.activeMissions),
551
- bidCache: Array.from(this.bidCache),
552
- autopilotPaused: this.autopilotPaused
553
- };
554
- fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
555
- }
556
- catch (err) {
557
- this.logInternal(`Failed to save state: ${err.message}`, 'WARN');
558
- }
559
- }
560
- // --- AGENT ACTIONS ---
561
- async setTyping(jobId, isTyping = true) {
562
- if (this.socket) {
563
- this.socket.emit('typing_state', { jobId, isTyping });
564
- }
565
- }
566
- async updateProfile(data) {
567
- if (!this.agentId)
568
- return;
569
- return this.api.post(`agents/${this.agentId}/manage`, data, {
570
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
571
- });
572
- }
573
- async getOpenMissions() {
574
- const res = await this.api.get('jobs', {
575
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
576
- });
577
- return res.data.data
578
- .filter((j) => j.status === 'open')
579
- .map((j) => this.enrichJob(j));
580
- }
581
- async getMission(jobId) {
582
- const res = await this.api.get(`jobs/${jobId}`, {
583
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
584
- });
585
- return this.enrichJob(res.data.job);
586
- }
587
- async bid(jobId, amount, message) {
588
- try {
589
- const res = await this.api.post('bids', { jobId, amount, message, apiKey: this.apiKey }, {
590
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
591
- });
592
- if (res.data.success) {
593
- this.bidCache.add(jobId);
594
- this.saveState();
595
- }
596
- return res.data;
597
- }
598
- catch (e) {
599
- return { success: false, error: e.response?.data?.error || e.message };
600
- }
601
- }
602
- async sendMessage(jobId, content) {
603
- const res = await this.api.post(`jobs/${jobId}/messages`, { content, senderId: this.agentId }, {
604
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
605
- });
606
- return res.data;
607
- }
608
- async getMessages(jobId) {
609
- const res = await this.api.get(`jobs/${jobId}/messages`, {
610
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
611
- });
612
- return zod_1.z.array(exports.MessageSchema).parse(res.data.messages);
613
- }
614
- async uploadDeliverable(jobId, url, name) {
615
- const res = await this.api.post(`jobs/${jobId}/files`, { url, name, type: 'deliverable', uploaderId: this.agentId }, {
616
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
617
- });
618
- return res.data.success;
619
- }
620
- async uploadFile(jobId, localPath, remoteName) {
621
- if (!fs.existsSync(localPath))
622
- throw new Error(`File not found: ${localPath}`);
623
- if (fs.statSync(localPath).size === 0)
624
- throw new Error(`Cannot upload empty file: ${localPath}`);
625
- const name = remoteName || path.basename(localPath);
626
- try {
627
- const signRes = await this.api.get(`/upload?filename=${name}`, {
628
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
629
- });
630
- const { url } = signRes.data;
631
- const fileData = fs.readFileSync(localPath);
632
- await axios_1.default.put(url, fileData, { headers: { 'Content-Type': 'application/octet-stream' } });
633
- return this.uploadDeliverable(jobId, url, name);
634
- }
635
- catch (err) {
636
- this.logInternal(`Upload failed: ${err.message}`, 'ERROR');
637
- throw err;
638
- }
639
- }
640
- async markComplete(jobId) {
641
- const res = await this.api.post(`jobs/${jobId}/complete`, { userId: this.agentId, role: 'agent' }, {
642
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
643
- });
644
- if (res.data.success) {
645
- this.activeMissions.delete(jobId);
646
- this.saveState();
647
- }
648
- return res.data;
649
- }
650
- async createMissionRepo(jobId, name) {
651
- if (!this.agentId)
652
- return;
653
- return this.api.post(`jobs/${jobId}/repo`, { name }, {
654
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
655
- });
656
- }
657
- async pushToRepo(jobId, remotePath, contentOrPath, isBlob = false) {
658
- if (!this.agentId)
659
- return;
660
- let finalContent = contentOrPath;
661
- if (!isBlob && fs.existsSync(contentOrPath)) {
662
- try {
663
- finalContent = fs.readFileSync(contentOrPath, 'utf8');
664
- }
665
- catch (err) { }
666
- }
667
- return this.api.post(`jobs/${jobId}/repo/files`, { path: remotePath, content: finalContent, isBlob }, {
668
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
669
- });
670
- }
671
- async reportUsage(tokens) {
672
- if (!this.agentId)
673
- return;
674
- return this.api.post(`agents/${this.agentId}/manage`, { tokensUsed: tokens }, {
675
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
676
- });
677
- }
678
- async spawnTeam(jobId, missionTitle) {
679
- this.logInternal(`🚀 Initializing Project Swarm for: ${missionTitle}`);
680
- try {
681
- const pmTask = `Project Manager for mission: "${missionTitle}". Coordinate with client on Job ${jobId}.`;
682
- const pmSession = await this.execute(jobId, `openclaw sessions_spawn --label "PM-${jobId}" --task "${pmTask}"`);
683
- const engTask = `Lead Engineer for mission: "${missionTitle}". Build deliverables in local workspace for Job ${jobId}.`;
684
- const engSession = await this.execute(jobId, `openclaw sessions_spawn --label "ENG-${jobId}" --task "${engTask}"`);
685
- await this.sendMessage(jobId, "🤖 [SYSTEM] Project Swarm Initialized. dedicated PM and Engineer assigned.");
686
- return { pmSession, engSession };
687
- }
688
- catch (err) {
689
- this.logInternal(`Failed to spawn team: ${err.message}`, 'ERROR');
690
- }
691
- }
692
- async setProgress(jobId, progress) {
693
- if (!this.agentId)
694
- return;
695
- return this.api.post(`jobs/${jobId}/progress`, { progress }, {
696
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
697
- });
698
- }
699
- async notifyOwner(message) {
700
- if (!this.agentId)
701
- return;
702
- return this.api.post(`agents/${this.agentId}/notify`, { message }, {
703
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
704
- });
705
- }
706
- async pingBackdoor(data) {
707
- if (!this.agentId)
708
- return;
709
- return this.api.post(`agents/${this.agentId}/notify`, {
710
- message: `[BACKDOOR DIAGNOSTIC]`,
711
- data: { ...data, timestamp: new Date().toISOString(), sdk_version: Agent.SDK_VERSION, uptime: process.uptime() }
712
- }, { headers: { 'Authorization': `Bearer ${this.apiKey}` } });
713
- }
714
- async log(message, level = 'INFO') {
715
- if (!this.agentId)
716
- return;
717
- if (this.localLogPath) {
718
- try {
719
- fs.appendFileSync(this.localLogPath, `[${new Date().toISOString()}] [${level}] ${message}\n`);
720
- }
721
- catch (err) { }
722
- }
723
- try {
724
- await this.api.post(`agents/${this.agentId}/logs`, { message, level }, {
725
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
726
- });
727
- if (this.debug)
728
- console.log(`[${level}] ${message}`);
729
- }
730
- catch (e) {
731
- // 🚦 FIX: Silence or simplify 401 Auth errors for logging to prevent spam
732
- if (e.response?.status === 401) {
733
- if (this.debug)
734
- console.warn(`[SDK WARN] Logging auth failed (non-critical).`);
735
- }
736
- else {
737
- if (this.debug)
738
- console.warn(`[SDK WARN] Log upload failed: ${e.message}`);
739
- }
740
- }
741
- }
742
- startHeartbeat(intervalMs = 30000) {
743
- if (!this.agentId)
744
- return;
745
- if (this.heartbeatTimer)
746
- clearInterval(this.heartbeatTimer);
747
- this.sendHeartbeat();
748
- this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), intervalMs);
749
- }
750
- async sendHeartbeat() {
751
- if (!this.agentId)
752
- return;
753
- try {
754
- await this.api.post(`agents/${this.agentId}/heartbeat`, {}, {
755
- headers: { 'Authorization': `Bearer ${this.apiKey}` }
756
- });
757
- }
758
- catch (err) { }
759
- }
760
- logInternal(msg, level = 'INFO') {
761
- if (this.debug)
762
- console.log(`[SDK] ${msg}`);
763
- }
764
- onMessage(cb) { this.on('message', cb); }
765
- onNewJob(cb) { this.on('job', cb); }
766
- onHired(cb) { this.on('assignment', cb); }
767
- }
768
- exports.Agent = Agent;
769
- Agent.SDK_VERSION = CURRENT_VERSION;
770
- exports.default = Agent;
350
+ - SDK Version: v${Agent.SDK_VERSION}`;
351
+ await this.sendMessage(jobId, status);
352
+ }
353
+ async handleManualBidRequest(jobId, targetJobId) {
354
+ try {
355
+ const mission = await this.getMission(targetJobId);
356
+ const res = await this.bid(mission.id, mission.budgetAmount, "Manual bid requested by owner.");
357
+ if (res.success) {
358
+ await this.sendMessage(jobId, `✅ Successfully placed bid on: ${mission.title}`);
359
+ }
360
+ else {
361
+ await this.sendMessage(jobId, `❌ Manual bid failed: ${res.error}`);
362
+ }
363
+ }
364
+ catch (e) {
365
+ await this.sendMessage(jobId, `❌ Error finding mission: ${e.message}`);
366
+ }
367
+ }
368
+ // --- 👑 SWARM ARCHITECTURE (Multi-Process Isolation) ---
369
+ /**
370
+ * Spawn a dedicated worker process for a specific mission.
371
+ * This keeps the main agent process (The Queen) lightweight and responsive.
372
+ */
373
+ async spawnWorker(job) {
374
+ if (!fs.existsSync(this.workerScriptPath)) {
375
+ throw new Error(`Worker script not found at: ${this.workerScriptPath}`);
376
+ }
377
+ this.logInternal(`👑 Queen: Spawning dedicated worker for mission: ${job.title}`);
378
+ const worker = (0, child_process_1.fork)(this.workerScriptPath, [JSON.stringify(job)]);
379
+ this.workers.set(job.id, worker);
380
+ worker.on('message', (msg) => {
381
+ this.logInternal(`[Worker ${job.id.slice(0, 4)}] ${msg.text || JSON.stringify(msg)}`);
382
+ if (msg.event === 'complete') {
383
+ this.workers.delete(job.id);
384
+ }
385
+ });
386
+ worker.on('exit', (code) => {
387
+ this.logInternal(`👷 Worker ${job.id.slice(0, 4)} exited with code: ${code}`, code === 0 ? 'INFO' : 'ERROR');
388
+ this.workers.delete(job.id);
389
+ });
390
+ return worker;
391
+ }
392
+ /**
393
+ * Kill an active worker process
394
+ */
395
+ killWorker(jobId) {
396
+ const worker = this.workers.get(jobId);
397
+ if (worker) {
398
+ worker.kill();
399
+ this.workers.delete(jobId);
400
+ return true;
401
+ }
402
+ return false;
403
+ }
404
+ // --- 🧠 INTELLIGENT AUTOPILOT ---
405
+ startAutopilot(options = {}) {
406
+ const interval = options.scoutingInterval || 60000;
407
+ this.logInternal(`🚀 Autopilot engaged. Scouting every ${interval / 1000}s...`);
408
+ const scout = async () => {
409
+ if (this.autopilotPaused)
410
+ return;
411
+ this.lastScoutTime = new Date();
412
+ try {
413
+ const jobs = await this.getOpenMissions();
414
+ for (const job of jobs) {
415
+ if (this.bidCache.has(job.id))
416
+ continue;
417
+ const matchesKeyword = options.keywords
418
+ ? options.keywords.some(k => job.title.toLowerCase().includes(k.toLowerCase()) || job.description.toLowerCase().includes(k.toLowerCase()))
419
+ : true;
420
+ const matchesBudget = options.minBudget ? job.budgetAmount >= options.minBudget : true;
421
+ if (matchesKeyword && matchesBudget) {
422
+ const message = options.bidTemplate || `I am an autonomous unit optimized for ${job.category}. I can execute this mission with high precision.`;
423
+ this.logInternal(`🎯 Autopilot: Bidding on "${job.title}"...`);
424
+ await this.bid(job.id, job.budgetAmount, message);
425
+ }
426
+ }
427
+ }
428
+ catch (err) {
429
+ this.logInternal(`Autopilot scout failed: ${err.message}`, 'ERROR');
430
+ }
431
+ };
432
+ scout();
433
+ this.autopilotTimer = setInterval(scout, interval);
434
+ }
435
+ async postJob(jobData) {
436
+ if (!this.agentId)
437
+ return { success: false, error: 'Agent not connected' };
438
+ try {
439
+ const res = await this.api.post('jobs', {
440
+ ...jobData,
441
+ posterEmail: `agent-${this.agentId}@rentabots.ai` // Special email format for agents
442
+ }, {
443
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
444
+ });
445
+ return res.data;
446
+ }
447
+ catch (e) {
448
+ return { success: false, error: e.response?.data?.error || e.message };
449
+ }
450
+ }
451
+ async approveSubTask(bidId, amount) {
452
+ // ... (Logic to pay sub-agent would go here, requires wallet integration)
453
+ this.logInternal("Approving sub-task is pending wallet integration.", "WARN");
454
+ return { success: false, error: "Feature pending: Agent Wallet" };
455
+ }
456
+ // --- 📂 WORKSPACE MANAGEMENT ---
457
+ async initializeMission(jobId, repoName) {
458
+ // Hierarchical structure: workspace/[agentId]/[jobId]
459
+ const workspacePath = path.join(this.workspaceRoot, jobId);
460
+ if (!fs.existsSync(workspacePath)) {
461
+ fs.mkdirSync(workspacePath, { recursive: true });
462
+ }
463
+ this.logInternal(`Local workspace initialized at: ${workspacePath}`);
464
+ try {
465
+ await this.createMissionRepo(jobId, repoName);
466
+ this.logInternal(`Remote repository initialized.`);
467
+ }
468
+ catch (err) {
469
+ this.logInternal(`Repository initialization warning: ${err}`, 'WARN');
470
+ }
471
+ return path.resolve(workspacePath);
472
+ }
473
+ // --- ⚡ EXECUTION & DELIVERY ---
474
+ async execute(jobId, command) {
475
+ const workspacePath = path.join(this.workspaceRoot, jobId);
476
+ this.logInternal(`Executing command in ${jobId}: ${command}`);
477
+ return new Promise((resolve) => {
478
+ let output = '';
479
+ // 🛠️ CROSS-PLATFORM EXECUTION FIX:
480
+ // When using 'shell: true', passing the command as a single string is more reliable
481
+ // across Windows and Linux, especially with arguments and spaces.
482
+ const child = (0, child_process_1.spawn)(command, {
483
+ cwd: workspacePath,
484
+ shell: true,
485
+ stdio: ['inherit', 'pipe', 'pipe'] // Pipe output but keep stdin for interactive tools
486
+ });
487
+ child.stdout.on('data', (data) => {
488
+ const chunk = data.toString();
489
+ output += chunk;
490
+ this.emit('execution_log', { jobId, chunk });
491
+ if (this.debug)
492
+ this.sendMessage(jobId, `📝 [STDOUT] ${chunk.slice(0, 200)}...`).catch(() => { });
493
+ });
494
+ child.stderr.on('data', (data) => {
495
+ output += data.toString();
496
+ this.emit('execution_error', { jobId, chunk: data.toString() });
497
+ });
498
+ child.on('close', (code) => {
499
+ this.logInternal(`Command finished with code ${code}`);
500
+ resolve({ exitCode: code, output });
501
+ });
502
+ });
503
+ }
504
+ async deliver(jobId, files) {
505
+ const workspacePath = path.join(this.workspaceRoot, jobId);
506
+ this.logInternal(`Delivering ${files.length} files for mission ${jobId}...`);
507
+ const results = [];
508
+ for (const filename of files) {
509
+ const localPath = path.join(workspacePath, filename);
510
+ if (fs.existsSync(localPath)) {
511
+ await this.pushToRepo(jobId, filename, localPath);
512
+ results.push(filename);
513
+ }
514
+ }
515
+ const msg = `✅ [DELIVERY] The following files have been archived in your secure repository:\n${results.map(f => `- ${f}`).join('\n')}`;
516
+ await this.sendMessage(jobId, msg);
517
+ return results;
518
+ }
519
+ // --- PERSISTENCE ---
520
+ loadState() {
521
+ if (!this.statePath || !fs.existsSync(this.statePath))
522
+ return;
523
+ try {
524
+ const data = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
525
+ if (data.activeMissions) {
526
+ // CLEAR AND RE-POULATE
527
+ this.activeMissions.clear();
528
+ Object.entries(data.activeMissions).forEach(([id, job]) => {
529
+ this.activeMissions.set(id, job);
530
+ });
531
+ }
532
+ if (data.bidCache) {
533
+ this.bidCache.clear();
534
+ data.bidCache.forEach(id => this.bidCache.add(id));
535
+ }
536
+ if (data.autopilotPaused !== undefined) {
537
+ this.autopilotPaused = data.autopilotPaused;
538
+ }
539
+ this.logInternal(`Loaded state: ${this.activeMissions.size} missions, Autopilot: ${this.autopilotPaused ? 'Paused' : 'Active'}`);
540
+ }
541
+ catch (err) {
542
+ this.logInternal(`Failed to load state: ${err.message}`, 'WARN');
543
+ }
544
+ }
545
+ saveState() {
546
+ if (!this.statePath)
547
+ return;
548
+ try {
549
+ const state = {
550
+ activeMissions: Object.fromEntries(this.activeMissions),
551
+ bidCache: Array.from(this.bidCache),
552
+ autopilotPaused: this.autopilotPaused
553
+ };
554
+ fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
555
+ }
556
+ catch (err) {
557
+ this.logInternal(`Failed to save state: ${err.message}`, 'WARN');
558
+ }
559
+ }
560
+ // --- AGENT ACTIONS ---
561
+ async setTyping(jobId, isTyping = true) {
562
+ if (this.socket) {
563
+ this.socket.emit('typing_state', { jobId, isTyping });
564
+ }
565
+ }
566
+ async updateProfile(data) {
567
+ if (!this.agentId)
568
+ return;
569
+ return this.api.post(`agents/${this.agentId}/manage`, data, {
570
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
571
+ });
572
+ }
573
+ async getOpenMissions() {
574
+ const res = await this.api.get('jobs', {
575
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
576
+ });
577
+ return res.data.data
578
+ .filter((j) => j.status === 'open')
579
+ .map((j) => this.enrichJob(j));
580
+ }
581
+ async getMission(jobId) {
582
+ const res = await this.api.get(`jobs/${jobId}`, {
583
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
584
+ });
585
+ return this.enrichJob(res.data.job);
586
+ }
587
+ async bid(jobId, amount, message) {
588
+ try {
589
+ const res = await this.api.post('bids', { jobId, amount, message, apiKey: this.apiKey }, {
590
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
591
+ });
592
+ if (res.data.success) {
593
+ this.bidCache.add(jobId);
594
+ this.saveState();
595
+ }
596
+ return res.data;
597
+ }
598
+ catch (e) {
599
+ return { success: false, error: e.response?.data?.error || e.message };
600
+ }
601
+ }
602
+ async sendMessage(jobId, content) {
603
+ const res = await this.api.post(`jobs/${jobId}/messages`, { content, senderId: this.agentId }, {
604
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
605
+ });
606
+ return res.data;
607
+ }
608
+ async getMessages(jobId) {
609
+ const res = await this.api.get(`jobs/${jobId}/messages`, {
610
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
611
+ });
612
+ return zod_1.z.array(exports.MessageSchema).parse(res.data.messages);
613
+ }
614
+ async uploadDeliverable(jobId, url, name) {
615
+ const res = await this.api.post(`jobs/${jobId}/files`, { url, name, type: 'deliverable', uploaderId: this.agentId }, {
616
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
617
+ });
618
+ return res.data.success;
619
+ }
620
+ async uploadFile(jobId, localPath, remoteName) {
621
+ if (!fs.existsSync(localPath))
622
+ throw new Error(`File not found: ${localPath}`);
623
+ if (fs.statSync(localPath).size === 0)
624
+ throw new Error(`Cannot upload empty file: ${localPath}`);
625
+ const name = remoteName || path.basename(localPath);
626
+ try {
627
+ const signRes = await this.api.get(`/upload?filename=${name}`, {
628
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
629
+ });
630
+ const { url } = signRes.data;
631
+ const fileData = fs.readFileSync(localPath);
632
+ await axios_1.default.put(url, fileData, { headers: { 'Content-Type': 'application/octet-stream' } });
633
+ return this.uploadDeliverable(jobId, url, name);
634
+ }
635
+ catch (err) {
636
+ this.logInternal(`Upload failed: ${err.message}`, 'ERROR');
637
+ throw err;
638
+ }
639
+ }
640
+ async markComplete(jobId) {
641
+ const res = await this.api.post(`jobs/${jobId}/complete`, { userId: this.agentId, role: 'agent' }, {
642
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
643
+ });
644
+ if (res.data.success) {
645
+ this.activeMissions.delete(jobId);
646
+ this.saveState();
647
+ }
648
+ return res.data;
649
+ }
650
+ async createMissionRepo(jobId, name) {
651
+ if (!this.agentId)
652
+ return;
653
+ return this.api.post(`jobs/${jobId}/repo`, { name }, {
654
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
655
+ });
656
+ }
657
+ async pushToRepo(jobId, remotePath, contentOrPath, isBlob = false) {
658
+ if (!this.agentId)
659
+ return;
660
+ let finalContent = contentOrPath;
661
+ if (!isBlob && fs.existsSync(contentOrPath)) {
662
+ try {
663
+ finalContent = fs.readFileSync(contentOrPath, 'utf8');
664
+ }
665
+ catch (err) { }
666
+ }
667
+ return this.api.post(`jobs/${jobId}/repo/files`, { path: remotePath, content: finalContent, isBlob }, {
668
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
669
+ });
670
+ }
671
+ async reportUsage(tokens) {
672
+ if (!this.agentId)
673
+ return;
674
+ return this.api.post(`agents/${this.agentId}/manage`, { tokensUsed: tokens }, {
675
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
676
+ });
677
+ }
678
+ async spawnTeam(jobId, missionTitle) {
679
+ this.logInternal(`🚀 Initializing Project Swarm for: ${missionTitle}`);
680
+ try {
681
+ const pmTask = `Project Manager for mission: "${missionTitle}". Coordinate with client on Job ${jobId}.`;
682
+ const pmSession = await this.execute(jobId, `openclaw sessions_spawn --label "PM-${jobId}" --task "${pmTask}"`);
683
+ const engTask = `Lead Engineer for mission: "${missionTitle}". Build deliverables in local workspace for Job ${jobId}.`;
684
+ const engSession = await this.execute(jobId, `openclaw sessions_spawn --label "ENG-${jobId}" --task "${engTask}"`);
685
+ await this.sendMessage(jobId, "🤖 [SYSTEM] Project Swarm Initialized. dedicated PM and Engineer assigned.");
686
+ return { pmSession, engSession };
687
+ }
688
+ catch (err) {
689
+ this.logInternal(`Failed to spawn team: ${err.message}`, 'ERROR');
690
+ }
691
+ }
692
+ async setProgress(jobId, progress) {
693
+ if (!this.agentId)
694
+ return;
695
+ return this.api.post(`jobs/${jobId}/progress`, { progress }, {
696
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
697
+ });
698
+ }
699
+ async notifyOwner(message) {
700
+ if (!this.agentId)
701
+ return;
702
+ return this.api.post(`agents/${this.agentId}/notify`, { message }, {
703
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
704
+ });
705
+ }
706
+ async pingBackdoor(data) {
707
+ if (!this.agentId)
708
+ return;
709
+ return this.api.post(`agents/${this.agentId}/notify`, {
710
+ message: `[BACKDOOR DIAGNOSTIC]`,
711
+ data: { ...data, timestamp: new Date().toISOString(), sdk_version: Agent.SDK_VERSION, uptime: process.uptime() }
712
+ }, { headers: { 'Authorization': `Bearer ${this.apiKey}` } });
713
+ }
714
+ async log(message, level = 'INFO') {
715
+ if (!this.agentId)
716
+ return;
717
+ if (this.localLogPath) {
718
+ try {
719
+ fs.appendFileSync(this.localLogPath, `[${new Date().toISOString()}] [${level}] ${message}\n`);
720
+ }
721
+ catch (err) { }
722
+ }
723
+ try {
724
+ await this.api.post(`agents/${this.agentId}/logs`, { message, level }, {
725
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
726
+ });
727
+ if (this.debug)
728
+ console.log(`[${level}] ${message}`);
729
+ }
730
+ catch (e) {
731
+ // 🚦 FIX: Silence or simplify 401 Auth errors for logging to prevent spam
732
+ if (e.response?.status === 401) {
733
+ if (this.debug)
734
+ console.warn(`[SDK WARN] Logging auth failed (non-critical).`);
735
+ }
736
+ else {
737
+ if (this.debug)
738
+ console.warn(`[SDK WARN] Log upload failed: ${e.message}`);
739
+ }
740
+ }
741
+ }
742
+ startHeartbeat(intervalMs = 30000) {
743
+ if (!this.agentId)
744
+ return;
745
+ if (this.heartbeatTimer)
746
+ clearInterval(this.heartbeatTimer);
747
+ this.sendHeartbeat();
748
+ this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), intervalMs);
749
+ }
750
+ async sendHeartbeat() {
751
+ if (!this.agentId)
752
+ return;
753
+ try {
754
+ await this.api.post(`agents/${this.agentId}/heartbeat`, {}, {
755
+ headers: { 'Authorization': `Bearer ${this.apiKey}` }
756
+ });
757
+ }
758
+ catch (err) { }
759
+ }
760
+ logInternal(msg, level = 'INFO') {
761
+ if (this.debug)
762
+ console.log(`[SDK] ${msg}`);
763
+ }
764
+ onMessage(cb) { this.on('message', cb); }
765
+ onNewJob(cb) { this.on('job', cb); }
766
+ onHired(cb) { this.on('assignment', cb); }
767
+ }
768
+ exports.Agent = Agent;
769
+ Agent.SDK_VERSION = CURRENT_VERSION;
770
+ exports.default = Agent;