@paperclipai/plugin-daytona 2026.1005.0 → 2026.1006.0-beta.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.
@@ -2175,6 +2175,112 @@ describe("Daytona sandbox provider plugin", () => {
2175
2175
  // stderr from the callback log form instead of the 50-ms poll, and falls
2176
2176
  // back to the poll only when the stream fails.
2177
2177
  const streamExecParams = (overrides = {}) => sessionExecParams(overrides);
2178
+ // Long-lived healthy streams keep producing output. Quiet streams now
2179
+ // switch to snapshots, which have separate lifetime coverage below.
2180
+ async function streamFor(ms, onStderr) {
2181
+ const timer = setInterval(() => onStderr?.("."), 10_000);
2182
+ try {
2183
+ await new Promise((resolve) => setTimeout(resolve, ms));
2184
+ }
2185
+ finally {
2186
+ clearInterval(timer);
2187
+ }
2188
+ }
2189
+ it.each(["", "prefix;🙂;"])("recovers unseen output and cancellation from a stuck socket after prefix %j", async (prefix) => {
2190
+ process.env.DAYTONA_API_KEY = "host-key";
2191
+ const executionLog = vi.fn();
2192
+ const restore = __setDaytonaPluginContextForTest({ execution: { log: executionLog } });
2193
+ const sandbox = createMockSandbox();
2194
+ let lateOutput;
2195
+ let rejectStream;
2196
+ let snapshots = 0;
2197
+ let cancelled = false;
2198
+ sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout) => {
2199
+ if (onStdout) {
2200
+ lateOutput = onStdout;
2201
+ onStdout(prefix);
2202
+ return new Promise((_resolve, reject) => { rejectStream = reject; });
2203
+ }
2204
+ snapshots += 1;
2205
+ return { stdout: prefix + "permission;" + (cancelled ? "cancelled;" : ""), stderr: "" };
2206
+ });
2207
+ sandbox.process.getSessionCommand.mockImplementation(async () => ({ exitCode: cancelled ? 0 : undefined }));
2208
+ mockGet.mockResolvedValue(sandbox);
2209
+ vi.useFakeTimers();
2210
+ try {
2211
+ const promise = plugin.definition.onEnvironmentExecute?.(streamExecParams({ timeoutMs: 400 }));
2212
+ let settled = false;
2213
+ void promise?.then(() => { settled = true; });
2214
+ await vi.advanceTimersByTimeAsync(14_999);
2215
+ expect(snapshots).toBe(0);
2216
+ expect(settled).toBe(false);
2217
+ await vi.advanceTimersByTimeAsync(1);
2218
+ expect(snapshots).toBe(1);
2219
+ expect(executionLog).toHaveBeenCalledWith("stdout", "permission;");
2220
+ // The abandoned socket cannot corrupt snapshot cursors or publish
2221
+ // output after the timeout, including a delayed rejection.
2222
+ lateOutput("stale bytes");
2223
+ rejectStream(new Error("late socket failure"));
2224
+ await vi.advanceTimersByTimeAsync(2_000);
2225
+ expect(settled).toBe(false);
2226
+ expect(snapshots).toBe(3);
2227
+ cancelled = true;
2228
+ await vi.advanceTimersByTimeAsync(1_000);
2229
+ expect(await promise).toMatchObject({ exitCode: 0, timedOut: false, stdout: prefix + "permission;cancelled;" });
2230
+ lateOutput("after settlement");
2231
+ expect(executionLog.mock.calls).toEqual([
2232
+ ...(prefix ? [["stdout", prefix]] : []), ["stdout", "permission;"], ["stdout", "cancelled;"],
2233
+ ]);
2234
+ expect(sandbox.process.executeSessionCommand).toHaveBeenCalledTimes(1);
2235
+ expect(sandbox.process.getSessionCommandLogs.mock.calls.filter((call) => call[2])).toHaveLength(1);
2236
+ expect(vi.getTimerCount()).toBe(0);
2237
+ }
2238
+ finally {
2239
+ vi.useRealTimers();
2240
+ restore();
2241
+ }
2242
+ });
2243
+ it.each(["status", "snapshot"])("bounds a stuck %s read after an idle socket without claiming exit", async (stalledRead) => {
2244
+ process.env.DAYTONA_API_KEY = "host-key";
2245
+ const sandbox = createMockSandbox();
2246
+ let releaseRead;
2247
+ const stalled = new Promise((resolve) => { releaseRead = resolve; });
2248
+ const executionLog = vi.fn();
2249
+ const restore = __setDaytonaPluginContextForTest({ execution: { log: executionLog } });
2250
+ sandbox.process.getSessionCommand.mockImplementation(async () => {
2251
+ if (stalledRead === "status")
2252
+ await stalled;
2253
+ return { exitCode: undefined };
2254
+ });
2255
+ sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout) => {
2256
+ if (onStdout) {
2257
+ onStdout("prefix;");
2258
+ return new Promise(() => { });
2259
+ }
2260
+ await stalled;
2261
+ return { stdout: "prefix;late;", stderr: "" };
2262
+ });
2263
+ mockGet.mockResolvedValue(sandbox);
2264
+ vi.useFakeTimers();
2265
+ try {
2266
+ const promise = plugin.definition.onEnvironmentExecute?.(streamExecParams({ timeoutMs: 400 }));
2267
+ await vi.advanceTimersByTimeAsync(15_400);
2268
+ expect(await promise).toMatchObject({
2269
+ exitCode: null, timedOut: true, stdout: "prefix;",
2270
+ metadata: { timeoutScope: "session_log_observation", commandExitConfirmed: false },
2271
+ });
2272
+ releaseRead();
2273
+ await vi.advanceTimersByTimeAsync(1_000);
2274
+ expect(executionLog.mock.calls).toEqual([["stdout", "prefix;"]]);
2275
+ expect(sandbox.process.executeSessionCommand).toHaveBeenCalledTimes(1);
2276
+ expect(vi.getTimerCount()).toBe(0);
2277
+ }
2278
+ finally {
2279
+ releaseRead();
2280
+ vi.useRealTimers();
2281
+ restore();
2282
+ }
2283
+ });
2178
2284
  it("streams ordered stdout and stderr from the callback log form (test_log_stream_delivers_ordered_chunks)", async () => {
2179
2285
  process.env.DAYTONA_API_KEY = "host-key";
2180
2286
  const sandbox = createMockSandbox();
@@ -2287,12 +2393,12 @@ describe("Daytona sandbox provider plugin", () => {
2287
2393
  process.env.DAYTONA_API_KEY = "host-key";
2288
2394
  const sandbox = createMockSandbox();
2289
2395
  let streams = 0;
2290
- sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout) => {
2396
+ sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout, onStderr) => {
2291
2397
  if (!onStdout)
2292
2398
  return { stdout: "first;last;", stderr: "" };
2293
2399
  streams += 1;
2294
2400
  onStdout("first;");
2295
- await new Promise((resolve) => setTimeout(resolve, 3_600_000));
2401
+ await streamFor(3_600_000, onStderr);
2296
2402
  if (streams === 1 && ending === "reject")
2297
2403
  throw new Error("socket error");
2298
2404
  if (streams === 2)
@@ -2326,14 +2432,14 @@ describe("Daytona sandbox provider plugin", () => {
2326
2432
  let streams = 0;
2327
2433
  let snapshots = 0;
2328
2434
  let exited = false;
2329
- sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout) => {
2435
+ sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout, onStderr) => {
2330
2436
  if (!onStdout) {
2331
2437
  snapshots += 1;
2332
2438
  return { stdout: "partial;", stderr: "" };
2333
2439
  }
2334
2440
  onStdout("partial;");
2335
2441
  if (++streams === 1)
2336
- await new Promise((resolve) => setTimeout(resolve, 3_600_000));
2442
+ await streamFor(3_600_000, onStderr);
2337
2443
  });
2338
2444
  sandbox.process.getSessionCommand.mockImplementation(async () => ({ exitCode: exited ? 0 : undefined }));
2339
2445
  mockGet.mockResolvedValue(sandbox);
@@ -2359,11 +2465,11 @@ describe("Daytona sandbox provider plugin", () => {
2359
2465
  process.env.DAYTONA_API_KEY = "host-key";
2360
2466
  const sandbox = createMockSandbox();
2361
2467
  let streams = 0;
2362
- sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout) => {
2468
+ sandbox.process.getSessionCommandLogs.mockImplementation(async (_sid, _cmdId, onStdout, onStderr) => {
2363
2469
  if (onStdout) {
2364
2470
  onStdout("partial;");
2365
2471
  if (++streams === 1 && streamLifetime > 0)
2366
- await new Promise((resolve) => setTimeout(resolve, streamLifetime));
2472
+ await streamFor(streamLifetime, onStderr);
2367
2473
  throw new Error("socket error");
2368
2474
  }
2369
2475
  return { stdout: "partial;", stderr: "" };