@hotcell/daemon 0.1.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.
Files changed (142) hide show
  1. package/dist/agent-check.d.ts +1 -0
  2. package/dist/agent-check.js +105 -0
  3. package/dist/agent-check.js.map +1 -0
  4. package/dist/api/server.d.ts +64 -0
  5. package/dist/api/server.js +1368 -0
  6. package/dist/api/server.js.map +1 -0
  7. package/dist/api/ws.d.ts +14 -0
  8. package/dist/api/ws.js +214 -0
  9. package/dist/api/ws.js.map +1 -0
  10. package/dist/applevz-check.d.ts +1 -0
  11. package/dist/applevz-check.js +280 -0
  12. package/dist/applevz-check.js.map +1 -0
  13. package/dist/backups.d.ts +29 -0
  14. package/dist/backups.js +76 -0
  15. package/dist/backups.js.map +1 -0
  16. package/dist/capacity.d.ts +63 -0
  17. package/dist/capacity.js +95 -0
  18. package/dist/capacity.js.map +1 -0
  19. package/dist/config.d.ts +241 -0
  20. package/dist/config.js +152 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/cost.d.ts +9 -0
  23. package/dist/cost.js +19 -0
  24. package/dist/cost.js.map +1 -0
  25. package/dist/density-bench.d.ts +1 -0
  26. package/dist/density-bench.js +97 -0
  27. package/dist/density-bench.js.map +1 -0
  28. package/dist/driver/agent-driver.d.ts +67 -0
  29. package/dist/driver/agent-driver.js +197 -0
  30. package/dist/driver/agent-driver.js.map +1 -0
  31. package/dist/driver/agent.d.ts +94 -0
  32. package/dist/driver/agent.js +319 -0
  33. package/dist/driver/agent.js.map +1 -0
  34. package/dist/driver/applevz.d.ts +95 -0
  35. package/dist/driver/applevz.js +527 -0
  36. package/dist/driver/applevz.js.map +1 -0
  37. package/dist/driver/container.d.ts +99 -0
  38. package/dist/driver/container.js +733 -0
  39. package/dist/driver/container.js.map +1 -0
  40. package/dist/driver/fc-api.d.ts +85 -0
  41. package/dist/driver/fc-api.js +173 -0
  42. package/dist/driver/fc-api.js.map +1 -0
  43. package/dist/driver/firecracker.d.ts +126 -0
  44. package/dist/driver/firecracker.js +560 -0
  45. package/dist/driver/firecracker.js.map +1 -0
  46. package/dist/driver/index.d.ts +17 -0
  47. package/dist/driver/index.js +61 -0
  48. package/dist/driver/index.js.map +1 -0
  49. package/dist/driver/router.d.ts +82 -0
  50. package/dist/driver/router.js +159 -0
  51. package/dist/driver/router.js.map +1 -0
  52. package/dist/driver/types.d.ts +234 -0
  53. package/dist/driver/types.js +2 -0
  54. package/dist/driver/types.js.map +1 -0
  55. package/dist/driver/unsupported.d.ts +50 -0
  56. package/dist/driver/unsupported.js +81 -0
  57. package/dist/driver/unsupported.js.map +1 -0
  58. package/dist/driver/vz-image.d.ts +35 -0
  59. package/dist/driver/vz-image.js +75 -0
  60. package/dist/driver/vz-image.js.map +1 -0
  61. package/dist/e2e-harness.d.ts +1 -0
  62. package/dist/e2e-harness.js +99 -0
  63. package/dist/e2e-harness.js.map +1 -0
  64. package/dist/egress-check.d.ts +8 -0
  65. package/dist/egress-check.js +317 -0
  66. package/dist/egress-check.js.map +1 -0
  67. package/dist/fc-check.d.ts +1 -0
  68. package/dist/fc-check.js +113 -0
  69. package/dist/fc-check.js.map +1 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +114 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/kernels.d.ts +22 -0
  74. package/dist/kernels.js +134 -0
  75. package/dist/kernels.js.map +1 -0
  76. package/dist/lifecycle.d.ts +49 -0
  77. package/dist/lifecycle.js +111 -0
  78. package/dist/lifecycle.js.map +1 -0
  79. package/dist/logger.d.ts +16 -0
  80. package/dist/logger.js +67 -0
  81. package/dist/logger.js.map +1 -0
  82. package/dist/metrics.d.ts +46 -0
  83. package/dist/metrics.js +92 -0
  84. package/dist/metrics.js.map +1 -0
  85. package/dist/net/firewall.d.ts +4 -0
  86. package/dist/net/firewall.js +80 -0
  87. package/dist/net/firewall.js.map +1 -0
  88. package/dist/pricing.d.ts +36 -0
  89. package/dist/pricing.js +81 -0
  90. package/dist/pricing.js.map +1 -0
  91. package/dist/proxy/allowlist-default.d.ts +23 -0
  92. package/dist/proxy/allowlist-default.js +53 -0
  93. package/dist/proxy/allowlist-default.js.map +1 -0
  94. package/dist/proxy/allowlist.d.ts +43 -0
  95. package/dist/proxy/allowlist.js +87 -0
  96. package/dist/proxy/allowlist.js.map +1 -0
  97. package/dist/proxy/egress.d.ts +49 -0
  98. package/dist/proxy/egress.js +599 -0
  99. package/dist/proxy/egress.js.map +1 -0
  100. package/dist/proxy/ratelimit.d.ts +49 -0
  101. package/dist/proxy/ratelimit.js +74 -0
  102. package/dist/proxy/ratelimit.js.map +1 -0
  103. package/dist/proxy/server.d.ts +26 -0
  104. package/dist/proxy/server.js +202 -0
  105. package/dist/proxy/server.js.map +1 -0
  106. package/dist/router-check.d.ts +1 -0
  107. package/dist/router-check.js +97 -0
  108. package/dist/router-check.js.map +1 -0
  109. package/dist/smoke-fc.d.ts +1 -0
  110. package/dist/smoke-fc.js +183 -0
  111. package/dist/smoke-fc.js.map +1 -0
  112. package/dist/smoke-microvm.d.ts +1 -0
  113. package/dist/smoke-microvm.js +187 -0
  114. package/dist/smoke-microvm.js.map +1 -0
  115. package/dist/smoke-remote.d.ts +20 -0
  116. package/dist/smoke-remote.js +155 -0
  117. package/dist/smoke-remote.js.map +1 -0
  118. package/dist/smoke-vz.d.ts +1 -0
  119. package/dist/smoke-vz.js +161 -0
  120. package/dist/smoke-vz.js.map +1 -0
  121. package/dist/smoke.d.ts +7 -0
  122. package/dist/smoke.js +962 -0
  123. package/dist/smoke.js.map +1 -0
  124. package/dist/store.d.ts +124 -0
  125. package/dist/store.js +578 -0
  126. package/dist/store.js.map +1 -0
  127. package/dist/tracing.d.ts +45 -0
  128. package/dist/tracing.js +144 -0
  129. package/dist/tracing.js.map +1 -0
  130. package/dist/types.d.ts +274 -0
  131. package/dist/types.js +3 -0
  132. package/dist/types.js.map +1 -0
  133. package/dist/util.d.ts +27 -0
  134. package/dist/util.js +63 -0
  135. package/dist/util.js.map +1 -0
  136. package/dist/warmpool-check.d.ts +1 -0
  137. package/dist/warmpool-check.js +83 -0
  138. package/dist/warmpool-check.js.map +1 -0
  139. package/dist/web/dashboard.d.ts +1 -0
  140. package/dist/web/dashboard.js +456 -0
  141. package/dist/web/dashboard.js.map +1 -0
  142. package/package.json +53 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Firecracker host-agnostic check. Exercises the pieces of the `firecracker`
