@mastra/docker 0.9.0-alpha.2 → 0.9.1-alpha.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/dist/index.js CHANGED
@@ -141,6 +141,61 @@ case "$err" in
141
141
  esac
142
142
  `;
143
143
  /**
144
+ * Upper bound on how long an early stream 'end' waits for an in-flight kill
145
+ * confirmation before settling without termination metadata. The kill helper's
146
+ * own work is bounded (the KILL_SCRIPT polls for at most ~4s), so this only
147
+ * absorbs daemon round-trips; it exists so a wedged `inspect()` cannot leave
148
+ * wait() pending forever.
149
+ */
150
+ const TERMINATION_CONFIRMATION_DEADLINE_MS = 1e4;
151
+ /**
152
+ * Resolves when `termination` settles, or after
153
+ * TERMINATION_CONFIRMATION_DEADLINE_MS — whichever comes first. Never rejects.
154
+ */
155
+ function waitForTermination(termination) {
156
+ return new Promise((resolve) => {
157
+ const timer = setTimeout(resolve, TERMINATION_CONFIRMATION_DEADLINE_MS);
158
+ const done = () => {
159
+ clearTimeout(timer);
160
+ resolve();
161
+ };
162
+ termination.then(done, done);
163
+ });
164
+ }
165
+ /**
166
+ * Resolves with `work`'s value, or `fallback` if it has not settled within `ms`.
167
+ * Never rejects. Bounds a single daemon round-trip, so one call that never
168
+ * answers cannot leave wait() pending past its deadline.
169
+ */
170
+ function withDeadline(work, ms, fallback) {
171
+ return new Promise((resolve) => {
172
+ const timer = setTimeout(() => resolve(fallback), Math.max(0, ms));
173
+ const done = (value) => {
174
+ clearTimeout(timer);
175
+ resolve(value);
176
+ };
177
+ work.then(done, () => done(fallback));
178
+ });
179
+ }
180
+ /**
181
+ * Polls `exec.inspect()` until the exec reports it is no longer running, bounded
182
+ * by `deadline` (an absolute `Date.now()` timestamp). A stream can close without
183
+ * 'end' while the process is still alive, and settling from inspect then would
184
+ * publish an exit for a process that never exited — but waiting unbounded could
185
+ * leave wait() pending forever, so each call is bounded too. Returns the last
186
+ * inspect result, which may still report Running: true if the bound was reached,
187
+ * or undefined if no call settled in time.
188
+ */
189
+ async function waitForExecToStop(exec, deadline) {
190
+ let info;
191
+ while (Date.now() < deadline) {
192
+ info = await withDeadline(exec.inspect(), deadline - Date.now(), void 0);
193
+ if (!info || !info.Running) return info;
194
+ await new Promise((resolve) => setTimeout(resolve, 10));
195
+ }
196
+ return info;
197
+ }
198
+ /**
144
199
  * Wraps a Docker exec instance to conform to Mastra's ProcessHandle.
145
200
  * Not exported — internal to this module.
146
201
  *
@@ -157,6 +212,12 @@ var DockerProcessHandle = class extends ProcessHandle {
157
212
  _killed = false;
158
213
  /** @internal Set by the timeout path to distinguish timeout kills from explicit kills */
159
214
  _timedOut = false;
215
+ /**
216
+ * @internal In-flight kill confirmation, set while kill() is verifying the
217
+ * process group is gone. An early stream 'end' awaits it (bounded) so
218
+ * termination metadata is not dropped when the target's stream closes first.
219
+ */
220
+ _terminationPromise = null;
160
221
  _waitPromise = null;
161
222
  _stdinStream = null;
162
223
  _execStream = null;
@@ -199,6 +260,20 @@ var DockerProcessHandle = class extends ProcessHandle {
199
260
  }
200
261
  async kill() {
201
262
  if (this._exitCode !== void 0) return false;
263
+ if (this._terminationPromise) return this._terminationPromise;
264
+ const termination = this._runKillHelper();
265
+ this._terminationPromise = termination;
266
+ try {
267
+ return await termination;
268
+ } finally {
269
+ if (this._terminationPromise === termination) this._terminationPromise = null;
270
+ }
271
+ }
272
+ /**
273
+ * Runs the kill helper and verifies the process group is gone before reporting
274
+ * success, so a `true` result means the targets have actually stopped.
275
+ */
276
+ async _runKillHelper() {
202
277
  try {
203
278
  const killExec = await this._container.exec({
204
279
  Cmd: [
@@ -325,52 +400,68 @@ var DockerProcessManager = class extends SandboxProcessManager {
325
400
  }
326
401
  if (combined.length > 0 && buffer.length === 0) buffer.push(combined);
327
402
  });
328
- stream.on("end", async () => {
329
- try {
330
- const exitCode = (await exec.inspect()).ExitCode ?? 1;
331
- handle._setExitCode(exitCode);
332
- resolve({
333
- success: exitCode === 0,
334
- exitCode,
335
- stdout: handle.stdout,
336
- stderr: handle.stderr,
337
- executionTimeMs: Date.now() - startTime
338
- });
339
- } catch {
340
- handle._setExitCode(1);
341
- resolve({
342
- success: false,
343
- exitCode: 1,
344
- stdout: handle.stdout,
345
- stderr: handle.stderr,
346
- executionTimeMs: Date.now() - startTime
347
- });
348
- }
349
- });
350
- stream.on("close", () => {
351
- if (handle.exitCode !== void 0) return;
352
- if (!handle._killed) return;
353
- handle._setExitCode(137);
403
+ let settled = false;
404
+ const settle = (exitCode, metadata = {}) => {
405
+ if (settled) return;
406
+ settled = true;
407
+ handle._setExitCode(exitCode);
354
408
  resolve({
355
- success: false,
356
- exitCode: 137,
409
+ success: exitCode === 0,
410
+ exitCode,
357
411
  stdout: handle.stdout,
358
412
  stderr: handle.stderr,
359
413
  executionTimeMs: Date.now() - startTime,
414
+ ...metadata
415
+ });
416
+ };
417
+ const settleTerminated = () => {
418
+ settle(137, {
360
419
  killed: true,
361
420
  timedOut: handle._timedOut
362
421
  });
422
+ };
423
+ stream.on("end", async () => {
424
+ if (handle._terminationPromise) await waitForTermination(handle._terminationPromise);
425
+ if (settled) return;
426
+ if (handle._killed) {
427
+ settleTerminated();
428
+ return;
429
+ }
430
+ try {
431
+ const info = await exec.inspect();
432
+ settle(info.ExitCode ?? 1);
433
+ } catch {
434
+ settle(1);
435
+ }
363
436
  });
364
- stream.on("error", () => {
365
- if (handle.exitCode !== void 0) return;
366
- handle._setExitCode(1);
367
- resolve({
368
- success: false,
369
- exitCode: 1,
370
- stdout: handle.stdout,
371
- stderr: handle.stderr || "Stream error",
372
- executionTimeMs: Date.now() - startTime
373
- });
437
+ stream.on("close", async () => {
438
+ const deadline = Date.now() + TERMINATION_CONFIRMATION_DEADLINE_MS;
439
+ if (handle._terminationPromise) await waitForTermination(handle._terminationPromise);
440
+ if (settled) return;
441
+ if (handle._killed) {
442
+ settleTerminated();
443
+ return;
444
+ }
445
+ try {
446
+ const info = await waitForExecToStop(exec, deadline);
447
+ if (settled) return;
448
+ if (handle._killed) {
449
+ settleTerminated();
450
+ return;
451
+ }
452
+ settle(info?.ExitCode ?? 1);
453
+ } catch {
454
+ settle(1);
455
+ }
456
+ });
457
+ stream.on("error", async () => {
458
+ if (handle._terminationPromise) await waitForTermination(handle._terminationPromise);
459
+ if (settled) return;
460
+ if (handle._killed) {
461
+ settleTerminated();
462
+ return;
463
+ }
464
+ settle(1, { stderr: handle.stderr || "Stream error" });
374
465
  });
375
466
  });
376
467
  const resolvedTimeout = options.timeout ?? this._defaultTimeout;