@agentuity/core 1.0.55 → 1.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/oauth/flow.d.ts +31 -0
- package/dist/services/oauth/flow.d.ts.map +1 -1
- package/dist/services/oauth/flow.js +138 -13
- package/dist/services/oauth/flow.js.map +1 -1
- package/dist/services/oauth/index.d.ts +1 -0
- package/dist/services/oauth/index.d.ts.map +1 -1
- package/dist/services/oauth/index.js +1 -0
- package/dist/services/oauth/index.js.map +1 -1
- package/dist/services/oauth/token-storage.d.ts +109 -0
- package/dist/services/oauth/token-storage.d.ts.map +1 -0
- package/dist/services/oauth/token-storage.js +140 -0
- package/dist/services/oauth/token-storage.js.map +1 -0
- package/dist/services/oauth/types.d.ts +11 -0
- package/dist/services/oauth/types.d.ts.map +1 -1
- package/dist/services/oauth/types.js +19 -0
- package/dist/services/oauth/types.js.map +1 -1
- package/dist/services/sandbox/execute.d.ts.map +1 -1
- package/dist/services/sandbox/execute.js +22 -11
- package/dist/services/sandbox/execute.js.map +1 -1
- package/dist/services/sandbox/run.d.ts.map +1 -1
- package/dist/services/sandbox/run.js +64 -25
- package/dist/services/sandbox/run.js.map +1 -1
- package/dist/services/sandbox/types.d.ts +8 -0
- package/dist/services/sandbox/types.d.ts.map +1 -1
- package/dist/services/sandbox/types.js +14 -0
- package/dist/services/sandbox/types.js.map +1 -1
- package/package.json +2 -2
- package/src/services/oauth/flow.ts +156 -15
- package/src/services/oauth/index.ts +1 -0
- package/src/services/oauth/token-storage.ts +220 -0
- package/src/services/oauth/types.ts +26 -0
- package/src/services/sandbox/execute.ts +26 -12
- package/src/services/sandbox/run.ts +105 -29
- 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.
|
|
230
|
-
//
|
|
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 =
|
|
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
|
|
296
|
+
// Exit code not yet propagated — wait before next poll.
|
|
244
297
|
if (attempt < maxStatusRetries - 1) {
|
|
245
|
-
await
|
|
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
|
|
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
|
-
|
|
473
|
+
_started?: number
|
|
410
474
|
): Promise<void> {
|
|
475
|
+
const streamStart = Date.now();
|
|
411
476
|
try {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
|
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
|
|
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(
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
)
|
|
444
|
-
|
|
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
|
|