@mastra/docker 0.9.0 → 0.9.1-alpha.1

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/index.cjs CHANGED
@@ -166,6 +166,61 @@ case "$err" in
166
166
  esac
167
167
  `;
168
168
  /**
169
+ * Upper bound on how long an early stream 'end' waits for an in-flight kill
170
+ * confirmation before settling without termination metadata. The kill helper's
171
+ * own work is bounded (the KILL_SCRIPT polls for at most ~4s), so this only
172
+ * absorbs daemon round-trips; it exists so a wedged `inspect()` cannot leave
173
+ * wait() pending forever.
174
+ */
175
+ const TERMINATION_CONFIRMATION_DEADLINE_MS = 1e4;
176
+ /**
177
+ * Resolves when `termination` settles, or after
178
+ * TERMINATION_CONFIRMATION_DEADLINE_MS — whichever comes first. Never rejects.
179
+ */
180
+ function waitForTermination(termination) {
181
+ return new Promise((resolve) => {
182
+ const timer = setTimeout(resolve, TERMINATION_CONFIRMATION_DEADLINE_MS);
183
+ const done = () => {
184
+ clearTimeout(timer);
185
+ resolve();
186
+ };
187
+ termination.then(done, done);
188
+ });
189
+ }
190
+ /**
191
+ * Resolves with `work`'s value, or `fallback` if it has not settled within `ms`.
192
+ * Never rejects. Bounds a single daemon round-trip, so one call that never
193
+ * answers cannot leave wait() pending past its deadline.
194
+ */
195
+ function withDeadline(work, ms, fallback) {
196
+ return new Promise((resolve) => {
197
+ const timer = setTimeout(() => resolve(fallback), Math.max(0, ms));
198
+ const done = (value) => {
199
+ clearTimeout(timer);
200
+ resolve(value);
201
+ };
202
+ work.then(done, () => done(fallback));
203
+ });
204
+ }
205
+ /**
206
+ * Polls `exec.inspect()` until the exec reports it is no longer running, bounded
207
+ * by `deadline` (an absolute `Date.now()` timestamp). A stream can close without
208
+ * 'end' while the process is still alive, and settling from inspect then would
209
+ * publish an exit for a process that never exited — but waiting unbounded could
210
+ * leave wait() pending forever, so each call is bounded too. Returns the last
211
+ * inspect result, which may still report Running: true if the bound was reached,
212
+ * or undefined if no call settled in time.
213
+ */
214
+ async function waitForExecToStop(exec, deadline) {
215
+ let info;
216
+ while (Date.now() < deadline) {
217
+ info = await withDeadline(exec.inspect(), deadline - Date.now(), void 0);
218
+ if (!info || !info.Running) return info;
219
+ await new Promise((resolve) => setTimeout(resolve, 10));
220
+ }
221
+ return info;
222
+ }
223
+ /**
169
224
  * Wraps a Docker exec instance to conform to Mastra's ProcessHandle.
170
225
  * Not exported — internal to this module.
171
226
  *
@@ -182,6 +237,12 @@ var DockerProcessHandle = class extends _mastra_core_workspace.ProcessHandle {
182
237
  _killed = false;
183
238
  /** @internal Set by the timeout path to distinguish timeout kills from explicit kills */
184
239
  _timedOut = false;
240
+ /**
241
+ * @internal In-flight kill confirmation, set while kill() is verifying the
242
+ * process group is gone. An early stream 'end' awaits it (bounded) so
243
+ * termination metadata is not dropped when the target's stream closes first.
244
+ */
245
+ _terminationPromise = null;
185
246
  _waitPromise = null;
186
247
  _stdinStream = null;
187
248
  _execStream = null;
@@ -224,6 +285,20 @@ var DockerProcessHandle = class extends _mastra_core_workspace.ProcessHandle {
224
285
  }
225
286
  async kill() {
226
287
  if (this._exitCode !== void 0) return false;
288
+ if (this._terminationPromise) return this._terminationPromise;
289
+ const termination = this._runKillHelper();
290
+ this._terminationPromise = termination;
291
+ try {
292
+ return await termination;
293
+ } finally {
294
+ if (this._terminationPromise === termination) this._terminationPromise = null;
295
+ }
296
+ }
297
+ /**
298
+ * Runs the kill helper and verifies the process group is gone before reporting
299
+ * success, so a `true` result means the targets have actually stopped.
300
+ */
301
+ async _runKillHelper() {
227
302
  try {
228
303
  const killExec = await this._container.exec({
229
304
  Cmd: [
@@ -350,52 +425,68 @@ var DockerProcessManager = class extends _mastra_core_workspace.SandboxProcessMa
350
425
  }
351
426
  if (combined.length > 0 && buffer.length === 0) buffer.push(combined);
352
427
  });
353
- stream.on("end", async () => {
354
- try {
355
- const exitCode = (await exec.inspect()).ExitCode ?? 1;
356
- handle._setExitCode(exitCode);
357
- resolve({
358
- success: exitCode === 0,
359
- exitCode,
360
- stdout: handle.stdout,
361
- stderr: handle.stderr,
362
- executionTimeMs: Date.now() - startTime
363
- });
364
- } catch {
365
- handle._setExitCode(1);
366
- resolve({
367
- success: false,
368
- exitCode: 1,
369
- stdout: handle.stdout,
370
- stderr: handle.stderr,
371
- executionTimeMs: Date.now() - startTime
372
- });
373
- }
374
- });
375
- stream.on("close", () => {
376
- if (handle.exitCode !== void 0) return;
377
- if (!handle._killed) return;
378
- handle._setExitCode(137);
428
+ let settled = false;
429
+ const settle = (exitCode, metadata = {}) => {
430
+ if (settled) return;
431
+ settled = true;
432
+ handle._setExitCode(exitCode);
379
433
  resolve({
380
- success: false,
381
- exitCode: 137,
434
+ success: exitCode === 0,
435
+ exitCode,
382
436
  stdout: handle.stdout,
383
437
  stderr: handle.stderr,
384
438
  executionTimeMs: Date.now() - startTime,
439
+ ...metadata
440
+ });
441
+ };
442
+ const settleTerminated = () => {
443
+ settle(137, {
385
444
  killed: true,
386
445
  timedOut: handle._timedOut
387
446
  });
447
+ };
448
+ stream.on("end", async () => {
449
+ if (handle._terminationPromise) await waitForTermination(handle._terminationPromise);
450
+ if (settled) return;
451
+ if (handle._killed) {
452
+ settleTerminated();
453
+ return;
454
+ }
455
+ try {
456
+ const info = await exec.inspect();
457
+ settle(info.ExitCode ?? 1);
458
+ } catch {
459
+ settle(1);
460
+ }
388
461
  });
389
- stream.on("error", () => {
390
- if (handle.exitCode !== void 0) return;
391
- handle._setExitCode(1);
392
- resolve({
393
- success: false,
394
- exitCode: 1,
395
- stdout: handle.stdout,
396
- stderr: handle.stderr || "Stream error",
397
- executionTimeMs: Date.now() - startTime
398
- });
462
+ stream.on("close", async () => {
463
+ const deadline = Date.now() + TERMINATION_CONFIRMATION_DEADLINE_MS;
464
+ if (handle._terminationPromise) await waitForTermination(handle._terminationPromise);
465
+ if (settled) return;
466
+ if (handle._killed) {
467
+ settleTerminated();
468
+ return;
469
+ }
470
+ try {
471
+ const info = await waitForExecToStop(exec, deadline);
472
+ if (settled) return;
473
+ if (handle._killed) {
474
+ settleTerminated();
475
+ return;
476
+ }
477
+ settle(info?.ExitCode ?? 1);
478
+ } catch {
479
+ settle(1);
480
+ }
481
+ });
482
+ stream.on("error", async () => {
483
+ if (handle._terminationPromise) await waitForTermination(handle._terminationPromise);
484
+ if (settled) return;
485
+ if (handle._killed) {
486
+ settleTerminated();
487
+ return;
488
+ }
489
+ settle(1, { stderr: handle.stderr || "Stream error" });
399
490
  });
400
491
  });
401
492
  const resolvedTimeout = options.timeout ?? this._defaultTimeout;
@@ -1840,7 +1931,8 @@ var DockerTemplate = class DockerTemplate {
1840
1931
  } else {
1841
1932
  const stream = await waitForAbortable(docker.buildImage(context, {
1842
1933
  t: tag,
1843
- nocache
1934
+ nocache,
1935
+ forcerm: true
1844
1936
  }).then((stream) => {
1845
1937
  if (abortSignal?.aborted) {
1846
1938
  stream.destroy?.();