@evident-ai/runner-cdk 3.6.1-dev.c77170b → 3.6.1-dev.d5a3d9d

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.
@@ -49795,6 +49795,7 @@ var RUN_HOOK_PAYLOAD_MAX_BYTES = 16384;
49795
49795
  var SUSPENDING_POLL_ATTEMPTS = 4;
49796
49796
  var SUSPENDING_POLL_INTERVAL_MS = 500;
49797
49797
  var MICROVM_MAX_RUN_SECONDS = 28800;
49798
+ var MICROVM_EXPIRY_REFRESH_WINDOW_MS = 2 * 60 * 6e4;
49798
49799
 
49799
49800
  // src/microvm/controller/handle-doorbell.ts
49800
49801
  var import_node_crypto12 = require("node:crypto");
@@ -49924,6 +49925,7 @@ var REASON = {
49924
49925
  alreadyStarting: "already_starting",
49925
49926
  terminated: "terminated_or_terminating",
49926
49927
  imageVersionOutdated: "image_version_outdated",
49928
+ expiringSoon: "microvm_expiring_soon",
49927
49929
  resumeFailedFallingBackToRun: "resume_failed_falling_back_to_run",
49928
49930
  suspendingPollExhausted: "suspending_poll_exhausted",
49929
49931
  suspendWithoutMicrovmId: "suspend_without_microvm_id",
@@ -50001,6 +50003,15 @@ function compareImageVersions(a7, b7) {
50001
50003
  }
50002
50004
  return 0;
50003
50005
  }
