@prbe.ai/electron-sdk 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/index.d.mts +332 -0
  2. package/dist/index.d.ts +331 -20
  3. package/dist/index.js +2471 -69
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2402 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/dist/types-DHT-JxMT.d.mts +401 -0
  8. package/dist/types-DHT-JxMT.d.ts +401 -0
  9. package/dist/types.d.mts +2 -0
  10. package/dist/types.d.ts +2 -14
  11. package/dist/types.js +160 -30
  12. package/dist/types.js.map +1 -1
  13. package/dist/types.mjs +125 -0
  14. package/dist/types.mjs.map +1 -0
  15. package/package.json +22 -12
  16. package/dist/agent.d.ts +0 -106
  17. package/dist/agent.d.ts.map +0 -1
  18. package/dist/agent.js +0 -878
  19. package/dist/agent.js.map +0 -1
  20. package/dist/assets/index.d.ts +0 -6
  21. package/dist/assets/index.d.ts.map +0 -1
  22. package/dist/assets/index.js +0 -13
  23. package/dist/assets/index.js.map +0 -1
  24. package/dist/electron/channels.d.ts +0 -21
  25. package/dist/electron/channels.d.ts.map +0 -1
  26. package/dist/electron/channels.js +0 -25
  27. package/dist/electron/channels.js.map +0 -1
  28. package/dist/electron/index.d.ts +0 -12
  29. package/dist/electron/index.d.ts.map +0 -1
  30. package/dist/electron/index.js +0 -22
  31. package/dist/electron/index.js.map +0 -1
  32. package/dist/electron/ipc-interaction-handler.d.ts +0 -21
  33. package/dist/electron/ipc-interaction-handler.d.ts.map +0 -1
  34. package/dist/electron/ipc-interaction-handler.js +0 -48
  35. package/dist/electron/ipc-interaction-handler.js.map +0 -1
  36. package/dist/electron/preload.d.ts +0 -20
  37. package/dist/electron/preload.d.ts.map +0 -1
  38. package/dist/electron/preload.js +0 -72
  39. package/dist/electron/preload.js.map +0 -1
  40. package/dist/electron/setup-handlers.d.ts +0 -30
  41. package/dist/electron/setup-handlers.d.ts.map +0 -1
  42. package/dist/electron/setup-handlers.js +0 -111
  43. package/dist/electron/setup-handlers.js.map +0 -1
  44. package/dist/electron/types.d.ts +0 -56
  45. package/dist/electron/types.d.ts.map +0 -1
  46. package/dist/electron/types.js +0 -9
  47. package/dist/electron/types.js.map +0 -1
  48. package/dist/index.d.ts.map +0 -1
  49. package/dist/interactions.d.ts +0 -63
  50. package/dist/interactions.d.ts.map +0 -1
  51. package/dist/interactions.js +0 -27
  52. package/dist/interactions.js.map +0 -1
  53. package/dist/models.d.ts +0 -218
  54. package/dist/models.d.ts.map +0 -1
  55. package/dist/models.js +0 -121
  56. package/dist/models.js.map +0 -1
  57. package/dist/serialization.d.ts +0 -51
  58. package/dist/serialization.d.ts.map +0 -1
  59. package/dist/serialization.js +0 -72
  60. package/dist/serialization.js.map +0 -1
  61. package/dist/state.d.ts +0 -70
  62. package/dist/state.d.ts.map +0 -1
  63. package/dist/state.js +0 -303
  64. package/dist/state.js.map +0 -1
  65. package/dist/tools/bash.d.ts +0 -30
  66. package/dist/tools/bash.d.ts.map +0 -1
  67. package/dist/tools/bash.js +0 -248
  68. package/dist/tools/bash.js.map +0 -1
  69. package/dist/tools/filesystem.d.ts +0 -63
  70. package/dist/tools/filesystem.d.ts.map +0 -1
  71. package/dist/tools/filesystem.js +0 -573
  72. package/dist/tools/filesystem.js.map +0 -1
  73. package/dist/tools/index.d.ts +0 -46
  74. package/dist/tools/index.d.ts.map +0 -1
  75. package/dist/tools/index.js +0 -171
  76. package/dist/tools/index.js.map +0 -1
  77. package/dist/tools/interactive.d.ts +0 -15
  78. package/dist/tools/interactive.d.ts.map +0 -1
  79. package/dist/tools/interactive.js +0 -58
  80. package/dist/tools/interactive.js.map +0 -1
  81. package/dist/tools/logs.d.ts +0 -72
  82. package/dist/tools/logs.d.ts.map +0 -1
  83. package/dist/tools/logs.js +0 -366
  84. package/dist/tools/logs.js.map +0 -1
  85. package/dist/types.d.ts.map +0 -1
