@agentuity/core 1.0.55 → 1.0.57

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 (34) hide show
  1. package/dist/services/oauth/flow.d.ts +31 -0
  2. package/dist/services/oauth/flow.d.ts.map +1 -1
  3. package/dist/services/oauth/flow.js +138 -13
  4. package/dist/services/oauth/flow.js.map +1 -1
  5. package/dist/services/oauth/index.d.ts +1 -0
  6. package/dist/services/oauth/index.d.ts.map +1 -1
  7. package/dist/services/oauth/index.js +1 -0
  8. package/dist/services/oauth/index.js.map +1 -1
  9. package/dist/services/oauth/token-storage.d.ts +109 -0
  10. package/dist/services/oauth/token-storage.d.ts.map +1 -0
  11. package/dist/services/oauth/token-storage.js +140 -0
  12. package/dist/services/oauth/token-storage.js.map +1 -0
  13. package/dist/services/oauth/types.d.ts +11 -0
  14. package/dist/services/oauth/types.d.ts.map +1 -1
  15. package/dist/services/oauth/types.js +19 -0
  16. package/dist/services/oauth/types.js.map +1 -1
  17. package/dist/services/sandbox/execute.d.ts.map +1 -1
  18. package/dist/services/sandbox/execute.js +22 -11
  19. package/dist/services/sandbox/execute.js.map +1 -1
  20. package/dist/services/sandbox/run.d.ts.map +1 -1
  21. package/dist/services/sandbox/run.js +64 -25
  22. package/dist/services/sandbox/run.js.map +1 -1
  23. package/dist/services/sandbox/types.d.ts +8 -0
  24. package/dist/services/sandbox/types.d.ts.map +1 -1
  25. package/dist/services/sandbox/types.js +14 -0
  26. package/dist/services/sandbox/types.js.map +1 -1
  27. package/package.json +2 -2
  28. package/src/services/oauth/flow.ts +156 -15
  29. package/src/services/oauth/index.ts +1 -0
  30. package/src/services/oauth/token-storage.ts +220 -0
  31. package/src/services/oauth/types.ts +26 -0
  32. package/src/services/sandbox/execute.ts +26 -12
  33. package/src/services/sandbox/run.ts +105 -29
  34. package/src/services/sandbox/types.ts +14 -0