50006
+ function isWithinExpiryRefreshWindow(doorbellRequest, startedAt, nowMs) {
50007
+ if (startedAt === void 0 || !Number.isFinite(startedAt.getTime())) {
50008
+ console.warn(
50009
+ `[doorbell] GetMicrovm reported no finite startedAt for runner ${doorbellRequest.runnerId}; resuming`
50010
+ );
50011
+ return false;
50012
+ }
50013
+ return startedAt.getTime() + MICROVM_MAX_RUN_SECONDS * 1e3 - nowMs < MICROVM_EXPIRY_REFRESH_WINDOW_MS;
50014
+ }
50004
50015
  async function shouldRecreateForNewerImage(doorbellRequest, shape, runningVersion, microvm) {
50005
50016
  if (runningVersion === void 0) {
50006
50017
  console.warn(
@@ -50039,6 +50050,7 @@ async function handleDoorbell({
50039
50050
  controllerSigningSecret,
50040
50051
  shapes,
50041
50052
  microvm,
50053
+ nowMs,
50042
50054
  sleep: sleep4,
50043
50055
  random
50044
50056
  }) {
@@ -50078,11 +50090,11 @@ async function handleDoorbell({
50078
50090
  microvmId: doorbellRequest.microvmId
50079
50091
  });
50080
50092
  }
50081
- return handleWakeDoorbell(doorbellRequest, resolvedShape, microvm, timing);
50093
+ return handleWakeDoorbell(doorbellRequest, resolvedShape, microvm, timing, nowMs);
50082
50094
  }
50083
50095
  return suspendMicrovm(doorbellRequest, microvm, timing);
50084
50096
  }
50085
- async function handleWakeDoorbell(doorbellRequest, shape, microvm, timing) {
50097
+ async function handleWakeDoorbell(doorbellRequest, shape, microvm, timing, nowMs) {
50086
50098
  if (doorbellRequest.microvmId === void 0) {
50087
50099
  return runMicrovm(doorbellRequest, shape, microvm, timing, false, REASON.noMicrovmId);
50088
50100
  }
@@ -50132,7 +50144,19 @@ async function handleWakeDoorbell(doorbellRequest, shape, microvm, timing) {
50132
50144
  startedAt: described.startedAt,
50133
50145
  imageVersion: described.imageVersion
50134
50146
  });
50135
- case "SUSPENDED":
50147
+ case "SUSPENDED": {
50148
+ const resumeStartedAt = described.startedAt === void 0 || Number.isFinite(described.startedAt.getTime()) ? described.startedAt : void 0;
50149
+ if (isWithinExpiryRefreshWindow(doorbellRequest, described.startedAt, nowMs)) {
50150
+ return runMicrovm(
50151
+ doorbellRequest,
50152
+ shape,
50153
+ microvm,
50154
+ timing,
50155
+ polled,
50156
+ REASON.expiringSoon,
50157
+ state3
50158
+ );
50159
+ }
50136
50160
  if (doorbellRequest.recreateOnOutdatedImage === true && await shouldRecreateForNewerImage(doorbellRequest, shape, described.imageVersion, microvm)) {
50137
50161
  return runMicrovm(
50138
50162
  doorbellRequest,
@@ -50151,9 +50175,10 @@ async function handleWakeDoorbell(doorbellRequest, shape, microvm, timing) {
50151
50175
  microvm,
50152
50176
  timing,
50153
50177
  polled,
50154
- described.startedAt,
50178
+ resumeStartedAt,
50155
50179
  described.imageVersion
50156
50180
  );
50181
+ }
50157
50182
  default:
50158
50183
  return runMicrovm(
50159
50184
  doorbellRequest,
@@ -50514,6 +50539,7 @@ var handler = async (event) => {
50514
50539
  controllerSigningSecret,
50515
50540
  shapes,
50516
50541
  microvm: createRuntimeMicrovm(executionRoleArn),
50542
+ nowMs: Date.now(),
50517
50543
  sleep: sleep3,
50518
50544
  random: Math.random
50519
50545
  });
@@ -5,5 +5,6 @@ export declare const RUN_HOOK_PAYLOAD_MAX_BYTES = 16384;
5
5
  export declare const SUSPENDING_POLL_ATTEMPTS = 4;
6
6
  export declare const SUSPENDING_POLL_INTERVAL_MS = 500;
7
7
  export declare const MICROVM_MAX_RUN_SECONDS = 28800;
8
+ export declare const MICROVM_EXPIRY_REFRESH_WINDOW_MS: number;
8
9
  export declare const SESSION_CLEANUP_MAX_AGE = "24h";
9
10
  export declare const CONTROLLER_SIGNING_SECRET_KEY = "CONTROLLER_SIGNING_SECRET";
@@ -4,7 +4,7 @@
4
4
  // across them. The image template pins matching copies, guarded by
5
5
  // `microvm/image/dockerfile.test.ts`.
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.CONTROLLER_SIGNING_SECRET_KEY = exports.SESSION_CLEANUP_MAX_AGE = exports.MICROVM_MAX_RUN_SECONDS = exports.SUSPENDING_POLL_INTERVAL_MS = exports.SUSPENDING_POLL_ATTEMPTS = exports.RUN_HOOK_PAYLOAD_MAX_BYTES = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_DIR = exports.HOOKS_PORT = void 0;
7
+ exports.CONTROLLER_SIGNING_SECRET_KEY = exports.SESSION_CLEANUP_MAX_AGE = exports.MICROVM_EXPIRY_REFRESH_WINDOW_MS = exports.MICROVM_MAX_RUN_SECONDS = exports.SUSPENDING_POLL_INTERVAL_MS = exports.SUSPENDING_POLL_ATTEMPTS = exports.RUN_HOOK_PAYLOAD_MAX_BYTES = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_DIR = exports.HOOKS_PORT = void 0;
8
8
  // The port the image's hook server binds, baked into the published Dockerfile
9
9
  // (`ENV HOOKS_PORT`) and passed to the construct as `hooksPort`. Exported from
10
10
  // the package index because a consumer building the published image context
@@ -32,6 +32,9 @@ exports.SUSPENDING_POLL_INTERVAL_MS = 500;
32
32
  // longer than any VM can possibly still be running, no VM started before the
33
33
  // deactivation can outlive it. The two MUST move together.
34
34
  exports.MICROVM_MAX_RUN_SECONDS = 28800;
35
+ // A SUSPENDED VM with less than this left against `MICROVM_MAX_RUN_SECONDS`
36
+ // is started fresh so AWS cannot terminate it during a task.
37
+ exports.MICROVM_EXPIRY_REFRESH_WINDOW_MS = 2 * 60 * 60_000;
35
38
  // Auto-cleanup (issue #190): `evident run` reads this directly from the Fargate
36
39
  // task and MicroVM image. Setting a retention rule makes it sweep OpenCode
37
40
  // sessions idle longer than 24h; protected/active sessions are never touched.
@@ -17,11 +17,12 @@ export type DoorbellResult = {
17
17
  kind: ActionKind;
18
18
  };
19
19
  };
20
- export declare function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, sleep, random, }: {
20
+ export declare function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, nowMs, sleep, random, }: {
21
21
  rawBody: string;
22
22
  signatureHeader: string | undefined;
23
23
  controllerSigningSecret: string;
24
24
  shapes: ShapeCatalogue;
25
25
  microvm: MicrovmClient;
26
+ nowMs: number;
26
27
  } & Timing): Promise<DoorbellResult>;
27
28
  export {};
@@ -14,6 +14,7 @@ const REASON = {
14
14
  alreadyStarting: 'already_starting',
15
15
  terminated: 'terminated_or_terminating',
16
16
  imageVersionOutdated: 'image_version_outdated',
17
+ expiringSoon: 'microvm_expiring_soon',
17
18
  resumeFailedFallingBackToRun: 'resume_failed_falling_back_to_run',
18
19
  suspendingPollExhausted: 'suspending_poll_exhausted',
19
20
  suspendWithoutMicrovmId: 'suspend_without_microvm_id',
@@ -128,6 +129,14 @@ function compareImageVersions(a, b) {
128
129
  }
129
130
  return 0;
130
131
  }
132
+ function isWithinExpiryRefreshWindow(doorbellRequest, startedAt, nowMs) {
133
+ if (startedAt === undefined || !Number.isFinite(startedAt.getTime())) {
134
+ console.warn(`[doorbell] GetMicrovm reported no finite startedAt for runner ${doorbellRequest.runnerId}; ` +
135
+ 'resuming');
136
+ return false;
137
+ }
138
+ return (startedAt.getTime() + constants_1.MICROVM_MAX_RUN_SECONDS * 1000 - nowMs < constants_1.MICROVM_EXPIRY_REFRESH_WINDOW_MS);
139
+ }
131
140
  /**
132
141
  * Whether an opted-in doorbell should throw away and recreate the SUSPENDED VM
133
142
  * because a newer image has been published since it booted.
@@ -174,7 +183,7 @@ async function shouldRecreateForNewerImage(doorbellRequest, shape, runningVersio
174
183
  // been deactivated, and recreating would not roll it forward. Resume.
175
184
  return compareImageVersions(running, latest) < 0;
176
185
  }
177
- async function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, sleep, random, }) {
186
+ async function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, nowMs, sleep, random, }) {
178
187
  if (!(0, sdk_1.verifyEvidentSignature)(rawBody, signatureHeader, controllerSigningSecret)) {
179
188
  return decide({
180
189
  statusCode: 401,
@@ -223,11 +232,11 @@ async function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecre
223
232
  microvmId: doorbellRequest.microvmId,
224
233
  });
225
234
  }
226
- return handleWakeDoorbell(doorbellRequest, resolvedShape, microvm, timing);
235
+ return handleWakeDoorbell(doorbellRequest, resolvedShape, microvm, timing, nowMs);
227
236
  }
228
237
  return suspendMicrovm(doorbellRequest, microvm, timing);
229
238
  }
230
- async function handleWakeDoorbell(doorbellRequest, shape, microvm, timing) {
239
+ async function handleWakeDoorbell(doorbellRequest, shape, microvm, timing, nowMs) {
231
240
  if (doorbellRequest.microvmId === undefined) {
232
241
  return runMicrovm(doorbellRequest, shape, microvm, timing, false, REASON.noMicrovmId);
233
242
  }
@@ -285,16 +294,23 @@ async function handleWakeDoorbell(doorbellRequest, shape, microvm, timing) {
285
294
  startedAt: described.startedAt,
286
295
  imageVersion: described.imageVersion,
287
296
  });
288
- case 'SUSPENDED':
297
+ case 'SUSPENDED': {
289
298
  // False/absent disables silent automatic roll-forward; an explicit restart
290
299
  // request (#1906) can still set the same wire flag. Recreate loses VM-local
291
300
  // filesystem state; durable provider credentials survive in object storage,
292
301
  // but a manual paste/upload not yet synced there may be lost (#2071).
302
+ const resumeStartedAt = described.startedAt === undefined || Number.isFinite(described.startedAt.getTime())
303
+ ? described.startedAt
304
+ : undefined;
305
+ if (isWithinExpiryRefreshWindow(doorbellRequest, described.startedAt, nowMs)) {
306
+ return runMicrovm(doorbellRequest, shape, microvm, timing, polled, REASON.expiringSoon, state);
307
+ }
293
308
  if (doorbellRequest.recreateOnOutdatedImage === true &&
294
309
  (await shouldRecreateForNewerImage(doorbellRequest, shape, described.imageVersion, microvm))) {
295
310
  return runMicrovm(doorbellRequest, shape, microvm, timing, polled, REASON.imageVersionOutdated, state);
296
311
  }
297
- return resumeMicrovm(doorbellRequest, doorbellRequest.microvmId, shape, microvm, timing, polled, described.startedAt, described.imageVersion);
312
+ return resumeMicrovm(doorbellRequest, doorbellRequest.microvmId, shape, microvm, timing, polled, resumeStartedAt, described.imageVersion);
313
+ }
298
314
  default:
299
315
  // TERMINATED, TERMINATING, not_found — gone or going, so a fresh VM either
300
316
  // way. This is what self-heals a stale `microvm_id`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evident-ai/runner-cdk",
3
- "version": "3.6.1-dev.c77170b",
3
+ "version": "3.6.1-dev.d5a3d9d",
4
4
  "description": "Reusable CDK constructs for an Evident agent runner: a single scale-to-zero Fargate runner (task + service + per-agent self-stop role + waker Lambda), or a per-session AWS Lambda MicroVM that boots on demand and suspends between messages. Instantiate once per agent from your own stack.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "//build-bundled-deps": "Builds the workspace dependencies whose dist/ output the direct runner-cdk build chain consumes: lambda-microvm-cdk for the public construct types, lambda-microvm-runtime for runner-microvm-image's hook-server bundle, and runner-microvm-image for that bundle itself. Chained into `build` rather than left to turbo's `^build` because callers outside turbo invoke `pnpm --filter @evident-ai/runner-cdk build` directly, which bypasses turbo entirely.",
19
19
  "build-bundled-deps": "pnpm --filter @evident-ai/lambda-microvm-cdk build && pnpm --filter @evident-ai/lambda-microvm-runtime build && pnpm --filter runner-microvm-image build",
20
20
  "typecheck": "tsc --noEmit",
21
- "test": "node --test --require ts-node/register 'src/**/*.test.ts'",
21
+ "test": "node --test --require tsx/cjs 'src/**/*.test.ts'",
22
22
  "format": "prettier --write 'src/**/*.ts'",
23
23
  "lint": "eslint 'src/**/*.ts' --max-warnings=0"
24
24
  },