package/dist/agent.js DELETED
@@ -1,878 +0,0 @@
1
- "use strict";
2
- /**
3
- * agent.ts — PRBEAgent class
4
- *
5
- * Main entry point for the PRBE debug agent SDK.
6
- * Handles WebSocket connection to middleware, investigation lifecycle,
7
- * background polling for context requests, file uploads, and persistence.
8
- *
9
- * Mirrors PRBEAgent.swift behavior.
10
- */
11
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- var desc = Object.getOwnPropertyDescriptor(m, k);
14
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
- desc = { enumerable: true, get: function() { return m[k]; } };
16
- }
17
- Object.defineProperty(o, k2, desc);
18
- }) : (function(o, m, k, k2) {
19
- if (k2 === undefined) k2 = k;
20
- o[k2] = m[k];
21
- }));
22
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
- Object.defineProperty(o, "default", { enumerable: true, value: v });
24
- }) : function(o, v) {
25
- o["default"] = v;
26
- });
27
- var __importStar = (this && this.__importStar) || (function () {
28
- var ownKeys = function(o) {
29
- ownKeys = Object.getOwnPropertyNames || function (o) {
30
- var ar = [];
31
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
- return ar;
33
- };
34
- return ownKeys(o);
35
- };
36
- return function (mod) {
37
- if (mod && mod.__esModule) return mod;
38
- var result = {};
39
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
- __setModuleDefault(result, mod);
41
- return result;
42
- };
43
- })();
44
- Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.PRBEAgent = void 0;
46
- const fs = __importStar(require("fs"));
47
- const path = __importStar(require("path"));
48
- const os = __importStar(require("os"));
49
- const crypto_1 = require("crypto");
50
- const models_1 = require("./models");
51
- const models_2 = require("./models");
52
- const state_1 = require("./state");
53
- const index_1 = require("./tools/index");
54
- const filesystem_1 = require("./tools/filesystem");
55
- const logs_1 = require("./tools/logs");
56
- const interactive_1 = require("./tools/interactive");
57
- const bash_1 = require("./tools/bash");
58
- const interactions_1 = require("./interactions");
59
- function getPersistencePath() {
60
- // Use platform-appropriate app data directory
61
- const appData = process.env["APPDATA"] ||
62
- (process.platform === "darwin"
63
- ? path.join(os.homedir(), "Library", "Application Support")
64
- : path.join(os.homedir(), ".local", "share"));
65
- const dir = path.join(appData, "prbe-agent");
66
- if (!fs.existsSync(dir)) {
67
- fs.mkdirSync(dir, { recursive: true });
68
- }
69
- return path.join(dir, "agent-state.json");
70
- }
71
- function loadPersistedData() {
72
- try {
73
- const filePath = getPersistencePath();
74
- if (fs.existsSync(filePath)) {
75
- const raw = fs.readFileSync(filePath, "utf-8");
76
- return JSON.parse(raw);
77
- }
78
- }
79
- catch {
80
- // Ignore read errors, return defaults
81
- }
82
- return {};
83
- }
84
- function savePersistedData(data) {
85
- try {
86
- const filePath = getPersistencePath();
87
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
88
- }
89
- catch {
90
- console.error("[PRBEAgent] Failed to save persisted data");
91
- }
92
- }
93
- // ---------------------------------------------------------------------------
94
- // PRBEAgent
95
- // ---------------------------------------------------------------------------
96
- class PRBEAgent {
97
- state;
98
- logCapture;
99
- config;
100
- appDataPath;
101
- interactionHandler;
102
- registry = new index_1.PRBEToolRegistry();
103
- grantedPaths = new Set();
104
- userCancelled = false;
105
- activeWS = null;
106
- pollingTimer = null;
107
- persistedData;
108
- fetchAbortController = null;
109
- currentInvestigationSource = interactions_1.InvestigationSource.USER;
110
- currentCRId = null;
111
- /** Files flagged during the current tool call — uploaded immediately after the tool returns. */
112
- pendingFlaggedFiles = [];
113
- // ---------- Persistence ----------
114
- get agentID() {
115
- if (this.persistedData.agentId) {
116
- return this.persistedData.agentId;
117
- }
118
- const newID = (0, crypto_1.randomUUID)();
119
- this.persistedData.agentId = newID;
120
- savePersistedData(this.persistedData);
121
- return newID;
122
- }
123
- get trackedTicketIDs() {
124
- return this.persistedData.ticketIds ?? [];
125
- }
126
- set trackedTicketIDs(ids) {
127
- this.persistedData.ticketIds = ids;
128
- savePersistedData(this.persistedData);
129
- this.state.updateTrackedTicketIDs(ids);
130
- this.syncPolling(ids.length > 0);
131
- }
132
- get respondedCRIDs() {
133
- return new Set(this.persistedData.respondedCRIds ?? []);
134
- }
135
- set respondedCRIDs(ids) {
136
- this.persistedData.respondedCRIds = Array.from(ids);
137
- savePersistedData(this.persistedData);
138
- }
139
- syncPolling(hasTickets) {
140
- if (this.config.backgroundPolling && hasTickets) {
141
- if (this.pollingTimer === null) {
142
- this.startPolling();
143
- }
144
- }
145
- else if (!hasTickets) {
146
- this.stopPolling();
147
- }
148
- }
149
- addTrackedTicket(id) {
150
- const ids = this.trackedTicketIDs;
151
- if (!ids.includes(id)) {
152
- this.trackedTicketIDs = [...ids, id];
153
- }
154
- }
155
- // ---------- Constructor ----------
156
- constructor(config) {
157
- this.config = {
158
- apiKey: config.apiKey,
159
- autoApprovedDirs: config.autoApprovedDirs,
160
- pollingInterval: config.pollingInterval ?? 600_000,
161
- maxLogEntries: config.maxLogEntries ?? 10_000,
162
- captureConsole: config.captureConsole ?? true,
163
- backgroundPolling: config.backgroundPolling ?? true,
164
- };
165
- this.interactionHandler = config.interactionHandler;
166
- this.appDataPath = config.appDataPath;
167
- // Auto-add appDataPath to autoApprovedDirs if not already present
168
- if (this.appDataPath && !this.config.autoApprovedDirs.includes(this.appDataPath)) {
169
- this.config.autoApprovedDirs.push(this.appDataPath);
170
- }
171
- this.state = new state_1.PRBEAgentState();
172
- this.logCapture = new logs_1.PRBELogCapture(this.config.maxLogEntries);
173
- this.persistedData = loadPersistedData();
174
- const roots = this.config.autoApprovedDirs;
175
- const requester = this.interactionHandler ? this : undefined;
176
- const grantedPaths = this.grantedPaths;
177
- // Register built-in filesystem tools
178
- this.registry.register(new filesystem_1.ListDirectoryTool(roots, requester, grantedPaths));
179
- this.registry.register(new filesystem_1.ReadFileTool(roots, requester, grantedPaths));
180
- this.registry.register(new filesystem_1.SearchContentTool(roots, requester, grantedPaths));
181
- this.registry.register(new filesystem_1.FindFilesTool(roots, requester, grantedPaths));
182
- this.registry.register(new filesystem_1.FlagFileTool(roots, (file) => {
183
- this.pendingFlaggedFiles.push(file);
184
- }, requester, grantedPaths));
185
- // Register built-in log tools
186
- this.registry.register(new logs_1.ReadAppLogsTool(this.logCapture));
187
- this.registry.register(new logs_1.SearchAppLogsTool(this.logCapture));
188
- this.registry.register(new logs_1.ClearAppLogsTool(this.logCapture));
189
- this.registry.register(new logs_1.FlagAppLogsTool(this.logCapture, (file) => {
190
- this.pendingFlaggedFiles.push(file);
191
- }));
192
- // Register interactive tools (only when handler is available)
193
- if (requester) {
194
- this.registry.register(new interactive_1.AskUserTool(requester));
195
- this.registry.register(new bash_1.BashExecuteTool(requester, roots, grantedPaths));
196
- }
197
- // Start console capture if configured
198
- if (this.config.captureConsole) {
199
- this.logCapture.startCapturing();
200
- }
201
- // Hook electron-log if provided
202
- if (config.electronLog) {
203
- this.hookElectronLog(config.electronLog);
204
- }
205
- // Listen for renderer log forwarding via IPC if provided
206
- if (config.ipcMain) {
207
- this.hookRendererLogs(config.ipcMain, config.rendererLogChannel ?? "prbe-renderer-log");
208
- }
209
- // Bootstrap: trigger syncPolling + state update for existing tickets
210
- const existingTickets = this.trackedTicketIDs;
211
- if (existingTickets.length > 0) {
212
- this.trackedTicketIDs = existingTickets;
213
- }
214
- }
215
- // ---------- Log integration ----------
216
- hookElectronLog(electronLog) {
217
- try {
218
- electronLog.hooks.push((message, _transportFn, transportName) => {
219
- if (transportName !== "file")
220
- return message;
221
- try {
222
- const text = message.data
223
- .map((d) => (typeof d === "string" ? d : JSON.stringify(d)))
224
- .join(" ");
225
- const level = PRBEAgent.mapElectronLogLevel(message.level);
226
- this.logCapture.log(text, level, `electron-log.${message.level}`);
227
- }
228
- catch {
229
- // ignore serialization errors
230
- }
231
- return message;
232
- });
233
- }
234
- catch {
235
- // electron-log not available or incompatible
236
- }
237
- }
238
- hookRendererLogs(ipcMain, channel) {
239
- ipcMain.on(channel, (_event, entry) => {
240
- this.logCapture.log(entry.message, entry.level, `renderer.${entry.category}`);
241
- });
242
- }
243
- static mapElectronLogLevel(level) {
244
- switch (level) {
245
- case "error": return "ERROR";
246
- case "warn": return "WARNING";
247
- case "debug":
248
- case "verbose":
249
- case "silly": return "DEBUG";
250
- default: return "INFO";
251
- }
252
- }
253
- // ---------- PRBEInteractionRequester implementation ----------
254
- get investigationSource() {
255
- return this.currentInvestigationSource;
256
- }
257
- async requestUserInteraction(payload) {
258
- if (!this.interactionHandler) {
259
- throw new models_2.PRBEAgentError(models_2.PRBEAgentErrorType.SERVER_ERROR, "No interaction handler configured");
260
- }
261
- // Update state to show pending interaction
262
- if (this.currentInvestigationSource === interactions_1.InvestigationSource.CONTEXT_REQUEST && this.currentCRId) {
263
- this.state.setCRPendingInteraction(this.currentCRId, payload);
264
- }
265
- else {
266
- this.state.setPendingInteraction(payload);
267
- }
268
- try {
269
- const response = await this.interactionHandler.handleInteraction(payload);
270
- if (this.currentInvestigationSource === interactions_1.InvestigationSource.CONTEXT_REQUEST && this.currentCRId) {
271
- this.state.resolveCRInteraction(this.currentCRId, response);
272
- }
273
- else {
274
- this.state.resolveInteraction(response);
275
- }
276
- return response;
277
- }
278
- catch (err) {
279
- if (this.currentInvestigationSource === interactions_1.InvestigationSource.CONTEXT_REQUEST && this.currentCRId) {
280
- this.state.clearCRPendingInteraction(this.currentCRId);
281
- }
282
- else {
283
- this.state.clearPendingInteraction();
284
- }
285
- throw err;
286
- }
287
- }
288
- /**
289
- * Add an additional root directory to the runtime allowed roots.
290
- */
291
- addAutoApprovedDir(rootPath) {
292
- if (!this.config.autoApprovedDirs.includes(rootPath)) {
293
- this.config.autoApprovedDirs.push(rootPath);
294
- }
295
- }
296
- // ---------- Public API ----------
297
- /**
298
- * Register a custom tool that the middleware can invoke during investigations.
299
- */
300
- registerTool(name, description, parameters, handler) {
301
- this.registry.register(new index_1.PRBEClosureTool(name, description, parameters, handler));
302
- }
303
- /**
304
- * User-initiated investigation. Updates `state` events/report directly.
305
- */
306
- async investigate(query, contextRequestID) {
307
- this.userCancelled = false;
308
- this.currentInvestigationSource = interactions_1.InvestigationSource.USER;
309
- this.currentCRId = null;
310
- this.state.beginInvestigation(query);
311
- const emitter = (status) => {
312
- // Update state based on status
313
- switch (status.type) {
314
- case models_2.PRBEAgentStatusType.STARTED:
315
- this.state.appendEvent("Starting investigation...");
316
- break;
317
- case models_2.PRBEAgentStatusType.THINKING:
318
- break;
319
- case models_2.PRBEAgentStatusType.TOOL_CALL:
320
- this.state.appendEvent(status.label);
321
- break;
322
- case models_2.PRBEAgentStatusType.OBSERVATION:
323
- this.state.attachObservation(status.text);
324
- break;
325
- case models_2.PRBEAgentStatusType.THOUGHT:
326
- this.state.appendEvent("Thinking", status.text);
327
- break;
328
- case models_2.PRBEAgentStatusType.COMPLETED:
329
- this.state.completeInvestigation(status.report, status.userSummary);
330
- break;
331
- case models_2.PRBEAgentStatusType.ERROR:
332
- this.state.failInvestigation(status.message);
333
- break;
334
- }
335
- };
336
- const result = await this.connectToProxy(query, contextRequestID, emitter, () => this.userCancelled);
337
- this.currentInvestigationSource = interactions_1.InvestigationSource.USER;
338
- this.currentCRId = null;
339
- if (result?.ticketId) {
340
- this.addTrackedTicket(result.ticketId);
341
- }
342
- else if (!result) {
343
- // No result — either cancelled or errored
344
- if (this.state.isInvestigating) {
345
- const message = this.userCancelled
346
- ? "Investigation cancelled"
347
- : "Investigation ended unexpectedly";
348
- this.state.failInvestigation(message);
349
- }
350
- }
351
- }
352
- /**
353
- * Cancel user-initiated investigation only (does not cancel background CRs).
354
- */
355
- cancelInvestigation() {
356
- this.userCancelled = true;
357
- if (this.activeWS) {
358
- this.activeWS.close(1000, "User cancelled");
359
- this.activeWS = null;
360
- }
361
- }
362
- /**
363
- * Cancel everything — user investigation and polling.
364
- */
365
- cancel() {
366
- this.userCancelled = true;
367
- if (this.activeWS) {
368
- this.activeWS.close(1000, "User cancelled");
369
- this.activeWS = null;
370
- }
371
- this.abortInFlightRequests();
372
- this.stopPolling();
373
- }
374
- /**
375
- * Poll the backend for context requests on tracked tickets.
376
- */
377
- async poll() {
378
- const ticketIDs = this.trackedTicketIDs;
379
- if (ticketIDs.length === 0)
380
- return null;
381
- const request = {
382
- agent_id: this.agentID,
383
- ticket_ids: ticketIDs,
384
- };
385
- try {
386
- const response = await this.post("/api/agent/poll", request);
387
- // Discard ticket IDs the backend didn't return (deleted/unknown)
388
- const returnedIDs = new Set(response.tickets.map((t) => t.ticket_id));
389
- const orphaned = ticketIDs.filter((id) => !returnedIDs.has(id));
390
- if (orphaned.length > 0) {
391
- this.trackedTicketIDs = this.trackedTicketIDs.filter((id) => !orphaned.includes(id));
392
- }
393
- // Collect all CR IDs the backend knows about for pruning
394
- const knownCRIDs = new Set();
395
- for (const ticket of response.tickets) {
396
- if (ticket.status === "resolved") {
397
- this.trackedTicketIDs = this.trackedTicketIDs.filter((id) => id !== ticket.ticket_id);
398
- }
399
- for (const cr of ticket.context_requests) {
400
- knownCRIDs.add(cr.id);
401
- // Skip inactive or already-responded CRs
402
- if (!cr.is_active || this.respondedCRIDs.has(cr.id))
403
- continue;
404
- await this.investigateForCR(cr, ticket.ticket_id);
405
- const ids = this.respondedCRIDs;
406
- ids.add(cr.id);
407
- this.respondedCRIDs = ids;
408
- }
409
- }
410
- // Prune responded CR IDs for CRs the backend no longer returns
411
- const currentRespondedIDs = this.respondedCRIDs;
412
- const stale = new Set([...currentRespondedIDs].filter((id) => !knownCRIDs.has(id)));
413
- if (stale.size > 0) {
414
- const pruned = new Set([...currentRespondedIDs].filter((id) => !stale.has(id)));
415
- this.respondedCRIDs = pruned;
416
- }
417
- return response;
418
- }
419
- catch {
420
- return null;
421
- }
422
- }
423
- /**
424
- * Fetch ticket display info for all tracked tickets.
425
- */
426
- async fetchTicketInfo() {
427
- const ticketIDs = this.trackedTicketIDs;
428
- if (ticketIDs.length === 0)
429
- return [];
430
- const request = { ticket_ids: ticketIDs };
431
- try {
432
- const response = await this.post("/api/agent/tickets", request);
433
- this.state.updateTicketInfo(response.tickets);
434
- return response.tickets;
435
- }
436
- catch {
437
- return [];
438
- }
439
- }
440
- stopPolling() {
441
- if (this.pollingTimer !== null) {
442
- clearInterval(this.pollingTimer);
443
- this.pollingTimer = null;
444
- }
445
- }
446
- resumePolling() {
447
- if (this.trackedTicketIDs.length === 0)
448
- return;
449
- this.startPolling();
450
- }
451
- /**
452
- * Destroy the agent — stops polling, stops console capture, closes WS.
453
- */
454
- destroy() {
455
- this.cancel();
456
- this.logCapture.stopCapturing();
457
- }
458
- /**
459
- * Reset all persisted data (agent ID, tracked tickets, responded CRs).
460
- * Also clears in-memory state.
461
- */
462
- resetPersistedData() {
463
- this.stopPolling();
464
- this.persistedData = {};
465
- savePersistedData(this.persistedData);
466
- this.state.resetInvestigation();
467
- this.state.completedInvestigations = [];
468
- this.state.activeCRs.clear();
469
- this.state.completedCRs = [];
470
- this.state.trackedTicketIDs = [];
471
- this.state.ticketInfo = [];
472
- this.state.emit(state_1.PRBEStateEvent.STATUS);
473
- }
474
- /**
475
- * Delete the persisted data file. Can be called without an agent instance.
476
- */
477
- static clearPersistedData() {
478
- try {
479
- const filePath = getPersistencePath();
480
- if (fs.existsSync(filePath)) {
481
- fs.unlinkSync(filePath);
482
- }
483
- }
484
- catch {
485
- // Ignore
486
- }
487
- }
488
- // ---------- CR Investigation ----------
489
- async investigateForCR(cr, ticketId) {
490
- const crID = cr.id;
491
- this.currentInvestigationSource = interactions_1.InvestigationSource.CONTEXT_REQUEST;
492
- this.currentCRId = crID;
493
- this.state.beginCR(crID, cr.query, cr.slug ?? undefined);
494
- const emitter = (status) => {
495
- switch (status.type) {
496
- case models_2.PRBEAgentStatusType.STARTED:
497
- this.state.appendCREvent(crID, "Starting investigation...");
498
- break;
499
- case models_2.PRBEAgentStatusType.THINKING:
500
- break;
501
- case models_2.PRBEAgentStatusType.TOOL_CALL:
502
- this.state.appendCREvent(crID, status.label);
503
- break;
504
- case models_2.PRBEAgentStatusType.OBSERVATION:
505
- this.state.attachCRObservation(crID, status.text);
506
- break;
507
- case models_2.PRBEAgentStatusType.THOUGHT:
508
- this.state.appendCREvent(crID, "Thinking", status.text);
509
- break;
510
- case models_2.PRBEAgentStatusType.COMPLETED:
511
- this.state.completeCR(crID, status.report, status.userSummary);
512
- break;
513
- case models_2.PRBEAgentStatusType.ERROR:
514
- this.state.failCR(crID, status.message);
515
- break;
516
- }
517
- };
518
- const result = await this.connectToProxy(cr.query, crID, emitter, () => false, // CRs are not user-cancellable
519
- ticketId);
520
- this.currentInvestigationSource = interactions_1.InvestigationSource.USER;
521
- this.currentCRId = null;
522
- if (result?.ticketId) {
523
- this.addTrackedTicket(result.ticketId);
524
- }
525
- }
526
- // ---------- WebSocket Investigation ----------
527
- connectToProxy(query, contextRequestID, emit, isCancelled, ticketId) {
528
- return new Promise((resolve) => {
529
- const wsUrl = `${models_1.MIDDLEWARE_URL}/api/agent/client/ws`;
530
- let ws;
531
- try {
532
- ws = new WebSocket(wsUrl, {
533
- headers: {
534
- "X-API-Key": this.config.apiKey,
535
- },
536
- });
537
- }
538
- catch (err) {
539
- emit({
540
- type: models_2.PRBEAgentStatusType.ERROR,
541
- message: `Failed to create WebSocket: ${err}`,
542
- });
543
- resolve(null);
544
- return;
545
- }
546
- this.activeWS = ws;
547
- let uploadBaseUrl;
548
- let resolved = false;
549
- const finish = (result) => {
550
- if (resolved)
551
- return;
552
- resolved = true;
553
- this.activeWS = null;
554
- resolve(result);
555
- };
556
- ws.onopen = () => {
557
- // Build tool declarations for the start message
558
- const toolDeclarations = this.registry
559
- .allDeclarations()
560
- .map((decl) => ({
561
- name: decl.name,
562
- description: decl.description,
563
- parameters: decl.parameters.map((param) => ({
564
- name: param.name,
565
- type: param.type,
566
- description: param.description,
567
- required: param.required,
568
- })),
569
- ...(decl.interactive ? { interactive: true } : {}),
570
- }));
571
- const startMetadata = {
572
- agent_id: this.agentID,
573
- custom_tools: toolDeclarations,
574
- };
575
- if (contextRequestID) {
576
- startMetadata["context_request_id"] = contextRequestID;
577
- }
578
- if (ticketId) {
579
- startMetadata["ticket_id"] = ticketId;
580
- }
581
- if (this.appDataPath) {
582
- startMetadata["app_data_path"] = this.appDataPath;
583
- }
584
- const startMsg = {
585
- type: models_1.WSMessageType.START,
586
- content: query,
587
- metadata: startMetadata,
588
- };
589
- try {
590
- ws.send(JSON.stringify(startMsg));
591
- }
592
- catch (err) {
593
- emit({
594
- type: models_2.PRBEAgentStatusType.ERROR,
595
- message: `Failed to send start message: ${err}`,
596
- });
597
- finish(null);
598
- return;
599
- }
600
- emit({ type: models_2.PRBEAgentStatusType.STARTED });
601
- this.pendingFlaggedFiles = [];
602
- };
603
- ws.onmessage = async (event) => {
604
- if (isCancelled()) {
605
- this.sendCancel(ws);
606
- ws.close(1000, "Cancelled");
607
- finish(null);
608
- return;
609
- }
610
- let raw;
611
- if (typeof event.data === "string") {
612
- raw = event.data;
613
- }
614
- else if (event.data instanceof Buffer) {
615
- raw = event.data.toString("utf-8");
616
- }
617
- else {
618
- return;
619
- }
620
- let msg;
621
- try {
622
- msg = JSON.parse(raw);
623
- }
624
- catch {
625
- return;
626
- }
627
- switch (msg.type) {
628
- case models_1.WSMessageType.THOUGHT:
629
- emit({
630
- type: models_2.PRBEAgentStatusType.THOUGHT,
631
- text: msg.content ?? "",
632
- });
633
- break;
634
- case models_1.WSMessageType.TOOL_CALL: {
635
- const toolName = msg.name ?? "";
636
- const callId = msg.id ?? "";
637
- const args = this.extractArgs(msg.metadata);
638
- emit({
639
- type: models_2.PRBEAgentStatusType.TOOL_CALL,
640
- name: toolName,
641
- label: msg.content ?? `Running ${toolName}`,
642
- });
643
- // Clear before execution so we only capture files flagged by this tool call
644
- this.pendingFlaggedFiles = [];
645
- const toolResult = (0, models_1.redactPII)(await this.registry.execute(toolName, args));
646
- emit({
647
- type: models_2.PRBEAgentStatusType.OBSERVATION,
648
- text: toolResult.substring(0, 200),
649
- });
650
- // Build refs for any files flagged during this tool call
651
- let resultMetadata;
652
- if (this.pendingFlaggedFiles.length > 0 && uploadBaseUrl) {
653
- const uploadPrefix = this.extractUploadPrefix(uploadBaseUrl);
654
- const uploadedRefs = [];
655
- for (const file of this.pendingFlaggedFiles) {
656
- const filename = path.basename(file.originalPath);
657
- const safeName = encodeURIComponent(filename);
658
- const storagePath = `${uploadPrefix}/${safeName}`;
659
- uploadedRefs.push({
660
- original_path: file.originalPath,
661
- reason: file.reason ?? "",
662
- storage_path: storagePath,
663
- file_size_bytes: file.data.length,
664
- });
665
- // Background upload — don't block the tool_result
666
- const uploadUrl = `${uploadBaseUrl}/${safeName}`;
667
- const fileData = file.data;
668
- const contentType = file.isText
669
- ? "text/plain"
670
- : "application/octet-stream";
671
- void PRBEAgent.backgroundUpload(fileData, uploadUrl, this.config.apiKey, contentType, this.getFetchSignal());
672
- }
673
- resultMetadata = { flagged_files: uploadedRefs };
674
- this.pendingFlaggedFiles = [];
675
- }
676
- const resultMsg = {
677
- type: models_1.WSMessageType.TOOL_RESULT,
678
- id: callId,
679
- name: toolName,
680
- content: toolResult,
681
- metadata: resultMetadata,
682
- };
683
- try {
684
- ws.send(JSON.stringify(resultMsg));
685
- }
686
- catch {
687
- // Send failed — will be caught by onerror/onclose
688
- }
689
- break;
690
- }
691
- case models_1.WSMessageType.SERVER_TOOL_CALL:
692
- emit({
693
- type: models_2.PRBEAgentStatusType.TOOL_CALL,
694
- name: msg.name ?? "",
695
- label: msg.content ?? "",
696
- });
697
- break;
698
- case models_1.WSMessageType.SERVER_OBSERVATION:
699
- emit({
700
- type: models_2.PRBEAgentStatusType.OBSERVATION,
701
- text: (msg.content ?? "").substring(0, 200),
702
- });
703
- break;
704
- case models_1.WSMessageType.COMPLETE: {
705
- const report = msg.content ?? "";
706
- const userSummary = msg.metadata?.["user_summary"] ?? "";
707
- const ticketId = msg.metadata?.["ticket_id"];
708
- emit({
709
- type: models_2.PRBEAgentStatusType.COMPLETED,
710
- report,
711
- userSummary,
712
- });
713
- ws.close(1000, "Complete");
714
- finish({ report, userSummary, ticketId });
715
- break;
716
- }
717
- case models_1.WSMessageType.ERROR:
718
- emit({
719
- type: models_2.PRBEAgentStatusType.ERROR,
720
- message: msg.content || "Unknown error",
721
- });
722
- ws.close(1000, "Error received");
723
- finish(null);
724
- break;
725
- case models_1.WSMessageType.SESSION_CONFIG:
726
- uploadBaseUrl = msg.metadata?.["upload_url"];
727
- break;
728
- case models_1.WSMessageType.PING: {
729
- const pongMsg = { type: models_1.WSMessageType.PONG };
730
- try {
731
- ws.send(JSON.stringify(pongMsg));
732
- }
733
- catch {
734
- // Ignore pong send failures
735
- }
736
- break;
737
- }
738
- // SDK-originated types and upload_url — ignore
739
- case models_1.WSMessageType.START:
740
- case models_1.WSMessageType.TOOL_RESULT:
741
- case models_1.WSMessageType.UPLOAD_REQUEST:
742
- case models_1.WSMessageType.CANCEL:
743
- case models_1.WSMessageType.PONG:
744
- case models_1.WSMessageType.UPLOAD_URL:
745
- break;
746
- }
747
- };
748
- ws.onerror = (event) => {
749
- if (isCancelled()) {
750
- finish(null);
751
- return;
752
- }
753
- const errorEvent = event;
754
- const message = errorEvent.message || "WebSocket connection error";
755
- emit({ type: models_2.PRBEAgentStatusType.ERROR, message });
756
- finish(null);
757
- };
758
- ws.onclose = (_event) => {
759
- // If we haven't resolved yet, treat as unexpected close
760
- if (!resolved) {
761
- if (isCancelled()) {
762
- finish(null);
763
- }
764
- else {
765
- emit({
766
- type: models_2.PRBEAgentStatusType.ERROR,
767
- message: "WebSocket connection closed unexpectedly",
768
- });
769
- finish(null);
770
- }
771
- }
772
- };
773
- });
774
- }
775
- sendCancel(ws) {
776
- try {
777
- const cancelMsg = { type: models_1.WSMessageType.CANCEL };
778
- ws.send(JSON.stringify(cancelMsg));
779
- }
780
- catch {
781
- // Ignore
782
- }
783
- }
784
- extractArgs(metadata) {
785
- if (!metadata)
786
- return {};
787
- const argsVal = metadata["args"];
788
- if (argsVal && typeof argsVal === "object" && !Array.isArray(argsVal)) {
789
- return argsVal;
790
- }
791
- return {};
792
- }
793
- extractUploadPrefix(baseUrl) {
794
- try {
795
- const url = new URL(baseUrl);
796
- const urlPath = url.pathname; // "/api/agent/upload/{tenant}/{uuid}/files"
797
- const marker = "/api/agent/upload/";
798
- const idx = urlPath.indexOf(marker);
799
- if (idx !== -1) {
800
- return urlPath.substring(idx + marker.length);
801
- }
802
- }
803
- catch {
804
- // Ignore URL parse errors
805
- }
806
- return "";
807
- }
808
- // ---------- File Upload ----------
809
- static async backgroundUpload(data, uploadUrl, apiKey, contentType, signal) {
810
- try {
811
- const response = await fetch(uploadUrl, {
812
- method: "PUT",
813
- headers: {
814
- "Content-Type": contentType,
815
- "X-API-Key": apiKey,
816
- },
817
- body: data,
818
- signal,
819
- });
820
- if (!response.ok) {
821
- console.error(`[PRBEAgent] file upload failed: HTTP ${response.status} for ${uploadUrl}`);
822
- }
823
- }
824
- catch (err) {
825
- console.error(`[PRBEAgent] file upload error: ${err} for ${uploadUrl}`);
826
- }
827
- }
828
- // ---------- Polling ----------
829
- startPolling() {
830
- this.stopPolling();
831
- this.pollingTimer = setInterval(() => {
832
- void this.poll().then(() => {
833
- if (this.trackedTicketIDs.length === 0) {
834
- this.stopPolling();
835
- }
836
- });
837
- }, this.config.pollingInterval);
838
- }
839
- // ---------- Networking ----------
840
- /**
841
- * Abort all in-flight fetch requests to prevent orphaned DNS lookups
842
- * that can crash the c-ares resolver during process shutdown.
843
- */
844
- abortInFlightRequests() {
845
- if (this.fetchAbortController) {
846
- this.fetchAbortController.abort();
847
- this.fetchAbortController = null;
848
- }
849
- }
850
- /**
851
- * Get an AbortSignal for fetch requests. Creates a new controller if needed.
852
- */
853
- getFetchSignal() {
854
- if (!this.fetchAbortController) {
855
- this.fetchAbortController = new AbortController();
856
- }
857
- return this.fetchAbortController.signal;
858
- }
859
- async post(urlPath, body) {
860
- const url = `${models_1.API_URL}${urlPath}`;
861
- const response = await fetch(url, {
862
- method: "POST",
863
- headers: {
864
- "Content-Type": "application/json",
865
- "X-API-Key": this.config.apiKey,
866
- },
867
- body: JSON.stringify(body),
868
- signal: this.getFetchSignal(),
869
- });
870
- if (!response.ok) {
871
- const message = await response.text().catch(() => "Unknown error");
872
- throw new models_2.PRBEAgentError(models_2.PRBEAgentErrorType.SERVER_ERROR, `Server error ${response.status}: ${message}`, response.status);
873
- }
874
- return (await response.json());
875
- }
876
- }
877
- exports.PRBEAgent = PRBEAgent;
878
- //# sourceMappingURL=agent.js.map