agentp 1.12.1 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/agentp CHANGED
@@ -10,6 +10,7 @@ const child_process = require('child_process');
10
10
  const { version } = require('../package.json');
11
11
  const {
12
12
  sendToSession,
13
+ sendToSessionAsync,
13
14
  listSessions,
14
15
  createSession,
15
16
  getSession,
@@ -108,6 +109,216 @@ function generateTempFilename() {
108
109
  return path.join(os.tmpdir(), `agentp_deferred_${timestamp}_${hash}.tmp`);
109
110
  }
110
111
 
112
+ // Poll interval for --defer waiting (ms). Overridable for tests.
113
+ const DEFER_POLL_MS = 250;
114
+ const FOLLOWUP_ROW = '📝 —————————————————\n';
115
+
116
+ function stripFollowupRow(text) {
117
+ return text.startsWith(FOLLOWUP_ROW) ? text.slice(FOLLOWUP_ROW.length) : text;
118
+ }
119
+
120
+ // Parse an `agentp_ticket <json>` reference from the start of input text.
121
+ // The ticket may be wrapped in whitespace/newlines and may be followed by
122
+ // additional text, which is returned separately for queued follow-ups.
123
+ function parseDeferredTicketInput(text) {
124
+ const trimmed = String(text).trim();
125
+ if (!trimmed.startsWith('agentp_ticket')) return null;
126
+ const afterPrefix = trimmed.slice('agentp_ticket'.length).trimStart();
127
+ if (!afterPrefix.startsWith('{')) return null;
128
+ let depth = 0;
129
+ let inString = false;
130
+ let escaped = false;
131
+ let jsonEnd = -1;
132
+ for (let i = 0; i < afterPrefix.length; i++) {
133
+ const ch = afterPrefix[i];
134
+ if (inString) {
135
+ if (escaped) {
136
+ escaped = false;
137
+ } else if (ch === '\\') {
138
+ escaped = true;
139
+ } else if (ch === '"') {
140
+ inString = false;
141
+ }
142
+ continue;
143
+ }
144
+ if (ch === '"') {
145
+ inString = true;
146
+ } else if (ch === '{') {
147
+ depth++;
148
+ } else if (ch === '}') {
149
+ depth--;
150
+ if (depth === 0) {
151
+ jsonEnd = i + 1;
152
+ break;
153
+ }
154
+ }
155
+ }
156
+ if (jsonEnd === -1) return null;
157
+ let data;
158
+ try {
159
+ data = JSON.parse(afterPrefix.slice(0, jsonEnd));
160
+ } catch {
161
+ return null;
162
+ }
163
+ if (!data || typeof data.path !== 'string' || !data.path) return null;
164
+ const rest = afterPrefix.slice(jsonEnd);
165
+ const followupText = rest.trim() ? stripFollowupRow(rest.trimStart()) : '';
166
+ return {
167
+ ctime: typeof data.ctime === 'string' ? data.ctime : null,
168
+ path: data.path,
169
+ defer: typeof data.defer === 'number' && data.defer > 0 ? data.defer : 0,
170
+ server: typeof data.server === 'string' ? data.server : null,
171
+ sessionId: typeof data.sessionId === 'string' ? data.sessionId : null,
172
+ followupText,
173
+ };
174
+ }
175
+
176
+ function parseDeferredTicket(text) {
177
+ const parsed = parseDeferredTicketInput(text);
178
+ if (!parsed) return null;
179
+ const { followupText, ...ticket } = parsed;
180
+ return ticket;
181
+ }
182
+
183
+ // Format a deferred ticket for printing. `elapsed` is only included on
184
+ // re-prints; `defer` only when a timeout > 0 was requested. Pretty-printed
185
+ // JSON by default (easier to read/edit); pass `compact` for a single line.
186
+ function formatTicket(ticket, elapsed, compact) {
187
+ const fields = { ctime: ticket.ctime, path: ticket.path };
188
+ if (ticket.server) fields.server = ticket.server;
189
+ if (ticket.sessionId) fields.sessionId = ticket.sessionId;
190
+ if (elapsed !== undefined) fields.elapsed = elapsed;
191
+ if (ticket.defer > 0) fields.defer = ticket.defer;
192
+ return 'agentp_ticket ' + (compact ? JSON.stringify(fields) : JSON.stringify(fields, null, 2));
193
+ }
194
+
195
+ async function resolveTargetSession(serverBase, sessionName, newSession) {
196
+ let sessions;
197
+ try {
198
+ sessions = await listSessions(serverBase);
199
+ } catch (err) {
200
+ throw new Error(`Error connecting to server: ${err.message}`);
201
+ }
202
+
203
+ if (sessionName) {
204
+ const matches = sessions.filter(s => {
205
+ const title = s.title || s.slug || s.id;
206
+ return title.toLowerCase() === sessionName.toLowerCase();
207
+ });
208
+ if (matches.length === 0) {
209
+ const partial = sessions.filter(s => {
210
+ const title = s.title || s.slug || s.id;
211
+ return title.toLowerCase().includes(sessionName.toLowerCase());
212
+ });
213
+ if (partial.length === 1) return partial[0].id;
214
+ if (partial.length > 1) throw new Error(`Error: multiple sessions match "${sessionName}"`);
215
+ if (!newSession) throw new Error(`Error: no session found matching "${sessionName}"`);
216
+ try {
217
+ const session = await createSession(serverBase, sessionName);
218
+ await selectSession(serverBase, session.id);
219
+ return session.id;
220
+ } catch (err) {
221
+ throw new Error(`Error creating session: ${err.message}`);
222
+ }
223
+ }
224
+ return matches[0].id;
225
+ }
226
+
227
+ if (sessions && sessions.length > 0) {
228
+ const sorted = sessions
229
+ .filter(s => s.time && (s.time.updated || s.time.created))
230
+ .sort((a, b) => {
231
+ const aTime = (a.time && a.time.updated) || (a.time && a.time.created) || 0;
232
+ const bTime = (b.time && b.time.updated) || (b.time && b.time.created) || 0;
233
+ return bTime - aTime;
234
+ });
235
+ if (sorted.length > 0) return sorted[0].id;
236
+ }
237
+
238
+ try {
239
+ const session = await createSession(serverBase, 'agentp');
240
+ return session.id;
241
+ } catch (err) {
242
+ throw new Error(`Error creating session: ${err.message}`);
243
+ }
244
+ }
245
+
246
+ // Read the deferred output file. Returns the answer string, or null if the
247
+ // answer is not ready yet (file missing, still locked, or still empty).
248
+ function readDeferredResult(filePath) {
249
+ if (!fs.existsSync(filePath)) return null;
250
+ if (isFileLocked(filePath)) return null;
251
+ let content = '';
252
+ try {
253
+ content = fs.readFileSync(filePath, 'utf8');
254
+ } catch {
255
+ return null;
256
+ }
257
+ return content === '' ? null : content;
258
+ }
259
+
260
+ // Poll up to timeoutMs for a ready deferred result. Returns the answer or null.
261
+ async function waitForDeferredResult(filePath, timeoutMs) {
262
+ const deadline = Date.now() + timeoutMs;
263
+ for (;;) {
264
+ const result = readDeferredResult(filePath);
265
+ if (result !== null) return result;
266
+ const remaining = deadline - Date.now();
267
+ if (remaining <= 0) return null;
268
+ await sleepMs(Math.min(DEFER_POLL_MS, remaining));
269
+ }
270
+ }
271
+
272
+ function sleepMs(ms) {
273
+ return new Promise((resolve) => setTimeout(resolve, ms));
274
+ }
275
+
276
+ function appendUnusedFollowup(result, followupText) {
277
+ if (!followupText) return result;
278
+ return result + (result.endsWith('\n') ? '' : '\n') + followupText;
279
+ }
280
+
281
+ function queuedFollowupsPath(filePath) {
282
+ return filePath + '.followups';
283
+ }
284
+
285
+ function readQueuedFollowups(filePath) {
286
+ try {
287
+ const text = fs.readFileSync(queuedFollowupsPath(filePath), 'utf8');
288
+ const parsed = JSON.parse(text);
289
+ return Array.isArray(parsed) ? parsed.filter(item => typeof item === 'string' && item) : [];
290
+ } catch {
291
+ return [];
292
+ }
293
+ }
294
+
295
+ function appendQueuedFollowup(filePath, followupText) {
296
+ if (!followupText) return;
297
+ const followups = readQueuedFollowups(filePath);
298
+ followups.push(followupText);
299
+ fs.writeFileSync(queuedFollowupsPath(filePath), JSON.stringify(followups, null, 2));
300
+ }
301
+
302
+ function removeQueuedFollowups(filePath) {
303
+ try { fs.unlinkSync(queuedFollowupsPath(filePath)); } catch {}
304
+ }
305
+
306
+ function injectQueuedFollowups(result, followups) {
307
+ if (!followups.length) return result;
308
+ const addition = followups.map(text => FOLLOWUP_ROW + text + (text.endsWith('\n') ? '' : '\n')).join('');
309
+ const idx = result.indexOf(AGENT_ROW);
310
+ if (idx === -1) return appendUnusedFollowup(result, addition.trimEnd());
311
+ const before = result.slice(0, idx);
312
+ const after = result.slice(idx);
313
+ return before + (before.endsWith('\n') ? '' : '\n') + addition + after;
314
+ }
315
+
316
+ function formatDeferredResult(filePath, result, qaMode, unusedFollowupText) {
317
+ const followups = qaMode ? readQueuedFollowups(filePath) : [];
318
+ const output = qaMode ? injectQueuedFollowups(result, followups) : result;
319
+ return appendUnusedFollowup(output, unusedFollowupText);
320
+ }
321
+
111
322
  async function readAllStdin() {
112
323
  let text = '';
113
324
  const rl = readline.createInterface({ input: process.stdin });
@@ -127,10 +338,13 @@ async function main() {
127
338
  let qaMode = false;
128
339
  let flushMode = false;
129
340
  let deferMode = false;
341
+ let deferSeconds = 0;
130
342
  let deferChildMode = false;
343
+ let onlineTicket = false;
131
344
  let promptFile = null;
132
345
  let outputFile = null;
133
346
  let sessionName = null;
347
+ let resolvedSessionId = null;
134
348
  let newSession = false;
135
349
  // tgMode: false = disabled, 'auto' = auto-detect, true = explicit --tg
136
350
  let tgMode = null;
@@ -148,7 +362,11 @@ async function main() {
148
362
  console.error('Options:');
149
363
  console.error(' --version Show version');
150
364
  console.error(' --qa Print the original prompt and answer with labels (useful as a filter)');
151
- console.error(' --defer Deferred execution: write prompt to temp file, return path immediately');
365
+ console.error(' --defer [N] Deferred execution: submit prompt, get a ticket immediately');
366
+ console.error(' (or wait up to N seconds for the answer and print it if it arrives)');
367
+ console.error(' Pipe the ticket back to agentp --defer to retrieve the result later');
368
+ console.error(' --onlineTicket');
369
+ console.error(' Print deferred tickets on a single line (default: pretty-printed JSON)');
152
370
  console.error(' --tg Forward the answer to Telegram via agentp gateway (error if unavailable)');
153
371
  console.error(' --no-tg Do not forward to Telegram');
154
372
  console.error(' --flush Flush tgagentp\'s recorded buffer without prepending it to output');
@@ -174,7 +392,7 @@ async function main() {
174
392
  console.error('Arguments:');
175
393
  console.error(' url OpenCode server URL or port number (default: 4096)');
176
394
  console.error(' Examples: 4096, http://localhost:4096, http://192.168.1.50:4096');
177
- console.error(' Use `ocmux` (with no args) to get the URL of the current project\'s server.');
395
+ console.error(' Tip: $(ocmux) expands to the URL of the current project\'s server.');
178
396
  console.error('');
179
397
  console.error('See also: tgagentp -- Telegram bridge (tgagentp --help), ocmux -- manage OpenCode tmux sessions (ocmux --help)');
180
398
  console.error('');
@@ -194,8 +412,17 @@ async function main() {
194
412
  qaMode = true;
195
413
  } else if (args[i] === '--defer') {
196
414
  deferMode = true;
415
+ const next = args[i + 1];
416
+ if (next !== undefined && /^\d+$/.test(next)) {
417
+ deferSeconds = parseInt(next, 10);
418
+ i++;
419
+ } else {
420
+ deferSeconds = 0;
421
+ }
197
422
  } else if (args[i] === '--defer-child') {
198
423
  deferChildMode = true;
424
+ } else if (args[i] === '--onlineTicket') {
425
+ onlineTicket = true;
199
426
  } else if (args[i] === '--prompt-file') {
200
427
  i++;
201
428
  promptFile = args[i];
@@ -211,6 +438,13 @@ async function main() {
211
438
  console.error('Error: --session requires a session name');
212
439
  process.exit(1);
213
440
  }
441
+ } else if (args[i] === '--resolved-session-id') {
442
+ i++;
443
+ resolvedSessionId = args[i];
444
+ if (!resolvedSessionId) {
445
+ console.error('Error: --resolved-session-id requires a session id');
446
+ process.exit(1);
447
+ }
214
448
  } else if (args[i] === '--new') {
215
449
  newSession = true;
216
450
  } else if (args[i] === '--getLast') {
@@ -354,85 +588,148 @@ async function main() {
354
588
 
355
589
  // Handle --defer mode
356
590
  if (deferMode) {
357
- // Check if stdin is a deferred reference
358
- const deferredMatch = promptText.match(/<agentp-deferred>(.*?)<\/agentp-deferred>/);
359
-
360
- if (deferredMatch) {
361
- // This is a deferred reference - retrieve the result
362
- const deferredPath = deferredMatch[1].trim();
363
-
591
+ // Check if stdin is a deferred ticket
592
+ const ticket = parseDeferredTicketInput(promptText);
593
+
594
+ if (ticket) {
595
+ // This is a ticket — retrieve the result. The --defer argument is
596
+ // ignored; the ticket's own "defer" timeout is used instead (default 0).
597
+ const deferredPath = ticket.path;
598
+
364
599
  if (!fs.existsSync(deferredPath)) {
365
- // File doesn't exist - error
366
600
  process.stdout.write(`Error: deferred file not found: ${deferredPath}\n`);
367
601
  process.exit(1);
368
602
  }
369
-
370
- // Check if file is locked (still processing) or empty (not ready yet)
371
- const isLocked = isFileLocked(deferredPath);
372
- let content = '';
373
- try {
374
- content = fs.readFileSync(deferredPath, 'utf8');
375
- } catch {
376
- content = '';
377
- }
378
-
379
- if (isLocked || content === '') {
380
- // File is locked (still processing) or empty (not ready yet) - identity filter
381
- process.stdout.write(promptText);
603
+
604
+ let result = ticket.followupText
605
+ ? readDeferredResult(deferredPath)
606
+ : await waitForDeferredResult(deferredPath, ticket.defer * 1000);
607
+
608
+ if (result !== null) {
609
+ // Answer available — return it and remove the temp file
610
+ try {
611
+ fs.unlinkSync(deferredPath);
612
+ } catch (err) {
613
+ process.stdout.write(`Error reading deferred file: ${err.message}\n`);
614
+ process.exit(1);
615
+ }
616
+ try { fs.unlinkSync(deferredPath + '.lock'); } catch {}
617
+ process.stdout.write(formatDeferredResult(deferredPath, result, qaMode, ticket.followupText));
618
+ removeQueuedFollowups(deferredPath);
382
619
  process.exit(0);
383
620
  }
384
-
385
- // File exists, not locked, and has content - read and delete
386
- try {
387
- fs.unlinkSync(deferredPath);
388
- process.stdout.write(content);
389
- process.exit(0);
390
- } catch (err) {
391
- process.stdout.write(`Error reading deferred file: ${err.message}\n`);
392
- process.exit(1);
621
+
622
+ if (ticket.followupText) {
623
+ if (!ticket.server || !ticket.sessionId) {
624
+ process.stdout.write('Error: deferred ticket does not include server/sessionId; cannot queue follow-up text. Re-submit only the ticket to check the result.\n');
625
+ process.exit(1);
626
+ }
627
+ try {
628
+ await sendToSessionAsync(ticket.server, ticket.sessionId, ticket.followupText);
629
+ appendQueuedFollowup(deferredPath, ticket.followupText);
630
+ } catch (err) {
631
+ process.stdout.write(`Error queueing follow-up prompt: ${err.message}\n`);
632
+ process.exit(1);
633
+ }
393
634
  }
394
- } else {
395
- // This is a regular prompt - create deferred execution
396
- const tempPath = generateTempFilename();
397
- const promptTempPath = tempPath + '.prompt';
398
-
399
- // Write prompt to temp file for child process
400
- fs.writeFileSync(promptTempPath, promptText);
401
-
402
- // Create and lock the output file
403
- fs.writeFileSync(tempPath, '');
404
- lockFile(tempPath);
405
-
406
- // Output the deferred reference
407
- process.stdout.write(`<agentp-deferred>${tempPath}</agentp-deferred>\n`);
408
-
409
- // Spawn detached child process
410
- const childArgs = [
411
- __filename,
412
- '--defer-child',
413
- '--prompt-file', promptTempPath,
414
- '--output-file', tempPath,
415
- ];
416
-
417
- // Pass through other flags
418
- if (qaMode) childArgs.push('--qa');
419
- if (tgMode === true) childArgs.push('--tg');
420
- if (tgMode === false) childArgs.push('--no-tg');
421
- if (flushMode) childArgs.push('--flush');
422
- if (sessionName) {
423
- childArgs.push('--session', sessionName);
424
- if (newSession) childArgs.push('--new');
635
+
636
+ if (result === null && ticket.defer > 0) {
637
+ result = await waitForDeferredResult(deferredPath, ticket.defer * 1000);
638
+ if (result !== null) {
639
+ try {
640
+ fs.unlinkSync(deferredPath);
641
+ } catch (err) {
642
+ process.stdout.write(`Error reading deferred file: ${err.message}\n`);
643
+ process.exit(1);
644
+ }
645
+ try { fs.unlinkSync(deferredPath + '.lock'); } catch {}
646
+ process.stdout.write(formatDeferredResult(deferredPath, result, qaMode, ''));
647
+ removeQueuedFollowups(deferredPath);
648
+ process.exit(0);
649
+ }
425
650
  }
426
- childArgs.push(serverBase);
427
-
428
- const child = child_process.spawn(process.execPath, childArgs, {
429
- detached: true,
430
- stdio: 'ignore',
431
- });
432
- child.unref();
433
-
651
+
652
+ // Answer not available yet — re-print the ticket with updated elapsed
653
+ const elapsed = ticket.ctime
654
+ ? Math.max(0, Math.floor((Date.now() - Date.parse(ticket.ctime)) / 1000))
655
+ : 0;
656
+ process.stdout.write(formatTicket(ticket, elapsed, onlineTicket) + '\n');
434
657
  process.exit(0);
435
658
  }
659
+
660
+ // This is a regular prompt — create deferred execution
661
+ const tempPath = generateTempFilename();
662
+ const promptTempPath = tempPath + '.prompt';
663
+
664
+ let targetSessionId;
665
+ try {
666
+ targetSessionId = await resolveTargetSession(serverBase, sessionName, newSession);
667
+ } catch (err) {
668
+ console.error(err.message);
669
+ process.exit(1);
670
+ }
671
+
672
+ // Write prompt to temp file for child process
673
+ fs.writeFileSync(promptTempPath, promptText);
674
+
675
+ // Create and lock the output file
676
+ fs.writeFileSync(tempPath, '');
677
+ lockFile(tempPath);
678
+
679
+ const newTicket = {
680
+ ctime: new Date().toISOString(),
681
+ path: tempPath,
682
+ defer: deferSeconds,
683
+ server: serverBase,
684
+ sessionId: targetSessionId,
685
+ };
686
+
687
+ // Spawn detached child process
688
+ const childArgs = [
689
+ __filename,
690
+ '--defer-child',
691
+ '--prompt-file', promptTempPath,
692
+ '--output-file', tempPath,
693
+ ];
694
+
695
+ // Pass through other flags
696
+ if (qaMode) childArgs.push('--qa');
697
+ if (tgMode === true) childArgs.push('--tg');
698
+ if (tgMode === false) childArgs.push('--no-tg');
699
+ if (flushMode) childArgs.push('--flush');
700
+ if (sessionName) {
701
+ childArgs.push('--session', sessionName);
702
+ if (newSession) childArgs.push('--new');
703
+ }
704
+ childArgs.push('--resolved-session-id', targetSessionId);
705
+ childArgs.push(serverBase);
706
+
707
+ const child = child_process.spawn(process.execPath, childArgs, {
708
+ detached: true,
709
+ stdio: 'ignore',
710
+ });
711
+ child.unref();
712
+
713
+ // If a timeout was given, block until the answer arrives or the timeout
714
+ // expires. Return the response when it arrives; otherwise print a ticket.
715
+ if (deferSeconds > 0) {
716
+ const result = await waitForDeferredResult(tempPath, deferSeconds * 1000);
717
+ if (result !== null) {
718
+ try {
719
+ fs.unlinkSync(tempPath);
720
+ } catch (err) {
721
+ process.stdout.write(`Error reading deferred file: ${err.message}\n`);
722
+ process.exit(1);
723
+ }
724
+ try { fs.unlinkSync(tempPath + '.lock'); } catch {}
725
+ process.stdout.write(result);
726
+ process.exit(0);
727
+ }
728
+ }
729
+
730
+ // Output the deferred ticket
731
+ process.stdout.write(formatTicket(newTicket, undefined, onlineTicket) + '\n');
732
+ process.exit(0);
436
733
  }
437
734
 
438
735
  // Handle --defer-child mode (child process)
@@ -450,67 +747,9 @@ async function main() {
450
747
  }
451
748
 
452
749
  // Wrap main processing in try-catch for deferChildMode to write errors to output file
750
+ let tgError = null;
453
751
  async function runMain() {
454
- // Find most recent session or create one
455
- let sessions;
456
- try {
457
- sessions = await listSessions(serverBase);
458
- } catch (err) {
459
- throw new Error(`Error connecting to server: ${err.message}`);
460
- }
461
-
462
- let sessionId = null;
463
-
464
- if (sessionName) {
465
- // Find session by name
466
- const matches = sessions.filter(s => {
467
- const title = s.title || s.slug || s.id;
468
- return title.toLowerCase() === sessionName.toLowerCase();
469
- });
470
- if (matches.length === 0) {
471
- // Try partial match
472
- const partial = sessions.filter(s => {
473
- const title = s.title || s.slug || s.id;
474
- return title.toLowerCase().includes(sessionName.toLowerCase());
475
- });
476
- if (partial.length === 1) {
477
- sessionId = partial[0].id;
478
- } else if (partial.length > 1) {
479
- throw new Error(`Error: multiple sessions match "${sessionName}"`);
480
- } else if (newSession) {
481
- // Create new session
482
- try {
483
- const session = await createSession(serverBase, sessionName);
484
- sessionId = session.id;
485
- await selectSession(serverBase, sessionId);
486
- } catch (err) {
487
- throw new Error(`Error creating session: ${err.message}`);
488
- }
489
- } else {
490
- throw new Error(`Error: no session found matching "${sessionName}"`);
491
- }
492
- } else {
493
- sessionId = matches[0].id;
494
- }
495
- } else if (sessions && sessions.length > 0) {
496
- const sorted = sessions
497
- .filter(s => s.time && (s.time.updated || s.time.created))
498
- .sort((a, b) => {
499
- const aTime = (a.time && a.time.updated) || (a.time && a.time.created) || 0;
500
- const bTime = (b.time && b.time.updated) || (b.time && b.time.created) || 0;
501
- return bTime - aTime;
502
- });
503
- if (sorted.length > 0) sessionId = sorted[0].id;
504
- }
505
-
506
- if (!sessionId) {
507
- try {
508
- const session = await createSession(serverBase, 'agentp');
509
- sessionId = session.id;
510
- } catch (err) {
511
- throw new Error(`Error creating session: ${err.message}`);
512
- }
513
- }
752
+ const sessionId = resolvedSessionId || await resolveTargetSession(serverBase, sessionName, newSession);
514
753
 
515
754
  let answer;
516
755
  try {
@@ -529,7 +768,6 @@ async function main() {
529
768
 
530
769
  // Post-send: notify tgagentp, get recorded context back
531
770
  let buffered = [];
532
- let tgError = null;
533
771
  if (tgPort) {
534
772
  try {
535
773
  buffered = await notifyAgentpGateway(tgPort, serverBase, tgMessage);
@@ -566,7 +804,7 @@ async function main() {
566
804
  fs.writeFileSync(outputFile, errMsg);
567
805
  unlockFile(outputFile);
568
806
  } else {
569
- process.stdout.write(errMsg);
807
+ console.error(err.message);
570
808
  }
571
809
  process.exit(1);
572
810
  }
@@ -605,5 +843,4 @@ if (require.main === module) {
605
843
  });
606
844
  }
607
845
 
608
- module.exports = { main, notifyAgentpGateway, readAllStdin };
609
-
846
+ module.exports = { main, notifyAgentpGateway, readAllStdin, parseDeferredTicket, parseDeferredTicketInput, formatTicket, waitForDeferredResult };