harness-dispatch 0.4.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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +582 -0
  3. package/config.default.yaml +397 -0
  4. package/data/coding_benchmarks.json +431 -0
  5. package/dist/auth.d.ts +9 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +73 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/billing.d.ts +7 -0
  10. package/dist/billing.d.ts.map +1 -0
  11. package/dist/billing.js +201 -0
  12. package/dist/billing.js.map +1 -0
  13. package/dist/bin.d.ts +6 -0
  14. package/dist/bin.d.ts.map +1 -0
  15. package/dist/bin.js +488 -0
  16. package/dist/bin.js.map +1 -0
  17. package/dist/breaker-store.d.ts +31 -0
  18. package/dist/breaker-store.d.ts.map +1 -0
  19. package/dist/breaker-store.js +103 -0
  20. package/dist/breaker-store.js.map +1 -0
  21. package/dist/circuit-breaker.d.ts +47 -0
  22. package/dist/circuit-breaker.d.ts.map +1 -0
  23. package/dist/circuit-breaker.js +102 -0
  24. package/dist/circuit-breaker.js.map +1 -0
  25. package/dist/config.d.ts +48 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +1248 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/dashboard/live.d.ts +49 -0
  30. package/dist/dashboard/live.d.ts.map +1 -0
  31. package/dist/dashboard/live.js +149 -0
  32. package/dist/dashboard/live.js.map +1 -0
  33. package/dist/dispatch-log.d.ts +46 -0
  34. package/dist/dispatch-log.d.ts.map +1 -0
  35. package/dist/dispatch-log.js +89 -0
  36. package/dist/dispatch-log.js.map +1 -0
  37. package/dist/dispatchers/base.d.ts +55 -0
  38. package/dist/dispatchers/base.d.ts.map +1 -0
  39. package/dist/dispatchers/base.js +80 -0
  40. package/dist/dispatchers/base.js.map +1 -0
  41. package/dist/dispatchers/generic-cli.d.ts +30 -0
  42. package/dist/dispatchers/generic-cli.d.ts.map +1 -0
  43. package/dist/dispatchers/generic-cli.js +471 -0
  44. package/dist/dispatchers/generic-cli.js.map +1 -0
  45. package/dist/dispatchers/openai-compatible.d.ts +57 -0
  46. package/dist/dispatchers/openai-compatible.d.ts.map +1 -0
  47. package/dist/dispatchers/openai-compatible.js +662 -0
  48. package/dist/dispatchers/openai-compatible.js.map +1 -0
  49. package/dist/dispatchers/shared/kill-tree.d.ts +21 -0
  50. package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -0
  51. package/dist/dispatchers/shared/kill-tree.js +35 -0
  52. package/dist/dispatchers/shared/kill-tree.js.map +1 -0
  53. package/dist/dispatchers/shared/rate-limit-headers.d.ts +22 -0
  54. package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -0
  55. package/dist/dispatchers/shared/rate-limit-headers.js +124 -0
  56. package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -0
  57. package/dist/dispatchers/shared/stream-subprocess.d.ts +53 -0
  58. package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -0
  59. package/dist/dispatchers/shared/stream-subprocess.js +274 -0
  60. package/dist/dispatchers/shared/stream-subprocess.js.map +1 -0
  61. package/dist/dispatchers/shared/subprocess.d.ts +25 -0
  62. package/dist/dispatchers/shared/subprocess.d.ts.map +1 -0
  63. package/dist/dispatchers/shared/subprocess.js +110 -0
  64. package/dist/dispatchers/shared/subprocess.js.map +1 -0
  65. package/dist/dispatchers/shared/which-available.d.ts +2 -0
  66. package/dist/dispatchers/shared/which-available.d.ts.map +1 -0
  67. package/dist/dispatchers/shared/which-available.js +8 -0
  68. package/dist/dispatchers/shared/which-available.js.map +1 -0
  69. package/dist/dispatchers/shared/windows-cmd.d.ts +38 -0
  70. package/dist/dispatchers/shared/windows-cmd.d.ts.map +1 -0
  71. package/dist/dispatchers/shared/windows-cmd.js +91 -0
  72. package/dist/dispatchers/shared/windows-cmd.js.map +1 -0
  73. package/dist/http/server.d.ts +15 -0
  74. package/dist/http/server.d.ts.map +1 -0
  75. package/dist/http/server.js +495 -0
  76. package/dist/http/server.js.map +1 -0
  77. package/dist/index.d.ts +27 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +29 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/job-runner.d.ts +19 -0
  82. package/dist/job-runner.d.ts.map +1 -0
  83. package/dist/job-runner.js +42 -0
  84. package/dist/job-runner.js.map +1 -0
  85. package/dist/jobs.d.ts +116 -0
  86. package/dist/jobs.d.ts.map +1 -0
  87. package/dist/jobs.js +542 -0
  88. package/dist/jobs.js.map +1 -0
  89. package/dist/leaderboard.d.ts +62 -0
  90. package/dist/leaderboard.d.ts.map +1 -0
  91. package/dist/leaderboard.js +287 -0
  92. package/dist/leaderboard.js.map +1 -0
  93. package/dist/mcp/config-hot-reload.d.ts +64 -0
  94. package/dist/mcp/config-hot-reload.d.ts.map +1 -0
  95. package/dist/mcp/config-hot-reload.js +144 -0
  96. package/dist/mcp/config-hot-reload.js.map +1 -0
  97. package/dist/mcp/dispatcher-factory.d.ts +20 -0
  98. package/dist/mcp/dispatcher-factory.d.ts.map +1 -0
  99. package/dist/mcp/dispatcher-factory.js +30 -0
  100. package/dist/mcp/dispatcher-factory.js.map +1 -0
  101. package/dist/mcp/resources.d.ts +8 -0
  102. package/dist/mcp/resources.d.ts.map +1 -0
  103. package/dist/mcp/resources.js +45 -0
  104. package/dist/mcp/resources.js.map +1 -0
  105. package/dist/mcp/server.d.ts +36 -0
  106. package/dist/mcp/server.d.ts.map +1 -0
  107. package/dist/mcp/server.js +80 -0
  108. package/dist/mcp/server.js.map +1 -0
  109. package/dist/mcp/tools.d.ts +171 -0
  110. package/dist/mcp/tools.d.ts.map +1 -0
  111. package/dist/mcp/tools.js +658 -0
  112. package/dist/mcp/tools.js.map +1 -0
  113. package/dist/observability/index.d.ts +52 -0
  114. package/dist/observability/index.d.ts.map +1 -0
  115. package/dist/observability/index.js +111 -0
  116. package/dist/observability/index.js.map +1 -0
  117. package/dist/observability/spans.d.ts +47 -0
  118. package/dist/observability/spans.d.ts.map +1 -0
  119. package/dist/observability/spans.js +72 -0
  120. package/dist/observability/spans.js.map +1 -0
  121. package/dist/quota.d.ts +91 -0
  122. package/dist/quota.d.ts.map +1 -0
  123. package/dist/quota.js +331 -0
  124. package/dist/quota.js.map +1 -0
  125. package/dist/route-policy.d.ts +24 -0
  126. package/dist/route-policy.d.ts.map +1 -0
  127. package/dist/route-policy.js +92 -0
  128. package/dist/route-policy.js.map +1 -0
  129. package/dist/router.d.ts +162 -0
  130. package/dist/router.d.ts.map +1 -0
  131. package/dist/router.js +878 -0
  132. package/dist/router.js.map +1 -0
  133. package/dist/safety.d.ts +7 -0
  134. package/dist/safety.d.ts.map +1 -0
  135. package/dist/safety.js +33 -0
  136. package/dist/safety.js.map +1 -0
  137. package/dist/status.d.ts +100 -0
  138. package/dist/status.d.ts.map +1 -0
  139. package/dist/status.js +228 -0
  140. package/dist/status.js.map +1 -0
  141. package/dist/types.d.ts +402 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +9 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/version.d.ts +2 -0
  146. package/dist/version.d.ts.map +1 -0
  147. package/dist/version.js +13 -0
  148. package/dist/version.js.map +1 -0
  149. package/dist/working-dir.d.ts +15 -0
  150. package/dist/working-dir.d.ts.map +1 -0
  151. package/dist/working-dir.js +22 -0
  152. package/dist/working-dir.js.map +1 -0
  153. package/dist/workspaces.d.ts +19 -0
  154. package/dist/workspaces.d.ts.map +1 -0
  155. package/dist/workspaces.js +352 -0
  156. package/dist/workspaces.js.map +1 -0
  157. package/package.json +96 -0
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Async subprocess runner.
3
+ *
4
+ * Wraps `child_process.spawn` with:
5
+ * - hard timeout (SIGTERM, then SIGKILL after grace period)
6
+ * - output byte cap (protects the agent from pathological CLI output)
7
+ * - DEVNULL stdin by default (prevents interactive auth prompts from hanging)
8
+ * - UTF-8 decoding with replacement on invalid sequences
9
+ */
10
+ import spawn from "cross-spawn";
11
+ import { killTree } from "./kill-tree.js";
12
+ const DEFAULT_TIMEOUT_MS = 300_000;
13
+ const DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
14
+ const KILL_GRACE_MS = 2_000;
15
+ export function runSubprocess(command, args, opts = {}) {
16
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
17
+ const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
18
+ const start = Date.now();
19
+ return new Promise((resolve, reject) => {
20
+ const spawnOpts = {
21
+ stdio: [opts.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
22
+ windowsHide: true,
23
+ env: opts.env ? { ...process.env, ...opts.env } : process.env,
24
+ };
25
+ if (opts.cwd !== undefined)
26
+ spawnOpts.cwd = opts.cwd;
27
+ let child;
28
+ try {
29
+ child = spawn(command, args, spawnOpts);
30
+ }
31
+ catch (err) {
32
+ reject(err instanceof Error ? err : new Error(String(err)));
33
+ return;
34
+ }
35
+ if (opts.stdin !== undefined) {
36
+ child.stdin?.end(opts.stdin);
37
+ }
38
+ const stdoutChunks = [];
39
+ const stderrChunks = [];
40
+ let stdoutBytes = 0;
41
+ let stderrBytes = 0;
42
+ let truncated = false;
43
+ let timedOut = false;
44
+ let settled = false;
45
+ const appendStdout = (chunk) => {
46
+ if (truncated)
47
+ return;
48
+ if (stdoutBytes + chunk.length > maxOutputBytes) {
49
+ const remaining = Math.max(0, maxOutputBytes - stdoutBytes);
50
+ if (remaining > 0)
51
+ stdoutChunks.push(chunk.subarray(0, remaining));
52
+ stdoutBytes = maxOutputBytes;
53
+ truncated = true;
54
+ killTree(child, "SIGTERM");
55
+ return;
56
+ }
57
+ stdoutChunks.push(chunk);
58
+ stdoutBytes += chunk.length;
59
+ };
60
+ const appendStderr = (chunk) => {
61
+ if (truncated)
62
+ return;
63
+ if (stderrBytes + chunk.length > maxOutputBytes) {
64
+ const remaining = Math.max(0, maxOutputBytes - stderrBytes);
65
+ if (remaining > 0)
66
+ stderrChunks.push(chunk.subarray(0, remaining));
67
+ stderrBytes = maxOutputBytes;
68
+ truncated = true;
69
+ killTree(child, "SIGTERM");
70
+ return;
71
+ }
72
+ stderrChunks.push(chunk);
73
+ stderrBytes += chunk.length;
74
+ };
75
+ child.stdout?.on("data", (chunk) => appendStdout(chunk));
76
+ child.stderr?.on("data", (chunk) => appendStderr(chunk));
77
+ const timer = setTimeout(() => {
78
+ timedOut = true;
79
+ killTree(child, "SIGTERM");
80
+ // Force-kill if the child ignores SIGTERM.
81
+ setTimeout(() => {
82
+ if (!settled)
83
+ killTree(child, "SIGKILL");
84
+ }, KILL_GRACE_MS).unref();
85
+ }, timeoutMs);
86
+ timer.unref();
87
+ child.on("error", (err) => {
88
+ if (settled)
89
+ return;
90
+ settled = true;
91
+ clearTimeout(timer);
92
+ reject(err);
93
+ });
94
+ child.on("close", (code, signal) => {
95
+ if (settled)
96
+ return;
97
+ settled = true;
98
+ clearTimeout(timer);
99
+ const exitCode = code ?? (signal ? 128 : -1);
100
+ resolve({
101
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
102
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
103
+ exitCode,
104
+ durationMs: Date.now() - start,
105
+ timedOut,
106
+ });
107
+ });
108
+ });
109
+ }
110
+ //# sourceMappingURL=subprocess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subprocess.js","sourceRoot":"","sources":["../../../src/dispatchers/shared/subprocess.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function commandAvailable(command: string): boolean;
2
+ //# sourceMappingURL=which-available.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"which-available.d.ts","sourceRoot":"","sources":["../../../src/dispatchers/shared/which-available.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMzD"}
@@ -0,0 +1,8 @@
1
+ import which from "which";
2
+ export function commandAvailable(command) {
3
+ const candidate = which;
4
+ if (typeof candidate.sync !== "function")
5
+ return true;
6
+ return Boolean(candidate.sync(command, { nothrow: true }));
7
+ }
8
+ //# sourceMappingURL=which-available.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"which-available.js","sourceRoot":"","sources":["../../../src/dispatchers/shared/which-available.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,SAAS,GAAG,KAEjB,CAAC;IACF,IAAI,OAAO,SAAS,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Cross-platform CLI binary resolution.
3
+ *
4
+ * On Windows, CLI tools installed via npm/scoop/winget are typically `.cmd` or
5
+ * `.bat` wrappers. Node's `spawn` cannot execute those directly without a
6
+ * shell — attempting to do so throws ENOENT.
7
+ *
8
+ * This used to hand back `{ command: "cmd", prefixArgs: ["/c", path] }` and
9
+ * let dispatchers spawn that directly via node:child_process. That's exactly
10
+ * the shape of a real Windows command-injection bug: Node's spawn() only
11
+ * safely escapes cmd.exe metacharacters in an argument when IT decides
12
+ * cmd.exe indirection is needed (i.e. when the target path itself ends in
13
+ * .bat/.cmd) — manually pre-constructing the "cmd /c <path>" invocation
14
+ * ourselves bypasses that. Confirmed empirically on Node 24.14.1: an
15
+ * argument containing a literal `"` character breaks out of the quoting and
16
+ * lets a subsequent `&`-chained command execute for real. `%ENV_VAR%`
17
+ * sequences also got expanded regardless (a separate info-leak/argument-
18
+ * splitting bug, since the CLI's own model/prompt text is never meant to be
19
+ * shell-interpreted at all).
20
+ *
21
+ * Fix: resolveCliCommand no longer constructs the cmd.exe wrapper itself.
22
+ * For the one case worth the extra step — npm's own generated .cmd shim,
23
+ * which just re-invokes `node <script>.js` — we skip cmd.exe entirely by
24
+ * spawning node directly on the underlying script (faster, and immune to
25
+ * shell metacharacters since there's no shell in the loop at all). For every
26
+ * other .cmd/.bat shape (pnpm, yarn, scoop, hand-rolled), we hand the
27
+ * resolved path straight to cross-spawn (see subprocess.ts/
28
+ * stream-subprocess.ts), which is a widely-used, purpose-built library for
29
+ * exactly this problem — it detects the .bat/.cmd target itself and applies
30
+ * correct, tested escaping, verified against the same injection payload
31
+ * above (no command execution, no %VAR% expansion).
32
+ */
33
+ export interface ResolvedCommand {
34
+ command: string;
35
+ prefixArgs: string[];
36
+ }
37
+ export declare function resolveCliCommand(bin: string): Promise<ResolvedCommand>;
38
+ //# sourceMappingURL=windows-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows-cmd.d.ts","sourceRoot":"","sources":["../../../src/dispatchers/shared/windows-cmd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAsCD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAuB7E"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Cross-platform CLI binary resolution.
3
+ *
4
+ * On Windows, CLI tools installed via npm/scoop/winget are typically `.cmd` or
5
+ * `.bat` wrappers. Node's `spawn` cannot execute those directly without a
6
+ * shell — attempting to do so throws ENOENT.
7
+ *
8
+ * This used to hand back `{ command: "cmd", prefixArgs: ["/c", path] }` and
9
+ * let dispatchers spawn that directly via node:child_process. That's exactly
10
+ * the shape of a real Windows command-injection bug: Node's spawn() only
11
+ * safely escapes cmd.exe metacharacters in an argument when IT decides
12
+ * cmd.exe indirection is needed (i.e. when the target path itself ends in
13
+ * .bat/.cmd) — manually pre-constructing the "cmd /c <path>" invocation
14
+ * ourselves bypasses that. Confirmed empirically on Node 24.14.1: an
15
+ * argument containing a literal `"` character breaks out of the quoting and
16
+ * lets a subsequent `&`-chained command execute for real. `%ENV_VAR%`
17
+ * sequences also got expanded regardless (a separate info-leak/argument-
18
+ * splitting bug, since the CLI's own model/prompt text is never meant to be
19
+ * shell-interpreted at all).
20
+ *
21
+ * Fix: resolveCliCommand no longer constructs the cmd.exe wrapper itself.
22
+ * For the one case worth the extra step — npm's own generated .cmd shim,
23
+ * which just re-invokes `node <script>.js` — we skip cmd.exe entirely by
24
+ * spawning node directly on the underlying script (faster, and immune to
25
+ * shell metacharacters since there's no shell in the loop at all). For every
26
+ * other .cmd/.bat shape (pnpm, yarn, scoop, hand-rolled), we hand the
27
+ * resolved path straight to cross-spawn (see subprocess.ts/
28
+ * stream-subprocess.ts), which is a widely-used, purpose-built library for
29
+ * exactly this problem — it detects the .bat/.cmd target itself and applies
30
+ * correct, tested escaping, verified against the same injection payload
31
+ * above (no command execution, no %VAR% expansion).
32
+ */
33
+ import path from "node:path";
34
+ import fs from "node:fs/promises";
35
+ import which from "which";
36
+ async function resolveWindowsCandidate(bin, first) {
37
+ try {
38
+ const all = (await which(bin, {
39
+ all: true,
40
+ nothrow: true,
41
+ }));
42
+ if (Array.isArray(all)) {
43
+ const native = all.find((candidate) => path.extname(candidate).toLowerCase() === ".exe" &&
44
+ !candidate.toLowerCase().includes("\\windowsapps\\"));
45
+ if (native)
46
+ return native;
47
+ }
48
+ }
49
+ catch {
50
+ // Fall back to the first candidate resolved by which.
51
+ }
52
+ return first;
53
+ }
54
+ async function resolveNpmCmdShim(cmdPath) {
55
+ try {
56
+ const text = await fs.readFile(cmdPath, "utf8");
57
+ const match = text.match(/"%dp0%\\([^"]+?\.js)"/i);
58
+ if (!match?.[1])
59
+ return null;
60
+ const scriptPath = path.join(path.dirname(cmdPath), match[1].replace(/\\/g, path.sep));
61
+ await fs.access(scriptPath);
62
+ return { command: process.execPath, prefixArgs: [scriptPath] };
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ export async function resolveCliCommand(bin) {
69
+ const resolved = await which(bin, { nothrow: true });
70
+ if (!resolved) {
71
+ // Let spawn surface the ENOENT — caller may be running in a sandbox where
72
+ // PATH resolution is deliberately stubbed.
73
+ return { command: bin, prefixArgs: [] };
74
+ }
75
+ if (process.platform !== "win32") {
76
+ return { command: resolved, prefixArgs: [] };
77
+ }
78
+ const windowsResolved = await resolveWindowsCandidate(bin, resolved);
79
+ const ext = path.extname(windowsResolved).toLowerCase();
80
+ if (ext === ".cmd") {
81
+ const npmShim = await resolveNpmCmdShim(windowsResolved);
82
+ if (npmShim)
83
+ return npmShim;
84
+ }
85
+ // Any other .cmd/.bat shape (pnpm, yarn, scoop, hand-rolled) — or a native
86
+ // .exe — is handed straight to cross-spawn as prefixArgs: []. It detects
87
+ // .bat/.cmd targets itself and applies correct escaping; native binaries
88
+ // pass through unchanged, same as before.
89
+ return { command: windowsResolved, prefixArgs: [] };
90
+ }
91
+ //# sourceMappingURL=windows-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windows-cmd.js","sourceRoot":"","sources":["../../../src/dispatchers/shared/windows-cmd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type BuildMcpOptions, type McpHandle } from "../mcp/server.js";
2
+ export interface HttpServerHandle extends McpHandle {
3
+ port: number;
4
+ host: string;
5
+ token: string | null;
6
+ }
7
+ export interface StartHttpOptions extends BuildMcpOptions {
8
+ port?: number;
9
+ host?: string;
10
+ mcpRoute?: string;
11
+ token?: string | null;
12
+ }
13
+ export declare function startHttpServer(opts?: StartHttpOptions): Promise<HttpServerHandle>;
14
+ export declare const startMcpHttpServer: typeof startHttpServer;
15
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AAO1B,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AA4ND,wBAAsB,eAAe,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyX5F;AAED,eAAO,MAAM,kBAAkB,wBAAkB,CAAC"}