@@ -226,38 +226,102 @@ export async function sandboxRun(
226
226
  // Stream EOF means the sandbox is done — hadron only closes streams after the
227
227
  // container exits. Poll for the exit code with retries because the lifecycle
228
228
  // event (carrying the exit code) may still be in flight to Catalyst when the
229
- // stream completes. Without retries, exitCode defaults to 0 and the CLI
230
- // incorrectly reports success for failed sandboxes.
229
+ // stream completes.
230
+ //
231
+ // Hadron drains container logs for up to 5s after exit, then closes the
232
+ // stream, then sends the lifecycle event in a goroutine. So the exit code
233
+ // typically arrives at Catalyst 5–7s after the container exits. We use a
234
+ // linear 1s polling interval (not exponential backoff) so we don't overshoot
235
+ // the window — 15 attempts × 1s = 15s total, which comfortably covers the
236
+ // drain + lifecycle propagation delay.
237
+ // Abort-aware sleep that rejects when the caller's signal fires.
238
+ const abortAwareSleep = (ms: number): Promise<void> =>
239
+ new Promise((resolve, reject) => {
240
+ if (signal?.aborted) {
241
+ reject(new DOMException('Aborted', 'AbortError'));
242
+ return;
243
+ }
244
+ const timer = setTimeout(resolve, ms);
245
+ signal?.addEventListener(
246
+ 'abort',
247
+ () => {
248
+ clearTimeout(timer);
249
+ reject(new DOMException('Aborted', 'AbortError'));
250
+ },
251
+ { once: true }
252
+ );
253
+ });
254
+
231
255
  let exitCode = 0;
232
- const maxStatusRetries = 10;
256
+ const maxStatusRetries = 15;
257
+ const statusPollInterval = 1000;
258
+ const statusPollStart = Date.now();
233
259
  for (let attempt = 0; attempt < maxStatusRetries; attempt++) {
260
+ if (signal?.aborted) {
261
+ break;
262
+ }
234
263
  try {
235
264
  const sandboxStatus = await sandboxGetStatus(client, { sandboxId, orgId });
236
265
  if (sandboxStatus.exitCode != null) {
237
266
  exitCode = sandboxStatus.exitCode;
267
+ logger?.debug(
268
+ '[run] exit code %d found on attempt %d/%d (+%dms)',
269
+ exitCode,
270
+ attempt + 1,
271
+ maxStatusRetries,
272
+ Date.now() - statusPollStart
273
+ );
238
274
  break;
239
275
  } else if (sandboxStatus.status === 'failed') {
240
276
  exitCode = 1;
277
+ logger?.debug(
278
+ '[run] sandbox failed on attempt %d/%d (+%dms)',
279
+ attempt + 1,
280
+ maxStatusRetries,
281
+ Date.now() - statusPollStart
282
+ );
283
+ break;
284
+ } else if (sandboxStatus.status === 'terminated') {
285
+ // Sandbox was destroyed. If exit code is missing, the
286
+ // terminated event may have overwritten it. Stop polling —
287
+ // no further updates will come.
288
+ logger?.debug(
289
+ '[run] sandbox terminated without exit code on attempt %d/%d (+%dms)',
290
+ attempt + 1,
291
+ maxStatusRetries,
292
+ Date.now() - statusPollStart
293
+ );
241
294
  break;
242
295
  }
243
- // Exit code not yet propagated — wait with exponential backoff.
296
+ // Exit code not yet propagated — wait before next poll.
244
297
  if (attempt < maxStatusRetries - 1) {
245
- await new Promise((r) => setTimeout(r, Math.min(100 * Math.pow(2, attempt), 2000)));
298
+ await abortAwareSleep(statusPollInterval);
246
299
  }
247
300
  } catch (err) {
301
+ if (err instanceof DOMException && err.name === 'AbortError') {
302
+ break;
303
+ }
248
304
  // Transient failure (sandbox briefly unavailable, network error).
249
305
  // Retry instead of giving up — the lifecycle event may still arrive.
250
306
  logger?.debug(
251
- 'sandboxGetStatus attempt %d/%d failed: %s',
307
+ '[run] sandboxGetStatus attempt %d/%d failed (+%dms): %s',
252
308
  attempt + 1,
253
309
  maxStatusRetries,
310
+ Date.now() - statusPollStart,
254
311
  err
255
312
  );
256
313
  if (attempt < maxStatusRetries - 1) {
257
- await new Promise((r) => setTimeout(r, Math.min(100 * Math.pow(2, attempt), 2000)));
314
+ await abortAwareSleep(statusPollInterval);
258
315
  }
259
316
  }
260
317
  }
318
+ if (exitCode === 0) {
319
+ logger?.debug(
320
+ '[run] exit code polling finished with default 0 after %d attempts (+%dms)',
321
+ maxStatusRetries,
322
+ Date.now() - statusPollStart
323
+ );
324
+ }
261
325
 
262
326
  if (timingLogsEnabled)
263
327
  console.error(
@@ -406,44 +470,56 @@ async function streamUrlToWritable(
406
470
  writable: Writable,
407
471
  signal: AbortSignal,
408
472
  logger?: Logger,
409
- started?: number
473
+ _started?: number
410
474
  ): Promise<void> {
475
+ const streamStart = Date.now();
411
476
  try {
412
- logger?.debug('fetching stream: %s', url);
413
- const response = await fetch(url, { signal });
414
- logger?.debug('stream response status: %d', response.status);
415
- if (timingLogsEnabled && started)
416
- console.error(
417
- `[TIMING] +${Date.now() - started}ms: stream response received (status: ${response.status})`
418
- );
477
+ // Signal to Pulse that this is a v2 stream so it waits for v2 metadata
478
+ // instead of falling back to the legacy download path on a short timeout.
479
+ const v2Url = new URL(url);
480
+ v2Url.searchParams.set('v', '2');
481
+ logger?.debug('[stream] fetching: %s', v2Url.href);
482
+ const response = await fetch(v2Url.href, { signal });
483
+ logger?.debug(
484
+ '[stream] response status=%d in %dms',
485
+ response.status,
486
+ Date.now() - streamStart
487
+ );
419
488
 
420
489
  if (!response.ok || !response.body) {
421
- logger?.debug('stream response not ok or no body');
490
+ logger?.debug('[stream] not ok or no body (status=%d) — returning empty', response.status);
422
491
  return;
423
492
  }
424
493
 
425
494
  const reader = response.body.getReader();
426
- let firstChunk = true;
495
+ let chunks = 0;
496
+ let totalBytes = 0;
427
497
 
428
498
  // Read until EOF - Pulse will block until data is available
429
499
  while (true) {
430
500
  const { done, value } = await reader.read();
431
501
  if (done) {
432
- logger?.debug('stream EOF');
433
- if (timingLogsEnabled && started)
434
- console.error(`[TIMING] +${Date.now() - started}ms: stream EOF`);
502
+ logger?.debug(
503
+ '[stream] EOF after %dms (%d chunks, %d bytes)',
504
+ Date.now() - streamStart,
505
+ chunks,
506
+ totalBytes
507
+ );
435
508
  break;
436
509
  }
437
510
 
438
511
  if (value) {
439
- if (firstChunk && started) {
440
- if (timingLogsEnabled)
441
- console.error(
442
- `[TIMING] +${Date.now() - started}ms: first chunk (${value.length} bytes)`
443
- );
444
- firstChunk = false;
512
+ chunks++;
513
+ totalBytes += value.length;
514
+ if (chunks <= 3 || chunks % 100 === 0) {
515
+ logger?.debug(
516
+ '[stream] chunk #%d: %d bytes (total: %d bytes, +%dms)',
517
+ chunks,
518
+ value.length,
519
+ totalBytes,
520
+ Date.now() - streamStart
521
+ );
445
522
  }
446
- logger?.debug('stream chunk: %d bytes', value.length);
447
523
  await writeAndDrain(writable, value);
448
524
  }
449
525
  }
@@ -452,9 +528,9 @@ async function streamUrlToWritable(
452
528
  writable.end();
453
529
  } catch (err) {
454
530
  if (err instanceof Error && err.name === 'AbortError') {
455
- logger?.debug('stream aborted');
531
+ logger?.debug('[stream] aborted after %dms', Date.now() - streamStart);
456
532
  return;
457
533
  }
458
- logger?.debug('stream error: %s', err);
534
+ logger?.debug('[stream] error after %dms: %s', Date.now() - streamStart, err);
459
535
  }
460
536
  }
@@ -398,6 +398,20 @@ export const SandboxSchema = z.object({
398
398
  .describe('Resume the sandbox from a paused or evacuated state.'),
399
399
  /** Destroy the sandbox */
400
400
  destroy: z.custom<() => Promise<void>>().describe('Destroy the sandbox'),
401
+ /** Create a new job in the sandbox */
402
+ createJob: z
403
+ .custom<(options: CreateJobOptions) => Promise<Job>>()
404
+ .describe('Create a new job in the sandbox'),
405
+ /** Get a job by ID */
406
+ getJob: z.custom<(jobId: string) => Promise<Job>>().describe('Get a job by ID'),
407
+ /** List jobs in the sandbox */
408
+ listJobs: z
409
+ .custom<(limit?: number) => Promise<{ jobs: Job[] }>>()
410
+ .describe('List jobs in the sandbox'),
411
+ /** Stop a running job */
412
+ stopJob: z
413
+ .custom<(jobId: string, force?: boolean) => Promise<Job>>()
414
+ .describe('Stop a running job'),
401
415
  });
402
416
  export type Sandbox = z.infer<typeof SandboxSchema>;
403
417