@erichll/pi-sandbox 0.15.3 → 0.16.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.
@@ -0,0 +1,250 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { createJiti } from "jiti";
5
+
6
+ export const PI_SUBAGENTS_VERSION = "0.65.0";
7
+ export const NATIVE_CHILD_TOOLS = ["bash", "read", "grep", "find", "ls"] as const;
8
+ export const PI_SANDBOX_ACKNOWLEDGEMENT = "@erichll:pi-sandbox";
9
+
10
+ type Agent = {
11
+ name: string;
12
+ localName?: string;
13
+ runner?: { type?: unknown };
14
+ disabled?: boolean;
15
+ extensions?: string[];
16
+ extensionsFromDefault?: boolean;
17
+ allowNestedSubagents?: boolean;
18
+ };
19
+
20
+ type DiscoveryModule = {
21
+ discoverAgents(cwd: string, scope: "both", preferredProvider?: string): {
22
+ agents: Agent[];
23
+ };
24
+ resolveAgentName(name: string, agents: Agent[]): {
25
+ agent?: Agent;
26
+ error?: string;
27
+ };
28
+ };
29
+
30
+ type ConfigModule = {
31
+ loadConfig(): { scheduledRuns?: { enabled?: boolean } };
32
+ };
33
+
34
+ export type CapabilityCeilingHandle = { dispose(): void };
35
+ type CeilingModule = {
36
+ SUBAGENT_CAPABILITY_CEILING_VERSION: unknown;
37
+ registerSubagentCapabilityCeiling(options: {
38
+ sessionId: string;
39
+ source: string;
40
+ ceiling: {
41
+ allowedAgents: readonly string[];
42
+ allowedTools: readonly string[];
43
+ };
44
+ }): CapabilityCeilingHandle;
45
+ };
46
+
47
+ export type PiSubagentsNativeRuntime = {
48
+ validateAllowedAgents(
49
+ cwd: string,
50
+ preferredProvider?: string,
51
+ ): string[];
52
+ registerCeiling(sessionId: string, allowedAgents: readonly string[]): CapabilityCeilingHandle;
53
+ };
54
+
55
+ function requiredFunction(
56
+ value: unknown,
57
+ name: string,
58
+ ): asserts value is (...args: never[]) => unknown {
59
+ if (typeof value !== "function") {
60
+ throw new Error(`pi-subagents 0.65.0 compatibility failure: missing ${name}`);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Load the public ceiling API and the exact 0.65.0 discovery/config internals.
66
+ * This intentionally fails closed on package version, export, or layout drift.
67
+ */
68
+ export async function loadPiSubagentsNativeRuntime(
69
+ configuredAgents: readonly string[],
70
+ ): Promise<PiSubagentsNativeRuntime> {
71
+ const entry = fileURLToPath(import.meta.resolve("pi-subagents"));
72
+ const root = dirname(entry);
73
+ const packageJson = JSON.parse(
74
+ await readFile(join(root, "package.json"), "utf8"),
75
+ ) as { version?: unknown; exports?: Record<string, unknown> };
76
+ if (packageJson.version !== PI_SUBAGENTS_VERSION) {
77
+ throw new Error(
78
+ `pi-subagents protected mode requires exactly ${PI_SUBAGENTS_VERSION}; found ${String(packageJson.version)}`,
79
+ );
80
+ }
81
+ if (packageJson.exports?.["./capability-ceiling"] !== "./src/api/capability-ceiling.ts") {
82
+ throw new Error(
83
+ "pi-subagents 0.65.0 compatibility failure: capability-ceiling export changed",
84
+ );
85
+ }
86
+
87
+ const jiti = createJiti(import.meta.url, {
88
+ interopDefault: false,
89
+ fsCache: false,
90
+ });
91
+ const [ceiling, discovery, config] = await Promise.all([
92
+ jiti.import(pathToFileURL(join(root, "src/api/capability-ceiling.ts")).href) as Promise<CeilingModule>,
93
+ jiti.import(pathToFileURL(join(root, "src/agents/agents.ts")).href) as Promise<DiscoveryModule>,
94
+ jiti.import(pathToFileURL(join(root, "src/extension/config.ts")).href) as Promise<ConfigModule>,
95
+ ]).catch((error) => {
96
+ throw new Error(
97
+ `pi-subagents 0.65.0 compatibility failure: ${error instanceof Error ? error.message : String(error)}`,
98
+ );
99
+ });
100
+ if (ceiling.SUBAGENT_CAPABILITY_CEILING_VERSION !== 1) {
101
+ throw new Error("pi-subagents 0.65.0 compatibility failure: unsupported capability ceiling version");
102
+ }
103
+ requiredFunction(ceiling.registerSubagentCapabilityCeiling, "registerSubagentCapabilityCeiling");
104
+ requiredFunction(discovery.discoverAgents, "discoverAgents");
105
+ requiredFunction(discovery.resolveAgentName, "resolveAgentName");
106
+ requiredFunction(config.loadConfig, "loadConfig");
107
+
108
+ const validateAllowedAgents = (
109
+ cwd: string,
110
+ preferredProvider?: string,
111
+ ): string[] => {
112
+ const upstreamConfig = config.loadConfig();
113
+ if (upstreamConfig.scheduledRuns?.enabled !== false) {
114
+ throw new Error(
115
+ "pi-subagents protected mode requires scheduledRuns.enabled=false in the pi-subagents config",
116
+ );
117
+ }
118
+ const discovered = discovery.discoverAgents(cwd, "both", preferredProvider);
119
+ const canonical: string[] = [];
120
+ for (const requested of configuredAgents) {
121
+ const resolved = discovery.resolveAgentName(requested, discovered.agents);
122
+ if (resolved.error) throw new Error(resolved.error);
123
+ if (!resolved.agent) {
124
+ throw new Error(`pi-subagents protected mode cannot resolve allowed agent '${requested}'`);
125
+ }
126
+ const agent = resolved.agent;
127
+ if (agent.name !== requested) {
128
+ throw new Error(
129
+ `pi-subagents protected mode requires canonical agent names; '${requested}' resolves to '${agent.name}'`,
130
+ );
131
+ }
132
+ if (agent.disabled === true) {
133
+ throw new Error(`pi-subagents protected mode rejects disabled agent '${requested}'`);
134
+ }
135
+ if (agent.runner !== undefined && agent.runner.type !== "pi") {
136
+ throw new Error(
137
+ `pi-subagents protected mode rejects runner '${String(agent.runner.type)}' for agent '${requested}'`,
138
+ );
139
+ }
140
+ if (agent.extensions !== undefined && agent.extensionsFromDefault !== true) {
141
+ throw new Error(
142
+ `pi-subagents protected mode requires ambient extensions for agent '${requested}'; remove its explicit extensions override`,
143
+ );
144
+ }
145
+ if (agent.allowNestedSubagents === true) {
146
+ throw new Error(
147
+ `pi-subagents protected mode rejects allowNestedSubagents for agent '${requested}'`,
148
+ );
149
+ }
150
+ canonical.push(agent.name);
151
+ }
152
+ return canonical;
153
+ };
154
+
155
+ // Validate once while constructing the adapter as an internal-structure gate.
156
+ if (configuredAgents.length === 0) {
157
+ throw new Error("pi-subagents protected mode requires at least one allowed agent");
158
+ }
159
+ return {
160
+ validateAllowedAgents,
161
+ registerCeiling(sessionId, allowedAgents) {
162
+ return ceiling.registerSubagentCapabilityCeiling({
163
+ sessionId,
164
+ source: PI_SANDBOX_ACKNOWLEDGEMENT,
165
+ ceiling: { allowedAgents, allowedTools: NATIVE_CHILD_TOOLS },
166
+ });
167
+ },
168
+ };
169
+ }
170
+
171
+ export function nativeSubagentCallBlockReason(
172
+ input: Record<string, unknown>,
173
+ ): string | undefined {
174
+ const action = typeof input.action === "string" ? input.action.trim() : undefined;
175
+ if (action?.startsWith("schedule.")) {
176
+ return "scheduled subagent operations are disabled in protected mode";
177
+ }
178
+ const forbiddenActions = new Set([
179
+ "create", "update", "delete", "eject", "enable", "disable", "reset",
180
+ "refine", "refine.rollback", "watchdog.configure", "grant-spawn-budget",
181
+ "mission.create", "mission.update", "mission.resolve-decision",
182
+ "mission.attach-run", "mission.close", "inspector.open", "inspector.close",
183
+ "project.open", "project.close", "worktree.discard", "worktree.cleanup",
184
+ "lane.recordMerge", "lane.recordSupersession",
185
+ ]);
186
+ if (action && forbiddenActions.has(action)) {
187
+ return `subagent management action '${action}' is disabled in protected mode`;
188
+ }
189
+ if (action === "resume") {
190
+ return "subagent resume may launch a child and is disabled in protected mode";
191
+ }
192
+ if (action !== undefined) return undefined;
193
+ if (input.workflow !== undefined) {
194
+ return "extension-owned named workflows are disabled in protected mode";
195
+ }
196
+ const isDirect = typeof input.agent === "string" && input.agent.trim() !== "";
197
+ if (input.workflowScript !== undefined || input.workflowScriptPath !== undefined) {
198
+ return "workflowScript and workflowScriptPath are disabled because pi-subagents 0.65.0 disables ambient extensions for their native children";
199
+ }
200
+ if (!isDirect) {
201
+ return "protected mode supports only direct agent launches";
202
+ }
203
+ if (input.async !== true) {
204
+ return "protected mode requires every child launch to set async: true explicitly";
205
+ }
206
+ return undefined;
207
+ }
208
+
209
+ export function hasSandboxAcknowledgement(value: unknown): boolean {
210
+ if (!value || typeof value !== "object") return false;
211
+ const record = value as Record<string, unknown>;
212
+ const acknowledgement = record.runtimeAcknowledgedExtensions;
213
+ if (
214
+ acknowledgement && typeof acknowledgement === "object" &&
215
+ Array.isArray((acknowledgement as { ids?: unknown }).ids) &&
216
+ (acknowledgement as { ids: unknown[] }).ids.includes(PI_SANDBOX_ACKNOWLEDGEMENT)
217
+ ) return true;
218
+ return Object.values(record).some((child) =>
219
+ Array.isArray(child)
220
+ ? child.some(hasSandboxAcknowledgement)
221
+ : hasSandboxAcknowledgement(child)
222
+ );
223
+ }
224
+
225
+ export function terminalChildrenHaveSandboxAcknowledgement(value: unknown): boolean {
226
+ let foundTerminalChild = false;
227
+ let missing = false;
228
+ const visit = (candidate: unknown): void => {
229
+ if (missing || !candidate || typeof candidate !== "object") return;
230
+ if (Array.isArray(candidate)) {
231
+ for (const item of candidate) visit(item);
232
+ return;
233
+ }
234
+ const record = candidate as Record<string, unknown>;
235
+ const state = record.state ?? record.status;
236
+ if (
237
+ typeof record.agent === "string" &&
238
+ ["completed", "failed", "stopped", "timed_out", "interrupted"].includes(String(state))
239
+ ) {
240
+ foundTerminalChild = true;
241
+ if (!hasSandboxAcknowledgement({
242
+ runtimeAcknowledgedExtensions: record.runtimeAcknowledgedExtensions,
243
+ })) missing = true;
244
+ return;
245
+ }
246
+ for (const child of Object.values(record)) visit(child);
247
+ };
248
+ visit(value);
249
+ return !foundTerminalChild || !missing;
250
+ }
@@ -1,8 +0,0 @@
1
- export type ExternalNetworkPolicy = {
2
- allowedDomains: string[];
3
- deniedDomains: string[];
4
- };
5
-
6
- export function decodeExternalNetworkPolicy(
7
- value: unknown,
8
- ): ExternalNetworkPolicy;
@@ -1,27 +0,0 @@
1
- import { NetworkConfigSchema } from "@anthropic-ai/sandbox-runtime";
2
-
3
- /**
4
- * Decode the supervisor-owned network policy delivered during registration.
5
- * Any missing, extra, or malformed data becomes a deny-all policy so a broken
6
- * transport can never widen an external worker's access.
7
- */
8
- export function decodeExternalNetworkPolicy(value) {
9
- if (
10
- !value ||
11
- typeof value !== "object" ||
12
- Array.isArray(value) ||
13
- Object.keys(value).some(
14
- (key) => key !== "allowedDomains" && key !== "deniedDomains",
15
- )
16
- ) {
17
- return { allowedDomains: [], deniedDomains: ["*"] };
18
- }
19
- const result = NetworkConfigSchema.safeParse(value);
20
- if (!result.success) {
21
- return { allowedDomains: [], deniedDomains: ["*"] };
22
- }
23
- return {
24
- allowedDomains: [...result.data.allowedDomains],
25
- deniedDomains: [...result.data.deniedDomains],
26
- };
27
- }
@@ -1,173 +0,0 @@
1
- // Optional FleetView integration for pi-sandbox's isolated external workers.
2
- //
3
- // When the host is running the external `pi-subagents` provider with
4
- // `externalWorkerIsolation: "enforce"`, pi-sandbox supervises the external
5
- // worker process trees itself. This module mirrors those supervised workers
6
- // into pi-subagents' caller-owned external-run registry so they show up in the
7
- // FleetView (pi-subagents >= 0.50.0).
8
- //
9
- // This is a pure observability enhancement: it participates in NO sandbox
10
- // allow/deny decision, and nothing here can downgrade an isolated worker into
11
- // host execution. `pi-subagents/external-runs` is loaded via a DYNAMIC import
12
- // only (never a top-level static import), because pi-subagents is an OPTIONAL
13
- // peer dependency: builtin/off deployments may not have it installed.
14
- //
15
- // Deliberate scope of v1: records are "running" only. No completed/failed/
16
- // endedAt are fabricated because neither the launcher nor the supervisor
17
- // protocol currently reports process exit evidence to this extension (the
18
- // record would therefore be a lie). See docs/compat-notes.md seam 5.
19
-
20
- type ExternalRunState = "running";
21
-
22
- /** Structural view of the specific pi-subagents API we consume. */
23
- type ExternalRunsApi = {
24
- registerExternalRun(input: {
25
- id: string;
26
- sessionId: string;
27
- source: string;
28
- label: string;
29
- state: ExternalRunState;
30
- startedAt: number;
31
- preview?: string;
32
- }): unknown;
33
- unregisterExternalRun(sessionId: string, id: string): boolean;
34
- };
35
-
36
- export type ExternalRunsView = {
37
- /** Whether the pi-subagents runtime is reachable (dynamic import succeeded). */
38
- readonly enabled: boolean;
39
- /** Diagnostic string when the runtime is disabled. */
40
- readonly error?: string;
41
- /** Mirror a supervisor lifecycle.registered event into the registry. */
42
- registered(worker: { id: string; cwd: string; startedAt: number }): void;
43
- /** Mirror a supervisor lifecycle.unregistered event out of the registry. */
44
- unregistered(workerId: string): void;
45
- /** Unregister every run this view registered (session shutdown / replace). */
46
- close(): void;
47
- };
48
-
49
- const VIEW_LABEL = "isolated pi-subagents worker";
50
- const VIEW_SOURCE = "pi-sandbox";
51
-
52
- /**
53
- * Whether the FleetView integration should be enabled for a given provider /
54
- * isolation combination. It is enabled ONLY when the external pi-subagents
55
- * provider owns orchestration AND outer worker isolation is enforced -- the
56
- * sole case where pi-sandbox actually supervises external workers and can
57
- * truthfully vouch for a "running" record it owns.
58
- */
59
- export function externalRunsViewEnabledWhen(
60
- provider: string,
61
- externalWorkerIsolation: string,
62
- ): boolean {
63
- return provider === "pi-subagents" && externalWorkerIsolation === "enforce";
64
- }
65
-
66
- function ident(value: string): { ok: true; value: string } | { ok: false; error: string } {
67
- const trimmed = value.trim();
68
- if (!trimmed || trimmed !== value) {
69
- return { ok: false, error: "must be a non-empty trimmed string" };
70
- }
71
- return { ok: true, value: trimmed };
72
- }
73
-
74
- export function createExternalRunsView(
75
- sessionIdInput: string,
76
- load: () => Promise<unknown> = () => import("pi-subagents/external-runs"),
77
- ): Promise<ExternalRunsView> {
78
- return (async () => {
79
- const sessionId = ident(sessionIdInput);
80
- if (!sessionId.ok) {
81
- return disabledView(`invalid session id: ${sessionId.error}`);
82
- }
83
- let api: ExternalRunsApi | undefined;
84
- let loadError: string | undefined;
85
- try {
86
- const mod = (await load()) as Record<string, unknown>;
87
- const registerExternalRun = mod.registerExternalRun;
88
- const unregisterExternalRun = mod.unregisterExternalRun;
89
- if (typeof registerExternalRun !== "function" || typeof unregisterExternalRun !== "function") {
90
- throw new Error("pi-subagents/external-runs does not expose registerExternalRun/unregisterExternalRun");
91
- }
92
- api = { registerExternalRun, unregisterExternalRun } as unknown as ExternalRunsApi;
93
- } catch (error) {
94
- loadError = `FleetView external-run registration unavailable: ${error instanceof Error ? error.message : String(error)}`;
95
- // Never thrown: disable only the display path. Sandbox fail-closed and
96
- // network approval for the supervisor are untouched.
97
- return disabledView(loadError);
98
- }
99
-
100
- // (sessionId, workerId) pairs this extension registered, so we never
101
- // duplicate-register and never unregister records owned by other sources.
102
- const registered = new Map<string, number>();
103
-
104
- return {
105
- get enabled() {
106
- return Boolean(api);
107
- },
108
- error: loadError,
109
- registered(worker) {
110
- const fullId = ident(worker.id);
111
- const cwd = ident(worker.cwd);
112
- if (!api || !fullId.ok || !cwd.ok) return;
113
- if (registered.has(fullId.value)) return; // avoid duplicate registration
114
- try {
115
- api!.registerExternalRun({
116
- id: fullId.value,
117
- sessionId: sessionId.value,
118
- source: VIEW_SOURCE,
119
- label: VIEW_LABEL,
120
- state: "running",
121
- startedAt:
122
- Number.isSafeInteger(worker.startedAt) && worker.startedAt > 0
123
- ? worker.startedAt
124
- : Date.now(),
125
- preview: cwd.value,
126
- });
127
- registered.set(fullId.value, Date.now());
128
- } catch (error) {
129
- // Registry full / malformed record / unknown fields: log, do not emit.
130
- console.error(
131
- `pi-sandbox: could not register external run for worker '${fullId.value}': ${error instanceof Error ? error.message : String(error)}`,
132
- );
133
- }
134
- },
135
- unregistered(workerId) {
136
- const fullId = ident(workerId);
137
- if (!fullId.ok) return;
138
- if (!registered.has(fullId.value)) return; // we did not register it
139
- registered.delete(fullId.value);
140
- if (api) {
141
- try {
142
- api.unregisterExternalRun(sessionId.value, fullId.value);
143
- } catch {
144
- // Best-effort; nothing else to fall back to.
145
- }
146
- }
147
- },
148
- close() {
149
- for (const workerId of registered.keys()) {
150
- if (api) {
151
- try {
152
- api.unregisterExternalRun(sessionId.value, workerId);
153
- } catch {
154
- // best-effort
155
- }
156
- }
157
- }
158
- registered.clear();
159
- },
160
- };
161
- })();
162
- }
163
-
164
- function disabledView(error: string): ExternalRunsView {
165
- console.error(`pi-sandbox: ${error}`);
166
- return {
167
- enabled: false,
168
- error,
169
- registered() {},
170
- unregistered() {},
171
- close() {},
172
- };
173
- }
@@ -1,189 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { createServer, type Server } from "node:net";
3
- import { chmod, mkdtemp, rm } from "node:fs/promises";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import type { NetworkEndpoint, TrapApprovalContext } from "./approval.ts";
7
- import { approveDomainEndpoint } from "./approval.ts";
8
- import type { NetworkConfig } from "./config.ts";
9
-
10
- const MAX_FRAME_BYTES = 16 * 1024;
11
- const PROTOCOL_VERSION = 1;
12
-
13
- type Request = {
14
- version?: unknown;
15
- capability?: unknown;
16
- id?: unknown;
17
- type?: unknown;
18
- workerId?: unknown;
19
- cwd?: unknown;
20
- hostname?: unknown;
21
- port?: unknown;
22
- };
23
-
24
- export type ExternalWorkerSupervisorLifecycle = {
25
- /** Called immediately after a worker finishes the register protocol. */
26
- registered?(worker: { id: string; cwd: string; startedAt: number }): void;
27
- /** Called immediately after a worker completes the unregister protocol. */
28
- unregistered?(worker: { id: string; cwd: string }): void;
29
- };
30
-
31
- export type ExternalWorkerSupervisor = {
32
- socketPath: string;
33
- capability: string;
34
- workers(): ReadonlyArray<{
35
- id: string;
36
- cwd: string;
37
- state: "active" | "exited";
38
- startedAt: number;
39
- requests: number;
40
- }>;
41
- close(): Promise<void>;
42
- };
43
-
44
- export async function createExternalWorkerSupervisor(
45
- approvalContext: (worker: { id: string; cwd: string }) => TrapApprovalContext,
46
- lifecycle: ExternalWorkerSupervisorLifecycle = {},
47
- network: NetworkConfig = { allowedDomains: [], deniedDomains: [] },
48
- ): Promise<ExternalWorkerSupervisor> {
49
- const trustedNetwork = {
50
- allowedDomains: [...network.allowedDomains],
51
- deniedDomains: [...network.deniedDomains],
52
- };
53
- const directory = await mkdtemp(join(tmpdir(), "pi-sandbox-external-"));
54
- await chmod(directory, 0o700);
55
- const socketPath = join(directory, "supervisor.sock");
56
- const capability = randomBytes(32).toString("hex");
57
- const seen = new Set<string>();
58
- const workers = new Map<string, {
59
- cwd: string;
60
- state: "active" | "exited";
61
- startedAt: number;
62
- requests: number;
63
- }>();
64
- const server: Server = createServer((socket) => {
65
- let buffer = "";
66
- socket.setEncoding("utf8");
67
- socket.on("data", (chunk: string) => {
68
- buffer += chunk;
69
- if (Buffer.byteLength(buffer, "utf8") > MAX_FRAME_BYTES) {
70
- socket.end('{"action":"deny"}\n');
71
- return;
72
- }
73
- let newline = buffer.indexOf("\n");
74
- while (newline >= 0) {
75
- const line = buffer.slice(0, newline);
76
- buffer = buffer.slice(newline + 1);
77
- void respond(line);
78
- newline = buffer.indexOf("\n");
79
- }
80
- });
81
- const respond = async (line: string): Promise<void> => {
82
- let request: Request | undefined;
83
- try { request = JSON.parse(line) as Request; } catch { /* denied below */ }
84
- if (!request) {
85
- socket.write('{"action":"deny"}\n');
86
- return;
87
- }
88
- const baseValid =
89
- request.version === PROTOCOL_VERSION &&
90
- request.capability === capability &&
91
- typeof request.id === "string" && request.id.length > 0 && request.id.length <= 128 &&
92
- !seen.has(request.id);
93
- if (!baseValid) {
94
- socket.write('{"action":"deny"}\n');
95
- return;
96
- }
97
- const registered =
98
- request.type === "register" &&
99
- typeof request.workerId === "string" && request.workerId.length > 0 && request.workerId.length <= 128 &&
100
- typeof request.cwd === "string" && request.cwd.startsWith("/") && request.cwd.length <= 4_096 &&
101
- !workers.has(request.workerId);
102
- if (registered) {
103
- seen.add(request.id as string);
104
- workers.set(request.workerId as string, {
105
- cwd: request.cwd as string,
106
- state: "active",
107
- startedAt: Date.now(),
108
- requests: 0,
109
- });
110
- socket.write(`${JSON.stringify({
111
- id: request.id,
112
- action: "allow",
113
- network: {
114
- allowedDomains: [...trustedNetwork.allowedDomains],
115
- deniedDomains: [...trustedNetwork.deniedDomains],
116
- },
117
- })}\n`);
118
- lifecycle.registered?.({
119
- id: request.workerId as string,
120
- cwd: request.cwd as string,
121
- startedAt: workers.get(request.workerId as string)!.startedAt,
122
- });
123
- return;
124
- }
125
- const knownWorker =
126
- typeof request.workerId === "string" && typeof request.cwd === "string"
127
- ? workers.get(request.workerId)
128
- : undefined;
129
- const unregister = request.type === "unregister" &&
130
- knownWorker?.state === "active" && knownWorker.cwd === request.cwd;
131
- if (unregister) {
132
- seen.add(request.id as string);
133
- knownWorker.state = "exited";
134
- socket.write(`${JSON.stringify({ id: request.id, action: "allow" })}\n`);
135
- lifecycle.unregistered?.({
136
- id: request.workerId as string,
137
- cwd: request.cwd as string,
138
- });
139
- return;
140
- }
141
- const workerCwd =
142
- knownWorker?.state === "active" ? knownWorker.cwd : undefined;
143
- const valid = request.type === "network" &&
144
- workerCwd === request.cwd &&
145
- typeof request.hostname === "string" && request.hostname.length <= 253 &&
146
- Number.isInteger(request.port) && Number(request.port) > 0 && Number(request.port) <= 65535 &&
147
- Boolean(workerCwd);
148
- if (!valid) {
149
- socket.write('{"action":"deny"}\n');
150
- return;
151
- }
152
- const approvedRequest = request as Required<Request>;
153
- seen.add(approvedRequest.id as string);
154
- knownWorker!.requests++;
155
- let action: "allow" | "deny" = "deny";
156
- try {
157
- const endpoint: NetworkEndpoint = {
158
- hostname: approvedRequest.hostname as string,
159
- port: approvedRequest.port as number,
160
- protocol: "tcp",
161
- };
162
- action = (await approveDomainEndpoint(endpoint, approvalContext({
163
- id: approvedRequest.workerId as string,
164
- cwd: workerCwd as string,
165
- }))).action;
166
- } catch {
167
- action = "deny";
168
- }
169
- socket.write(`${JSON.stringify({ id: approvedRequest.id, action })}\n`);
170
- };
171
- });
172
- await new Promise<void>((resolve, reject) => {
173
- server.once("error", reject);
174
- server.listen(socketPath, () => resolve());
175
- });
176
- return {
177
- socketPath,
178
- capability,
179
- workers() {
180
- return [...workers.entries()].map(([id, worker]) => ({ id, ...worker }));
181
- },
182
- async close() {
183
- await new Promise<void>((resolve) => server.close(() => resolve()));
184
- await rm(directory, { recursive: true, force: true });
185
- },
186
- };
187
- }
188
-
189
- export const EXTERNAL_SUPERVISOR_PROTOCOL_VERSION = PROTOCOL_VERSION;