@prbe.ai/electron-sdk 0.1.4 → 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 (61) hide show
  1. package/dist/index.d.mts +332 -0
  2. package/dist/index.d.ts +331 -15
  3. package/dist/index.js +2471 -60
  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 -8
  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/index.d.ts.map +0 -1
  25. package/dist/interactions.d.ts +0 -63
  26. package/dist/interactions.d.ts.map +0 -1
  27. package/dist/interactions.js +0 -27
  28. package/dist/interactions.js.map +0 -1
  29. package/dist/models.d.ts +0 -218
  30. package/dist/models.d.ts.map +0 -1
  31. package/dist/models.js +0 -121
  32. package/dist/models.js.map +0 -1
  33. package/dist/serialization.d.ts +0 -51
  34. package/dist/serialization.d.ts.map +0 -1
  35. package/dist/serialization.js +0 -72
  36. package/dist/serialization.js.map +0 -1
  37. package/dist/state.d.ts +0 -70
  38. package/dist/state.d.ts.map +0 -1
  39. package/dist/state.js +0 -303
  40. package/dist/state.js.map +0 -1
  41. package/dist/tools/bash.d.ts +0 -30
  42. package/dist/tools/bash.d.ts.map +0 -1
  43. package/dist/tools/bash.js +0 -248
  44. package/dist/tools/bash.js.map +0 -1
  45. package/dist/tools/filesystem.d.ts +0 -63
  46. package/dist/tools/filesystem.d.ts.map +0 -1
  47. package/dist/tools/filesystem.js +0 -573
  48. package/dist/tools/filesystem.js.map +0 -1
  49. package/dist/tools/index.d.ts +0 -46
  50. package/dist/tools/index.d.ts.map +0 -1
  51. package/dist/tools/index.js +0 -171
  52. package/dist/tools/index.js.map +0 -1
  53. package/dist/tools/interactive.d.ts +0 -15
  54. package/dist/tools/interactive.d.ts.map +0 -1
  55. package/dist/tools/interactive.js +0 -58
  56. package/dist/tools/interactive.js.map +0 -1
  57. package/dist/tools/logs.d.ts +0 -72
  58. package/dist/tools/logs.d.ts.map +0 -1
  59. package/dist/tools/logs.js +0 -366
  60. package/dist/tools/logs.js.map +0 -1
  61. package/dist/types.d.ts.map +0 -1