3
+ * driver that need no `/dev/kvm` — the API-call builders, the vsock CONNECT
4
+ * handshake parser, and the driver's fail-fast `ping`/`hostInfo` — so the
5
+ * Linux-specific code is validated on any host (incl. this Mac / CI). The live
6
+ * boot is gated behind `smoke:fc` on a KVM box. Run: `npm run check:fc`.
7
+ */
8
+ import assert from "node:assert/strict";
9
+ import { buildFcApiCalls, buildSnapshotCreateCall, buildSnapshotLoadCall, FC_DEFAULT_BOOT_ARGS, FC_PAUSE_VM, FC_START_ACTION, parseVsockHandshake, } from "./driver/fc-api.js";
10
+ import { FirecrackerDriver } from "./driver/firecracker.js";
11
+ let passed = 0;
12
+ const ok = (l) => {
13
+ passed++;
14
+ console.log(` ✓ ${l}`);
15
+ };
16
+ async function main() {
17
+ // 1. API calls fully configure a VM in the right order with the right shapes.
18
+ const calls = buildFcApiCalls({
19
+ kernelPath: "/k/vmlinux",
20
+ rootfsPath: "/k/rootfs.img",
21
+ workspacePath: "/k/ws.img",
22
+ vcpus: 2,
23
+ memMib: 512,
24
+ vsockUds: "/k/v.sock",
25
+ });
26
+ assert.deepEqual(calls.map((c) => c.path), ["/boot-source", "/machine-config", "/drives/rootfs", "/drives/workspace", "/vsock"], "API calls cover boot/machine/drives/vsock in order");
27
+ const boot = calls[0].body;
28
+ assert.equal(boot.kernel_image_path, "/k/vmlinux");
29
+ assert.equal(boot.boot_args, FC_DEFAULT_BOOT_ARGS);
30
+ const mc = calls[1].body;
31
+ assert.equal(mc.vcpu_count, 2);
32
+ assert.equal(mc.mem_size_mib, 512);
33
+ // The root device is /dev/vda (ro), the workspace /dev/vdb (rw) — matches the
34
+ // shared guest init that mounts /dev/vdb at /workspace.
35
+ const rootDrive = calls[2].body;
36
+ assert.equal(rootDrive.is_root_device, true);
37
+ assert.equal(rootDrive.is_read_only, true);
38
+ const wsDrive = calls[3].body;
39
+ assert.equal(wsDrive.is_root_device, false);
40
+ assert.equal(wsDrive.is_read_only, false);
41
+ const vsock = calls[4].body;
42
+ assert.equal(vsock.uds_path, "/k/v.sock");
43
+ assert.equal(vsock.guest_cid, 3);
44
+ ok("buildFcApiCalls: correct order, shapes, and vda(ro)/vdb(rw) drive layout");
45
+ assert.equal(FC_START_ACTION.body.action_type, "InstanceStart");
46
+ ok("FC_START_ACTION boots the VM");
47
+ // 1b. Snapshot API calls: pause → create(Full) → load(File mem backend, resume).
48
+ assert.equal(FC_PAUSE_VM.method, "PATCH");
49
+ assert.equal(FC_PAUSE_VM.path, "/vm");
50
+ assert.equal(FC_PAUSE_VM.body.state, "Paused");
51
+ const snapCreate = buildSnapshotCreateCall("/s/x.vmstate", "/s/x.mem");
52
+ assert.equal(snapCreate.path, "/snapshot/create");
53
+ const scBody = snapCreate.body;
54
+ assert.equal(scBody.snapshot_type, "Full");
55
+ assert.equal(scBody.snapshot_path, "/s/x.vmstate");
56
+ assert.equal(scBody.mem_file_path, "/s/x.mem");
57
+ const snapLoad = buildSnapshotLoadCall("/s/x.vmstate", "/s/x.mem");
58
+ assert.equal(snapLoad.path, "/snapshot/load");
59
+ const slBody = snapLoad.body;
60
+ assert.equal(slBody.snapshot_path, "/s/x.vmstate");
61
+ assert.equal(slBody.mem_backend.backend_type, "File");
62
+ assert.equal(slBody.mem_backend.backend_path, "/s/x.mem");
63
+ assert.equal(slBody.resume_vm, true);
64
+ ok("snapshot API calls: PATCH /vm Paused → /snapshot/create(Full) → /snapshot/load(resume)");
65
+ // 2. Custom boot args (e.g. the pidsLimit cmdline) pass through.
66
+ const withPids = buildFcApiCalls({
67
+ kernelPath: "/k",
68
+ rootfsPath: "/r",
69
+ workspacePath: "/w",
70
+ vcpus: 1,
71
+ memMib: 256,
72
+ vsockUds: "/v",
73
+ bootArgs: `${FC_DEFAULT_BOOT_ARGS} hotcell.pids=64 sbx.pids=64`,
74
+ });
75
+ assert.match(withPids[0].body.boot_args, /hotcell\.pids=64/);
76
+ ok("custom boot args (pidsLimit cmdline) flow through");
77
+ // 3. vsock handshake parser: incomplete → null; OK line → split leftover; bad → not ok.
78
+ assert.equal(parseVsockHandshake(Buffer.from("OK 1024")), null, "no newline yet → null");
79
+ const okParse = parseVsockHandshake(Buffer.from("OK 1024\nHELLOBYTES"));
80
+ assert.ok(okParse && okParse.ok, "OK line parses as ok");
81
+ assert.equal(okParse.leftover.toString(), "HELLOBYTES", "post-handshake bytes preserved");
82
+ const badParse = parseVsockHandshake(Buffer.from("CONNECTION RESET\n"));
83
+ assert.ok(badParse && !badParse.ok, "non-OK line parses as not ok");
84
+ ok("vsock CONNECT handshake parser: incomplete / ok+leftover / failure");
85
+ // 4. Driver fail-fast on a non-KVM host (this Mac): ping names /dev/kvm.
86
+ const driver = new FirecrackerDriver({
87
+ fcBin: "firecracker",
88
+ kernel: "/nonexistent/vmlinux",
89
+ rootfs: "helpers/hotcell-vz/guest/rootfs.img",
90
+ stateDir: "/tmp/hotcell-fc-check",
91
+ diskGb: 8,
92
+ imageCacheDir: "/tmp/hotcell-fc-check/images",
93
+ });
94
+ assert.equal(driver.name, "firecracker");
95
+ if (process.platform !== "linux") {
96
+ await assert.rejects(() => driver.ping(), /\/dev\/kvm/, "ping fails fast with a /dev/kvm message off-KVM");
97
+ ok("ping fails fast naming /dev/kvm (host without KVM)");
98
+ }
99
+ else {
100
+ ok("ping skipped (on Linux — may have /dev/kvm; live boot is smoke:fc)");
101
+ }
102
+ // 5. hostInfo works anywhere (os fallback when /proc is absent).
103
+ const host = await driver.hostInfo();
104
+ assert.ok(host.memoryMb > 0, "hostInfo reports memory");
105
+ assert.ok(host.cpus > 0, "hostInfo reports cpus");
106
+ ok(`hostInfo: ${host.memoryMb}MB / ${host.cpus} cpus`);
107
+ console.log(`\nfc-check: ${passed} checks passed (host-agnostic; live boot gated on KVM)`);
108
+ }
109
+ main().then(() => process.exit(0), (err) => {
110
+ console.error("fc-check FAILED:", err);
111
+ process.exit(1);
112
+ });
113
+ //# sourceMappingURL=fc-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fc-check.js","sourceRoot":"","sources":["../src/fc-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ import { BackupRegistry } from "./backups.js";
3
+ import { Capacity } from "./capacity.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { DriverRouter } from "./driver/router.js";
6
+ import { startReaper } from "./lifecycle.js";
7
+ import { configureLogger, log } from "./logger.js";
8
+ import { MetricsHistory, startSampler } from "./metrics.js";
9
+ import { configureTracing, stopTracing } from "./tracing.js";
10
+ import { SandboxStore } from "./store.js";
11
+ import { createApiServer } from "./api/server.js";
12
+ import { createProxyServer } from "./proxy/server.js";
13
+ import { buildProviders, createEgressProxy } from "./proxy/egress.js";
14
+ import { loadModelPrices } from "./pricing.js";
15
+ import { ensureEgressFirewall } from "./net/firewall.js";
16
+ async function main() {
17
+ const config = loadConfig();
18
+ configureLogger({ level: config.logLevel, format: config.logFormat });
19
+ configureTracing({
20
+ serviceName: "sbd",
21
+ otlpEndpoint: config.otlpEndpoint,
22
+ ringSize: config.traceRing,
23
+ });
24
+ const store = new SandboxStore(config.dbPath);
25
+ // Per-sandbox driver router: dispatches each sandbox's ops to the runtime
26
+ // driver it was created with (container ↔ applevz), defaulting to SBX_DRIVER.
27
+ const driver = new DriverRouter(config, store, config.driver);
28
+ const backups = new BackupRegistry(config.backupDir);
29
+ const history = new MetricsHistory(config.metricsHistory);
30
+ // Fail fast with a friendly message if the selected driver isn't available
31
+ // (Docker not running, or a microVM driver requested without host support).
32
+ try {
33
+ await driver.ping();
34
+ }
35
+ catch (err) {
36
+ log.error(`driver "${driver.name}" is not available`, { error: String(err) });
37
+ if (driver.name === "container") {
38
+ log.error("start Docker (or colima / Apple 'container') and retry");
39
+ }
40
+ process.exit(1);
41
+ }
42
+ // Host capacity for the meter + admission control (best-effort detection).
43
+ const host = await driver.hostInfo().catch(() => null);
44
+ const capacity = new Capacity(store, config, host, history);
45
+ if (host) {
46
+ log.info("host capacity", {
47
+ memoryMb: host.memoryMb,
48
+ cpus: host.cpus,
49
+ admission: capacity.enforced ? "enforce" : "off",
50
+ });
51
+ }
52
+ const server = createApiServer({ config, driver, store, backups, history, capacity });
53
+ server.listen(config.port, config.host, () => {
54
+ log.info("daemon listening", {
55
+ url: `http://${config.host}:${config.port}`,
56
+ driver: driver.name,
57
+ image: config.defaultImage,
58
+ auth: config.apiKey ? "on" : "off",
59
+ otlp: config.otlpEndpoint || "off",
60
+ });
61
+ });
62
+ const proxy = createProxyServer({ config, driver, store });
63
+ proxy.listen(config.proxyPort, config.proxyHost, () => {
64
+ log.info("preview proxy listening", {
65
+ url: `http://${config.proxyHost}:${config.proxyPort}`,
66
+ previews: `http://<id>-<port>.localhost:${config.proxyPort}/`,
67
+ });
68
+ });
69
+ // Egress credential proxy (LLM gateway): injects provider keys into outbound
70
+ // calls and meters them. Runs whenever a port is configured; routes 404 until
71
+ // provider keys (SBX_PROVIDER_KEY_*) are set.
72
+ // Install the host default-deny egress firewall (Linux + SBX_EGRESS_ENFORCE only;
73
+ // advisory no-op otherwise). Best-effort: logs and continues if it can't.
74
+ await ensureEgressFirewall(config);
75
+ const providers = buildProviders(config);
76
+ const prices = loadModelPrices(config.modelPricesPath);
77
+ const egress = config.egressPort > 0 ? createEgressProxy({ config, store, providers, prices }) : undefined;
78
+ egress?.listen(config.egressPort, config.egressHost, () => {
79
+ log.info("egress proxy listening", {
80
+ url: `http://${config.egressHost}:${config.egressPort}`,
81
+ providers: Object.keys(providers).length ? Object.keys(providers).join(",") : "none configured",
82
+ });
83
+ });
84
+ // Idle reaper: auto-pause sandboxes left idle past their sleepAfterMs.
85
+ const reaper = config.reapIntervalMs > 0
86
+ ? startReaper({ driver, store, intervalMs: config.reapIntervalMs })
87
+ : undefined;
88
+ // Metrics sampler: integrate per-sandbox CPU/mem usage for the cost meter.
89
+ const sampler = config.metricsIntervalMs > 0
90
+ ? startSampler({ driver, store, history, intervalMs: config.metricsIntervalMs })
91
+ : undefined;
92
+ const shutdown = async () => {
93
+ log.info("shutting down");
94
+ if (reaper)
95
+ clearInterval(reaper);
96
+ if (sampler)
97
+ clearInterval(sampler);
98
+ await driver.shutdown().catch(() => { }); // tear down the VZ warm pool, if any
99
+ stopTracing();
100
+ egress?.close();
101
+ proxy.close();
102
+ server.close(() => {
103
+ store.close();
104
+ process.exit(0);
105
+ });
106
+ };
107
+ process.on("SIGINT", () => void shutdown());
108
+ process.on("SIGTERM", () => void shutdown());
109
+ }
110
+ main().catch((err) => {
111
+ log.error("fatal", { error: String(err?.stack ?? err) });
112
+ process.exit(1);
113
+ });
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IACtE,gBAAgB,CAAC;QACf,WAAW,EAAE,KAAK;QAClB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,QAAQ,EAAE,MAAM,CAAC,SAAS;KAC3B,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9C,0EAA0E;IAC1E,8EAA8E;IAC9E,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAE1D,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,IAAI,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9E,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,2EAA2E;IAC3E,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5D,IAAI,IAAI,EAAE,CAAC;QACT,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE;YACxB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK;SACjD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QAC3C,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC3B,GAAG,EAAE,UAAU,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE;YAC3C,MAAM,EAAE,MAAM,CAAC,IAAI;YACnB,KAAK,EAAE,MAAM,CAAC,YAAY;YAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK;YAClC,IAAI,EAAE,MAAM,CAAC,YAAY,IAAI,KAAK;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,GAAG,EAAE;QACpD,GAAG,CAAC,IAAI,CAAC,yBAAyB,EAAE;YAClC,GAAG,EAAE,UAAU,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,EAAE;YACrD,QAAQ,EAAE,gCAAgC,MAAM,CAAC,SAAS,GAAG;SAC9D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,8EAA8E;IAC9E,8CAA8C;IAC9C,kFAAkF;IAClF,0EAA0E;IAC1E,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAEnC,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACvD,MAAM,MAAM,GACV,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9F,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,EAAE;QACxD,GAAG,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACjC,GAAG,EAAE,UAAU,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,EAAE;YACvD,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB;SAChG,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IACvE,MAAM,MAAM,GACV,MAAM,CAAC,cAAc,GAAG,CAAC;QACvB,CAAC,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;QACnE,CAAC,CAAC,SAAS,CAAC;IAEhB,2EAA2E;IAC3E,MAAM,OAAO,GACX,MAAM,CAAC,iBAAiB,GAAG,CAAC;QAC1B,CAAC,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC;QAChF,CAAC,CAAC,SAAS,CAAC;IAEhB,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC1B,IAAI,MAAM;YAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,OAAO;YAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,qCAAqC;QAC9E,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;YAChB,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * In-sandbox code-interpreter kernels.
3
+ *
4
+ * Each code context runs one of these as a long-lived background process. A
5
+ * kernel keeps a persistent namespace (so variables/imports survive across
6
+ * `runCode` calls, Jupyter-style) and serves one cell at a time over a pair of
7
+ * named pipes in its context directory:
8
+ *
9
+ * - reads a sequence number from `in.fifo`
10
+ * - executes `cell-<seq>.code`
11
+ * - writes a JSON `cell-<seq>.result.json` ({ stdout, stderr, results, error })
12
+ * - signals completion by writing a line to `out.fifo`
13
+ *
14
+ * The daemon coordinates a single round-trip per call (see runCode in the API),
15
+ * so cells on one context are serialized.
16
+ */
17
+ export type KernelLanguage = "python" | "javascript";
18
+ export declare function kernelFor(language: KernelLanguage): {
19
+ filename: string;
20
+ source: string;
21
+ command: (dir: string) => string;
22
+ };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * In-sandbox code-interpreter kernels.
3
+ *
4
+ * Each code context runs one of these as a long-lived background process. A
5
+ * kernel keeps a persistent namespace (so variables/imports survive across
6
+ * `runCode` calls, Jupyter-style) and serves one cell at a time over a pair of
7
+ * named pipes in its context directory:
8
+ *
9
+ * - reads a sequence number from `in.fifo`
10
+ * - executes `cell-<seq>.code`
11
+ * - writes a JSON `cell-<seq>.result.json` ({ stdout, stderr, results, error })
12
+ * - signals completion by writing a line to `out.fifo`
13
+ *
14
+ * The daemon coordinates a single round-trip per call (see runCode in the API),
15
+ * so cells on one context are serialized.
16
+ */
17
+ export function kernelFor(language) {
18
+ if (language === "python") {
19
+ return {
20
+ filename: "kernel.py",
21
+ source: PYTHON_KERNEL,
22
+ command: (dir) => `python3 -u ${dir}/kernel.py`,
23
+ };
24
+ }
25
+ return {
26
+ filename: "kernel.js",
27
+ source: JS_KERNEL,
28
+ command: (dir) => `node ${dir}/kernel.js`,
29
+ };
30
+ }
31
+ const PYTHON_KERNEL = String.raw `
32
+ import sys, os, json, ast, io, traceback, contextlib
33
+
34
+ CTX_DIR = os.path.dirname(os.path.abspath(__file__))
35
+ IN = os.path.join(CTX_DIR, "in.fifo")
36
+ OUT = os.path.join(CTX_DIR, "out.fifo")
37
+
38
+ # Persistent namespace shared across cells.
39
+ ns = {"__name__": "__main__"}
40
+
41
+ def run_cell(code):
42
+ stdout, stderr = io.StringIO(), io.StringIO()
43
+ results = []
44
+ error = None
45
+ try:
46
+ tree = ast.parse(code)
47
+ last = tree.body[-1] if tree.body else None
48
+ with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
49
+ if isinstance(last, ast.Expr):
50
+ # Run everything but the trailing expression, then eval it so its
51
+ # value becomes a result (mirrors a notebook cell).
52
+ head = ast.Module(body=tree.body[:-1], type_ignores=[])
53
+ exec(compile(head, "<cell>", "exec"), ns)
54
+ value = eval(compile(ast.Expression(last.value), "<cell>", "eval"), ns)
55
+ if value is not None:
56
+ results.append({"type": "text", "text": repr(value)})
57
+ else:
58
+ exec(compile(tree, "<cell>", "exec"), ns)
59
+ except Exception:
60
+ error = traceback.format_exc()
61
+ return {"stdout": stdout.getvalue(), "stderr": stderr.getvalue(),
62
+ "results": results, "error": error}
63
+
64
+ while True:
65
+ with open(IN) as f:
66
+ seq = f.read().strip()
67
+ if not seq:
68
+ continue
69
+ try:
70
+ with open(os.path.join(CTX_DIR, "cell-%s.code" % seq)) as cf:
71
+ code = cf.read()
72
+ result = run_cell(code)
73
+ except Exception:
74
+ result = {"stdout": "", "stderr": "", "results": [],
75
+ "error": traceback.format_exc()}
76
+ with open(os.path.join(CTX_DIR, "cell-%s.result.json" % seq), "w") as rf:
77
+ json.dump(result, rf)
78
+ with open(OUT, "w") as of:
79
+ of.write("done\n")
80
+ `;
81
+ const JS_KERNEL = String.raw `
82
+ const fs = require("fs");
83
+ const path = require("path");
84
+ const vm = require("vm");
85
+ const util = require("util");
86
+
87
+ const CTX_DIR = __dirname;
88
+ const IN = path.join(CTX_DIR, "in.fifo");
89
+ const OUT = path.join(CTX_DIR, "out.fifo");
90
+
91
+ // Persistent context shared across cells (var/global assignments survive).
92
+ const sandbox = { require, process, Buffer, setTimeout, clearTimeout };
93
+ sandbox.global = sandbox;
94
+ const context = vm.createContext(sandbox);
95
+
96
+ function fmt(v) {
97
+ return typeof v === "string" ? v : util.inspect(v, { depth: 4 });
98
+ }
99
+
100
+ function runCell(code) {
101
+ let stdout = "", stderr = "";
102
+ const results = [];
103
+ let error = null;
104
+ sandbox.console = {
105
+ log: (...a) => { stdout += a.map(fmt).join(" ") + "\n"; },
106
+ info: (...a) => { stdout += a.map(fmt).join(" ") + "\n"; },
107
+ error: (...a) => { stderr += a.map(fmt).join(" ") + "\n"; },
108
+ warn: (...a) => { stderr += a.map(fmt).join(" ") + "\n"; },
109
+ };
110
+ try {
111
+ const value = vm.runInContext(code, context, { filename: "<cell>" });
112
+ if (value !== undefined) results.push({ type: "text", text: fmt(value) });
113
+ } catch (e) {
114
+ error = (e && e.stack) ? String(e.stack) : String(e);
115
+ }
116
+ return { stdout, stderr, results, error };
117
+ }
118
+
119
+ for (;;) {
120
+ const seq = fs.readFileSync(IN, "utf8").trim();
121
+ if (!seq) continue;
122
+ let result;
123
+ try {
124
+ const code = fs.readFileSync(path.join(CTX_DIR, "cell-" + seq + ".code"), "utf8");
125
+ result = runCell(code);
126
+ } catch (e) {
127
+ result = { stdout: "", stderr: "", results: [],
128
+ error: (e && e.stack) ? String(e.stack) : String(e) };
129
+ }
130
+ fs.writeFileSync(path.join(CTX_DIR, "cell-" + seq + ".result.json"), JSON.stringify(result));
131
+ fs.writeFileSync(OUT, "done\n");
132
+ }
133
+ `;
134
+ //# sourceMappingURL=kernels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernels.js","sourceRoot":"","sources":["../src/kernels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,MAAM,UAAU,SAAS,CAAC,QAAwB;IAKhD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO;YACL,QAAQ,EAAE,WAAW;YACrB,MAAM,EAAE,aAAa;YACrB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,GAAG,YAAY;SAChD,CAAC;IACJ,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,WAAW;QACrB,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,GAAG,YAAY;KAC1C,CAAC;AACJ,CAAC;AAED,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoD3B,CAAC"}
@@ -0,0 +1,49 @@
1
+ import type { Driver } from "./driver/types.js";
2
+ import type { SandboxStore } from "./store.js";
3
+ import type { SandboxRecord } from "./types.js";
4
+ /**
5
+ * Lifecycle FSM transitions and the idle reaper.
6
+ *
7
+ * created ─▶ running ──(idle > sleepAfterMs, or POST /pause)──▶ paused ──(any op)──▶ running
8
+ * │ │
9
+ * └────────────── stop (manual) ────────────────────┴──▶ stopped ──(start)──▶ running
10
+ *
11
+ * A `paused` sandbox has had its compute freed but keeps its workspace, so the
12
+ * next operation transparently resumes it. Pause comes in two strengths, picked
13
+ * automatically per sandbox: on microVM drivers that support it, pause is a
14
+ * **memory snapshot** (guest RAM + devices saved; background processes come back
15
+ * alive on resume — nothing is lost by pausing); otherwise it's the cold
16
+ * stop-with-volume (workspace survives, processes don't). A `stopped` sandbox is
17
+ * user-intent and is never auto-resumed nor auto-paused.
18
+ */
19
+ /**
20
+ * Pause a running sandbox, preferring a memory snapshot (fast resume, live
21
+ * state preserved) and falling back to a cold stop. Runtime state (tracked
22
+ * processes, exposed ports) is only dropped on a cold pause — after a snapshot
23
+ * pause it stays valid, because resume restores those processes live.
24
+ */
25
+ export declare function pauseSandbox(driver: Driver, store: SandboxStore, record: SandboxRecord): Promise<void>;
26
+ /**
27
+ * Resume a paused (or stopped) sandbox: recreate its container, reattaching the
28
+ * persistent volume, and mark it running + active.
29
+ */
30
+ export declare function resumeSandbox(driver: Driver, store: SandboxStore, record: SandboxRecord): Promise<void>;
31
+ /**
32
+ * Auto-pause sandboxes that have been idle past their `sleepAfterMs`. A sandbox
33
+ * advertising a service (an exposed port) or running a tracked background
34
+ * process is paused only when the driver can **memory-snapshot** it — the
35
+ * processes come back alive, and the preview proxy wakes a paused sandbox on
36
+ * inbound traffic, so nothing is lost by hibernating. On cold-pause drivers
37
+ * such sandboxes are skipped, since pausing would kill that work.
38
+ * Returns the ids that were paused.
39
+ */
40
+ export declare function reapIdle(driver: Driver, store: SandboxStore, now?: number): Promise<string[]>;
41
+ /**
42
+ * Start the periodic idle reaper. Returns the interval handle (unref'd so it
43
+ * won't keep the process alive); pass it to `clearInterval` on shutdown.
44
+ */
45
+ export declare function startReaper(opts: {
46
+ driver: Driver;
47
+ store: SandboxStore;
48
+ intervalMs: number;
49
+ }): NodeJS.Timeout;
@@ -0,0 +1,111 @@
1
+ import { log } from "./logger.js";
2
+ /**
3
+ * Lifecycle FSM transitions and the idle reaper.
4
+ *
5
+ * created ─▶ running ──(idle > sleepAfterMs, or POST /pause)──▶ paused ──(any op)──▶ running
6
+ * │ │
7
+ * └────────────── stop (manual) ────────────────────┴──▶ stopped ──(start)──▶ running
8
+ *
9
+ * A `paused` sandbox has had its compute freed but keeps its workspace, so the
10
+ * next operation transparently resumes it. Pause comes in two strengths, picked
11
+ * automatically per sandbox: on microVM drivers that support it, pause is a
12
+ * **memory snapshot** (guest RAM + devices saved; background processes come back
13
+ * alive on resume — nothing is lost by pausing); otherwise it's the cold
14
+ * stop-with-volume (workspace survives, processes don't). A `stopped` sandbox is
15
+ * user-intent and is never auto-resumed nor auto-paused.
16
+ */
17
+ /**
18
+ * Pause a running sandbox, preferring a memory snapshot (fast resume, live
19
+ * state preserved) and falling back to a cold stop. Runtime state (tracked
20
+ * processes, exposed ports) is only dropped on a cold pause — after a snapshot
21
+ * pause it stays valid, because resume restores those processes live.
22
+ */
23
+ export async function pauseSandbox(driver, store, record) {
24
+ let hibernated = false;
25
+ if (typeof driver.snapshot === "function" && (driver.canSnapshot?.(record.id) ?? true)) {
26
+ try {
27
+ await driver.snapshot(record.id);
28
+ hibernated = true;
29
+ }
30
+ catch (err) {
31
+ log.warn("snapshot pause failed; falling back to a cold stop", {
32
+ sandbox: record.id,
33
+ error: String(err),
34
+ });
35
+ }
36
+ }
37
+ if (!hibernated) {
38
+ await driver.stop(record.id);
39
+ store.clearRuntimeState(record.id); // processes/ports died with the compute
40
+ }
41
+ record.status = "paused";
42
+ store.add(record);
43
+ }
44
+ /**
45
+ * Resume a paused (or stopped) sandbox: recreate its container, reattaching the
46
+ * persistent volume, and mark it running + active.
47
+ */
48
+ export async function resumeSandbox(driver, store, record) {
49
+ await driver.start({
50
+ id: record.id,
51
+ image: record.image,
52
+ driver: record.driver,
53
+ env: record.env,
54
+ labels: record.labels,
55
+ persist: record.persist,
56
+ limits: record.limits,
57
+ });
58
+ record.status = "running";
59
+ record.lastActivityAt = new Date().toISOString();
60
+ store.add(record);
61
+ }
62
+ /**
63
+ * Auto-pause sandboxes that have been idle past their `sleepAfterMs`. A sandbox
64
+ * advertising a service (an exposed port) or running a tracked background
65
+ * process is paused only when the driver can **memory-snapshot** it — the
66
+ * processes come back alive, and the preview proxy wakes a paused sandbox on
67
+ * inbound traffic, so nothing is lost by hibernating. On cold-pause drivers
68
+ * such sandboxes are skipped, since pausing would kill that work.
69
+ * Returns the ids that were paused.
70
+ */
71
+ export async function reapIdle(driver, store, now = Date.now()) {
72
+ const paused = [];
73
+ for (const record of store.list()) {
74
+ if (record.status !== "running")
75
+ continue;
76
+ if (!record.sleepAfterMs || record.sleepAfterMs <= 0)
77
+ continue;
78
+ const hasService = store.listExposed(record.id).length > 0;
79
+ const hasLiveProcs = store.listProcesses(record.id).some((p) => p.status === "running");
80
+ if ((hasService || hasLiveProcs) && !driver.canSnapshot?.(record.id))
81
+ continue;
82
+ if (now - Date.parse(record.lastActivityAt) < record.sleepAfterMs)
83
+ continue;
84
+ try {
85
+ await pauseSandbox(driver, store, record);
86
+ paused.push(record.id);
87
+ }
88
+ catch (err) {
89
+ log.warn("reaper failed to pause sandbox", { sandbox: record.id, error: String(err) });
90
+ }
91
+ }
92
+ return paused;
93
+ }
94
+ /**
95
+ * Start the periodic idle reaper. Returns the interval handle (unref'd so it
96
+ * won't keep the process alive); pass it to `clearInterval` on shutdown.
97
+ */
98
+ export function startReaper(opts) {
99
+ const { driver, store, intervalMs } = opts;
100
+ const timer = setInterval(() => {
101
+ reapIdle(driver, store)
102
+ .then((ids) => {
103
+ if (ids.length)
104
+ log.info("auto-paused idle sandboxes", { ids });
105
+ })
106
+ .catch((err) => log.error("reaper error", { error: String(err) }));
107
+ }, intervalMs);
108
+ timer.unref?.();
109
+ return timer;
110
+ }
111
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { LogFormat, LogLevel } from "./config.js";
2
+ export type LogFields = Record<string, unknown>;
3
+ export interface Logger {
4
+ debug(msg: string, fields?: LogFields): void;
5
+ info(msg: string, fields?: LogFields): void;
6
+ warn(msg: string, fields?: LogFields): void;
7
+ error(msg: string, fields?: LogFields): void;
8
+ /** Derive a logger that stamps `fields` onto every line (e.g. a request id). */
9
+ child(fields: LogFields): Logger;
10
+ }
11
+ /** Process-wide logger. Replaced by `configureLogger`; defaults are quiet-ish. */
12
+ export declare let log: Logger;
13
+ export declare function configureLogger(opts: {
14
+ level: LogLevel;
15
+ format: LogFormat;
16
+ }): Logger;