@agentuity/core 1.0.54 → 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 +83 -30
- 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 +129 -34
- package/src/services/sandbox/types.ts +14 -0
|
@@ -224,20 +224,103 @@ export async function sandboxRun(
|
|
|
224
224
|
logger?.debug('streams completed, fetching final status');
|
|
225
225
|
|
|
226
226
|
// Stream EOF means the sandbox is done — hadron only closes streams after the
|
|
227
|
-
// container exits.
|
|
228
|
-
//
|
|
227
|
+
// container exits. Poll for the exit code with retries because the lifecycle
|
|
228
|
+
// event (carrying the exit code) may still be in flight to Catalyst when the
|
|
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
|
+
|
|
229
255
|
let exitCode = 0;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
256
|
+
const maxStatusRetries = 15;
|
|
257
|
+
const statusPollInterval = 1000;
|
|
258
|
+
const statusPollStart = Date.now();
|
|
259
|
+
for (let attempt = 0; attempt < maxStatusRetries; attempt++) {
|
|
260
|
+
if (signal?.aborted) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const sandboxStatus = await sandboxGetStatus(client, { sandboxId, orgId });
|
|
265
|
+
if (sandboxStatus.exitCode != null) {
|
|
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
|
+
);
|
|
274
|
+
break;
|
|
275
|
+
} else if (sandboxStatus.status === 'failed') {
|
|
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
|
+
);
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
// Exit code not yet propagated — wait before next poll.
|
|
297
|
+
if (attempt < maxStatusRetries - 1) {
|
|
298
|
+
await abortAwareSleep(statusPollInterval);
|
|
299
|
+
}
|
|
300
|
+
} catch (err) {
|
|
301
|
+
if (err instanceof DOMException && err.name === 'AbortError') {
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
// Transient failure (sandbox briefly unavailable, network error).
|
|
305
|
+
// Retry instead of giving up — the lifecycle event may still arrive.
|
|
306
|
+
logger?.debug(
|
|
307
|
+
'[run] sandboxGetStatus attempt %d/%d failed (+%dms): %s',
|
|
308
|
+
attempt + 1,
|
|
309
|
+
maxStatusRetries,
|
|
310
|
+
Date.now() - statusPollStart,
|
|
311
|
+
err
|
|
312
|
+
);
|
|
313
|
+
if (attempt < maxStatusRetries - 1) {
|
|
314
|
+
await abortAwareSleep(statusPollInterval);
|
|
315
|
+
}
|
|
236
316
|
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
+
);
|
|
241
324
|
}
|
|
242
325
|
|
|
243
326
|
if (timingLogsEnabled)
|
|
@@ -387,44 +470,56 @@ async function streamUrlToWritable(
|
|
|
387
470
|
writable: Writable,
|
|
388
471
|
signal: AbortSignal,
|
|
389
472
|
logger?: Logger,
|
|
390
|
-
|
|
473
|
+
_started?: number
|
|
391
474
|
): Promise<void> {
|
|
475
|
+
const streamStart = Date.now();
|
|
392
476
|
try {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
+
);
|
|
400
488
|
|
|
401
489
|
if (!response.ok || !response.body) {
|
|
402
|
-
logger?.debug('stream
|
|
490
|
+
logger?.debug('[stream] not ok or no body (status=%d) — returning empty', response.status);
|
|
403
491
|
return;
|
|
404
492
|
}
|
|
405
493
|
|
|
406
494
|
const reader = response.body.getReader();
|
|
407
|
-
let
|
|
495
|
+
let chunks = 0;
|
|
496
|
+
let totalBytes = 0;
|
|
408
497
|
|
|
409
498
|
// Read until EOF - Pulse will block until data is available
|
|
410
499
|
while (true) {
|
|
411
500
|
const { done, value } = await reader.read();
|
|
412
501
|
if (done) {
|
|
413
|
-
logger?.debug(
|
|
414
|
-
|
|
415
|
-
|
|
502
|
+
logger?.debug(
|
|
503
|
+
'[stream] EOF after %dms (%d chunks, %d bytes)',
|
|
504
|
+
Date.now() - streamStart,
|
|
505
|
+
chunks,
|
|
506
|
+
totalBytes
|
|
507
|
+
);
|
|
416
508
|
break;
|
|
417
509
|
}
|
|
418
510
|
|
|
419
511
|
if (value) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
)
|
|
425
|
-
|
|
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
|
+
);
|
|
426
522
|
}
|
|
427
|
-
logger?.debug('stream chunk: %d bytes', value.length);
|
|
428
523
|
await writeAndDrain(writable, value);
|
|
429
524
|
}
|
|
430
525
|
}
|
|
@@ -433,9 +528,9 @@ async function streamUrlToWritable(
|
|
|
433
528
|
writable.end();
|
|
434
529
|
} catch (err) {
|
|
435
530
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
436
|
-
logger?.debug('stream aborted');
|
|
531
|
+
logger?.debug('[stream] aborted after %dms', Date.now() - streamStart);
|
|
437
532
|
return;
|
|
438
533
|
}
|
|
439
|
-
logger?.debug('stream error: %s', err);
|
|
534
|
+
logger?.debug('[stream] error after %dms: %s', Date.now() - streamStart, err);
|
|
440
535
|
}
|
|
441
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
|
|