package/dist/index.mjs ADDED
@@ -0,0 +1,2402 @@
1
+ // src/agent.ts
2
+ import * as fs2 from "fs";
3
+ import * as path4 from "path";
4
+ import * as os from "os";
5
+ import { randomUUID as randomUUID5 } from "crypto";
6
+
7
+ // src/models.ts
8
+ var WSMessageType = /* @__PURE__ */ ((WSMessageType2) => {
9
+ WSMessageType2["START"] = "start";
10
+ WSMessageType2["TOOL_RESULT"] = "tool_result";
11
+ WSMessageType2["UPLOAD_REQUEST"] = "upload_request";
12
+ WSMessageType2["CANCEL"] = "cancel";
13
+ WSMessageType2["PONG"] = "pong";
14
+ WSMessageType2["THOUGHT"] = "thought";
15
+ WSMessageType2["TOOL_CALL"] = "tool_call";
16
+ WSMessageType2["SERVER_TOOL_CALL"] = "server_tool_call";
17
+ WSMessageType2["SERVER_OBSERVATION"] = "server_observation";
18
+ WSMessageType2["UPLOAD_URL"] = "upload_url";
19
+ WSMessageType2["SESSION_CONFIG"] = "session_config";
20
+ WSMessageType2["COMPLETE"] = "complete";
21
+ WSMessageType2["ERROR"] = "error";
22
+ WSMessageType2["PING"] = "ping";
23
+ return WSMessageType2;
24
+ })(WSMessageType || {});
25
+ var ToolParamType = /* @__PURE__ */ ((ToolParamType2) => {
26
+ ToolParamType2["STRING"] = "STRING";
27
+ ToolParamType2["BOOLEAN"] = "BOOLEAN";
28
+ ToolParamType2["INTEGER"] = "INTEGER";
29
+ return ToolParamType2;
30
+ })(ToolParamType || {});
31
+ var ToolName = /* @__PURE__ */ ((ToolName2) => {
32
+ ToolName2["CLIENT_LIST_DIRECTORY"] = "client_list_directory";
33
+ ToolName2["CLIENT_READ_FILE"] = "client_read_file";
34
+ ToolName2["CLIENT_SEARCH_CONTENT"] = "client_search_content";
35
+ ToolName2["CLIENT_FIND_FILES"] = "client_find_files";
36
+ ToolName2["CLIENT_FLAG_FILE"] = "client_flag_file";
37
+ ToolName2["CLIENT_READ_APP_LOGS"] = "client_read_app_logs";
38
+ ToolName2["CLIENT_SEARCH_APP_LOGS"] = "client_search_app_logs";
39
+ ToolName2["CLIENT_CLEAR_APP_LOGS"] = "client_clear_app_logs";
40
+ ToolName2["CLIENT_FLAG_APP_LOGS"] = "client_flag_app_logs";
41
+ ToolName2["CLIENT_ASK_USER"] = "client_ask_user";
42
+ ToolName2["CLIENT_BASH_EXECUTE"] = "client_bash_execute";
43
+ return ToolName2;
44
+ })(ToolName || {});
45
+ var PRBEAgentConfigKey = /* @__PURE__ */ ((PRBEAgentConfigKey3) => {
46
+ PRBEAgentConfigKey3["API_KEY"] = "apiKey";
47
+ PRBEAgentConfigKey3["AUTO_APPROVED_DIRS"] = "autoApprovedDirs";
48
+ PRBEAgentConfigKey3["POLLING_INTERVAL"] = "pollingInterval";
49
+ PRBEAgentConfigKey3["MAX_LOG_ENTRIES"] = "maxLogEntries";
50
+ PRBEAgentConfigKey3["CAPTURE_CONSOLE"] = "captureConsole";
51
+ PRBEAgentConfigKey3["BACKGROUND_POLLING"] = "backgroundPolling";
52
+ PRBEAgentConfigKey3["INTERACTION_HANDLER"] = "interactionHandler";
53
+ PRBEAgentConfigKey3["ELECTRON_LOG"] = "electronLog";
54
+ PRBEAgentConfigKey3["IPC_MAIN"] = "ipcMain";
55
+ PRBEAgentConfigKey3["RENDERER_LOG_CHANNEL"] = "rendererLogChannel";
56
+ PRBEAgentConfigKey3["APP_DATA_PATH"] = "appDataPath";
57
+ return PRBEAgentConfigKey3;
58
+ })(PRBEAgentConfigKey || {});
59
+ var PRBEAgentStatusType = /* @__PURE__ */ ((PRBEAgentStatusType2) => {
60
+ PRBEAgentStatusType2["STARTED"] = "started";
61
+ PRBEAgentStatusType2["THINKING"] = "thinking";
62
+ PRBEAgentStatusType2["TOOL_CALL"] = "tool_call";
63
+ PRBEAgentStatusType2["THOUGHT"] = "thought";
64
+ PRBEAgentStatusType2["OBSERVATION"] = "observation";
65
+ PRBEAgentStatusType2["COMPLETED"] = "completed";
66
+ PRBEAgentStatusType2["ERROR"] = "error";
67
+ PRBEAgentStatusType2["AWAITING_INTERACTION"] = "awaiting_interaction";
68
+ return PRBEAgentStatusType2;
69
+ })(PRBEAgentStatusType || {});
70
+ var PRBEAgentErrorType = /* @__PURE__ */ ((PRBEAgentErrorType2) => {
71
+ PRBEAgentErrorType2["SERVER_ERROR"] = "server_error";
72
+ PRBEAgentErrorType2["NETWORK_ERROR"] = "network_error";
73
+ PRBEAgentErrorType2["CANCELLED"] = "cancelled";
74
+ PRBEAgentErrorType2["MAX_ITERATIONS"] = "max_iterations";
75
+ return PRBEAgentErrorType2;
76
+ })(PRBEAgentErrorType || {});
77
+ var PRBEAgentError = class extends Error {
78
+ errorType;
79
+ statusCode;
80
+ constructor(errorType, message, statusCode) {
81
+ super(message);
82
+ this.name = "PRBEAgentError";
83
+ this.errorType = errorType;
84
+ this.statusCode = statusCode;
85
+ }
86
+ };
87
+ function redactPII(text) {
88
+ return text;
89
+ }
90
+ var API_URL = "https://api.prbe.ai";
91
+ var MIDDLEWARE_URL = "wss://middleware.prbe.ai";
92
+
93
+ // src/state.ts
94
+ import { EventEmitter } from "events";
95
+ import { randomUUID } from "crypto";
96
+ var PRBEStateEvent = /* @__PURE__ */ ((PRBEStateEvent2) => {
97
+ PRBEStateEvent2["STATUS"] = "status";
98
+ PRBEStateEvent2["EVENT"] = "event";
99
+ PRBEStateEvent2["COMPLETE"] = "complete";
100
+ PRBEStateEvent2["ERROR"] = "error";
101
+ PRBEStateEvent2["CR_START"] = "cr-start";
102
+ PRBEStateEvent2["CR_COMPLETE"] = "cr-complete";
103
+ PRBEStateEvent2["TICKETS_CHANGED"] = "tickets-changed";
104
+ PRBEStateEvent2["TICKET_INFO"] = "ticket-info";
105
+ PRBEStateEvent2["INTERACTION_REQUESTED"] = "interaction-requested";
106
+ PRBEStateEvent2["INTERACTION_RESOLVED"] = "interaction-resolved";
107
+ return PRBEStateEvent2;
108
+ })(PRBEStateEvent || {});
109
+ var PRBEAgentState = class extends EventEmitter {
110
+ // User-initiated investigation
111
+ isInvestigating = false;
112
+ events = [];
113
+ report = "";
114
+ summary = "";
115
+ currentQuery = "";
116
+ investigationError;
117
+ pendingInteraction;
118
+ resolvedInteractions = [];
119
+ // Completed user investigations (history)
120
+ completedInvestigations = [];
121
+ // Background context requests
122
+ activeCRs = /* @__PURE__ */ new Map();
123
+ completedCRs = [];
124
+ // Tracked tickets
125
+ trackedTicketIDs = [];
126
+ ticketInfo = [];
127
+ // Computed
128
+ get hasActiveWork() {
129
+ return this.isInvestigating || this.activeCRs.size > 0;
130
+ }
131
+ get activeCRCount() {
132
+ return this.activeCRs.size;
133
+ }
134
+ get isActive() {
135
+ return this.isInvestigating || this.report.length > 0 || this.investigationError != null;
136
+ }
137
+ // ---------- User investigation mutations ----------
138
+ beginInvestigation(query) {
139
+ this.isInvestigating = true;
140
+ this.events = [];
141
+ this.resolvedInteractions = [];
142
+ this.report = "";
143
+ this.summary = "";
144
+ this.currentQuery = query;
145
+ this.investigationError = void 0;
146
+ this.emit("status" /* STATUS */);
147
+ }
148
+ resetInvestigation() {
149
+ this.isInvestigating = false;
150
+ this.events = [];
151
+ this.resolvedInteractions = [];
152
+ this.report = "";
153
+ this.summary = "";
154
+ this.currentQuery = "";
155
+ this.investigationError = void 0;
156
+ this.pendingInteraction = void 0;
157
+ this.emit("status" /* STATUS */);
158
+ }
159
+ appendEvent(label, detail, completed = false) {
160
+ if (this.events.length > 0) {
161
+ const last = this.events[this.events.length - 1];
162
+ if (!last.isCompleted && !completed) {
163
+ last.isCompleted = true;
164
+ }
165
+ }
166
+ const event = {
167
+ id: randomUUID(),
168
+ label,
169
+ detail,
170
+ isCompleted: completed,
171
+ isExpanded: false
172
+ };
173
+ this.events.push(event);
174
+ this.emit("event" /* EVENT */, event);
175
+ this.emit("status" /* STATUS */);
176
+ }
177
+ attachObservation(text) {
178
+ if (this.events.length > 0) {
179
+ this.events[this.events.length - 1].detail = text;
180
+ this.emit("status" /* STATUS */);
181
+ }
182
+ }
183
+ completeInvestigation(report, summary) {
184
+ if (this.events.length > 0) {
185
+ this.events[this.events.length - 1].isCompleted = true;
186
+ }
187
+ this.appendEvent("Done", void 0, true);
188
+ this.completedInvestigations.unshift({
189
+ id: randomUUID(),
190
+ query: this.currentQuery,
191
+ report,
192
+ summary,
193
+ completedAt: /* @__PURE__ */ new Date()
194
+ });
195
+ this.report = report;
196
+ this.summary = summary;
197
+ this.isInvestigating = false;
198
+ this.emit("complete" /* COMPLETE */, { report, summary });
199
+ this.emit("status" /* STATUS */);
200
+ }
201
+ failInvestigation(message) {
202
+ this.appendEvent(`Error: ${message}`);
203
+ this.investigationError = message;
204
+ this.isInvestigating = false;
205
+ this.emit("error" /* ERROR */, { message });
206
+ this.emit("status" /* STATUS */);
207
+ }
208
+ // ---------- Interaction state mutations ----------
209
+ setPendingInteraction(payload) {
210
+ this.pendingInteraction = payload;
211
+ this.emit("interaction-requested" /* INTERACTION_REQUESTED */, payload);
212
+ this.emit("status" /* STATUS */);
213
+ }
214
+ clearPendingInteraction() {
215
+ this.pendingInteraction = void 0;
216
+ this.emit("interaction-resolved" /* INTERACTION_RESOLVED */);
217
+ this.emit("status" /* STATUS */);
218
+ }
219
+ setCRPendingInteraction(crID, payload) {
220
+ const cr = this.activeCRs.get(crID);
221
+ if (!cr) return;
222
+ cr.pendingInteraction = payload;
223
+ this.emit("interaction-requested" /* INTERACTION_REQUESTED */, payload);
224
+ this.emit("status" /* STATUS */);
225
+ }
226
+ clearCRPendingInteraction(crID) {
227
+ const cr = this.activeCRs.get(crID);
228
+ if (!cr) return;
229
+ cr.pendingInteraction = void 0;
230
+ this.emit("interaction-resolved" /* INTERACTION_RESOLVED */);
231
+ this.emit("status" /* STATUS */);
232
+ }
233
+ resolveInteraction(response) {
234
+ if (!this.pendingInteraction) return;
235
+ this.resolvedInteractions.push({
236
+ interactionId: this.pendingInteraction.interactionId,
237
+ payload: this.pendingInteraction,
238
+ response,
239
+ eventIndex: this.events.length
240
+ });
241
+ this.pendingInteraction = void 0;
242
+ this.emit("interaction-resolved" /* INTERACTION_RESOLVED */);
243
+ this.emit("status" /* STATUS */);
244
+ }
245
+ resolveCRInteraction(crID, response) {
246
+ const cr = this.activeCRs.get(crID);
247
+ if (!cr || !cr.pendingInteraction) return;
248
+ const resolved = cr.resolvedInteractions ?? [];
249
+ resolved.push({
250
+ interactionId: cr.pendingInteraction.interactionId,
251
+ payload: cr.pendingInteraction,
252
+ response,
253
+ eventIndex: cr.events.length
254
+ });
255
+ cr.resolvedInteractions = resolved;
256
+ cr.pendingInteraction = void 0;
257
+ this.emit("interaction-resolved" /* INTERACTION_RESOLVED */);
258
+ this.emit("status" /* STATUS */);
259
+ }
260
+ toggleExpansion(eventId) {
261
+ const event = this.events.find((e) => e.id === eventId);
262
+ if (event) {
263
+ event.isExpanded = !event.isExpanded;
264
+ this.emit("status" /* STATUS */);
265
+ }
266
+ }
267
+ // ---------- CR state mutations ----------
268
+ beginCR(id, query, slug) {
269
+ const cr = {
270
+ id,
271
+ query,
272
+ slug,
273
+ events: [],
274
+ resolvedInteractions: [],
275
+ isRunning: true,
276
+ isCompleted: false,
277
+ isFailed: false,
278
+ report: "",
279
+ summary: "",
280
+ startedAt: /* @__PURE__ */ new Date()
281
+ };
282
+ this.activeCRs.set(id, cr);
283
+ this.emit("cr-start" /* CR_START */, cr);
284
+ this.emit("status" /* STATUS */);
285
+ }
286
+ appendCREvent(crID, label, detail, completed = false) {
287
+ const cr = this.activeCRs.get(crID);
288
+ if (!cr) return;
289
+ if (cr.events.length > 0) {
290
+ const last = cr.events[cr.events.length - 1];
291
+ if (!last.isCompleted && !completed) {
292
+ last.isCompleted = true;
293
+ }
294
+ }
295
+ cr.events.push({
296
+ id: randomUUID(),
297
+ label,
298
+ detail,
299
+ isCompleted: completed,
300
+ isExpanded: false
301
+ });
302
+ this.emit("status" /* STATUS */);
303
+ }
304
+ attachCRObservation(crID, text) {
305
+ const cr = this.activeCRs.get(crID);
306
+ if (!cr || cr.events.length === 0) return;
307
+ cr.events[cr.events.length - 1].detail = text;
308
+ this.emit("status" /* STATUS */);
309
+ }
310
+ completeCR(id, report, summary) {
311
+ const cr = this.activeCRs.get(id);
312
+ if (!cr) return;
313
+ this.activeCRs.delete(id);
314
+ if (cr.events.length > 0) {
315
+ cr.events[cr.events.length - 1].isCompleted = true;
316
+ }
317
+ cr.events.push({
318
+ id: randomUUID(),
319
+ label: "Done",
320
+ isCompleted: true,
321
+ isExpanded: false
322
+ });
323
+ cr.isRunning = false;
324
+ cr.isCompleted = true;
325
+ cr.report = report;
326
+ cr.summary = summary;
327
+ this.completedCRs.unshift(cr);
328
+ this.emit("cr-complete" /* CR_COMPLETE */, cr);
329
+ this.emit("status" /* STATUS */);
330
+ }
331
+ failCR(id, message) {
332
+ const cr = this.activeCRs.get(id);
333
+ if (!cr) return;
334
+ this.activeCRs.delete(id);
335
+ cr.events.push({
336
+ id: randomUUID(),
337
+ label: `Error: ${message}`,
338
+ isCompleted: false,
339
+ isExpanded: false
340
+ });
341
+ cr.isRunning = false;
342
+ cr.isFailed = true;
343
+ cr.errorMessage = message;
344
+ this.completedCRs.unshift(cr);
345
+ this.emit("cr-complete" /* CR_COMPLETE */, cr);
346
+ this.emit("status" /* STATUS */);
347
+ }
348
+ // ---------- Tickets ----------
349
+ updateTrackedTicketIDs(ids) {
350
+ this.trackedTicketIDs = ids;
351
+ this.emit("tickets-changed" /* TICKETS_CHANGED */, ids);
352
+ this.emit("status" /* STATUS */);
353
+ }
354
+ updateTicketInfo(info) {
355
+ this.ticketInfo = info;
356
+ this.emit("ticket-info" /* TICKET_INFO */, info);
357
+ this.emit("status" /* STATUS */);
358
+ }
359
+ };
360
+
361
+ // src/tools/index.ts
362
+ import { randomUUID as randomUUID2 } from "crypto";
363
+
364
+ // src/interactions.ts
365
+ var InteractionType = /* @__PURE__ */ ((InteractionType2) => {
366
+ InteractionType2["ASK_QUESTION"] = "ask_question";
367
+ InteractionType2["REQUEST_PERMISSION"] = "request_permission";
368
+ InteractionType2["REQUEST_PATH_ACCESS"] = "request_path_access";
369
+ return InteractionType2;
370
+ })(InteractionType || {});
371
+ var InvestigationSource = /* @__PURE__ */ ((InvestigationSource2) => {
372
+ InvestigationSource2["USER"] = "user";
373
+ InvestigationSource2["CONTEXT_REQUEST"] = "context_request";
374
+ return InvestigationSource2;
375
+ })(InvestigationSource || {});
376
+
377
+ // src/tools/index.ts
378
+ import * as path from "path";
379
+ var PRBEToolRegistry = class {
380
+ tools = /* @__PURE__ */ new Map();
381
+ register(tool) {
382
+ this.tools.set(tool.declaration.name, tool);
383
+ }
384
+ allDeclarations() {
385
+ return Array.from(this.tools.values()).map((t) => t.declaration);
386
+ }
387
+ async execute(name, args) {
388
+ const tool = this.tools.get(name);
389
+ if (!tool) {
390
+ return `Error: unknown tool '${name}'`;
391
+ }
392
+ return tool.execute(args);
393
+ }
394
+ };
395
+ var PRBEClosureTool = class {
396
+ declaration;
397
+ handler;
398
+ constructor(name, description, parameters, handler) {
399
+ this.declaration = { name, description, parameters };
400
+ this.handler = handler;
401
+ }
402
+ async execute(args) {
403
+ return this.handler(args);
404
+ }
405
+ };
406
+ function resolveAndValidate(pathStr, autoApprovedDirs) {
407
+ let resolved;
408
+ if (path.isAbsolute(pathStr)) {
409
+ resolved = path.resolve(pathStr);
410
+ } else if (autoApprovedDirs.length > 0) {
411
+ resolved = path.resolve(autoApprovedDirs[0], pathStr);
412
+ } else {
413
+ return null;
414
+ }
415
+ const normalizedResolved = path.resolve(resolved);
416
+ for (const root of autoApprovedDirs) {
417
+ const normalizedRoot = path.resolve(root);
418
+ if (normalizedResolved === normalizedRoot || normalizedResolved.startsWith(normalizedRoot + path.sep)) {
419
+ return normalizedResolved;
420
+ }
421
+ }
422
+ return null;
423
+ }
424
+ async function resolveWithAccessRequest(pathStr, autoApprovedDirs, grantedPaths, requester) {
425
+ const allRoots = [...autoApprovedDirs, ...grantedPaths];
426
+ const resolved = resolveAndValidate(pathStr, allRoots);
427
+ if (resolved) return { path: resolved };
428
+ if (!requester) return null;
429
+ let absolute;
430
+ if (path.isAbsolute(pathStr)) {
431
+ absolute = path.resolve(pathStr);
432
+ } else if (autoApprovedDirs.length > 0) {
433
+ absolute = path.resolve(autoApprovedDirs[0], pathStr);
434
+ } else {
435
+ return null;
436
+ }
437
+ const depth = absolute.split(path.sep).filter(Boolean).length;
438
+ if (depth < 3) {
439
+ return { error: `Path '${pathStr}' is too broad. Use a more specific path (e.g. a subdirectory at least 3 levels deep).` };
440
+ }
441
+ const response = await requester.requestUserInteraction({
442
+ type: "request_path_access" /* REQUEST_PATH_ACCESS */,
443
+ interactionId: randomUUID2(),
444
+ path: absolute,
445
+ reason: `The agent needs access to '${pathStr}' which is outside the allowed directories.`
446
+ });
447
+ const pathResponse = response;
448
+ if (!pathResponse.granted) return { error: "Path access denied by user." };
449
+ grantedPaths.add(absolute);
450
+ return { path: absolute };
451
+ }
452
+ function humanReadableSize(bytes) {
453
+ if (bytes < 1024) return `${bytes} B`;
454
+ const kb = bytes / 1024;
455
+ if (kb < 1024) return `${kb.toFixed(1)} KB`;
456
+ const mb = kb / 1024;
457
+ if (mb < 1024) return `${mb.toFixed(1)} MB`;
458
+ const gb = mb / 1024;
459
+ return `${gb.toFixed(1)} GB`;
460
+ }
461
+
462
+ // src/tools/filesystem.ts
463
+ import * as fs from "fs";
464
+ import * as path2 from "path";
465
+ async function resolvePath(pathStr, autoApprovedDirs, requester, grantedPaths) {
466
+ if (requester && grantedPaths) {
467
+ const result = await resolveWithAccessRequest(pathStr, autoApprovedDirs, grantedPaths, requester);
468
+ if (result && "error" in result) return [null, result.error];
469
+ if (result && "path" in result) return [result.path, null];
470
+ }
471
+ const resolved = resolveAndValidate(pathStr, autoApprovedDirs);
472
+ return [resolved, resolved ? null : "path is outside auto-approved directories"];
473
+ }
474
+ var ListDirectoryTool = class _ListDirectoryTool {
475
+ autoApprovedDirs;
476
+ requester;
477
+ grantedPaths;
478
+ constructor(autoApprovedDirs, requester, grantedPaths) {
479
+ this.autoApprovedDirs = autoApprovedDirs;
480
+ this.requester = requester;
481
+ this.grantedPaths = grantedPaths;
482
+ }
483
+ get declaration() {
484
+ return {
485
+ name: "client_list_directory" /* CLIENT_LIST_DIRECTORY */,
486
+ description: "List directory contents",
487
+ parameters: [
488
+ { name: "path", type: "STRING" /* STRING */, description: "Directory path", required: true },
489
+ { name: "recursive", type: "BOOLEAN" /* BOOLEAN */, description: "List recursively", required: false },
490
+ { name: "max_depth", type: "INTEGER" /* INTEGER */, description: "Max depth (default 3)", required: false }
491
+ ]
492
+ };
493
+ }
494
+ async execute(args) {
495
+ const pathStr = args["path"];
496
+ if (!pathStr) return "Error: 'path' parameter is required";
497
+ const [resolved, resolveErr] = await resolvePath(pathStr, this.autoApprovedDirs, this.requester, this.grantedPaths);
498
+ if (!resolved) return `Error: ${resolveErr}`;
499
+ try {
500
+ const stat = fs.statSync(resolved);
501
+ if (!stat.isDirectory()) return `Error: '${pathStr}' is not a directory`;
502
+ } catch {
503
+ return `Error: '${pathStr}' does not exist or is not accessible`;
504
+ }
505
+ const recursive = args["recursive"] === true;
506
+ const maxDepth = typeof args["max_depth"] === "number" ? args["max_depth"] : 3;
507
+ const lines = [];
508
+ this.listDir(resolved, 0, recursive ? maxDepth : 1, lines);
509
+ if (lines.length >= _ListDirectoryTool.MAX_ENTRIES) {
510
+ lines.push(`
511
+ (truncated at ${_ListDirectoryTool.MAX_ENTRIES} entries \u2014 use a more specific path)`);
512
+ }
513
+ return lines.join("\n") || "(empty directory)";
514
+ }
515
+ static MAX_ENTRIES = 1e3;
516
+ listDir(dirPath, depth, maxDepth, lines) {
517
+ if (depth >= maxDepth) return;
518
+ if (lines.length >= _ListDirectoryTool.MAX_ENTRIES) return;
519
+ const indent = " ".repeat(depth);
520
+ let entries;
521
+ try {
522
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
523
+ } catch {
524
+ lines.push(`${indent}(access denied)`);
525
+ return;
526
+ }
527
+ const sorted = entries.filter((e) => !e.name.startsWith(".")).sort((a, b) => a.name.localeCompare(b.name));
528
+ for (const entry of sorted) {
529
+ const fullPath = path2.join(dirPath, entry.name);
530
+ if (entry.isDirectory()) {
531
+ lines.push(`${indent}${entry.name}/`);
532
+ this.listDir(fullPath, depth + 1, maxDepth, lines);
533
+ } else {
534
+ try {
535
+ const stat = fs.statSync(fullPath);
536
+ lines.push(`${indent}${entry.name} (${humanReadableSize(stat.size)})`);
537
+ } catch {
538
+ lines.push(`${indent}${entry.name} (unknown size)`);
539
+ }
540
+ }
541
+ }
542
+ }
543
+ };
544
+ var ReadFileTool = class {
545
+ autoApprovedDirs;
546
+ requester;
547
+ grantedPaths;
548
+ constructor(autoApprovedDirs, requester, grantedPaths) {
549
+ this.autoApprovedDirs = autoApprovedDirs;
550
+ this.requester = requester;
551
+ this.grantedPaths = grantedPaths;
552
+ }
553
+ get declaration() {
554
+ return {
555
+ name: "client_read_file" /* CLIENT_READ_FILE */,
556
+ description: "Read file contents",
557
+ parameters: [
558
+ { name: "path", type: "STRING" /* STRING */, description: "File path", required: true },
559
+ { name: "offset", type: "INTEGER" /* INTEGER */, description: "Line offset", required: false },
560
+ { name: "limit", type: "INTEGER" /* INTEGER */, description: "Max lines (default 200)", required: false }
561
+ ]
562
+ };
563
+ }
564
+ async execute(args) {
565
+ const pathStr = args["path"];
566
+ if (!pathStr) return "Error: 'path' parameter is required";
567
+ const [resolved, resolveErr] = await resolvePath(pathStr, this.autoApprovedDirs, this.requester, this.grantedPaths);
568
+ if (!resolved) return `Error: ${resolveErr}`;
569
+ let content;
570
+ try {
571
+ content = fs.readFileSync(resolved, "utf-8");
572
+ } catch {
573
+ return `Error: could not read file at '${pathStr}'`;
574
+ }
575
+ const allLines = content.split(/\r?\n/);
576
+ const totalLines = allLines.length;
577
+ const limit = typeof args["limit"] === "number" ? args["limit"] : 200;
578
+ const rawOffset = typeof args["offset"] === "number" ? args["offset"] : 0;
579
+ let startIndex;
580
+ if (rawOffset < 0) {
581
+ startIndex = Math.max(0, totalLines + rawOffset);
582
+ } else {
583
+ startIndex = rawOffset;
584
+ }
585
+ const endIndex = Math.min(startIndex + limit, totalLines);
586
+ if (startIndex >= totalLines) {
587
+ return `File has ${totalLines} lines; offset ${rawOffset} is out of range.`;
588
+ }
589
+ let stat;
590
+ try {
591
+ stat = fs.statSync(resolved);
592
+ } catch {
593
+ stat = { size: 0 };
594
+ }
595
+ const sizeStr = humanReadableSize(stat.size);
596
+ let result = `File: ${pathStr} (${totalLines} lines, ${sizeStr}, showing ${startIndex + 1}-${endIndex})
597
+ `;
598
+ for (let i = startIndex; i < endIndex; i++) {
599
+ const lineNum = String(i + 1).padStart(4, " ");
600
+ result += `${lineNum} | ${allLines[i]}
601
+ `;
602
+ }
603
+ return result;
604
+ }
605
+ };
606
+ var SearchContentTool = class {
607
+ autoApprovedDirs;
608
+ requester;
609
+ grantedPaths;
610
+ constructor(autoApprovedDirs, requester, grantedPaths) {
611
+ this.autoApprovedDirs = autoApprovedDirs;
612
+ this.requester = requester;
613
+ this.grantedPaths = grantedPaths;
614
+ }
615
+ get declaration() {
616
+ return {
617
+ name: "client_search_content" /* CLIENT_SEARCH_CONTENT */,
618
+ description: "Search file contents",
619
+ parameters: [
620
+ { name: "pattern", type: "STRING" /* STRING */, description: "Regex pattern", required: true },
621
+ { name: "path", type: "STRING" /* STRING */, description: "File or directory to search", required: true },
622
+ { name: "context_lines", type: "INTEGER" /* INTEGER */, description: "Context lines (default 2)", required: false },
623
+ { name: "max_results", type: "INTEGER" /* INTEGER */, description: "Max results (default 50)", required: false }
624
+ ]
625
+ };
626
+ }
627
+ async execute(args) {
628
+ const pattern = args["pattern"];
629
+ if (!pattern) return "Error: 'pattern' parameter is required";
630
+ const pathStr = args["path"];
631
+ if (!pathStr) return "Error: 'path' parameter is required";
632
+ const [resolved, resolveErr] = await resolvePath(pathStr, this.autoApprovedDirs, this.requester, this.grantedPaths);
633
+ if (!resolved) return `Error: ${resolveErr}`;
634
+ const contextLines = typeof args["context_lines"] === "number" ? args["context_lines"] : 2;
635
+ const maxResults = typeof args["max_results"] === "number" ? args["max_results"] : 50;
636
+ let regex;
637
+ try {
638
+ regex = new RegExp(pattern);
639
+ } catch {
640
+ return `Error: invalid regex pattern '${pattern}'`;
641
+ }
642
+ const fileURLs = [];
643
+ let isDirectory = false;
644
+ try {
645
+ const stat = fs.statSync(resolved);
646
+ if (stat.isDirectory()) {
647
+ isDirectory = true;
648
+ this.collectFiles(resolved, fileURLs);
649
+ } else {
650
+ fileURLs.push(resolved);
651
+ }
652
+ } catch {
653
+ return `Error: could not access '${pathStr}'`;
654
+ }
655
+ const results = [];
656
+ for (const filePath of fileURLs) {
657
+ if (results.length >= maxResults) break;
658
+ let content;
659
+ try {
660
+ content = fs.readFileSync(filePath, "utf-8");
661
+ } catch {
662
+ continue;
663
+ }
664
+ const lines = content.split(/\r?\n/);
665
+ const relativePath = isDirectory ? path2.relative(resolved, filePath) : path2.basename(filePath);
666
+ for (let idx = 0; idx < lines.length; idx++) {
667
+ if (results.length >= maxResults) break;
668
+ if (!regex.test(lines[idx])) continue;
669
+ let snippet;
670
+ if (contextLines > 0) {
671
+ const start = Math.max(0, idx - contextLines);
672
+ const end = Math.min(lines.length - 1, idx + contextLines);
673
+ snippet = "";
674
+ for (let ci = start; ci <= end; ci++) {
675
+ const marker = ci === idx ? ">" : " ";
676
+ snippet += `${relativePath}:${ci + 1}:${marker} ${lines[ci]}
677
+ `;
678
+ }
679
+ } else {
680
+ snippet = `${relativePath}:${idx + 1}: ${lines[idx]}`;
681
+ }
682
+ results.push(snippet);
683
+ }
684
+ }
685
+ if (results.length === 0) {
686
+ return `No matches found for '${pattern}' in '${pathStr}'`;
687
+ }
688
+ return `Found ${results.length} match(es):
689
+
690
+ ${results.join("\n---\n")}`;
691
+ }
692
+ collectFiles(dirPath, fileURLs) {
693
+ let entries;
694
+ try {
695
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
696
+ } catch {
697
+ return;
698
+ }
699
+ for (const entry of entries) {
700
+ if (entry.name.startsWith(".")) continue;
701
+ const fullPath = path2.join(dirPath, entry.name);
702
+ if (entry.isDirectory()) {
703
+ this.collectFiles(fullPath, fileURLs);
704
+ } else if (entry.isFile()) {
705
+ fileURLs.push(fullPath);
706
+ }
707
+ }
708
+ }
709
+ };
710
+ var FindFilesTool = class {
711
+ autoApprovedDirs;
712
+ requester;
713
+ grantedPaths;
714
+ constructor(autoApprovedDirs, requester, grantedPaths) {
715
+ this.autoApprovedDirs = autoApprovedDirs;
716
+ this.requester = requester;
717
+ this.grantedPaths = grantedPaths;
718
+ }
719
+ get declaration() {
720
+ return {
721
+ name: "client_find_files" /* CLIENT_FIND_FILES */,
722
+ description: "Find files by name",
723
+ parameters: [
724
+ { name: "pattern", type: "STRING" /* STRING */, description: "Glob pattern", required: true },
725
+ { name: "path", type: "STRING" /* STRING */, description: "Directory to search", required: true },
726
+ { name: "max_results", type: "INTEGER" /* INTEGER */, description: "Max results (default 50)", required: false }
727
+ ]
728
+ };
729
+ }
730
+ async execute(args) {
731
+ const pattern = args["pattern"];
732
+ if (!pattern) return "Error: 'pattern' parameter is required";
733
+ const pathStr = args["path"];
734
+ if (!pathStr) return "Error: 'path' parameter is required";
735
+ const [resolved, resolveErr] = await resolvePath(pathStr, this.autoApprovedDirs, this.requester, this.grantedPaths);
736
+ if (!resolved) return `Error: ${resolveErr}`;
737
+ const maxResults = typeof args["max_results"] === "number" ? args["max_results"] : 50;
738
+ const matches = [];
739
+ this.walkAndMatch(resolved, pattern, matches);
740
+ matches.sort((a, b) => b.modified.getTime() - a.modified.getTime());
741
+ const limited = matches.slice(0, maxResults);
742
+ if (limited.length === 0) {
743
+ return `No files matching '${pattern}' found in '${pathStr}'`;
744
+ }
745
+ let result = `Found ${matches.length} file(s) matching '${pattern}':
746
+
747
+ `;
748
+ for (const m of limited) {
749
+ const sizeStr = humanReadableSize(m.size);
750
+ const dateStr = m.modified.toISOString();
751
+ result += `${m.path} (${sizeStr}, ${dateStr})
752
+ `;
753
+ }
754
+ return result;
755
+ }
756
+ walkAndMatch(dirPath, pattern, matches) {
757
+ let entries;
758
+ try {
759
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
760
+ } catch {
761
+ return;
762
+ }
763
+ for (const entry of entries) {
764
+ if (entry.name.startsWith(".")) continue;
765
+ const fullPath = path2.join(dirPath, entry.name);
766
+ if (entry.isDirectory()) {
767
+ this.walkAndMatch(fullPath, pattern, matches);
768
+ } else if (entry.isFile()) {
769
+ if (this.globMatch(entry.name, pattern)) {
770
+ try {
771
+ const stat = fs.statSync(fullPath);
772
+ matches.push({
773
+ path: fullPath,
774
+ size: stat.size,
775
+ modified: stat.mtime
776
+ });
777
+ } catch {
778
+ }
779
+ }
780
+ }
781
+ }
782
+ }
783
+ /**
784
+ * Simple glob matching: supports *, ?, and character classes [...].
785
+ * Converts glob to regex and tests against the filename.
786
+ */
787
+ globMatch(filename, pattern) {
788
+ let regexStr = "^";
789
+ for (let i = 0; i < pattern.length; i++) {
790
+ const c = pattern[i];
791
+ switch (c) {
792
+ case "*":
793
+ regexStr += ".*";
794
+ break;
795
+ case "?":
796
+ regexStr += ".";
797
+ break;
798
+ case "[":
799
+ const closeBracket = pattern.indexOf("]", i + 1);
800
+ if (closeBracket === -1) {
801
+ regexStr += "\\[";
802
+ } else {
803
+ regexStr += pattern.substring(i, closeBracket + 1);
804
+ i = closeBracket;
805
+ }
806
+ break;
807
+ case ".":
808
+ case "(":
809
+ case ")":
810
+ case "+":
811
+ case "^":
812
+ case "$":
813
+ case "|":
814
+ case "{":
815
+ case "}":
816
+ case "\\":
817
+ regexStr += `\\${c}`;
818
+ break;
819
+ default:
820
+ regexStr += c;
821
+ }
822
+ }
823
+ regexStr += "$";
824
+ try {
825
+ return new RegExp(regexStr).test(filename);
826
+ } catch {
827
+ return false;
828
+ }
829
+ }
830
+ };
831
+ var FlagFileTool = class _FlagFileTool {
832
+ autoApprovedDirs;
833
+ onFlag;
834
+ requester;
835
+ grantedPaths;
836
+ static MAX_BINARY_SIZE = 100 * 1024 * 1024;
837
+ // 100MB
838
+ constructor(autoApprovedDirs, onFlag, requester, grantedPaths) {
839
+ this.autoApprovedDirs = autoApprovedDirs;
840
+ this.onFlag = onFlag;
841
+ this.requester = requester;
842
+ this.grantedPaths = grantedPaths;
843
+ }
844
+ get declaration() {
845
+ return {
846
+ name: "client_flag_file" /* CLIENT_FLAG_FILE */,
847
+ description: "Flag a file (or part of a text file) as evidence. For large text files, use offset and limit to flag only the relevant section. Binary files are flagged in full (up to 100MB).",
848
+ parameters: [
849
+ { name: "path", type: "STRING" /* STRING */, description: "File path", required: true },
850
+ { name: "reason", type: "STRING" /* STRING */, description: "Reason for flagging", required: true },
851
+ { name: "offset", type: "INTEGER" /* INTEGER */, description: "Starting line number (1-based, text files only). Negative = from end.", required: false },
852
+ { name: "limit", type: "INTEGER" /* INTEGER */, description: "Max number of lines to include (text files only).", required: false }
853
+ ]
854
+ };
855
+ }
856
+ async execute(args) {
857
+ const pathStr = args["path"];
858
+ if (!pathStr) return "Error: 'path' parameter is required";
859
+ const reason = args["reason"];
860
+ if (!reason) return "Error: 'reason' parameter is required";
861
+ const [resolved, resolveErr] = await resolvePath(pathStr, this.autoApprovedDirs, this.requester, this.grantedPaths);
862
+ if (!resolved) return `Error: ${resolveErr}`;
863
+ if (!fs.existsSync(resolved)) {
864
+ return `Error: file does not exist at '${pathStr}'`;
865
+ }
866
+ const offset = typeof args["offset"] === "number" ? args["offset"] : void 0;
867
+ const limit = typeof args["limit"] === "number" ? args["limit"] : void 0;
868
+ let textContent = null;
869
+ try {
870
+ textContent = fs.readFileSync(resolved, "utf-8");
871
+ } catch {
872
+ }
873
+ if (textContent !== null) {
874
+ let content = textContent;
875
+ if (offset !== void 0 || limit !== void 0) {
876
+ let lines = content.split(/\r?\n/);
877
+ const totalLines = lines.length;
878
+ let startIdx = 0;
879
+ if (offset !== void 0) {
880
+ startIdx = offset < 0 ? Math.max(0, totalLines + offset) : Math.max(0, offset - 1);
881
+ }
882
+ lines = lines.slice(startIdx);
883
+ if (limit !== void 0 && limit > 0) {
884
+ lines = lines.slice(0, limit);
885
+ }
886
+ const rangeDesc = `lines ${startIdx + 1}-${startIdx + lines.length} of ${totalLines}`;
887
+ content = `[${rangeDesc}]
888
+ ${lines.join("\n")}`;
889
+ }
890
+ const redacted = redactPII(content);
891
+ this.onFlag({
892
+ originalPath: resolved,
893
+ reason,
894
+ data: Buffer.from(redacted, "utf-8"),
895
+ isText: true
896
+ });
897
+ const sizeDesc = offset !== void 0 || limit !== void 0 ? " (partial)" : "";
898
+ return `Flagged '${pathStr}'${sizeDesc} \u2014 reason: ${reason}`;
899
+ }
900
+ if (offset !== void 0 || limit !== void 0) {
901
+ return "Error: offset/limit not supported for binary files. Flag the full file instead.";
902
+ }
903
+ let data;
904
+ try {
905
+ data = fs.readFileSync(resolved);
906
+ } catch {
907
+ return `Error: could not read file at '${pathStr}'`;
908
+ }
909
+ if (data.length > _FlagFileTool.MAX_BINARY_SIZE) {
910
+ const sizeMB = Math.floor(data.length / (1024 * 1024));
911
+ return `Error: file is ${sizeMB}MB \u2014 exceeds 100MB limit`;
912
+ }
913
+ this.onFlag({
914
+ originalPath: resolved,
915
+ reason,
916
+ data,
917
+ isText: false
918
+ });
919
+ return `Flagged '${pathStr}' (binary, ${data.length} bytes) \u2014 reason: ${reason}`;
920
+ }
921
+ };
922
+
923
+ // src/tools/logs.ts
924
+ var PRBELogCapture = class {
925
+ entries = [];
926
+ maxEntries;
927
+ isCapturing = false;
928
+ // Original console methods, saved for restoration
929
+ originalLog;
930
+ originalWarn;
931
+ originalError;
932
+ originalDebug;
933
+ originalInfo;
934
+ constructor(maxEntries = 1e4) {
935
+ this.maxEntries = maxEntries;
936
+ }
937
+ // ---------- Structured Logging ----------
938
+ log(message, level = "PRINT", category = "print") {
939
+ this.append({
940
+ timestamp: /* @__PURE__ */ new Date(),
941
+ level,
942
+ category,
943
+ message
944
+ });
945
+ }
946
+ // ---------- Query ----------
947
+ get count() {
948
+ return this.entries.length;
949
+ }
950
+ getEntries(options = {}) {
951
+ const { offset = 0, limit = 100, level, from, to } = options;
952
+ let filtered = this.entries;
953
+ if (level) {
954
+ filtered = filtered.filter((e) => e.level === level);
955
+ }
956
+ if (from) {
957
+ filtered = filtered.filter((e) => e.timestamp >= from);
958
+ }
959
+ if (to) {
960
+ filtered = filtered.filter((e) => e.timestamp <= to);
961
+ }
962
+ if (offset >= filtered.length) return [];
963
+ const end = Math.min(offset + limit, filtered.length);
964
+ return filtered.slice(offset, end);
965
+ }
966
+ search(pattern, contextLines = 2, maxResults = 50) {
967
+ let regex;
968
+ try {
969
+ regex = new RegExp(pattern, "i");
970
+ } catch {
971
+ return [];
972
+ }
973
+ const results = [];
974
+ for (let i = 0; i < this.entries.length; i++) {
975
+ if (results.length >= maxResults) break;
976
+ if (regex.test(this.entries[i].message)) {
977
+ results.push({ index: i, entry: this.entries[i] });
978
+ }
979
+ }
980
+ return results;
981
+ }
982
+ // ---------- Clear ----------
983
+ clearLogs() {
984
+ const count = this.entries.length;
985
+ this.entries = [];
986
+ return count;
987
+ }
988
+ // ---------- Console Capture ----------
989
+ startCapturing() {
990
+ if (this.isCapturing) return;
991
+ this.isCapturing = true;
992
+ this.originalLog = console.log;
993
+ this.originalWarn = console.warn;
994
+ this.originalError = console.error;
995
+ this.originalDebug = console.debug;
996
+ this.originalInfo = console.info;
997
+ const self = this;
998
+ console.log = (...args) => {
999
+ self.originalLog?.apply(console, args);
1000
+ self.log(args.map(String).join(" "), "PRINT", "console.log");
1001
+ };
1002
+ console.warn = (...args) => {
1003
+ self.originalWarn?.apply(console, args);
1004
+ self.log(args.map(String).join(" "), "WARNING", "console.warn");
1005
+ };
1006
+ console.error = (...args) => {
1007
+ self.originalError?.apply(console, args);
1008
+ self.log(args.map(String).join(" "), "ERROR", "console.error");
1009
+ };
1010
+ console.debug = (...args) => {
1011
+ self.originalDebug?.apply(console, args);
1012
+ self.log(args.map(String).join(" "), "DEBUG", "console.debug");
1013
+ };
1014
+ console.info = (...args) => {
1015
+ self.originalInfo?.apply(console, args);
1016
+ self.log(args.map(String).join(" "), "INFO", "console.info");
1017
+ };
1018
+ }
1019
+ stopCapturing() {
1020
+ if (!this.isCapturing) return;
1021
+ this.isCapturing = false;
1022
+ if (this.originalLog) console.log = this.originalLog;
1023
+ if (this.originalWarn) console.warn = this.originalWarn;
1024
+ if (this.originalError) console.error = this.originalError;
1025
+ if (this.originalDebug) console.debug = this.originalDebug;
1026
+ if (this.originalInfo) console.info = this.originalInfo;
1027
+ this.originalLog = void 0;
1028
+ this.originalWarn = void 0;
1029
+ this.originalError = void 0;
1030
+ this.originalDebug = void 0;
1031
+ this.originalInfo = void 0;
1032
+ }
1033
+ // ---------- Raw access to all entries (for tools) ----------
1034
+ getAllEntries() {
1035
+ return this.entries;
1036
+ }
1037
+ // ---------- Private ----------
1038
+ append(entry) {
1039
+ this.entries.push(entry);
1040
+ if (this.entries.length > this.maxEntries) {
1041
+ this.entries.splice(0, this.entries.length - this.maxEntries);
1042
+ }
1043
+ }
1044
+ };
1045
+ var ReadAppLogsTool = class {
1046
+ logCapture;
1047
+ constructor(logCapture) {
1048
+ this.logCapture = logCapture;
1049
+ }
1050
+ get declaration() {
1051
+ return {
1052
+ name: "client_read_app_logs" /* CLIENT_READ_APP_LOGS */,
1053
+ description: "Read application log entries captured from stdout and structured logging",
1054
+ parameters: [
1055
+ { name: "position", type: "STRING" /* STRING */, description: '"top" (oldest first) or "bottom" (newest first). Default: "bottom"', required: false },
1056
+ { name: "count", type: "INTEGER" /* INTEGER */, description: "Number of entries to return. Default: 100", required: false },
1057
+ { name: "level", type: "STRING" /* STRING */, description: 'Filter by log level (e.g. "ERROR", "INFO", "PRINT")', required: false },
1058
+ { name: "from", type: "STRING" /* STRING */, description: "ISO8601 start timestamp filter", required: false },
1059
+ { name: "to", type: "STRING" /* STRING */, description: "ISO8601 end timestamp filter", required: false }
1060
+ ]
1061
+ };
1062
+ }
1063
+ async execute(args) {
1064
+ const position = args["position"] ?? "bottom";
1065
+ const count = typeof args["count"] === "number" ? args["count"] : 100;
1066
+ const level = args["level"];
1067
+ const fromDate = args["from"] ? new Date(args["from"]) : void 0;
1068
+ const toDate = args["to"] ? new Date(args["to"]) : void 0;
1069
+ const totalCount = this.logCapture.count;
1070
+ const allEntries = this.logCapture.getEntries({
1071
+ offset: 0,
1072
+ limit: totalCount,
1073
+ level,
1074
+ from: fromDate,
1075
+ to: toDate
1076
+ });
1077
+ if (allEntries.length === 0) {
1078
+ return "No log entries found matching the specified filters.";
1079
+ }
1080
+ let selected;
1081
+ if (position === "top") {
1082
+ selected = allEntries.slice(0, count);
1083
+ } else {
1084
+ selected = allEntries.slice(-count);
1085
+ }
1086
+ let result = `Log entries (${selected.length} of ${allEntries.length} total):
1087
+
1088
+ `;
1089
+ for (const entry of selected) {
1090
+ result += `[${entry.timestamp.toISOString()}] [${entry.level}] [${entry.category}] ${entry.message}
1091
+ `;
1092
+ }
1093
+ return result;
1094
+ }
1095
+ };
1096
+ var SearchAppLogsTool = class {
1097
+ logCapture;
1098
+ constructor(logCapture) {
1099
+ this.logCapture = logCapture;
1100
+ }
1101
+ get declaration() {
1102
+ return {
1103
+ name: "client_search_app_logs" /* CLIENT_SEARCH_APP_LOGS */,
1104
+ description: "Search application logs by regex pattern with surrounding context",
1105
+ parameters: [
1106
+ { name: "pattern", type: "STRING" /* STRING */, description: "Regex pattern to search for", required: true },
1107
+ { name: "context_lines", type: "INTEGER" /* INTEGER */, description: "Number of surrounding log entries for context. Default: 2", required: false },
1108
+ { name: "max_results", type: "INTEGER" /* INTEGER */, description: "Maximum matches to return. Default: 50", required: false }
1109
+ ]
1110
+ };
1111
+ }
1112
+ async execute(args) {
1113
+ const pattern = args["pattern"];
1114
+ if (!pattern) return "Error: 'pattern' parameter is required";
1115
+ const contextLines = typeof args["context_lines"] === "number" ? args["context_lines"] : 2;
1116
+ const maxResults = typeof args["max_results"] === "number" ? args["max_results"] : 50;
1117
+ const matches = this.logCapture.search(pattern, contextLines, maxResults);
1118
+ if (matches.length === 0) {
1119
+ return `No log entries matching '${pattern}' found.`;
1120
+ }
1121
+ const allEntries = this.logCapture.getAllEntries();
1122
+ let result = `Found ${matches.length} match(es) for '${pattern}':
1123
+
1124
+ `;
1125
+ for (const match of matches) {
1126
+ const startIdx = Math.max(0, match.index - contextLines);
1127
+ const endIdx = Math.min(allEntries.length - 1, match.index + contextLines);
1128
+ for (let i = startIdx; i <= endIdx; i++) {
1129
+ const entry = allEntries[i];
1130
+ const marker = i === match.index ? ">" : " ";
1131
+ result += `${marker} [${entry.timestamp.toISOString()}] [${entry.level}] [${entry.category}] ${entry.message}
1132
+ `;
1133
+ }
1134
+ result += "---\n";
1135
+ }
1136
+ return result;
1137
+ }
1138
+ };
1139
+ var ClearAppLogsTool = class {
1140
+ logCapture;
1141
+ constructor(logCapture) {
1142
+ this.logCapture = logCapture;
1143
+ }
1144
+ get declaration() {
1145
+ return {
1146
+ name: "client_clear_app_logs" /* CLIENT_CLEAR_APP_LOGS */,
1147
+ description: "Clear all captured application logs",
1148
+ parameters: []
1149
+ };
1150
+ }
1151
+ async execute(_args) {
1152
+ const cleared = this.logCapture.clearLogs();
1153
+ return `Cleared ${cleared} log entries.`;
1154
+ }
1155
+ };
1156
+ var FlagAppLogsTool = class {
1157
+ logCapture;
1158
+ onFlag;
1159
+ constructor(logCapture, onFlag) {
1160
+ this.logCapture = logCapture;
1161
+ this.onFlag = onFlag;
1162
+ }
1163
+ get declaration() {
1164
+ return {
1165
+ name: "client_flag_app_logs" /* CLIENT_FLAG_APP_LOGS */,
1166
+ description: "Flag application log entries as evidence for the debug report. Snapshots matching log entries and includes them in the output package. Call with no filters to flag all logs, or use pattern to filter by message content. Note: 'level' filters by the log level metadata (e.g. PRINT, ERROR), NOT by message content \u2014 use 'pattern' to match words in log messages.",
1167
+ parameters: [
1168
+ { name: "reason", type: "STRING" /* STRING */, description: "Why these logs are relevant", required: true },
1169
+ { name: "pattern", type: "STRING" /* STRING */, description: "Regex pattern to filter log messages by content", required: false },
1170
+ { name: "level", type: "STRING" /* STRING */, description: "Filter by log level metadata (PRINT, DEBUG, INFO, ERROR, etc.) \u2014 this is NOT a content search", required: false },
1171
+ { name: "from", type: "STRING" /* STRING */, description: "ISO8601 start timestamp filter", required: false },
1172
+ { name: "to", type: "STRING" /* STRING */, description: "ISO8601 end timestamp filter", required: false }
1173
+ ]
1174
+ };
1175
+ }
1176
+ async execute(args) {
1177
+ const reason = args["reason"];
1178
+ if (!reason) return "Error: 'reason' parameter is required";
1179
+ const level = args["level"];
1180
+ const fromDate = args["from"] ? new Date(args["from"]) : void 0;
1181
+ const toDate = args["to"] ? new Date(args["to"]) : void 0;
1182
+ const totalCount = this.logCapture.count;
1183
+ let entries = this.logCapture.getEntries({
1184
+ offset: 0,
1185
+ limit: totalCount,
1186
+ level,
1187
+ from: fromDate,
1188
+ to: toDate
1189
+ });
1190
+ const patternStr = args["pattern"];
1191
+ if (patternStr) {
1192
+ let regex;
1193
+ try {
1194
+ regex = new RegExp(patternStr, "i");
1195
+ } catch {
1196
+ return `Error: invalid regex pattern '${patternStr}'`;
1197
+ }
1198
+ entries = entries.filter((entry) => regex.test(entry.message));
1199
+ }
1200
+ if (entries.length === 0) {
1201
+ const filters = [];
1202
+ if (level) filters.push(`level=${level}`);
1203
+ if (patternStr) filters.push(`pattern=${patternStr}`);
1204
+ if (fromDate) filters.push(`from=${args["from"]}`);
1205
+ if (toDate) filters.push(`to=${args["to"]}`);
1206
+ const hint = filters.length === 0 ? `The log buffer is empty (${totalCount} entries).` : `No entries matched filters: ${filters.join(", ")}. Total log entries: ${totalCount}. Try calling with no filters, or use 'pattern' instead of 'level' to match message content.`;
1207
+ return hint;
1208
+ }
1209
+ let content = "";
1210
+ for (const entry of entries) {
1211
+ content += `[${entry.timestamp.toISOString()}] [${entry.level}] [${entry.category}] ${entry.message}
1212
+ `;
1213
+ }
1214
+ const nameParts = ["app_logs"];
1215
+ if (level) nameParts.push(level.toLowerCase());
1216
+ if (patternStr) {
1217
+ const safe = patternStr.replace(/[^a-zA-Z0-9]/g, "_");
1218
+ const trimmed = safe.substring(0, 30).replace(/^_+|_+$/g, "");
1219
+ if (trimmed.length > 0) nameParts.push(trimmed);
1220
+ }
1221
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:\-]/g, "").substring(0, 15);
1222
+ nameParts.push(ts);
1223
+ const filename = nameParts.join("_") + ".txt";
1224
+ this.onFlag({
1225
+ originalPath: filename,
1226
+ reason,
1227
+ data: Buffer.from(redactPII(content), "utf-8"),
1228
+ isText: true
1229
+ });
1230
+ return `Flagged ${entries.length} log entries as '${filename}' \u2014 reason: ${reason}`;
1231
+ }
1232
+ };
1233
+
1234
+ // src/tools/interactive.ts
1235
+ import { randomUUID as randomUUID3 } from "crypto";
1236
+ var AskUserTool = class {
1237
+ requester;
1238
+ constructor(requester) {
1239
+ this.requester = requester;
1240
+ }
1241
+ get declaration() {
1242
+ return {
1243
+ name: "client_ask_user" /* CLIENT_ASK_USER */,
1244
+ description: "Ask the user a question and wait for their response. Use this when you need clarification or additional information from the user to continue the investigation.",
1245
+ interactive: true,
1246
+ parameters: [
1247
+ {
1248
+ name: "question",
1249
+ type: "STRING" /* STRING */,
1250
+ description: "The question to ask the user",
1251
+ required: true
1252
+ },
1253
+ {
1254
+ name: "context",
1255
+ type: "STRING" /* STRING */,
1256
+ description: "Optional context explaining why you need this information",
1257
+ required: false
1258
+ }
1259
+ ]
1260
+ };
1261
+ }
1262
+ async execute(args) {
1263
+ const question = args["question"];
1264
+ if (!question) return "Error: 'question' parameter is required";
1265
+ if (this.requester.investigationSource !== "user" /* USER */) {
1266
+ return "This is a context request investigation \u2014 you cannot ask the user questions. Use the available tools to answer the query autonomously.";
1267
+ }
1268
+ const context = args["context"];
1269
+ const response = await this.requester.requestUserInteraction({
1270
+ type: "ask_question" /* ASK_QUESTION */,
1271
+ interactionId: randomUUID3(),
1272
+ question,
1273
+ context
1274
+ });
1275
+ const askResponse = response;
1276
+ return askResponse.answer;
1277
+ }
1278
+ };
1279
+
1280
+ // src/tools/bash.ts
1281
+ import { exec } from "child_process";
1282
+ import { randomUUID as randomUUID4 } from "crypto";
1283
+ import * as path3 from "path";
1284
+ var IS_WINDOWS = process.platform === "win32";
1285
+ var UNIX_SAFE_COMMANDS = /* @__PURE__ */ new Set([
1286
+ "ls",
1287
+ "cat",
1288
+ "head",
1289
+ "tail",
1290
+ "grep",
1291
+ "rg",
1292
+ "find",
1293
+ "which",
1294
+ "whoami",
1295
+ "pwd",
1296
+ "echo",
1297
+ "printf",
1298
+ "wc",
1299
+ "sort",
1300
+ "uniq",
1301
+ "diff",
1302
+ "file",
1303
+ "stat",
1304
+ "du",
1305
+ "df",
1306
+ "uname",
1307
+ "env",
1308
+ "printenv",
1309
+ "date",
1310
+ "id",
1311
+ "hostname",
1312
+ "ps",
1313
+ "top",
1314
+ "uptime",
1315
+ "free"
1316
+ ]);
1317
+ var WINDOWS_SAFE_COMMANDS = /* @__PURE__ */ new Set([
1318
+ "dir",
1319
+ "type",
1320
+ "findstr",
1321
+ "where",
1322
+ "whoami",
1323
+ "echo",
1324
+ "sort",
1325
+ "fc",
1326
+ "hostname",
1327
+ "date",
1328
+ "systeminfo",
1329
+ "tasklist",
1330
+ "set",
1331
+ "ver",
1332
+ "vol",
1333
+ "tree",
1334
+ // PowerShell cmdlets (when shell is PowerShell)
1335
+ "get-childitem",
1336
+ "get-content",
1337
+ "select-string",
1338
+ "get-process",
1339
+ "get-date",
1340
+ "get-host",
1341
+ "get-computerinfo",
1342
+ "get-volume",
1343
+ "test-path",
1344
+ "resolve-path",
1345
+ "measure-object"
1346
+ ]);
1347
+ var SAFE_COMMANDS = IS_WINDOWS ? WINDOWS_SAFE_COMMANDS : UNIX_SAFE_COMMANDS;
1348
+ var SAFE_COMMAND_PREFIXES = /* @__PURE__ */ new Set([
1349
+ "git status",
1350
+ "git log",
1351
+ "git diff",
1352
+ "git show",
1353
+ "git branch",
1354
+ "node --version",
1355
+ "npm --version",
1356
+ "npm list",
1357
+ "npm ls"
1358
+ ]);
1359
+ var MAX_OUTPUT_BYTES = 100 * 1024;
1360
+ var DEFAULT_TIMEOUT_MS = 3e4;
1361
+ var MAX_TIMEOUT_MS = 12e4;
1362
+ var BashExecuteTool = class {
1363
+ requester;
1364
+ autoApprovedDirs;
1365
+ grantedPaths;
1366
+ constructor(requester, autoApprovedDirs, grantedPaths) {
1367
+ this.requester = requester;
1368
+ this.autoApprovedDirs = autoApprovedDirs;
1369
+ this.grantedPaths = grantedPaths;
1370
+ }
1371
+ get declaration() {
1372
+ return {
1373
+ name: "client_bash_execute" /* CLIENT_BASH_EXECUTE */,
1374
+ description: "Execute a shell command on the user's machine. Safe read-only commands run immediately; potentially destructive commands require user approval first.",
1375
+ interactive: true,
1376
+ parameters: [
1377
+ {
1378
+ name: "command",
1379
+ type: "STRING" /* STRING */,
1380
+ description: "The shell command to execute",
1381
+ required: true
1382
+ },
1383
+ {
1384
+ name: "cwd",
1385
+ type: "STRING" /* STRING */,
1386
+ description: "Working directory for the command (must be within auto-approved directories)",
1387
+ required: false
1388
+ },
1389
+ {
1390
+ name: "timeout",
1391
+ type: "INTEGER" /* INTEGER */,
1392
+ description: "Timeout in seconds (default 30, max 120)",
1393
+ required: false
1394
+ }
1395
+ ]
1396
+ };
1397
+ }
1398
+ async execute(args) {
1399
+ const command = args["command"];
1400
+ if (!command) return "Error: 'command' parameter is required";
1401
+ const cwdArg = args["cwd"];
1402
+ const timeoutSec = typeof args["timeout"] === "number" ? args["timeout"] : DEFAULT_TIMEOUT_MS / 1e3;
1403
+ const timeoutMs = Math.min(timeoutSec * 1e3, MAX_TIMEOUT_MS);
1404
+ let cwd;
1405
+ if (cwdArg) {
1406
+ const allRoots = [...this.autoApprovedDirs, ...this.grantedPaths];
1407
+ const resolved = resolveAndValidate(cwdArg, allRoots);
1408
+ if (!resolved) {
1409
+ return `Error: working directory '${cwdArg}' is outside auto-approved directories`;
1410
+ }
1411
+ cwd = resolved;
1412
+ }
1413
+ const isSafe = this.isCommandSafe(command);
1414
+ if (!isSafe) {
1415
+ const response = await this.requester.requestUserInteraction({
1416
+ type: "request_permission" /* REQUEST_PERMISSION */,
1417
+ interactionId: randomUUID4(),
1418
+ action: "Execute shell command",
1419
+ command,
1420
+ reason: `The agent wants to run this command${cwd ? ` in ${cwd}` : ""}.`
1421
+ });
1422
+ const permResponse = response;
1423
+ if (!permResponse.approved) {
1424
+ return "Command execution denied by user.";
1425
+ }
1426
+ }
1427
+ return new Promise((resolve2) => {
1428
+ exec(
1429
+ command,
1430
+ {
1431
+ cwd,
1432
+ timeout: timeoutMs,
1433
+ maxBuffer: MAX_OUTPUT_BYTES,
1434
+ env: process.env,
1435
+ // On Windows, use PowerShell for more consistent behavior
1436
+ ...IS_WINDOWS ? { shell: "powershell.exe" } : {}
1437
+ },
1438
+ (error, stdout, stderr) => {
1439
+ let output = "";
1440
+ if (stdout) {
1441
+ output += stdout;
1442
+ }
1443
+ if (stderr) {
1444
+ if (output) output += "\n--- stderr ---\n";
1445
+ output += stderr;
1446
+ }
1447
+ if (error) {
1448
+ if (error.killed) {
1449
+ output += `
1450
+ [Command timed out after ${timeoutSec}s]`;
1451
+ } else if (!stdout && !stderr) {
1452
+ output = `Error: ${error.message}`;
1453
+ }
1454
+ }
1455
+ if (Buffer.byteLength(output, "utf-8") > MAX_OUTPUT_BYTES) {
1456
+ output = output.slice(0, MAX_OUTPUT_BYTES) + "\n[Output truncated at 100KB]";
1457
+ }
1458
+ resolve2(output || "(no output)");
1459
+ }
1460
+ );
1461
+ });
1462
+ }
1463
+ /**
1464
+ * Check if a command is on the safe whitelist.
1465
+ */
1466
+ isCommandSafe(command) {
1467
+ const trimmed = command.trim();
1468
+ for (const prefix of SAFE_COMMAND_PREFIXES) {
1469
+ if (trimmed === prefix || trimmed.startsWith(prefix + " ")) {
1470
+ return true;
1471
+ }
1472
+ }
1473
+ const firstCommand = IS_WINDOWS ? trimmed.split(/[|;]/, 1)[0].trim() : trimmed.split(/[|;&]/, 1)[0].trim();
1474
+ const baseCommand = firstCommand.split(/\s+/, 1)[0];
1475
+ const commandName = IS_WINDOWS ? baseCommand.toLowerCase() : path3.basename(baseCommand);
1476
+ if (SAFE_COMMANDS.has(commandName)) {
1477
+ return this.areAllPipeSegmentsSafe(trimmed);
1478
+ }
1479
+ return false;
1480
+ }
1481
+ /**
1482
+ * For piped commands, check that every segment uses a safe command.
1483
+ */
1484
+ areAllPipeSegmentsSafe(command) {
1485
+ if (IS_WINDOWS) {
1486
+ if (/;/.test(command) && command.split("|").length <= 1) return false;
1487
+ } else {
1488
+ if (/[;&]|&&|\|\|/.test(command)) return false;
1489
+ }
1490
+ const segments = command.split("|").map((s) => s.trim());
1491
+ for (const segment of segments) {
1492
+ const baseCommand = segment.split(/\s+/, 1)[0];
1493
+ const commandName = IS_WINDOWS ? baseCommand.toLowerCase() : path3.basename(baseCommand);
1494
+ if (!SAFE_COMMANDS.has(commandName)) {
1495
+ return false;
1496
+ }
1497
+ }
1498
+ return true;
1499
+ }
1500
+ };
1501
+
1502
+ // src/agent.ts
1503
+ function getPersistencePath() {
1504
+ const appData = process.env["APPDATA"] || (process.platform === "darwin" ? path4.join(os.homedir(), "Library", "Application Support") : path4.join(os.homedir(), ".local", "share"));
1505
+ const dir = path4.join(appData, "prbe-agent");
1506
+ if (!fs2.existsSync(dir)) {
1507
+ fs2.mkdirSync(dir, { recursive: true });
1508
+ }
1509
+ return path4.join(dir, "agent-state.json");
1510
+ }
1511
+ function loadPersistedData() {
1512
+ try {
1513
+ const filePath = getPersistencePath();
1514
+ if (fs2.existsSync(filePath)) {
1515
+ const raw = fs2.readFileSync(filePath, "utf-8");
1516
+ return JSON.parse(raw);
1517
+ }
1518
+ } catch {
1519
+ }
1520
+ return {};
1521
+ }
1522
+ function savePersistedData(data) {
1523
+ try {
1524
+ const filePath = getPersistencePath();
1525
+ fs2.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
1526
+ } catch {
1527
+ console.error("[PRBEAgent] Failed to save persisted data");
1528
+ }
1529
+ }
1530
+ var PRBEAgent = class _PRBEAgent {
1531
+ state;
1532
+ logCapture;
1533
+ config;
1534
+ appDataPath;
1535
+ interactionHandler;
1536
+ registry = new PRBEToolRegistry();
1537
+ grantedPaths = /* @__PURE__ */ new Set();
1538
+ userCancelled = false;
1539
+ activeWS = null;
1540
+ pollingTimer = null;
1541
+ persistedData;
1542
+ fetchAbortController = null;
1543
+ currentInvestigationSource = "user" /* USER */;
1544
+ currentCRId = null;
1545
+ /** Files flagged during the current tool call — uploaded immediately after the tool returns. */
1546
+ pendingFlaggedFiles = [];
1547
+ // ---------- Persistence ----------
1548
+ get agentID() {
1549
+ if (this.persistedData.agentId) {
1550
+ return this.persistedData.agentId;
1551
+ }
1552
+ const newID = randomUUID5();
1553
+ this.persistedData.agentId = newID;
1554
+ savePersistedData(this.persistedData);
1555
+ return newID;
1556
+ }
1557
+ get trackedTicketIDs() {
1558
+ return this.persistedData.ticketIds ?? [];
1559
+ }
1560
+ set trackedTicketIDs(ids) {
1561
+ this.persistedData.ticketIds = ids;
1562
+ savePersistedData(this.persistedData);
1563
+ this.state.updateTrackedTicketIDs(ids);
1564
+ this.syncPolling(ids.length > 0);
1565
+ }
1566
+ get respondedCRIDs() {
1567
+ return new Set(this.persistedData.respondedCRIds ?? []);
1568
+ }
1569
+ set respondedCRIDs(ids) {
1570
+ this.persistedData.respondedCRIds = Array.from(ids);
1571
+ savePersistedData(this.persistedData);
1572
+ }
1573
+ syncPolling(hasTickets) {
1574
+ if (this.config.backgroundPolling && hasTickets) {
1575
+ if (this.pollingTimer === null) {
1576
+ this.startPolling();
1577
+ }
1578
+ } else if (!hasTickets) {
1579
+ this.stopPolling();
1580
+ }
1581
+ }
1582
+ addTrackedTicket(id) {
1583
+ const ids = this.trackedTicketIDs;
1584
+ if (!ids.includes(id)) {
1585
+ this.trackedTicketIDs = [...ids, id];
1586
+ }
1587
+ }
1588
+ // ---------- Constructor ----------
1589
+ constructor(config) {
1590
+ this.config = {
1591
+ apiKey: config.apiKey,
1592
+ autoApprovedDirs: config.autoApprovedDirs,
1593
+ pollingInterval: config.pollingInterval ?? 6e5,
1594
+ maxLogEntries: config.maxLogEntries ?? 1e4,
1595
+ captureConsole: config.captureConsole ?? true,
1596
+ backgroundPolling: config.backgroundPolling ?? true
1597
+ };
1598
+ this.interactionHandler = config.interactionHandler;
1599
+ this.appDataPath = config.appDataPath;
1600
+ if (this.appDataPath && !this.config.autoApprovedDirs.includes(this.appDataPath)) {
1601
+ this.config.autoApprovedDirs.push(this.appDataPath);
1602
+ }
1603
+ this.state = new PRBEAgentState();
1604
+ this.logCapture = new PRBELogCapture(this.config.maxLogEntries);
1605
+ this.persistedData = loadPersistedData();
1606
+ const roots = this.config.autoApprovedDirs;
1607
+ const requester = this.interactionHandler ? this : void 0;
1608
+ const grantedPaths = this.grantedPaths;
1609
+ this.registry.register(new ListDirectoryTool(roots, requester, grantedPaths));
1610
+ this.registry.register(new ReadFileTool(roots, requester, grantedPaths));
1611
+ this.registry.register(new SearchContentTool(roots, requester, grantedPaths));
1612
+ this.registry.register(new FindFilesTool(roots, requester, grantedPaths));
1613
+ this.registry.register(
1614
+ new FlagFileTool(roots, (file) => {
1615
+ this.pendingFlaggedFiles.push(file);
1616
+ }, requester, grantedPaths)
1617
+ );
1618
+ this.registry.register(new ReadAppLogsTool(this.logCapture));
1619
+ this.registry.register(new SearchAppLogsTool(this.logCapture));
1620
+ this.registry.register(new ClearAppLogsTool(this.logCapture));
1621
+ this.registry.register(
1622
+ new FlagAppLogsTool(this.logCapture, (file) => {
1623
+ this.pendingFlaggedFiles.push(file);
1624
+ })
1625
+ );
1626
+ if (requester) {
1627
+ this.registry.register(new AskUserTool(requester));
1628
+ this.registry.register(new BashExecuteTool(requester, roots, grantedPaths));
1629
+ }
1630
+ if (this.config.captureConsole) {
1631
+ this.logCapture.startCapturing();
1632
+ }
1633
+ if (config.electronLog) {
1634
+ this.hookElectronLog(config.electronLog);
1635
+ }
1636
+ if (config.ipcMain) {
1637
+ this.hookRendererLogs(config.ipcMain, config.rendererLogChannel ?? "prbe-renderer-log");
1638
+ }
1639
+ const existingTickets = this.trackedTicketIDs;
1640
+ if (existingTickets.length > 0) {
1641
+ this.trackedTicketIDs = existingTickets;
1642
+ }
1643
+ }
1644
+ // ---------- Log integration ----------
1645
+ hookElectronLog(electronLog) {
1646
+ try {
1647
+ electronLog.hooks.push(
1648
+ (message, _transportFn, transportName) => {
1649
+ if (transportName !== "file") return message;
1650
+ try {
1651
+ const text = message.data.map((d) => typeof d === "string" ? d : JSON.stringify(d)).join(" ");
1652
+ const level = _PRBEAgent.mapElectronLogLevel(message.level);
1653
+ this.logCapture.log(text, level, `electron-log.${message.level}`);
1654
+ } catch {
1655
+ }
1656
+ return message;
1657
+ }
1658
+ );
1659
+ } catch {
1660
+ }
1661
+ }
1662
+ hookRendererLogs(ipcMain, channel) {
1663
+ ipcMain.on(channel, (_event, entry) => {
1664
+ this.logCapture.log(entry.message, entry.level, `renderer.${entry.category}`);
1665
+ });
1666
+ }
1667
+ static mapElectronLogLevel(level) {
1668
+ switch (level) {
1669
+ case "error":
1670
+ return "ERROR";
1671
+ case "warn":
1672
+ return "WARNING";
1673
+ case "debug":
1674
+ case "verbose":
1675
+ case "silly":
1676
+ return "DEBUG";
1677
+ default:
1678
+ return "INFO";
1679
+ }
1680
+ }
1681
+ // ---------- PRBEInteractionRequester implementation ----------
1682
+ get investigationSource() {
1683
+ return this.currentInvestigationSource;
1684
+ }
1685
+ async requestUserInteraction(payload) {
1686
+ if (!this.interactionHandler) {
1687
+ throw new PRBEAgentError(
1688
+ "server_error" /* SERVER_ERROR */,
1689
+ "No interaction handler configured"
1690
+ );
1691
+ }
1692
+ if (this.currentInvestigationSource === "context_request" /* CONTEXT_REQUEST */ && this.currentCRId) {
1693
+ this.state.setCRPendingInteraction(this.currentCRId, payload);
1694
+ } else {
1695
+ this.state.setPendingInteraction(payload);
1696
+ }
1697
+ try {
1698
+ const response = await this.interactionHandler.handleInteraction(payload);
1699
+ if (this.currentInvestigationSource === "context_request" /* CONTEXT_REQUEST */ && this.currentCRId) {
1700
+ this.state.resolveCRInteraction(this.currentCRId, response);
1701
+ } else {
1702
+ this.state.resolveInteraction(response);
1703
+ }
1704
+ return response;
1705
+ } catch (err) {
1706
+ if (this.currentInvestigationSource === "context_request" /* CONTEXT_REQUEST */ && this.currentCRId) {
1707
+ this.state.clearCRPendingInteraction(this.currentCRId);
1708
+ } else {
1709
+ this.state.clearPendingInteraction();
1710
+ }
1711
+ throw err;
1712
+ }
1713
+ }
1714
+ /**
1715
+ * Add an additional root directory to the runtime allowed roots.
1716
+ */
1717
+ addAutoApprovedDir(rootPath) {
1718
+ if (!this.config.autoApprovedDirs.includes(rootPath)) {
1719
+ this.config.autoApprovedDirs.push(rootPath);
1720
+ }
1721
+ }
1722
+ // ---------- Public API ----------
1723
+ /**
1724
+ * Register a custom tool that the middleware can invoke during investigations.
1725
+ */
1726
+ registerTool(name, description, parameters, handler) {
1727
+ this.registry.register(
1728
+ new PRBEClosureTool(name, description, parameters, handler)
1729
+ );
1730
+ }
1731
+ /**
1732
+ * User-initiated investigation. Updates `state` events/report directly.
1733
+ */
1734
+ async investigate(query, contextRequestID) {
1735
+ this.userCancelled = false;
1736
+ this.currentInvestigationSource = "user" /* USER */;
1737
+ this.currentCRId = null;
1738
+ this.state.beginInvestigation(query);
1739
+ const emitter = (status) => {
1740
+ switch (status.type) {
1741
+ case "started" /* STARTED */:
1742
+ this.state.appendEvent("Starting investigation...");
1743
+ break;
1744
+ case "thinking" /* THINKING */:
1745
+ break;
1746
+ case "tool_call" /* TOOL_CALL */:
1747
+ this.state.appendEvent(status.label);
1748
+ break;
1749
+ case "observation" /* OBSERVATION */:
1750
+ this.state.attachObservation(status.text);
1751
+ break;
1752
+ case "thought" /* THOUGHT */:
1753
+ this.state.appendEvent("Thinking", status.text);
1754
+ break;
1755
+ case "completed" /* COMPLETED */:
1756
+ this.state.completeInvestigation(status.report, status.userSummary);
1757
+ break;
1758
+ case "error" /* ERROR */:
1759
+ this.state.failInvestigation(status.message);
1760
+ break;
1761
+ }
1762
+ };
1763
+ const result = await this.connectToProxy(
1764
+ query,
1765
+ contextRequestID,
1766
+ emitter,
1767
+ () => this.userCancelled
1768
+ );
1769
+ this.currentInvestigationSource = "user" /* USER */;
1770
+ this.currentCRId = null;
1771
+ if (result?.ticketId) {
1772
+ this.addTrackedTicket(result.ticketId);
1773
+ } else if (!result) {
1774
+ if (this.state.isInvestigating) {
1775
+ const message = this.userCancelled ? "Investigation cancelled" : "Investigation ended unexpectedly";
1776
+ this.state.failInvestigation(message);
1777
+ }
1778
+ }
1779
+ }
1780
+ /**
1781
+ * Cancel user-initiated investigation only (does not cancel background CRs).
1782
+ */
1783
+ cancelInvestigation() {
1784
+ this.userCancelled = true;
1785
+ if (this.activeWS) {
1786
+ this.activeWS.close(1e3, "User cancelled");
1787
+ this.activeWS = null;
1788
+ }
1789
+ }
1790
+ /**
1791
+ * Cancel everything — user investigation and polling.
1792
+ */
1793
+ cancel() {
1794
+ this.userCancelled = true;
1795
+ if (this.activeWS) {
1796
+ this.activeWS.close(1e3, "User cancelled");
1797
+ this.activeWS = null;
1798
+ }
1799
+ this.abortInFlightRequests();
1800
+ this.stopPolling();
1801
+ }
1802
+ /**
1803
+ * Poll the backend for context requests on tracked tickets.
1804
+ */
1805
+ async poll() {
1806
+ const ticketIDs = this.trackedTicketIDs;
1807
+ if (ticketIDs.length === 0) return null;
1808
+ const request = {
1809
+ agent_id: this.agentID,
1810
+ ticket_ids: ticketIDs
1811
+ };
1812
+ try {
1813
+ const response = await this.post(
1814
+ "/api/agent/poll",
1815
+ request
1816
+ );
1817
+ const returnedIDs = new Set(response.tickets.map((t) => t.ticket_id));
1818
+ const orphaned = ticketIDs.filter((id) => !returnedIDs.has(id));
1819
+ if (orphaned.length > 0) {
1820
+ this.trackedTicketIDs = this.trackedTicketIDs.filter(
1821
+ (id) => !orphaned.includes(id)
1822
+ );
1823
+ }
1824
+ const knownCRIDs = /* @__PURE__ */ new Set();
1825
+ for (const ticket of response.tickets) {
1826
+ if (ticket.status === "resolved") {
1827
+ this.trackedTicketIDs = this.trackedTicketIDs.filter(
1828
+ (id) => id !== ticket.ticket_id
1829
+ );
1830
+ }
1831
+ for (const cr of ticket.context_requests) {
1832
+ knownCRIDs.add(cr.id);
1833
+ if (!cr.is_active || this.respondedCRIDs.has(cr.id)) continue;
1834
+ await this.investigateForCR(cr, ticket.ticket_id);
1835
+ const ids = this.respondedCRIDs;
1836
+ ids.add(cr.id);
1837
+ this.respondedCRIDs = ids;
1838
+ }
1839
+ }
1840
+ const currentRespondedIDs = this.respondedCRIDs;
1841
+ const stale = new Set(
1842
+ [...currentRespondedIDs].filter((id) => !knownCRIDs.has(id))
1843
+ );
1844
+ if (stale.size > 0) {
1845
+ const pruned = new Set(
1846
+ [...currentRespondedIDs].filter((id) => !stale.has(id))
1847
+ );
1848
+ this.respondedCRIDs = pruned;
1849
+ }
1850
+ return response;
1851
+ } catch {
1852
+ return null;
1853
+ }
1854
+ }
1855
+ /**
1856
+ * Fetch ticket display info for all tracked tickets.
1857
+ */
1858
+ async fetchTicketInfo() {
1859
+ const ticketIDs = this.trackedTicketIDs;
1860
+ if (ticketIDs.length === 0) return [];
1861
+ const request = { ticket_ids: ticketIDs };
1862
+ try {
1863
+ const response = await this.post(
1864
+ "/api/agent/tickets",
1865
+ request
1866
+ );
1867
+ this.state.updateTicketInfo(response.tickets);
1868
+ return response.tickets;
1869
+ } catch {
1870
+ return [];
1871
+ }
1872
+ }
1873
+ stopPolling() {
1874
+ if (this.pollingTimer !== null) {
1875
+ clearInterval(this.pollingTimer);
1876
+ this.pollingTimer = null;
1877
+ }
1878
+ }
1879
+ resumePolling() {
1880
+ if (this.trackedTicketIDs.length === 0) return;
1881
+ this.startPolling();
1882
+ }
1883
+ /**
1884
+ * Destroy the agent — stops polling, stops console capture, closes WS.
1885
+ */
1886
+ destroy() {
1887
+ this.cancel();
1888
+ this.logCapture.stopCapturing();
1889
+ }
1890
+ /**
1891
+ * Reset all persisted data (agent ID, tracked tickets, responded CRs).
1892
+ * Also clears in-memory state.
1893
+ */
1894
+ resetPersistedData() {
1895
+ this.stopPolling();
1896
+ this.persistedData = {};
1897
+ savePersistedData(this.persistedData);
1898
+ this.state.resetInvestigation();
1899
+ this.state.completedInvestigations = [];
1900
+ this.state.activeCRs.clear();
1901
+ this.state.completedCRs = [];
1902
+ this.state.trackedTicketIDs = [];
1903
+ this.state.ticketInfo = [];
1904
+ this.state.emit("status" /* STATUS */);
1905
+ }
1906
+ /**
1907
+ * Delete the persisted data file. Can be called without an agent instance.
1908
+ */
1909
+ static clearPersistedData() {
1910
+ try {
1911
+ const filePath = getPersistencePath();
1912
+ if (fs2.existsSync(filePath)) {
1913
+ fs2.unlinkSync(filePath);
1914
+ }
1915
+ } catch {
1916
+ }
1917
+ }
1918
+ // ---------- CR Investigation ----------
1919
+ async investigateForCR(cr, ticketId) {
1920
+ const crID = cr.id;
1921
+ this.currentInvestigationSource = "context_request" /* CONTEXT_REQUEST */;
1922
+ this.currentCRId = crID;
1923
+ this.state.beginCR(crID, cr.query, cr.slug ?? void 0);
1924
+ const emitter = (status) => {
1925
+ switch (status.type) {
1926
+ case "started" /* STARTED */:
1927
+ this.state.appendCREvent(crID, "Starting investigation...");
1928
+ break;
1929
+ case "thinking" /* THINKING */:
1930
+ break;
1931
+ case "tool_call" /* TOOL_CALL */:
1932
+ this.state.appendCREvent(crID, status.label);
1933
+ break;
1934
+ case "observation" /* OBSERVATION */:
1935
+ this.state.attachCRObservation(crID, status.text);
1936
+ break;
1937
+ case "thought" /* THOUGHT */:
1938
+ this.state.appendCREvent(crID, "Thinking", status.text);
1939
+ break;
1940
+ case "completed" /* COMPLETED */:
1941
+ this.state.completeCR(crID, status.report, status.userSummary);
1942
+ break;
1943
+ case "error" /* ERROR */:
1944
+ this.state.failCR(crID, status.message);
1945
+ break;
1946
+ }
1947
+ };
1948
+ const result = await this.connectToProxy(
1949
+ cr.query,
1950
+ crID,
1951
+ emitter,
1952
+ () => false,
1953
+ // CRs are not user-cancellable
1954
+ ticketId
1955
+ );
1956
+ this.currentInvestigationSource = "user" /* USER */;
1957
+ this.currentCRId = null;
1958
+ if (result?.ticketId) {
1959
+ this.addTrackedTicket(result.ticketId);
1960
+ }
1961
+ }
1962
+ // ---------- WebSocket Investigation ----------
1963
+ connectToProxy(query, contextRequestID, emit, isCancelled, ticketId) {
1964
+ return new Promise((resolve2) => {
1965
+ const wsUrl = `${MIDDLEWARE_URL}/api/agent/client/ws`;
1966
+ let ws;
1967
+ try {
1968
+ ws = new WebSocket(wsUrl, {
1969
+ headers: {
1970
+ "X-API-Key": this.config.apiKey
1971
+ }
1972
+ });
1973
+ } catch (err) {
1974
+ emit({
1975
+ type: "error" /* ERROR */,
1976
+ message: `Failed to create WebSocket: ${err}`
1977
+ });
1978
+ resolve2(null);
1979
+ return;
1980
+ }
1981
+ this.activeWS = ws;
1982
+ let uploadBaseUrl;
1983
+ let resolved = false;
1984
+ const finish = (result) => {
1985
+ if (resolved) return;
1986
+ resolved = true;
1987
+ this.activeWS = null;
1988
+ resolve2(result);
1989
+ };
1990
+ ws.onopen = () => {
1991
+ const toolDeclarations = this.registry.allDeclarations().map((decl) => ({
1992
+ name: decl.name,
1993
+ description: decl.description,
1994
+ parameters: decl.parameters.map((param) => ({
1995
+ name: param.name,
1996
+ type: param.type,
1997
+ description: param.description,
1998
+ required: param.required
1999
+ })),
2000
+ ...decl.interactive ? { interactive: true } : {}
2001
+ }));
2002
+ const startMetadata = {
2003
+ agent_id: this.agentID,
2004
+ custom_tools: toolDeclarations
2005
+ };
2006
+ if (contextRequestID) {
2007
+ startMetadata["context_request_id"] = contextRequestID;
2008
+ }
2009
+ if (ticketId) {
2010
+ startMetadata["ticket_id"] = ticketId;
2011
+ }
2012
+ if (this.appDataPath) {
2013
+ startMetadata["app_data_path"] = this.appDataPath;
2014
+ }
2015
+ const startMsg = {
2016
+ type: "start" /* START */,
2017
+ content: query,
2018
+ metadata: startMetadata
2019
+ };
2020
+ try {
2021
+ ws.send(JSON.stringify(startMsg));
2022
+ } catch (err) {
2023
+ emit({
2024
+ type: "error" /* ERROR */,
2025
+ message: `Failed to send start message: ${err}`
2026
+ });
2027
+ finish(null);
2028
+ return;
2029
+ }
2030
+ emit({ type: "started" /* STARTED */ });
2031
+ this.pendingFlaggedFiles = [];
2032
+ };
2033
+ ws.onmessage = async (event) => {
2034
+ if (isCancelled()) {
2035
+ this.sendCancel(ws);
2036
+ ws.close(1e3, "Cancelled");
2037
+ finish(null);
2038
+ return;
2039
+ }
2040
+ let raw;
2041
+ if (typeof event.data === "string") {
2042
+ raw = event.data;
2043
+ } else if (event.data instanceof Buffer) {
2044
+ raw = event.data.toString("utf-8");
2045
+ } else {
2046
+ return;
2047
+ }
2048
+ let msg;
2049
+ try {
2050
+ msg = JSON.parse(raw);
2051
+ } catch {
2052
+ return;
2053
+ }
2054
+ switch (msg.type) {
2055
+ case "thought" /* THOUGHT */:
2056
+ emit({
2057
+ type: "thought" /* THOUGHT */,
2058
+ text: msg.content ?? ""
2059
+ });
2060
+ break;
2061
+ case "tool_call" /* TOOL_CALL */: {
2062
+ const toolName = msg.name ?? "";
2063
+ const callId = msg.id ?? "";
2064
+ const args = this.extractArgs(msg.metadata);
2065
+ emit({
2066
+ type: "tool_call" /* TOOL_CALL */,
2067
+ name: toolName,
2068
+ label: msg.content ?? `Running ${toolName}`
2069
+ });
2070
+ this.pendingFlaggedFiles = [];
2071
+ const toolResult = redactPII(
2072
+ await this.registry.execute(toolName, args)
2073
+ );
2074
+ emit({
2075
+ type: "observation" /* OBSERVATION */,
2076
+ text: toolResult.substring(0, 200)
2077
+ });
2078
+ let resultMetadata;
2079
+ if (this.pendingFlaggedFiles.length > 0 && uploadBaseUrl) {
2080
+ const uploadPrefix = this.extractUploadPrefix(uploadBaseUrl);
2081
+ const uploadedRefs = [];
2082
+ for (const file of this.pendingFlaggedFiles) {
2083
+ const filename = path4.basename(file.originalPath);
2084
+ const safeName = encodeURIComponent(filename);
2085
+ const storagePath = `${uploadPrefix}/${safeName}`;
2086
+ uploadedRefs.push({
2087
+ original_path: file.originalPath,
2088
+ reason: file.reason ?? "",
2089
+ storage_path: storagePath,
2090
+ file_size_bytes: file.data.length
2091
+ });
2092
+ const uploadUrl = `${uploadBaseUrl}/${safeName}`;
2093
+ const fileData = file.data;
2094
+ const contentType = file.isText ? "text/plain" : "application/octet-stream";
2095
+ void _PRBEAgent.backgroundUpload(
2096
+ fileData,
2097
+ uploadUrl,
2098
+ this.config.apiKey,
2099
+ contentType,
2100
+ this.getFetchSignal()
2101
+ );
2102
+ }
2103
+ resultMetadata = { flagged_files: uploadedRefs };
2104
+ this.pendingFlaggedFiles = [];
2105
+ }
2106
+ const resultMsg = {
2107
+ type: "tool_result" /* TOOL_RESULT */,
2108
+ id: callId,
2109
+ name: toolName,
2110
+ content: toolResult,
2111
+ metadata: resultMetadata
2112
+ };
2113
+ try {
2114
+ ws.send(JSON.stringify(resultMsg));
2115
+ } catch {
2116
+ }
2117
+ break;
2118
+ }
2119
+ case "server_tool_call" /* SERVER_TOOL_CALL */:
2120
+ emit({
2121
+ type: "tool_call" /* TOOL_CALL */,
2122
+ name: msg.name ?? "",
2123
+ label: msg.content ?? ""
2124
+ });
2125
+ break;
2126
+ case "server_observation" /* SERVER_OBSERVATION */:
2127
+ emit({
2128
+ type: "observation" /* OBSERVATION */,
2129
+ text: (msg.content ?? "").substring(0, 200)
2130
+ });
2131
+ break;
2132
+ case "complete" /* COMPLETE */: {
2133
+ const report = msg.content ?? "";
2134
+ const userSummary = msg.metadata?.["user_summary"] ?? "";
2135
+ const ticketId2 = msg.metadata?.["ticket_id"];
2136
+ emit({
2137
+ type: "completed" /* COMPLETED */,
2138
+ report,
2139
+ userSummary
2140
+ });
2141
+ ws.close(1e3, "Complete");
2142
+ finish({ report, userSummary, ticketId: ticketId2 });
2143
+ break;
2144
+ }
2145
+ case "error" /* ERROR */:
2146
+ emit({
2147
+ type: "error" /* ERROR */,
2148
+ message: msg.content || "Unknown error"
2149
+ });
2150
+ ws.close(1e3, "Error received");
2151
+ finish(null);
2152
+ break;
2153
+ case "session_config" /* SESSION_CONFIG */:
2154
+ uploadBaseUrl = msg.metadata?.["upload_url"];
2155
+ break;
2156
+ case "ping" /* PING */: {
2157
+ const pongMsg = { type: "pong" /* PONG */ };
2158
+ try {
2159
+ ws.send(JSON.stringify(pongMsg));
2160
+ } catch {
2161
+ }
2162
+ break;
2163
+ }
2164
+ // SDK-originated types and upload_url — ignore
2165
+ case "start" /* START */:
2166
+ case "tool_result" /* TOOL_RESULT */:
2167
+ case "upload_request" /* UPLOAD_REQUEST */:
2168
+ case "cancel" /* CANCEL */:
2169
+ case "pong" /* PONG */:
2170
+ case "upload_url" /* UPLOAD_URL */:
2171
+ break;
2172
+ }
2173
+ };
2174
+ ws.onerror = (event) => {
2175
+ if (isCancelled()) {
2176
+ finish(null);
2177
+ return;
2178
+ }
2179
+ const errorEvent = event;
2180
+ const message = errorEvent.message || "WebSocket connection error";
2181
+ emit({ type: "error" /* ERROR */, message });
2182
+ finish(null);
2183
+ };
2184
+ ws.onclose = (_event) => {
2185
+ if (!resolved) {
2186
+ if (isCancelled()) {
2187
+ finish(null);
2188
+ } else {
2189
+ emit({
2190
+ type: "error" /* ERROR */,
2191
+ message: "WebSocket connection closed unexpectedly"
2192
+ });
2193
+ finish(null);
2194
+ }
2195
+ }
2196
+ };
2197
+ });
2198
+ }
2199
+ sendCancel(ws) {
2200
+ try {
2201
+ const cancelMsg = { type: "cancel" /* CANCEL */ };
2202
+ ws.send(JSON.stringify(cancelMsg));
2203
+ } catch {
2204
+ }
2205
+ }
2206
+ extractArgs(metadata) {
2207
+ if (!metadata) return {};
2208
+ const argsVal = metadata["args"];
2209
+ if (argsVal && typeof argsVal === "object" && !Array.isArray(argsVal)) {
2210
+ return argsVal;
2211
+ }
2212
+ return {};
2213
+ }
2214
+ extractUploadPrefix(baseUrl) {
2215
+ try {
2216
+ const url = new URL(baseUrl);
2217
+ const urlPath = url.pathname;
2218
+ const marker = "/api/agent/upload/";
2219
+ const idx = urlPath.indexOf(marker);
2220
+ if (idx !== -1) {
2221
+ return urlPath.substring(idx + marker.length);
2222
+ }
2223
+ } catch {
2224
+ }
2225
+ return "";
2226
+ }
2227
+ // ---------- File Upload ----------
2228
+ static async backgroundUpload(data, uploadUrl, apiKey, contentType, signal) {
2229
+ try {
2230
+ const response = await fetch(uploadUrl, {
2231
+ method: "PUT",
2232
+ headers: {
2233
+ "Content-Type": contentType,
2234
+ "X-API-Key": apiKey
2235
+ },
2236
+ body: data,
2237
+ signal
2238
+ });
2239
+ if (!response.ok) {
2240
+ console.error(
2241
+ `[PRBEAgent] file upload failed: HTTP ${response.status} for ${uploadUrl}`
2242
+ );
2243
+ }
2244
+ } catch (err) {
2245
+ console.error(`[PRBEAgent] file upload error: ${err} for ${uploadUrl}`);
2246
+ }
2247
+ }
2248
+ // ---------- Polling ----------
2249
+ startPolling() {
2250
+ this.stopPolling();
2251
+ this.pollingTimer = setInterval(() => {
2252
+ void this.poll().then(() => {
2253
+ if (this.trackedTicketIDs.length === 0) {
2254
+ this.stopPolling();
2255
+ }
2256
+ });
2257
+ }, this.config.pollingInterval);
2258
+ }
2259
+ // ---------- Networking ----------
2260
+ /**
2261
+ * Abort all in-flight fetch requests to prevent orphaned DNS lookups
2262
+ * that can crash the c-ares resolver during process shutdown.
2263
+ */
2264
+ abortInFlightRequests() {
2265
+ if (this.fetchAbortController) {
2266
+ this.fetchAbortController.abort();
2267
+ this.fetchAbortController = null;
2268
+ }
2269
+ }
2270
+ /**
2271
+ * Get an AbortSignal for fetch requests. Creates a new controller if needed.
2272
+ */
2273
+ getFetchSignal() {
2274
+ if (!this.fetchAbortController) {
2275
+ this.fetchAbortController = new AbortController();
2276
+ }
2277
+ return this.fetchAbortController.signal;
2278
+ }
2279
+ async post(urlPath, body) {
2280
+ const url = `${API_URL}${urlPath}`;
2281
+ const response = await fetch(url, {
2282
+ method: "POST",
2283
+ headers: {
2284
+ "Content-Type": "application/json",
2285
+ "X-API-Key": this.config.apiKey
2286
+ },
2287
+ body: JSON.stringify(body),
2288
+ signal: this.getFetchSignal()
2289
+ });
2290
+ if (!response.ok) {
2291
+ const message = await response.text().catch(() => "Unknown error");
2292
+ throw new PRBEAgentError(
2293
+ "server_error" /* SERVER_ERROR */,
2294
+ `Server error ${response.status}: ${message}`,
2295
+ response.status
2296
+ );
2297
+ }
2298
+ return await response.json();
2299
+ }
2300
+ };
2301
+
2302
+ // src/serialization.ts
2303
+ var DEFAULT_PRBE_STATE = {
2304
+ isInvestigating: false,
2305
+ events: [],
2306
+ report: "",
2307
+ summary: "",
2308
+ currentQuery: "",
2309
+ resolvedInteractions: [],
2310
+ completedInvestigations: [],
2311
+ activeCRs: [],
2312
+ completedCRs: [],
2313
+ trackedTicketIDs: [],
2314
+ ticketInfo: [],
2315
+ hasActiveWork: false
2316
+ };
2317
+ function serializeCR(cr) {
2318
+ return {
2319
+ id: cr.id,
2320
+ query: cr.query,
2321
+ slug: cr.slug,
2322
+ events: cr.events,
2323
+ isRunning: cr.isRunning,
2324
+ isCompleted: cr.isCompleted,
2325
+ isFailed: cr.isFailed,
2326
+ report: cr.report,
2327
+ summary: cr.summary,
2328
+ errorMessage: cr.errorMessage,
2329
+ startedAt: cr.startedAt.toISOString(),
2330
+ pendingInteraction: cr.pendingInteraction,
2331
+ resolvedInteractions: cr.resolvedInteractions ?? []
2332
+ };
2333
+ }
2334
+ function serializePRBEState(state) {
2335
+ return {
2336
+ isInvestigating: state.isInvestigating,
2337
+ events: state.events,
2338
+ report: state.report,
2339
+ summary: state.summary,
2340
+ currentQuery: state.currentQuery,
2341
+ investigationError: state.investigationError,
2342
+ pendingInteraction: state.pendingInteraction,
2343
+ resolvedInteractions: state.resolvedInteractions,
2344
+ completedInvestigations: state.completedInvestigations.map((inv) => ({
2345
+ id: inv.id,
2346
+ query: inv.query,
2347
+ report: inv.report,
2348
+ summary: inv.summary,
2349
+ completedAt: inv.completedAt.toISOString()
2350
+ })),
2351
+ activeCRs: Array.from(state.activeCRs.values()).map(serializeCR),
2352
+ completedCRs: state.completedCRs.map(serializeCR),
2353
+ trackedTicketIDs: state.trackedTicketIDs,
2354
+ ticketInfo: state.ticketInfo,
2355
+ hasActiveWork: state.hasActiveWork
2356
+ };
2357
+ }
2358
+
2359
+ // src/assets/index.ts
2360
+ var PROBE_MARK_SVG = `<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
2361
+ <path d="M50 112 L114 148 L50 184 Z" fill="#111111" opacity="0.07"/>
2362
+ <path d="M70 86 L148 134 L70 182 Z" fill="#111111" opacity="0.18"/>
2363
+ <path d="M92 56 L192 118 L92 180 Z" fill="#111111"/>
2364
+ </svg>`;
2365
+ export {
2366
+ API_URL,
2367
+ AskUserTool,
2368
+ BashExecuteTool,
2369
+ ClearAppLogsTool,
2370
+ DEFAULT_PRBE_STATE,
2371
+ FindFilesTool,
2372
+ FlagAppLogsTool,
2373
+ FlagFileTool,
2374
+ InteractionType,
2375
+ InvestigationSource,
2376
+ ListDirectoryTool,
2377
+ MIDDLEWARE_URL,
2378
+ PRBEAgent,
2379
+ PRBEAgentConfigKey,
2380
+ PRBEAgentError,
2381
+ PRBEAgentErrorType,
2382
+ PRBEAgentState,
2383
+ PRBEAgentStatusType,
2384
+ PRBEClosureTool,
2385
+ PRBELogCapture,
2386
+ PRBEStateEvent,
2387
+ PRBEToolRegistry,
2388
+ PROBE_MARK_SVG,
2389
+ ReadAppLogsTool,
2390
+ ReadFileTool,
2391
+ SearchAppLogsTool,
2392
+ SearchContentTool,
2393
+ ToolName,
2394
+ ToolParamType,
2395
+ WSMessageType,
2396
+ humanReadableSize,
2397
+ redactPII,
2398
+ resolveAndValidate,
2399
+ resolveWithAccessRequest,
2400
+ serializePRBEState
2401
+ };
2402
+ //# sourceMappingURL=index.mjs.map