@evident-ai/runner-cdk 3.5.2-dev.da3340e → 3.5.2-dev.df1ae49

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.
@@ -49799,9 +49799,9 @@ var MICROVM_MAX_RUN_SECONDS = 28800;
49799
49799
  // src/microvm/controller/handle-doorbell.ts
49800
49800
  var import_node_crypto12 = require("node:crypto");
49801
49801
 
49802
- // ../webhook-signature/src/verify-wake-signature.ts
49802
+ // ../../sdk/src/signature.ts
49803
49803
  var import_node_crypto11 = require("node:crypto");
49804
- function verifyWakeSignature(rawBody, signatureHeader, secret) {
49804
+ function verifyEvidentSignature(rawBody, signatureHeader, secret) {
49805
49805
  if (!signatureHeader || !secret) {
49806
49806
  return false;
49807
49807
  }
@@ -50042,7 +50042,7 @@ async function handleDoorbell({
50042
50042
  sleep: sleep4,
50043
50043
  random
50044
50044
  }) {
50045
- if (!verifyWakeSignature(rawBody, signatureHeader, doorbellSecret)) {
50045
+ if (!verifyEvidentSignature(rawBody, signatureHeader, doorbellSecret)) {
50046
50046
  return decide({
50047
50047
  statusCode: 401,
50048
50048
  action: "rejected",
@@ -50470,7 +50470,7 @@ function createRuntimeMicrovm(executionRoleArn) {
50470
50470
  // runner is outbound-only (it dials the Cloudflare relay), so AWS sees
50471
50471
  // an idle endpoint no matter how busy the agent is. Idle is instead
50472
50472
  // decided IN the VM by `evident run --idle-timeout`
50473
- // (packages/runner-cdk/microvm-image/hooks/common.sh's `IDLE_TIMEOUT_SECONDS`), whose
50473
+ // (runner/docker-images/microvm/hooks/common.sh's `IDLE_TIMEOUT_SECONDS`), whose
50474
50474
  // clean exit is what drives the controller's own `suspend()` below via
50475
50475
  // the `runner.suspend_requested` doorbell. See `idle-policy.test.ts`
50476
50476
  // in this directory, which scans this file's source for the key and
@@ -445,6 +445,26 @@ stop_litestream() {
445
445
  log "litestream stopped ${outcome}"
446
446
  }
447
447
 
448
+ SESSION_DB_LITESTREAM_QUIESCED=0
449
+
450
+ quiesce_litestream_for_session_db() {
451
+ if [ "${SESSION_DB_LITESTREAM_QUIESCED}" -eq 1 ]; then
452
+ return 0
453
+ fi
454
+
455
+ if [ -z "${PERSISTENCE_BUCKET:-}" ] || [ -e "${SESSION_DB_NO_REPLICATE_MARKER}" ] || [ ! -s "${LITESTREAM_CONFIG_FILE}" ]; then
456
+ return 0
457
+ fi
458
+
459
+ if litestream_is_running; then
460
+ stop_litestream
461
+ else
462
+ warn "SESSION-DB-REPLICATOR-DIED: no live litestream at ${LITESTREAM_PID_FILE}; flushing one-shot anyway"
463
+ fi
464
+
465
+ SESSION_DB_LITESTREAM_QUIESCED=1
466
+ }
467
+
448
468
  # The cheap stop: one SIGTERM, one rm, one log line, NO poll loop — `/run`'s
449
469
  # own cleanup calls this, never stop_litestream, because that path only runs
450
470
  # after /run has already FAILED: no session happened yet, so a graceful final
@@ -519,11 +539,7 @@ flush_session_db() {
519
539
  return 0
520
540
  fi
521
541
 
522
- if litestream_is_running; then
523
- stop_litestream
524
- else
525
- warn "SESSION-DB-REPLICATOR-DIED: no live litestream at ${LITESTREAM_PID_FILE}; flushing one-shot anyway"
526
- fi
542
+ quiesce_litestream_for_session_db
527
543
 
528
544
  # Only reached with the daemon confirmed stopped, or never running — never
529
545
  # concurrently with it (see above).
@@ -539,6 +555,44 @@ flush_session_db() {
539
555
  }
540
556
  # --- flush_session_db (end) --------------------------------------------------
541
557
 
558
+ SESSION_DB_MAINTENANCE_DEADLINE_SECONDS=4
559
+ SESSION_DB_MAINTENANCE_KILL_GRACE_SECONDS=1
560
+ SESSION_DB_MAINTENANCE_MAX_BYTES=268435456
561
+
562
+ maintain_session_db() {
563
+ if [ -z "${PERSISTENCE_BUCKET:-}" ]; then
564
+ warn "SESSION-DB-MAINTENANCE-SKIP: persistence is disabled"
565
+ return 0
566
+ fi
567
+
568
+ if [ -e "${SESSION_DB_NO_REPLICATE_MARKER}" ]; then
569
+ warn "SESSION-DB-MAINTENANCE-SKIP: this boot's session DB was not proven safe to replicate"
570
+ return 0
571
+ fi
572
+
573
+ if [ ! -s "${LITESTREAM_CONFIG_FILE}" ]; then
574
+ error "SESSION-DB-MAINTENANCE-SKIP: no usable ${LITESTREAM_CONFIG_FILE}"
575
+ return 0
576
+ fi
577
+
578
+ if litestream_is_running; then
579
+ warn "SESSION-DB-MAINTENANCE-SKIP: litestream is still running"
580
+ return 0
581
+ fi
582
+
583
+ local maintenance_rc=0
584
+ timeout -k "${SESSION_DB_MAINTENANCE_KILL_GRACE_SECONDS}" "${SESSION_DB_MAINTENANCE_DEADLINE_SECONDS}" \
585
+ node "$(dirname "${BASH_SOURCE[0]}")/session-db-maintenance.mjs" \
586
+ "${HOME:-/home/runner}/.local/share/opencode/opencode.db" \
587
+ "${SESSION_DB_MAINTENANCE_MAX_BYTES}" || maintenance_rc=$?
588
+
589
+ if [ "${maintenance_rc}" -ne 0 ]; then
590
+ warn "SESSION-DB-MAINTENANCE: helper exited ${maintenance_rc}; continuing teardown"
591
+ fi
592
+
593
+ return 0
594
+ }
595
+
542
596
  # How long the guest CLI runs with no activity before it exits itself
543
597
  # (`evident run --idle-timeout`), which is what
544
598
  # turns a truly-abandoned VM into the clean-offline POST that lets Evident
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { statSync } from 'node:fs';
4
+ import { DatabaseSync } from 'node:sqlite';
5
+ import { performance } from 'node:perf_hooks';
6
+
7
+ const [dbPath, maxBytesText] = process.argv.slice(2);
8
+ const maxBytes = Number(maxBytesText);
9
+ const startedAt = performance.now();
10
+
11
+ function fileBytes(path) {
12
+ try {
13
+ return statSync(path).size;
14
+ } catch (error) {
15
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return 0;
16
+ throw error;
17
+ }
18
+ }
19
+
20
+ function walFrames(path, pageSize) {
21
+ const bytes = fileBytes(path);
22
+ return bytes < 32 ? 0 : Math.floor((bytes - 32) / (pageSize + 24));
23
+ }
24
+
25
+ function elapsedMs() {
26
+ return (performance.now() - startedAt).toFixed(3);
27
+ }
28
+
29
+ let database;
30
+
31
+ try {
32
+ if (!dbPath || !Number.isInteger(maxBytes) || maxBytes <= 0) {
33
+ throw new Error('expected a database path and a positive byte limit');
34
+ }
35
+
36
+ const dbBytes = statSync(dbPath).size;
37
+ database = new DatabaseSync(dbPath);
38
+ const pageSize = Number(database.prepare('PRAGMA page_size').get().page_size);
39
+ const walPath = `${dbPath}-wal`;
40
+ const walBytes = fileBytes(walPath);
41
+ const totalBytes = dbBytes + walBytes;
42
+ const walFramesBefore = walFrames(walPath, pageSize);
43
+
44
+ if (totalBytes > maxBytes) {
45
+ console.log(
46
+ `SESSION-DB-MAINTENANCE-SKIP: reason=size-gate db_bytes=${dbBytes} ` +
47
+ `wal_bytes=${walBytes} total_bytes=${totalBytes} max_bytes=${maxBytes} ` +
48
+ `wal_frames_before=${walFramesBefore} wal_frames_after=${walFramesBefore} ` +
49
+ `elapsed_ms=${elapsedMs()}`,
50
+ );
51
+ } else {
52
+ const checkpoint = database.prepare('PRAGMA wal_checkpoint(TRUNCATE)').get();
53
+ const walFramesAfter = walFrames(walPath, pageSize);
54
+ console.log(
55
+ `SESSION-DB-MAINTENANCE: operation=wal_checkpoint_truncate ` +
56
+ `db_bytes=${dbBytes} wal_bytes=${walBytes} wal_frames_before=${walFramesBefore} ` +
57
+ `wal_frames_after=${walFramesAfter} busy=${checkpoint.busy} log=${checkpoint.log} ` +
58
+ `checkpointed=${checkpoint.checkpointed} elapsed_ms=${elapsedMs()}`,
59
+ );
60
+ }
61
+ } catch (error) {
62
+ console.error(
63
+ `SESSION-DB-MAINTENANCE-ERROR: ${error instanceof Error ? error.message : String(error)} ` +
64
+ `elapsed_ms=${elapsedMs()}`,
65
+ );
66
+ process.exitCode = 1;
67
+ } finally {
68
+ if (database) database.close();
69
+ }
@@ -20,6 +20,8 @@ stop_runner_and_flush_credentials
20
20
  # stop_opencode — /terminate can afford the real drain, the VM is being torn
21
21
  # down either way.
22
22
  stop_opencode_and_wait
23
+ quiesce_litestream_for_session_db
24
+ maintain_session_db
23
25
  flush_session_db
24
26
 
25
27
  # The key must not outlive the VM's last useful moment. The no-replicate
@@ -27,9 +27,9 @@ module.exports = __toCommonJS(handler_exports);
27
27
  var import_client_ecs = require("@aws-sdk/client-ecs");
28
28
  var import_client_secrets_manager = require("@aws-sdk/client-secrets-manager");
29
29
 
30
- // ../webhook-signature/src/verify-wake-signature.ts
30
+ // ../../sdk/src/signature.ts
31
31
  var import_node_crypto = require("node:crypto");
32
- function verifyWakeSignature(rawBody, signatureHeader, secret) {
32
+ function verifyEvidentSignature(rawBody, signatureHeader, secret) {
33
33
  if (!signatureHeader || !secret) {
34
34
  return false;
35
35
  }
@@ -64,7 +64,7 @@ async function handleWake({
64
64
  service,
65
65
  ecs
66
66
  }) {
67
- if (!verifyWakeSignature(rawBody, signatureHeader, wakeSecret)) {
67
+ if (!verifyEvidentSignature(rawBody, signatureHeader, wakeSecret)) {
68
68
  const { statusCode: statusCode2, body: body2 } = jsonResponse(401, "invalid signature");
69
69
  return { statusCode: statusCode2, body: body2, calledUpdateService: false };
70
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evident-ai/runner-cdk",
3
- "version": "3.5.2-dev.da3340e",
3
+ "version": "3.5.2-dev.df1ae49",
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",