harnery 0.41.0 → 0.42.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.
- package/dist/commander.js +3 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +9 -1
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +113 -12
- package/dist/commands/browse.d.ts +164 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +263 -51
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +52 -0
- package/dist/commands/disk.d.ts +6 -0
- package/dist/commands/disk.d.ts.map +1 -0
- package/dist/commands/disk.js +80 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +48 -1
- package/dist/commands/files.d.ts +16 -0
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +54 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +43 -9
- package/dist/commands/rm.d.ts +4 -0
- package/dist/commands/rm.d.ts.map +1 -0
- package/dist/commands/rm.js +87 -0
- package/dist/commands/servers.d.ts +7 -0
- package/dist/commands/servers.d.ts.map +1 -0
- package/dist/commands/servers.js +394 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +82 -2
- package/dist/core/adapters/profiles.d.ts +9 -0
- package/dist/core/adapters/profiles.d.ts.map +1 -1
- package/dist/core/adapters/profiles.js +22 -1
- package/dist/core/adapters/types.d.ts +9 -0
- package/dist/core/adapters/types.d.ts.map +1 -1
- package/dist/core/agents/cli.js +13 -6
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +2 -0
- package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +60 -0
- package/dist/core/artifacts/delivery-card.js +9 -2
- package/dist/core/artifacts/index.d.ts +140 -4
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +522 -49
- package/dist/core/config.d.ts +68 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +128 -3
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +2 -2
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +16 -0
- package/dist/core/events/v3/projection.js +9 -1
- package/dist/core/hooks/adapter/events.d.ts +17 -0
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +5 -0
- package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
- package/dist/core/hooks/adapter/wiring.d.ts +37 -7
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +62 -4
- package/dist/core/hooks/cli.js +45 -6
- package/dist/core/hooks/run-markers.d.ts +37 -0
- package/dist/core/hooks/run-markers.d.ts.map +1 -0
- package/dist/core/hooks/run-markers.js +109 -0
- package/dist/core/servers/index.d.ts +211 -0
- package/dist/core/servers/index.d.ts.map +1 -0
- package/dist/core/servers/index.js +560 -0
- package/dist/core/servers/net.d.ts +58 -0
- package/dist/core/servers/net.d.ts.map +1 -0
- package/dist/core/servers/net.js +270 -0
- package/dist/core/servers/tunnels.d.ts +14 -0
- package/dist/core/servers/tunnels.d.ts.map +1 -0
- package/dist/core/servers/tunnels.js +33 -0
- package/dist/core/storage/builtins.js +1 -0
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +30 -1
- package/dist/core/workflow/sandbox-projection.d.ts +13 -2
- package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
- package/dist/core/workflow/sandbox-projection.js +23 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +6 -1
- package/dist/core/workflow/spawn-codex.d.ts +2 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +20 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +6 -1
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +4 -1
- package/dist/core/workflow/types.d.ts +47 -2
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/worker-access.d.ts +92 -0
- package/dist/core/workflow/worker-access.d.ts.map +1 -0
- package/dist/core/workflow/worker-access.js +450 -0
- package/dist/lib/browser/batch-steps.d.ts +18 -0
- package/dist/lib/browser/batch-steps.d.ts.map +1 -0
- package/dist/lib/browser/batch-steps.js +82 -0
- package/dist/lib/browser/collect/collector.d.ts +99 -0
- package/dist/lib/browser/collect/collector.d.ts.map +1 -0
- package/dist/lib/browser/collect/collector.js +202 -0
- package/dist/lib/browser/collect/fields.d.ts +31 -0
- package/dist/lib/browser/collect/fields.d.ts.map +1 -0
- package/dist/lib/browser/collect/fields.js +75 -0
- package/dist/lib/browser/collect/index.d.ts +6 -0
- package/dist/lib/browser/collect/index.d.ts.map +1 -0
- package/dist/lib/browser/collect/index.js +5 -0
- package/dist/lib/browser/collect/page-driver.d.ts +14 -0
- package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
- package/dist/lib/browser/collect/page-driver.js +306 -0
- package/dist/lib/browser/collect/presets.d.ts +52 -0
- package/dist/lib/browser/collect/presets.d.ts.map +1 -0
- package/dist/lib/browser/collect/presets.js +176 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
- package/dist/lib/browser/collect/scroll-plan.js +118 -0
- package/dist/lib/browser/index.d.ts +1 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.d.ts +2 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +13 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +1 -0
- package/dist/lib/browser/windows-chrome.d.ts +59 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
- package/dist/lib/browser/windows-chrome.js +179 -0
- package/dist/lib/claude-desktop-share.d.ts +136 -0
- package/dist/lib/claude-desktop-share.d.ts.map +1 -0
- package/dist/lib/claude-desktop-share.js +394 -0
- package/dist/lib/disk-usage.d.ts +50 -0
- package/dist/lib/disk-usage.d.ts.map +1 -0
- package/dist/lib/disk-usage.js +303 -0
- package/dist/lib/docs-lint.d.ts +6 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +8 -3
- package/dist/lib/guarded-remove.d.ts +34 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -0
- package/dist/lib/guarded-remove.js +310 -0
- package/dist/lib/instructions/templates.js +8 -8
- package/dist/lib/tunnel/error-page.d.ts +1 -1
- package/dist/lib/tunnel/error-page.d.ts.map +1 -1
- package/dist/lib/tunnel/error-page.js +19 -8
- package/dist/lib/tunnel/gate.js +20 -1
- package/dist/lib/tunnel/path-scope.d.ts +22 -0
- package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
- package/dist/lib/tunnel/path-scope.js +62 -0
- package/dist/lib/tunnel/state.d.ts +5 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +17 -3
- package/package.json +6 -1
- package/schemas/config.schema.json +91 -0
- package/src/commander.ts +19 -0
- package/src/commands/agents.ts +14 -0
- package/src/commands/artifacts.ts +171 -19
- package/src/commands/browse.ts +400 -57
- package/src/commands/claude-desktop.ts +61 -0
- package/src/commands/disk.ts +114 -0
- package/src/commands/doctor.ts +52 -1
- package/src/commands/files.ts +93 -2
- package/src/commands/init.ts +55 -5
- package/src/commands/rm.ts +115 -0
- package/src/commands/servers.ts +506 -0
- package/src/commands/tunnel.ts +102 -2
- package/src/core/adapters/profiles.ts +22 -1
- package/src/core/adapters/types.ts +10 -0
- package/src/core/agents/cli.ts +14 -6
- package/src/core/agents/render/session-context.ts +1 -0
- package/src/core/agents/state/live-coordination-view.ts +73 -0
- package/src/core/artifacts/delivery-card.ts +10 -2
- package/src/core/artifacts/index.ts +697 -44
- package/src/core/config.ts +183 -4
- package/src/core/diagnostics/bundle.ts +2 -1
- package/src/core/events/v3/producers/recorder.ts +16 -0
- package/src/core/events/v3/projection.ts +11 -1
- package/src/core/hooks/adapter/events.ts +19 -0
- package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
- package/src/core/hooks/adapter/wiring.ts +116 -8
- package/src/core/hooks/cli.ts +47 -6
- package/src/core/hooks/run-markers.ts +132 -0
- package/src/core/servers/index.ts +712 -0
- package/src/core/servers/net.ts +283 -0
- package/src/core/servers/tunnels.ts +39 -0
- package/src/core/storage/builtins.ts +1 -0
- package/src/core/workflow/engine.ts +38 -1
- package/src/core/workflow/sandbox-projection.ts +38 -2
- package/src/core/workflow/spawn-claude.ts +5 -1
- package/src/core/workflow/spawn-codex.ts +18 -1
- package/src/core/workflow/spawn-cursor.ts +5 -1
- package/src/core/workflow/spawn-opencode.ts +4 -1
- package/src/core/workflow/types.ts +62 -2
- package/src/core/workflow/worker-access.ts +525 -0
- package/src/lib/browser/batch-steps.ts +79 -0
- package/src/lib/browser/collect/collector.ts +306 -0
- package/src/lib/browser/collect/fields.ts +87 -0
- package/src/lib/browser/collect/index.ts +47 -0
- package/src/lib/browser/collect/page-driver.ts +327 -0
- package/src/lib/browser/collect/presets.ts +237 -0
- package/src/lib/browser/collect/scroll-plan.ts +179 -0
- package/src/lib/browser/index.ts +1 -0
- package/src/lib/browser/page-review-judge.ts +16 -0
- package/src/lib/browser/qa-run-contracts.ts +11 -0
- package/src/lib/browser/qa-run.ts +1 -0
- package/src/lib/browser/windows-chrome.ts +227 -0
- package/src/lib/claude-desktop-share.ts +511 -0
- package/src/lib/disk-usage.ts +347 -0
- package/src/lib/docs-lint.ts +8 -3
- package/src/lib/guarded-remove.ts +376 -0
- package/src/lib/instructions/templates.ts +8 -8
- package/src/lib/tunnel/error-page.ts +25 -9
- package/src/lib/tunnel/gate.ts +22 -1
- package/src/lib/tunnel/path-scope.ts +73 -0
- package/src/lib/tunnel/state.ts +20 -3
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local server registry.
|
|
3
|
+
*
|
|
4
|
+
* Every command that starts a long-lived local server (a preview UI, a tuning
|
|
5
|
+
* panel, a tunnel gate, a dashboard) writes one record under
|
|
6
|
+
* `.harnery/servers/<id>.json` and removes it when the server stops. One
|
|
7
|
+
* reader can then list every running server, stop any of them, and clean up
|
|
8
|
+
* session servers whose starting agent is gone.
|
|
9
|
+
*
|
|
10
|
+
* Two kinds:
|
|
11
|
+
* - `service`: one long-running copy (a dashboard, a tunnel). Listed and
|
|
12
|
+
* stoppable by hand; never stopped automatically.
|
|
13
|
+
* - `session`: one copy per piece of work (a preview for one scene). Eligible
|
|
14
|
+
* for `gc` once its starting agent has ended or been unobserved for a long
|
|
15
|
+
* time AND nobody has used the server for the idle window.
|
|
16
|
+
*
|
|
17
|
+
* Fail-closed rules: a record whose pid was recycled is treated as dead, never
|
|
18
|
+
* signalled; an unreadable coordination view makes every owner `unknown`,
|
|
19
|
+
* which `gc` never stops; a record without an owner is never stopped
|
|
20
|
+
* automatically.
|
|
21
|
+
*/
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
25
|
+
import { hostname } from "node:os";
|
|
26
|
+
import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
27
|
+
import { listStates } from "../../lib/tunnel/state.js";
|
|
28
|
+
import { resolveCoordRoot } from "../agents/coord-client.js";
|
|
29
|
+
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
|
|
30
|
+
import { checkPidToken, processStartToken } from "../agents/state/proc-start.js";
|
|
31
|
+
import { serverPortRange } from "../config.js";
|
|
32
|
+
import { readCoordinationViewV3, requireAuthoritySafeCoordinationViewV3, } from "../events/v3/coordination-view.js";
|
|
33
|
+
import { liveInstanceIdV3 } from "../events/v3/live-route-observer.js";
|
|
34
|
+
import { resolveOwner } from "../hooks/resolve/owner.js";
|
|
35
|
+
import { writePrivateJsonAtomic } from "../storage/atomic-json.js";
|
|
36
|
+
import { ancestry, establishedConnectionCounts, listListeningPorts, listListeningSockets, processStartedAt, readProcessInfo, scanSupport, } from "./net.js";
|
|
37
|
+
import { tunnelServerId, tunnelServerInput } from "./tunnels.js";
|
|
38
|
+
export { ancestry, establishedConnectionCounts, listListeningPorts, listListeningSockets, lsofConnectionCounts, lsofListeningSockets, parseElapsed, parseLsofListen, parseProcNetTcp, processStartedAt, psProcessInfo, readProcessInfo, scanSupport, } from "./net.js";
|
|
39
|
+
export { tunnelServerId, tunnelServerInput } from "./tunnels.js";
|
|
40
|
+
export const SERVER_RECORD_SCHEMA_VERSION = 1;
|
|
41
|
+
export const SERVER_DEFAULT_IDLE_HOURS = 2;
|
|
42
|
+
export const SERVER_DEFAULT_OWNER_STALE_HOURS = 24;
|
|
43
|
+
const TOUCH_INTERVAL_MS = 60_000;
|
|
44
|
+
export function serversDir(coordRoot) {
|
|
45
|
+
return join(coordRoot, ".harnery", "servers");
|
|
46
|
+
}
|
|
47
|
+
function rootOf(options) {
|
|
48
|
+
return options?.coordRoot === undefined ? resolveCoordRoot() : options.coordRoot;
|
|
49
|
+
}
|
|
50
|
+
/** A stable id for one server family serving one scope. */
|
|
51
|
+
export function serverId(type, scope) {
|
|
52
|
+
const family = slug(type) || "server";
|
|
53
|
+
if (!scope)
|
|
54
|
+
return family;
|
|
55
|
+
const digest = createHash("sha256").update(resolve(scope)).digest("hex").slice(0, 6);
|
|
56
|
+
const name = slug(basename(resolve(scope))).slice(0, 32);
|
|
57
|
+
return [family, name, digest].filter(Boolean).join("-");
|
|
58
|
+
}
|
|
59
|
+
function slug(value) {
|
|
60
|
+
return value
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
63
|
+
.replace(/^-+|-+$/g, "");
|
|
64
|
+
}
|
|
65
|
+
export function isValidServerId(id) {
|
|
66
|
+
return /^[a-z0-9][a-z0-9._-]{0,95}$/.test(id);
|
|
67
|
+
}
|
|
68
|
+
function recordPath(coordRoot, id) {
|
|
69
|
+
if (!isValidServerId(id))
|
|
70
|
+
throw new Error(`invalid server id: ${id}`);
|
|
71
|
+
return join(serversDir(coordRoot), `${id}.json`);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Record a running server. Returns null when no coordination root resolves
|
|
75
|
+
* (a tool running outside any Harnery project), so callers never fail
|
|
76
|
+
* because registration was impossible.
|
|
77
|
+
*/
|
|
78
|
+
export function registerServer(input, options) {
|
|
79
|
+
const coordRoot = rootOf(options);
|
|
80
|
+
if (!coordRoot)
|
|
81
|
+
return null;
|
|
82
|
+
const pid = input.pid ?? process.pid;
|
|
83
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
84
|
+
throw new Error(`invalid server pid: ${pid}`);
|
|
85
|
+
if (input.port !== undefined &&
|
|
86
|
+
!(Number.isInteger(input.port) && input.port > 0 && input.port < 65536))
|
|
87
|
+
throw new Error(`invalid server port: ${input.port}`);
|
|
88
|
+
const id = input.id ?? serverId(input.type, input.scope);
|
|
89
|
+
const now = new Date().toISOString();
|
|
90
|
+
const startToken = processStartToken(pid);
|
|
91
|
+
const owner = input.owner === undefined ? callingOwner(coordRoot) : (input.owner ?? undefined);
|
|
92
|
+
const record = {
|
|
93
|
+
schema_version: SERVER_RECORD_SCHEMA_VERSION,
|
|
94
|
+
id,
|
|
95
|
+
kind: input.kind,
|
|
96
|
+
type: input.type,
|
|
97
|
+
label: input.label ?? id,
|
|
98
|
+
...(input.url ? { url: input.url } : {}),
|
|
99
|
+
...(input.port ? { port: input.port } : {}),
|
|
100
|
+
pid,
|
|
101
|
+
...(startToken ? { start_token: startToken } : {}),
|
|
102
|
+
...(input.pids?.length
|
|
103
|
+
? { pids: input.pids.filter((p) => Number.isSafeInteger(p) && p > 0) }
|
|
104
|
+
: {}),
|
|
105
|
+
host: hostname(),
|
|
106
|
+
...(input.scope ? { scope: resolve(input.scope) } : {}),
|
|
107
|
+
...(input.cwd ? { cwd: resolve(input.cwd) } : {}),
|
|
108
|
+
...(input.log ? { log: resolve(input.log) } : {}),
|
|
109
|
+
...(input.stop_argv?.length ? { stop_argv: input.stop_argv } : {}),
|
|
110
|
+
...(owner ? { owner } : {}),
|
|
111
|
+
started_at: input.started_at ?? now,
|
|
112
|
+
registered_at: now,
|
|
113
|
+
last_active_at: now,
|
|
114
|
+
};
|
|
115
|
+
writePrivateJsonAtomic(recordPath(coordRoot, id), record);
|
|
116
|
+
return record;
|
|
117
|
+
}
|
|
118
|
+
/** Best-effort registration that never throws (for server start paths). */
|
|
119
|
+
export function tryRegisterServer(input, options) {
|
|
120
|
+
try {
|
|
121
|
+
return registerServer(input, options);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function callingOwner(coordRoot) {
|
|
128
|
+
try {
|
|
129
|
+
const owner = resolveOwner({ payload: null, coordRoot });
|
|
130
|
+
if (!owner)
|
|
131
|
+
return undefined;
|
|
132
|
+
const name = readLiveCoordinationRow(coordRoot, owner.instance_id)?.name;
|
|
133
|
+
return { instance_id: owner.instance_id, ...(name ? { name } : {}) };
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Remove a server's record. With `pid`, only a record still naming that pid
|
|
141
|
+
* is removed, so a replacement that re-registered the same id survives.
|
|
142
|
+
*/
|
|
143
|
+
export function unregisterServer(id, options) {
|
|
144
|
+
const coordRoot = rootOf(options);
|
|
145
|
+
if (!coordRoot || !isValidServerId(id))
|
|
146
|
+
return false;
|
|
147
|
+
const path = recordPath(coordRoot, id);
|
|
148
|
+
if (options?.pid !== undefined) {
|
|
149
|
+
const record = readRecord(path);
|
|
150
|
+
if (record && record.pid !== options.pid)
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (!existsSync(path))
|
|
154
|
+
return false;
|
|
155
|
+
rmSync(path, { force: true });
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
export function readServer(id, options) {
|
|
159
|
+
const coordRoot = rootOf(options);
|
|
160
|
+
if (!coordRoot || !isValidServerId(id))
|
|
161
|
+
return null;
|
|
162
|
+
return readRecord(recordPath(coordRoot, id));
|
|
163
|
+
}
|
|
164
|
+
/** Mark a server as used now. */
|
|
165
|
+
export function touchServer(id, options) {
|
|
166
|
+
const coordRoot = rootOf(options);
|
|
167
|
+
if (!coordRoot || !isValidServerId(id))
|
|
168
|
+
return;
|
|
169
|
+
const path = recordPath(coordRoot, id);
|
|
170
|
+
const record = readRecord(path);
|
|
171
|
+
if (!record)
|
|
172
|
+
return;
|
|
173
|
+
writePrivateJsonAtomic(path, {
|
|
174
|
+
...record,
|
|
175
|
+
last_active_at: (options?.now ?? new Date()).toISOString(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* A request hook for servers that want precise idle tracking: call the
|
|
180
|
+
* returned function on every request; it writes at most once a minute.
|
|
181
|
+
*/
|
|
182
|
+
export function createServerToucher(id, options) {
|
|
183
|
+
let last = 0;
|
|
184
|
+
return () => {
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
if (now - last < TOUCH_INTERVAL_MS)
|
|
187
|
+
return;
|
|
188
|
+
last = now;
|
|
189
|
+
try {
|
|
190
|
+
touchServer(id, options);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Activity tracking must never break the server.
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function readRecord(path) {
|
|
198
|
+
try {
|
|
199
|
+
if (!existsSync(path) || statSync(path).size > 64 * 1024)
|
|
200
|
+
return null;
|
|
201
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
202
|
+
if (value?.schema_version !== SERVER_RECORD_SCHEMA_VERSION ||
|
|
203
|
+
typeof value.id !== "string" ||
|
|
204
|
+
!Number.isSafeInteger(value.pid) ||
|
|
205
|
+
(value.kind !== "service" && value.kind !== "session"))
|
|
206
|
+
return null;
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export function readServerRecords(options) {
|
|
214
|
+
const coordRoot = rootOf(options);
|
|
215
|
+
if (!coordRoot)
|
|
216
|
+
return [];
|
|
217
|
+
const dir = serversDir(coordRoot);
|
|
218
|
+
if (!existsSync(dir))
|
|
219
|
+
return [];
|
|
220
|
+
return readdirSync(dir)
|
|
221
|
+
.filter((name) => name.endsWith(".json"))
|
|
222
|
+
.map((name) => readRecord(join(dir, name)))
|
|
223
|
+
.filter((record) => record !== null)
|
|
224
|
+
.sort((a, b) => a.kind.localeCompare(b.kind) || a.type.localeCompare(b.type) || a.id.localeCompare(b.id));
|
|
225
|
+
}
|
|
226
|
+
export function pidAlive(pid) {
|
|
227
|
+
try {
|
|
228
|
+
process.kill(pid, 0);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
return error.code === "EPERM";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
export function serverState(record) {
|
|
236
|
+
if (record.host !== hostname())
|
|
237
|
+
return "other-host";
|
|
238
|
+
if (!pidAlive(record.pid))
|
|
239
|
+
return "dead";
|
|
240
|
+
return checkPidToken(record.pid, record.start_token) === "mismatch" ? "dead" : "running";
|
|
241
|
+
}
|
|
242
|
+
function ownerReader(coordRoot, ownerStaleMs, nowMs) {
|
|
243
|
+
let view = null;
|
|
244
|
+
let failed = false;
|
|
245
|
+
try {
|
|
246
|
+
view = requireAuthoritySafeCoordinationViewV3(readCoordinationViewV3(coordRoot));
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
failed = true;
|
|
250
|
+
}
|
|
251
|
+
return (instanceId) => {
|
|
252
|
+
if (failed || !view)
|
|
253
|
+
return "unknown";
|
|
254
|
+
const generation = view.instances[liveInstanceIdV3(instanceId)];
|
|
255
|
+
if (generation?.phase !== "live")
|
|
256
|
+
return "ended";
|
|
257
|
+
const observed = Date.parse(generation.last_observed_at);
|
|
258
|
+
if (!Number.isFinite(observed))
|
|
259
|
+
return "unknown";
|
|
260
|
+
return nowMs - observed > ownerStaleMs ? "abandoned" : "live";
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/** Read every record, observe it, and optionally find unregistered listeners. */
|
|
264
|
+
export function listServers(options) {
|
|
265
|
+
const coordRoot = rootOf(options);
|
|
266
|
+
if (!coordRoot)
|
|
267
|
+
throw new Error("no Harnery project found (coordination root did not resolve)");
|
|
268
|
+
const now = options?.now ?? new Date();
|
|
269
|
+
const nowMs = now.getTime();
|
|
270
|
+
const ownerStaleMs = hours(options?.ownerStaleHours ?? SERVER_DEFAULT_OWNER_STALE_HOURS);
|
|
271
|
+
adoptRunningTunnels(coordRoot);
|
|
272
|
+
const records = readServerRecords({ coordRoot });
|
|
273
|
+
const ownerOf = ownerReader(coordRoot, ownerStaleMs, nowMs);
|
|
274
|
+
const states = records.map((record) => serverState(record));
|
|
275
|
+
const counts = establishedConnectionCounts(records.filter((_, i) => states[i] === "running").map((r) => r.port ?? 0));
|
|
276
|
+
const support = scanSupport();
|
|
277
|
+
const pruned = [];
|
|
278
|
+
const servers = [];
|
|
279
|
+
records.forEach((record, i) => {
|
|
280
|
+
const state = states[i];
|
|
281
|
+
if (state === "dead" && options?.prune !== false) {
|
|
282
|
+
unregisterServer(record.id, { coordRoot, pid: record.pid });
|
|
283
|
+
pruned.push(record.id);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const connections = state === "running" && record.port && support !== "unsupported"
|
|
287
|
+
? (counts.get(record.port) ?? 0)
|
|
288
|
+
: null;
|
|
289
|
+
let lastActive = record.last_active_at ?? record.registered_at;
|
|
290
|
+
if (connections && options?.sample !== false) {
|
|
291
|
+
lastActive = now.toISOString();
|
|
292
|
+
try {
|
|
293
|
+
touchServer(record.id, { coordRoot, now });
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// Observation stays correct for this read even if the write fails.
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const lastMs = Date.parse(lastActive);
|
|
300
|
+
servers.push({
|
|
301
|
+
record,
|
|
302
|
+
state,
|
|
303
|
+
owner_state: record.owner ? ownerOf(record.owner.instance_id) : "none",
|
|
304
|
+
connections,
|
|
305
|
+
last_active_at: lastActive,
|
|
306
|
+
idle_ms: Number.isFinite(lastMs) ? Math.max(0, nowMs - lastMs) : 0,
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
const unregistered = options?.scan === false ? [] : findUnregisteredListeners(coordRoot, servers);
|
|
310
|
+
return {
|
|
311
|
+
schema_version: 1,
|
|
312
|
+
coord_root: coordRoot,
|
|
313
|
+
scan: support,
|
|
314
|
+
servers,
|
|
315
|
+
unregistered,
|
|
316
|
+
pruned,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/** Register running tunnels that have no current record. Returns adopted ids. */
|
|
320
|
+
export function adoptRunningTunnels(coordRoot) {
|
|
321
|
+
const adopted = [];
|
|
322
|
+
let states;
|
|
323
|
+
try {
|
|
324
|
+
states = listStates(coordRoot);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return adopted;
|
|
328
|
+
}
|
|
329
|
+
for (const state of states) {
|
|
330
|
+
if (!state.gate_pid || !pidAlive(state.gate_pid))
|
|
331
|
+
continue;
|
|
332
|
+
const id = tunnelServerId(state.name);
|
|
333
|
+
if (readServer(id, { coordRoot })?.pid === state.gate_pid)
|
|
334
|
+
continue;
|
|
335
|
+
if (tryRegisterServer(tunnelServerInput(state, coordRoot), { coordRoot }))
|
|
336
|
+
adopted.push(id);
|
|
337
|
+
}
|
|
338
|
+
return adopted;
|
|
339
|
+
}
|
|
340
|
+
/** Processes that browser automation and editors start; never project servers. */
|
|
341
|
+
const IGNORED_LISTENER_COMMANDS = [
|
|
342
|
+
/(^|\/)(chrome|chromium|chrome-headless-shell|headless_shell|msedge|firefox)( |$)/i,
|
|
343
|
+
/\/(\.vscode-server|\.cursor-server|\.windsurf-server)\//,
|
|
344
|
+
/--type=(renderer|gpu-process|utility)/,
|
|
345
|
+
];
|
|
346
|
+
/**
|
|
347
|
+
* Listening processes that run inside the project (working directory or
|
|
348
|
+
* command line within the coordination root) but are not covered by any
|
|
349
|
+
* registered server: not a recorded pid, not a descendant of one, and not on
|
|
350
|
+
* a recorded port.
|
|
351
|
+
*/
|
|
352
|
+
export function findUnregisteredListeners(coordRoot, servers) {
|
|
353
|
+
const running = servers.filter((server) => server.state === "running");
|
|
354
|
+
const covered = new Set();
|
|
355
|
+
const ports = new Set();
|
|
356
|
+
for (const { record } of running) {
|
|
357
|
+
covered.add(record.pid);
|
|
358
|
+
for (const pid of record.pids ?? [])
|
|
359
|
+
covered.add(pid);
|
|
360
|
+
if (record.port)
|
|
361
|
+
ports.add(record.port);
|
|
362
|
+
}
|
|
363
|
+
const results = [];
|
|
364
|
+
const seenPids = new Map();
|
|
365
|
+
for (const socket of listListeningSockets()) {
|
|
366
|
+
if (ports.has(socket.port) || socket.pid === process.pid)
|
|
367
|
+
continue;
|
|
368
|
+
let inside = seenPids.get(socket.pid);
|
|
369
|
+
const info = readProcessInfo(socket.pid);
|
|
370
|
+
if (!info)
|
|
371
|
+
continue;
|
|
372
|
+
if (inside === undefined) {
|
|
373
|
+
inside =
|
|
374
|
+
!IGNORED_LISTENER_COMMANDS.some((pattern) => pattern.test(info.command)) &&
|
|
375
|
+
(within(coordRoot, info.cwd) || mentions(info.command, coordRoot)) &&
|
|
376
|
+
!ancestry(socket.pid).some((pid) => covered.has(pid));
|
|
377
|
+
seenPids.set(socket.pid, inside);
|
|
378
|
+
}
|
|
379
|
+
if (!inside)
|
|
380
|
+
continue;
|
|
381
|
+
results.push({
|
|
382
|
+
pid: socket.pid,
|
|
383
|
+
port: socket.port,
|
|
384
|
+
address: socket.address,
|
|
385
|
+
command: info.command.slice(0, 300),
|
|
386
|
+
cwd: info.cwd,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return results;
|
|
390
|
+
}
|
|
391
|
+
function within(root, path) {
|
|
392
|
+
if (!path)
|
|
393
|
+
return false;
|
|
394
|
+
const rel = relative(root, path);
|
|
395
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
396
|
+
}
|
|
397
|
+
function mentions(command, root) {
|
|
398
|
+
return command.split(/\s+/).some((arg) => arg === root || arg.startsWith(`${root}/`));
|
|
399
|
+
}
|
|
400
|
+
function hours(value) {
|
|
401
|
+
return value * 3_600_000;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Decide which session servers to stop. Pure: the caller supplies the views.
|
|
405
|
+
* A server is stopped only when all hold: kind `session`, running on this
|
|
406
|
+
* host, its owner ended or went unobserved past the stale window, and it has
|
|
407
|
+
* been idle for the idle window.
|
|
408
|
+
*/
|
|
409
|
+
export function planServerGc(views, policy) {
|
|
410
|
+
const idleMs = hours(policy?.idleHours ?? SERVER_DEFAULT_IDLE_HOURS);
|
|
411
|
+
return views.map(({ record, state, owner_state, idle_ms }) => {
|
|
412
|
+
const keep = (reason) => ({ id: record.id, action: "keep", reason });
|
|
413
|
+
if (state !== "running")
|
|
414
|
+
return keep(`server is ${state}`);
|
|
415
|
+
if (record.kind !== "session")
|
|
416
|
+
return keep("services are only stopped by hand");
|
|
417
|
+
if (owner_state === "none")
|
|
418
|
+
return keep("no starting agent was recorded");
|
|
419
|
+
if (owner_state === "unknown")
|
|
420
|
+
return keep("the starting agent's state could not be read");
|
|
421
|
+
if (owner_state === "live")
|
|
422
|
+
return keep("the starting agent is still active");
|
|
423
|
+
if (idle_ms < idleMs)
|
|
424
|
+
return keep(`used within the last ${policy?.idleHours ?? SERVER_DEFAULT_IDLE_HOURS}h`);
|
|
425
|
+
return {
|
|
426
|
+
id: record.id,
|
|
427
|
+
action: "stop",
|
|
428
|
+
reason: `starting agent ${owner_state === "ended" ? "has ended" : "has been unobserved past the stale window"} and the server has been idle ${Math.round(idle_ms / 60_000)} min`,
|
|
429
|
+
};
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Stop one server: its graceful stop command first when recorded, then
|
|
434
|
+
* SIGTERM to its process group, then SIGKILL after the grace period. A pid
|
|
435
|
+
* whose start token no longer matches is never signalled.
|
|
436
|
+
*/
|
|
437
|
+
export async function stopServer(record, options) {
|
|
438
|
+
const coordRoot = rootOf(options);
|
|
439
|
+
const graceMs = options?.graceMs ?? 5_000;
|
|
440
|
+
const finish = (result) => {
|
|
441
|
+
if (result.stopped || result.method === "not-running")
|
|
442
|
+
unregisterServer(record.id, { coordRoot, pid: record.pid });
|
|
443
|
+
return result;
|
|
444
|
+
};
|
|
445
|
+
const state = serverState(record);
|
|
446
|
+
if (state === "other-host")
|
|
447
|
+
return { id: record.id, stopped: false, method: "refused", detail: `runs on ${record.host}` };
|
|
448
|
+
if (state === "dead")
|
|
449
|
+
return finish({ id: record.id, stopped: false, method: "not-running" });
|
|
450
|
+
if (record.stop_argv?.length) {
|
|
451
|
+
const [command, ...args] = record.stop_argv;
|
|
452
|
+
spawnSync(command, args, { stdio: "ignore", timeout: 30_000, cwd: record.cwd });
|
|
453
|
+
if (await exited(record, graceMs))
|
|
454
|
+
return finish({ id: record.id, stopped: true, method: "stop-command" });
|
|
455
|
+
}
|
|
456
|
+
signal(record.pid, "SIGTERM");
|
|
457
|
+
for (const pid of record.pids ?? [])
|
|
458
|
+
signal(pid, "SIGTERM");
|
|
459
|
+
if (await exited(record, graceMs))
|
|
460
|
+
return finish({ id: record.id, stopped: true, method: "signal" });
|
|
461
|
+
if (serverState(record) === "running") {
|
|
462
|
+
signal(record.pid, "SIGKILL");
|
|
463
|
+
for (const pid of record.pids ?? [])
|
|
464
|
+
signal(pid, "SIGKILL");
|
|
465
|
+
}
|
|
466
|
+
const stopped = await exited(record, 2_000);
|
|
467
|
+
return finish({
|
|
468
|
+
id: record.id,
|
|
469
|
+
stopped,
|
|
470
|
+
method: "signal",
|
|
471
|
+
...(stopped ? {} : { detail: "process survived SIGKILL" }),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
function signal(pid, name) {
|
|
475
|
+
// Detached servers lead their own process group; signal the group so a
|
|
476
|
+
// wrapper (npm, bunx) does not orphan the real listener.
|
|
477
|
+
try {
|
|
478
|
+
process.kill(-pid, name);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
// Not a group leader, or already gone.
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
process.kill(pid, name);
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// Already gone.
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async function exited(record, waitMs) {
|
|
492
|
+
const deadline = Date.now() + waitMs;
|
|
493
|
+
while (Date.now() < deadline) {
|
|
494
|
+
if (serverState(record) !== "running")
|
|
495
|
+
return true;
|
|
496
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
497
|
+
}
|
|
498
|
+
return serverState(record) !== "running";
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* The first free port in the configured range for `type`
|
|
502
|
+
* (`.harnery/config.jsonc` `servers.port_ranges`). Skips ports any process is
|
|
503
|
+
* listening on and ports a registered server claims. Returns null when the
|
|
504
|
+
* type has no configured range; throws when the range is exhausted.
|
|
505
|
+
*/
|
|
506
|
+
export function allocateServerPort(type, options) {
|
|
507
|
+
const coordRoot = rootOf(options);
|
|
508
|
+
const range = serverPortRange(type, coordRoot);
|
|
509
|
+
if (!range)
|
|
510
|
+
return null;
|
|
511
|
+
const taken = listListeningPorts();
|
|
512
|
+
for (const record of readServerRecords({ coordRoot })) {
|
|
513
|
+
if (record.port && serverState(record) === "running")
|
|
514
|
+
taken.add(record.port);
|
|
515
|
+
}
|
|
516
|
+
for (let port = range[0]; port <= range[1]; port++)
|
|
517
|
+
if (!taken.has(port))
|
|
518
|
+
return port;
|
|
519
|
+
throw new Error(`no free port for ${type} in ${range[0]}-${range[1]}`);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Register a running server that never registered itself. Only a pid the scan
|
|
523
|
+
* reports as an unregistered listener inside the project can be adopted.
|
|
524
|
+
*/
|
|
525
|
+
export function adoptServer(pid, options) {
|
|
526
|
+
const coordRoot = rootOf(options);
|
|
527
|
+
if (!coordRoot)
|
|
528
|
+
throw new Error("no Harnery project found (coordination root did not resolve)");
|
|
529
|
+
const report = listServers({ coordRoot, sample: false });
|
|
530
|
+
const listeners = report.unregistered.filter((item) => item.pid === pid);
|
|
531
|
+
if (!listeners.length)
|
|
532
|
+
throw new Error(`pid ${pid} is not an unregistered listener inside this project`);
|
|
533
|
+
const ports = listeners.map((item) => item.port).sort((a, b) => a - b);
|
|
534
|
+
const first = listeners[0];
|
|
535
|
+
const type = options?.type ?? "adopted";
|
|
536
|
+
const scope = options?.scope ?? first.cwd ?? undefined;
|
|
537
|
+
const started = processStartedAt(pid);
|
|
538
|
+
const record = registerServer({
|
|
539
|
+
id: serverId(type, `${scope ?? ""}#${pid}`),
|
|
540
|
+
kind: options?.kind ?? "session",
|
|
541
|
+
type,
|
|
542
|
+
label: options?.label ?? `Adopted: ${first.command.slice(0, 80)}`,
|
|
543
|
+
url: `http://127.0.0.1:${ports[0]}/`,
|
|
544
|
+
port: ports[0],
|
|
545
|
+
pid,
|
|
546
|
+
...(scope ? { scope } : {}),
|
|
547
|
+
...(first.cwd ? { cwd: first.cwd } : {}),
|
|
548
|
+
...(options?.owner === "none" ? { owner: null } : {}),
|
|
549
|
+
...(started ? { started_at: started } : {}),
|
|
550
|
+
}, { coordRoot });
|
|
551
|
+
if (!record)
|
|
552
|
+
throw new Error("registration failed");
|
|
553
|
+
return record;
|
|
554
|
+
}
|
|
555
|
+
/** Ensure the registry directory exists (for hosts that write records directly). */
|
|
556
|
+
export function ensureServersDir(coordRoot) {
|
|
557
|
+
const dir = serversDir(coordRoot);
|
|
558
|
+
mkdirSync(dir, { recursive: true });
|
|
559
|
+
return dir;
|
|
560
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local socket and process observation for the server registry.
|
|
3
|
+
*
|
|
4
|
+
* Linux reads procfs directly (no `ss`/`lsof` dependency, works under WSL).
|
|
5
|
+
* macOS shells out to `lsof` and `ps`. Other platforms report `unsupported`
|
|
6
|
+
* so callers can say the scan did not run rather than implying it found
|
|
7
|
+
* nothing.
|
|
8
|
+
*/
|
|
9
|
+
export type ScanSupport = "procfs" | "lsof" | "unsupported";
|
|
10
|
+
export interface ListeningSocket {
|
|
11
|
+
pid: number;
|
|
12
|
+
port: number;
|
|
13
|
+
address: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ProcessInfo {
|
|
16
|
+
pid: number;
|
|
17
|
+
ppid: number | null;
|
|
18
|
+
command: string;
|
|
19
|
+
cwd: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function scanSupport(): ScanSupport;
|
|
22
|
+
interface ProcSocketRow {
|
|
23
|
+
localAddress: string;
|
|
24
|
+
localPort: number;
|
|
25
|
+
state: string;
|
|
26
|
+
inode: string;
|
|
27
|
+
}
|
|
28
|
+
/** Parse `/proc/net/tcp` or `/proc/net/tcp6` content. */
|
|
29
|
+
export declare function parseProcNetTcp(content: string): ProcSocketRow[];
|
|
30
|
+
/** Listening TCP sockets whose owning process this user can see. */
|
|
31
|
+
export declare function listListeningSockets(): ListeningSocket[];
|
|
32
|
+
/**
|
|
33
|
+
* Every listening TCP port on this machine, including ports whose owner this
|
|
34
|
+
* user cannot see (containers, other users). Used to avoid port collisions.
|
|
35
|
+
*/
|
|
36
|
+
export declare function listListeningPorts(): Set<number>;
|
|
37
|
+
/** Parse `lsof -Fpn` output for listening sockets. */
|
|
38
|
+
export declare function parseLsofListen(output: string): ListeningSocket[];
|
|
39
|
+
/**
|
|
40
|
+
* Count established server-side connections per local port. A browser tab
|
|
41
|
+
* holding a live-reload socket or keep-alive connection counts as activity.
|
|
42
|
+
*/
|
|
43
|
+
export declare function establishedConnectionCounts(ports: number[]): Map<number, number>;
|
|
44
|
+
/** Listening sockets via `lsof` (the macOS path; also runs wherever lsof exists). */
|
|
45
|
+
export declare function lsofListeningSockets(): ListeningSocket[];
|
|
46
|
+
/** Server-side established connections per wanted local port, via `lsof`. */
|
|
47
|
+
export declare function lsofConnectionCounts(wanted: Set<number>): Map<number, number>;
|
|
48
|
+
/** Parent, command line, and working directory via `ps` and `lsof` (the macOS path). */
|
|
49
|
+
export declare function psProcessInfo(pid: number): ProcessInfo | null;
|
|
50
|
+
export declare function readProcessInfo(pid: number): ProcessInfo | null;
|
|
51
|
+
/** Parse `ps -o etime=` output (`[[dd-]hh:]mm:ss`) into seconds. */
|
|
52
|
+
export declare function parseElapsed(value: string): number | null;
|
|
53
|
+
/** When a process started, from its elapsed run time; null when unavailable. */
|
|
54
|
+
export declare function processStartedAt(pid: number, now?: number): string | null;
|
|
55
|
+
/** The pid followed by its ancestors, nearest first (bounded). */
|
|
56
|
+
export declare function ancestry(pid: number, maxHops?: number): number[];
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=net.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"net.d.ts","sourceRoot":"","sources":["../../../src/core/servers/net.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,wBAAgB,WAAW,IAAI,WAAW,CAIzC;AAED,UAAU,aAAa;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAehE;AA4DD,oEAAoE;AACpE,wBAAgB,oBAAoB,IAAI,eAAe,EAAE,CAmBxD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,GAAG,CAAC,MAAM,CAAC,CAUhD;AAED,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,CAiBjE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBhF;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,IAAI,eAAe,EAAE,CAExD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAS7E;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAa7D;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAoB/D;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,MAAM,GAAG,IAAI,CAI7E;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,EAAE,CAQ5D"}
|