@modootoday/pmcp 0.10.0 → 0.12.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/README.md +93 -356
- package/dist/cli.js +12075 -2046
- package/dist/harness-egress-bridge.d.ts +2 -0
- package/dist/harness-egress-bridge.js +74 -0
- package/dist/harness-launch-gate.d.ts +2 -0
- package/dist/harness-launch-gate.js +1187 -0
- package/dist/harness-pane-runner.d.ts +2 -0
- package/dist/harness-pane-runner.js +118 -0
- package/dist/harness-worker.d.ts +2 -0
- package/dist/harness-worker.js +2706 -0
- package/dist/hook-spec-PRSnYGLV.d.ts +10 -0
- package/dist/http.js +2102 -12
- package/dist/index.d.ts +20 -3
- package/dist/index.js +4541 -85
- package/dist/mailbox/index.js +1556 -47
- package/dist/project/hook-adapter.d.ts +11 -0
- package/dist/project/hook-adapter.js +191 -0
- package/dist/stdio.js +3050 -9
- package/docs/embeddings.md +125 -0
- package/docs/hosting.md +46 -0
- package/docs/mailbox.md +59 -0
- package/docs/project-configuration.md +203 -0
- package/docs/skills.md +74 -0
- package/docs/terminal-harness.md +393 -0
- package/package.json +3 -2
- package/dist/chunk-HNAVQG6R.js +0 -1198
- package/dist/chunk-JRSR4JBR.js +0 -88
- package/dist/chunk-MSTKY33C.js +0 -147
- package/dist/chunk-VRZFM2M5.js +0 -1774
- package/dist/chunk-XEH4ZGGJ.js +0 -336
- package/dist/chunk-XOH2CW6P.js +0 -297
- package/dist/chunk-ZUFFVIHQ.js +0 -1191
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
// src/harness/adapters/process/launch-gate.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4
|
+
import { join as join7 } from "path";
|
|
5
|
+
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
6
|
+
|
|
7
|
+
// src/harness/adapters/tmux/records.ts
|
|
8
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
9
|
+
import {
|
|
10
|
+
lstatSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
readdirSync,
|
|
13
|
+
realpathSync as realpathSync2,
|
|
14
|
+
writeFileSync as writeFileSync2
|
|
15
|
+
} from "fs";
|
|
16
|
+
import { homedir } from "os";
|
|
17
|
+
import { join as join2 } from "path";
|
|
18
|
+
|
|
19
|
+
// src/harness/groups/store.ts
|
|
20
|
+
import { randomUUID } from "crypto";
|
|
21
|
+
import {
|
|
22
|
+
closeSync,
|
|
23
|
+
constants,
|
|
24
|
+
fstatSync,
|
|
25
|
+
fsyncSync,
|
|
26
|
+
openSync,
|
|
27
|
+
readFileSync,
|
|
28
|
+
realpathSync,
|
|
29
|
+
renameSync,
|
|
30
|
+
writeFileSync
|
|
31
|
+
} from "fs";
|
|
32
|
+
import { dirname, join } from "path";
|
|
33
|
+
|
|
34
|
+
// src/harness/validation.ts
|
|
35
|
+
function object(value) {
|
|
36
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
37
|
+
throw new Error("invalid_object");
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
function text(value, name) {
|
|
42
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
43
|
+
throw new Error(`invalid_${name}`);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function integer(value, name, min, max) {
|
|
48
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) {
|
|
49
|
+
throw new Error(`invalid_${name}`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function strings(value, name) {
|
|
54
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 64) {
|
|
55
|
+
throw new Error(`invalid_${name}`);
|
|
56
|
+
}
|
|
57
|
+
return value.map((entry) => text(entry, name));
|
|
58
|
+
}
|
|
59
|
+
function id(value, name) {
|
|
60
|
+
const result = text(value, name);
|
|
61
|
+
if (!/^[a-zA-Z0-9-]{1,80}$/.test(result)) throw new Error(`invalid_${name}`);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/harness/runtimes/docker-policy.ts
|
|
66
|
+
import { isAbsolute as isAbsolute3, posix as posix3 } from "path";
|
|
67
|
+
|
|
68
|
+
// src/harness/runtimes/registry.ts
|
|
69
|
+
var runtimeExecutables = {
|
|
70
|
+
"codex-cli": "codex",
|
|
71
|
+
"claude-code": "claude",
|
|
72
|
+
"gemini-cli": "gemini",
|
|
73
|
+
"grok-cli": "grok",
|
|
74
|
+
antigravity: "agy"
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// src/harness/runtimes/egress-policy.ts
|
|
78
|
+
import { isAbsolute, posix } from "path";
|
|
79
|
+
function egressPolicy(value) {
|
|
80
|
+
const policy = object(value);
|
|
81
|
+
const hosts = strings(policy.hosts, "egress_hosts").map(
|
|
82
|
+
(host) => host.toLowerCase()
|
|
83
|
+
);
|
|
84
|
+
if (hosts.length > 16 || new Set(hosts).size !== hosts.length)
|
|
85
|
+
throw new Error("invalid_egress_hosts");
|
|
86
|
+
for (const host of hosts) {
|
|
87
|
+
if (host.length > 253 || !host.includes(".") || /^[0-9.]+$/.test(host) || !host.split(".").every(
|
|
88
|
+
(label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)
|
|
89
|
+
) || /\.(local|localhost|internal|invalid|test)$/.test(host))
|
|
90
|
+
throw new Error("invalid_egress_host");
|
|
91
|
+
}
|
|
92
|
+
if (policy.bridgeExecutable === void 0) return { hosts };
|
|
93
|
+
const executable = text(policy.bridgeExecutable, "egress_bridge_executable");
|
|
94
|
+
if (!isAbsolute(executable) || posix.normalize(executable) !== executable || /[\r\n\x1b]/.test(executable))
|
|
95
|
+
throw new Error("invalid_egress_bridge_executable");
|
|
96
|
+
return { hosts, bridgeExecutable: executable };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/harness/runtimes/private-profiles.ts
|
|
100
|
+
import { isAbsolute as isAbsolute2, posix as posix2 } from "path";
|
|
101
|
+
function profileName(value) {
|
|
102
|
+
const name = text(value, "private_profile");
|
|
103
|
+
if (!/^[a-z][a-z0-9-]{0,31}$/.test(name))
|
|
104
|
+
throw new Error("invalid_private_profile_name");
|
|
105
|
+
return name;
|
|
106
|
+
}
|
|
107
|
+
function privateProfiles(value) {
|
|
108
|
+
const entries = Object.entries(object(value));
|
|
109
|
+
if (!entries.length || entries.length > 16)
|
|
110
|
+
throw new Error("invalid_private_profile_grants");
|
|
111
|
+
const result = {};
|
|
112
|
+
for (const [name, entry] of entries) {
|
|
113
|
+
profileName(name);
|
|
114
|
+
const grant = object(entry);
|
|
115
|
+
const runtime = text(grant.runtime, "private_profile_runtime");
|
|
116
|
+
const directory = absolute(grant.directory, "private_profile_directory");
|
|
117
|
+
const lockExecutable = absolute(
|
|
118
|
+
grant.lockExecutable,
|
|
119
|
+
"private_profile_lock_executable"
|
|
120
|
+
);
|
|
121
|
+
const identity = grant.identity === void 0 ? void 0 : text(grant.identity, "private_profile_identity");
|
|
122
|
+
if (identity !== void 0 && !/^\d+:\d+:\d+:\d+$/.test(identity))
|
|
123
|
+
throw new Error("invalid_private_profile_identity");
|
|
124
|
+
result[name] = {
|
|
125
|
+
runtime,
|
|
126
|
+
directory,
|
|
127
|
+
lockExecutable,
|
|
128
|
+
...identity ? { identity } : {}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (new Set(Object.values(result).map((grant) => grant.directory)).size !== entries.length)
|
|
132
|
+
throw new Error("duplicate_private_profile_directory");
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
function absolute(value, field) {
|
|
136
|
+
const path = text(value, field);
|
|
137
|
+
if (!isAbsolute2(path) || posix2.normalize(path) !== path || /[\r\n\x1b]/.test(path))
|
|
138
|
+
throw new Error("invalid_private_profile_path");
|
|
139
|
+
return path;
|
|
140
|
+
}
|
|
141
|
+
function selectedProfile(policy, name, runtime) {
|
|
142
|
+
if (name === void 0) return;
|
|
143
|
+
const grant = policy.privateProfiles?.[profileName(name)];
|
|
144
|
+
if (!grant || grant.runtime !== runtime || !grant.identity)
|
|
145
|
+
throw new Error("private_profile_not_granted");
|
|
146
|
+
return grant;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/harness/runtimes/docker-policy.ts
|
|
150
|
+
function dockerPolicy(value) {
|
|
151
|
+
const policy = object(value);
|
|
152
|
+
if (policy.schemaVersion !== 1)
|
|
153
|
+
throw new Error("invalid_docker_policy_version");
|
|
154
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(text(policy.image, "docker_image")))
|
|
155
|
+
throw new Error("docker_image_requires_digest");
|
|
156
|
+
if (policy.network !== "none")
|
|
157
|
+
throw new Error("docker_network_not_qualified");
|
|
158
|
+
if (policy.workspaceAccess !== "read-only" && policy.workspaceAccess !== "read-write")
|
|
159
|
+
throw new Error("invalid_docker_workspace_access");
|
|
160
|
+
const commands = object(policy.commands);
|
|
161
|
+
const entries = Object.entries(commands);
|
|
162
|
+
if (!entries.length || entries.length > 5)
|
|
163
|
+
throw new Error("invalid_docker_runtime_commands");
|
|
164
|
+
const parsed = {};
|
|
165
|
+
for (const [runtime, value2] of entries) {
|
|
166
|
+
if (!Object.hasOwn(runtimeExecutables, runtime))
|
|
167
|
+
throw new Error("unsupported_docker_runtime");
|
|
168
|
+
const command2 = strings(value2, "docker_runtime_command");
|
|
169
|
+
const executable = command2[0];
|
|
170
|
+
if (!isAbsolute3(executable) || posix3.normalize(executable) !== executable || command2.some((argument) => /[\r\n\x1b]/.test(argument)))
|
|
171
|
+
throw new Error("invalid_docker_runtime_command");
|
|
172
|
+
parsed[runtime] = command2;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
schemaVersion: 1,
|
|
176
|
+
image: text(policy.image, "docker_image"),
|
|
177
|
+
network: "none",
|
|
178
|
+
workspaceAccess: policy.workspaceAccess,
|
|
179
|
+
commands: parsed,
|
|
180
|
+
...policy.egress === void 0 ? {} : { egress: egressPolicy(policy.egress) },
|
|
181
|
+
...policy.privateProfiles === void 0 ? {} : { privateProfiles: privateProfiles(policy.privateProfiles) }
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/harness/groups/validation.ts
|
|
186
|
+
function member(value, allowed) {
|
|
187
|
+
if (!allowed.includes(value)) throw new Error("invalid_state_value");
|
|
188
|
+
}
|
|
189
|
+
function records(value) {
|
|
190
|
+
if (!Array.isArray(value) || value.length > 4096)
|
|
191
|
+
throw new Error("invalid_state_collection");
|
|
192
|
+
return value.map(object);
|
|
193
|
+
}
|
|
194
|
+
function unique(values) {
|
|
195
|
+
if (new Set(values).size !== values.length)
|
|
196
|
+
throw new Error("duplicate_state_identity");
|
|
197
|
+
}
|
|
198
|
+
function validateState(value) {
|
|
199
|
+
const state = object(value);
|
|
200
|
+
const group = object(state.group);
|
|
201
|
+
member(group.schemaVersion, [1]);
|
|
202
|
+
for (const field of ["owner", "id", "generation"]) id(group[field], field);
|
|
203
|
+
text(group.projectRoot, "project_root");
|
|
204
|
+
strings(group.allowedRuntimes, "allowed_runtimes");
|
|
205
|
+
member(group.profile, ["native", "docker"]);
|
|
206
|
+
if (group.profile === "docker") dockerPolicy(group.dockerPolicy);
|
|
207
|
+
if (group.profile === "native" && group.dockerPolicy !== void 0)
|
|
208
|
+
throw new Error("docker_policy_requires_docker_profile");
|
|
209
|
+
member(group.state, ["ready", "paused", "stopped"]);
|
|
210
|
+
integer(group.memoryMb, "memory_budget", 128, 4096);
|
|
211
|
+
integer(group.maxActive, "max_active", 1, 5);
|
|
212
|
+
const sessions = records(group.sessions);
|
|
213
|
+
for (const session of sessions) {
|
|
214
|
+
id(session.id, "session_id");
|
|
215
|
+
member(session.role, ["main", "worker"]);
|
|
216
|
+
member(session.stopped, [true, false]);
|
|
217
|
+
}
|
|
218
|
+
unique(sessions.map((session) => session.id));
|
|
219
|
+
if (sessions.filter((s) => s.role === "main" && !s.stopped).length > 1)
|
|
220
|
+
throw new Error("multiple_active_main_sessions");
|
|
221
|
+
const leases = records(state.leases);
|
|
222
|
+
for (const lease of leases) {
|
|
223
|
+
id(lease.token, "control_token");
|
|
224
|
+
id(lease.controller, "controller");
|
|
225
|
+
member(lease.generation, [group.generation]);
|
|
226
|
+
member(lease.mode, ["cli", "native"]);
|
|
227
|
+
member(
|
|
228
|
+
lease.sessionId,
|
|
229
|
+
sessions.map((session) => session.id)
|
|
230
|
+
);
|
|
231
|
+
if (!Number.isFinite(Date.parse(text(lease.expiresAt, "control_expiry"))))
|
|
232
|
+
throw new Error("invalid_control_expiry");
|
|
233
|
+
}
|
|
234
|
+
unique(leases.map((lease) => lease.token));
|
|
235
|
+
unique(leases.map((lease) => lease.sessionId));
|
|
236
|
+
const deliveries = records(state.deliveries);
|
|
237
|
+
for (const delivery of deliveries) {
|
|
238
|
+
if (delivery.controller !== void 0)
|
|
239
|
+
id(delivery.controller, "controller");
|
|
240
|
+
id(delivery.requestId, "request_id");
|
|
241
|
+
id(delivery.sessionId, "session_id");
|
|
242
|
+
id(delivery.generation, "generation");
|
|
243
|
+
member(delivery.operation, ["write", "submit"]);
|
|
244
|
+
member(delivery.state, ["intent", "input_written", "delivery_uncertain"]);
|
|
245
|
+
if (!/^[a-f0-9]{64}$/.test(text(delivery.digest, "delivery_digest")))
|
|
246
|
+
throw new Error("invalid_delivery_digest");
|
|
247
|
+
}
|
|
248
|
+
unique(deliveries.map((delivery) => delivery.requestId));
|
|
249
|
+
return state;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/harness/groups/store.ts
|
|
253
|
+
function privateJson(path, value) {
|
|
254
|
+
const temporary = `${path}.${randomUUID()}.new`;
|
|
255
|
+
const descriptor = openSync(temporary, "wx", 384);
|
|
256
|
+
try {
|
|
257
|
+
writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}
|
|
258
|
+
`);
|
|
259
|
+
fsyncSync(descriptor);
|
|
260
|
+
} finally {
|
|
261
|
+
closeSync(descriptor);
|
|
262
|
+
}
|
|
263
|
+
renameSync(temporary, path);
|
|
264
|
+
const directory = openSync(dirname(path), "r");
|
|
265
|
+
try {
|
|
266
|
+
fsyncSync(directory);
|
|
267
|
+
} finally {
|
|
268
|
+
closeSync(directory);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function readPrivateJson(path) {
|
|
272
|
+
const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
273
|
+
try {
|
|
274
|
+
const stat = fstatSync(descriptor);
|
|
275
|
+
if (!stat.isFile() || (stat.mode & 63) !== 0 || stat.size > 2097152)
|
|
276
|
+
throw new Error("unsafe_state_file");
|
|
277
|
+
if (stat.uid !== process.getuid?.()) throw new Error("foreign_state_file");
|
|
278
|
+
return JSON.parse(readFileSync(descriptor, "utf8"));
|
|
279
|
+
} finally {
|
|
280
|
+
closeSync(descriptor);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
var GroupStore = class {
|
|
284
|
+
path;
|
|
285
|
+
state;
|
|
286
|
+
constructor(path) {
|
|
287
|
+
this.path = realpathSync(path);
|
|
288
|
+
this.state = validateState(readPrivateJson(path));
|
|
289
|
+
const metadata = object(
|
|
290
|
+
readPrivateJson(join(dirname(this.path), "harness.json"))
|
|
291
|
+
);
|
|
292
|
+
if (this.state.group.schemaVersion !== 1 || this.state.group.owner !== metadata.owner) {
|
|
293
|
+
throw new Error("foreign_group");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
save() {
|
|
297
|
+
validateState(this.state);
|
|
298
|
+
privateJson(this.path, this.state);
|
|
299
|
+
}
|
|
300
|
+
session(id2) {
|
|
301
|
+
if (!this.state.group.sessions.some((entry) => entry.id === id2)) {
|
|
302
|
+
throw new Error("foreign_session");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// src/harness/adapters/docker/claims.ts
|
|
308
|
+
import { createHash } from "crypto";
|
|
309
|
+
|
|
310
|
+
// src/harness/adapters/docker/identity.ts
|
|
311
|
+
var containerLabels = {
|
|
312
|
+
owner: "pmcp.harness.owner",
|
|
313
|
+
nonce: "pmcp.harness.nonce",
|
|
314
|
+
role: "pmcp.harness.role"
|
|
315
|
+
};
|
|
316
|
+
function containerName(owner, sessionId) {
|
|
317
|
+
return `pmcp-harness-${owner.slice(0, 8)}-${sessionId}`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/harness/adapters/docker/claims.ts
|
|
321
|
+
function resourceClaim(value, owner) {
|
|
322
|
+
const record = object(value);
|
|
323
|
+
if (record.kind !== "docker" || record.owner !== owner)
|
|
324
|
+
throw new Error("foreign_container_resource");
|
|
325
|
+
if (!/^[a-f0-9]{64}$/.test(text(record.id, "container_id")))
|
|
326
|
+
throw new Error("invalid_container_id");
|
|
327
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(text(record.image, "container_image")))
|
|
328
|
+
throw new Error("invalid_container_image");
|
|
329
|
+
if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(
|
|
330
|
+
text(record.nonce, "container_nonce")
|
|
331
|
+
))
|
|
332
|
+
throw new Error("invalid_container_nonce");
|
|
333
|
+
if (record.role !== "main" && record.role !== "worker")
|
|
334
|
+
throw new Error("invalid_container_role");
|
|
335
|
+
if (!/^[a-f0-9]{64}$/.test(
|
|
336
|
+
text(record.configurationDigest, "container_configuration")
|
|
337
|
+
))
|
|
338
|
+
throw new Error("invalid_container_configuration");
|
|
339
|
+
integer(record.memoryMb, "container_memory", 32, 1472);
|
|
340
|
+
integer(record.cpuQuota, "container_cpu", 1, 5e4);
|
|
341
|
+
if (record.managed !== void 0 && record.managed !== true)
|
|
342
|
+
throw new Error("invalid_container_custody");
|
|
343
|
+
if (record.privateProfile !== void 0) {
|
|
344
|
+
profileName(record.privateProfile);
|
|
345
|
+
if (record.managed !== true)
|
|
346
|
+
throw new Error("private_profile_requires_cli_owned_creation");
|
|
347
|
+
}
|
|
348
|
+
return record;
|
|
349
|
+
}
|
|
350
|
+
function assertResourceOwner(resource, actual) {
|
|
351
|
+
const config = object(actual.Config);
|
|
352
|
+
const labels = object(config.Labels);
|
|
353
|
+
if (actual.Id !== resource.id || actual.Image !== resource.image || labels[containerLabels.owner] !== resource.owner || labels[containerLabels.nonce] !== resource.nonce || labels[containerLabels.role] !== resource.role)
|
|
354
|
+
throw new Error("foreign_container_identity");
|
|
355
|
+
}
|
|
356
|
+
function configurationDigest(actual) {
|
|
357
|
+
const host = object(actual.HostConfig);
|
|
358
|
+
if (host.OomKillDisable !== false && host.OomKillDisable !== null)
|
|
359
|
+
throw new Error("container_oom_protection_disabled");
|
|
360
|
+
if (!Array.isArray(actual.Mounts))
|
|
361
|
+
throw new Error("invalid_container_mounts");
|
|
362
|
+
const mounts = actual.Mounts.map(object).sort(
|
|
363
|
+
(left, right) => text(left.Destination, "mount_destination").localeCompare(
|
|
364
|
+
text(right.Destination, "mount_destination")
|
|
365
|
+
)
|
|
366
|
+
);
|
|
367
|
+
return createHash("sha256").update(
|
|
368
|
+
JSON.stringify(
|
|
369
|
+
canonicalValue({
|
|
370
|
+
config: actual.Config,
|
|
371
|
+
host: { ...host, OomKillDisable: false },
|
|
372
|
+
mounts
|
|
373
|
+
})
|
|
374
|
+
)
|
|
375
|
+
).digest("hex");
|
|
376
|
+
}
|
|
377
|
+
function canonicalValue(value) {
|
|
378
|
+
if (Array.isArray(value)) return value.map(canonicalValue);
|
|
379
|
+
if (value === null || typeof value !== "object") return value;
|
|
380
|
+
return Object.fromEntries(
|
|
381
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/harness/adapters/tmux/records.ts
|
|
386
|
+
function stateDirectory() {
|
|
387
|
+
return join2(
|
|
388
|
+
process.env.XDG_STATE_HOME ?? join2(homedir(), ".local/state"),
|
|
389
|
+
"pmcp/harness"
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
function runtimeUnit(id2) {
|
|
393
|
+
if (!/^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(id2))
|
|
394
|
+
throw new Error("invalid_session_id");
|
|
395
|
+
return `pmcp-${id2}.scope`;
|
|
396
|
+
}
|
|
397
|
+
function budgetSlice(owner) {
|
|
398
|
+
runtimeUnit(owner);
|
|
399
|
+
return `pmcp${owner.replaceAll("-", "")}.slice`;
|
|
400
|
+
}
|
|
401
|
+
var RecordStore = class _RecordStore {
|
|
402
|
+
directory;
|
|
403
|
+
socket;
|
|
404
|
+
metadataPath;
|
|
405
|
+
configPath;
|
|
406
|
+
owner;
|
|
407
|
+
constructor(directory) {
|
|
408
|
+
const stat = lstatSync(directory);
|
|
409
|
+
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
410
|
+
throw new Error("unsafe_group_directory");
|
|
411
|
+
this.directory = realpathSync2(directory);
|
|
412
|
+
this.socket = join2(this.directory, "s");
|
|
413
|
+
if (Buffer.byteLength(this.socket) > 100)
|
|
414
|
+
throw new Error("socket_path_too_long");
|
|
415
|
+
this.metadataPath = join2(this.directory, "harness.json");
|
|
416
|
+
this.configPath = join2(this.directory, "tmux.conf");
|
|
417
|
+
this.owner = text(
|
|
418
|
+
object(readPrivateJson(this.metadataPath)).owner,
|
|
419
|
+
"owner"
|
|
420
|
+
);
|
|
421
|
+
runtimeUnit(this.owner);
|
|
422
|
+
}
|
|
423
|
+
static create() {
|
|
424
|
+
const root = stateDirectory();
|
|
425
|
+
mkdirSync(root, { recursive: true, mode: 448 });
|
|
426
|
+
const directory = join2(root, randomUUID2().slice(0, 16));
|
|
427
|
+
if (Buffer.byteLength(join2(directory, "s")) > 100)
|
|
428
|
+
throw new Error("socket_path_too_long");
|
|
429
|
+
mkdirSync(directory, { mode: 448 });
|
|
430
|
+
mkdirSync(join2(directory, "sessions"), { mode: 448 });
|
|
431
|
+
privateJson(join2(directory, "harness.json"), {
|
|
432
|
+
owner: randomUUID2(),
|
|
433
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
434
|
+
});
|
|
435
|
+
writeFileSync2(
|
|
436
|
+
join2(directory, "tmux.conf"),
|
|
437
|
+
[
|
|
438
|
+
"set -g remain-on-exit on",
|
|
439
|
+
"set -g history-limit 2000",
|
|
440
|
+
"set -g default-terminal tmux-256color",
|
|
441
|
+
"set -g status off",
|
|
442
|
+
"set -g mouse off",
|
|
443
|
+
"set -g destroy-unattached off",
|
|
444
|
+
""
|
|
445
|
+
].join("\n"),
|
|
446
|
+
{ flag: "wx", mode: 384 }
|
|
447
|
+
);
|
|
448
|
+
return new _RecordStore(directory);
|
|
449
|
+
}
|
|
450
|
+
path(id2) {
|
|
451
|
+
runtimeUnit(id2);
|
|
452
|
+
return join2(this.directory, "sessions", `${id2}.json`);
|
|
453
|
+
}
|
|
454
|
+
read(id2) {
|
|
455
|
+
const record = object(readPrivateJson(this.path(id2)));
|
|
456
|
+
if (record.id !== id2 || record.owner !== this.owner || record.socket !== this.socket || record.unit !== runtimeUnit(id2))
|
|
457
|
+
throw new Error("foreign_backend_record");
|
|
458
|
+
for (const [field, pattern] of [
|
|
459
|
+
["paneId", /^%\d+$/],
|
|
460
|
+
["windowId", /^@\d+$/],
|
|
461
|
+
["tmuxSessionId", /^\$\d+$/],
|
|
462
|
+
["serverIdentity", /^\d+:\d+$/]
|
|
463
|
+
]) {
|
|
464
|
+
if (!pattern.test(text(record[field], field)))
|
|
465
|
+
throw new Error("invalid_terminal_identity");
|
|
466
|
+
}
|
|
467
|
+
if (!Number.isSafeInteger(record.panePid) || Number(record.panePid) < 1)
|
|
468
|
+
throw new Error("invalid_pane_process");
|
|
469
|
+
if (record.resource !== void 0)
|
|
470
|
+
resourceClaim(record.resource, this.owner);
|
|
471
|
+
return record;
|
|
472
|
+
}
|
|
473
|
+
all() {
|
|
474
|
+
return readdirSync(join2(this.directory, "sessions")).filter((name) => name.endsWith(".json")).map((name) => this.read(name.slice(0, -5)));
|
|
475
|
+
}
|
|
476
|
+
save(record) {
|
|
477
|
+
privateJson(this.path(record.id), record);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// src/harness/adapters/process/command.ts
|
|
482
|
+
import {
|
|
483
|
+
execFileSync
|
|
484
|
+
} from "child_process";
|
|
485
|
+
function command(binary, args, options = {}) {
|
|
486
|
+
return execFileSync(binary, args, {
|
|
487
|
+
encoding: "utf8",
|
|
488
|
+
timeout: 1e4,
|
|
489
|
+
maxBuffer: 256 * 1024,
|
|
490
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
491
|
+
...options
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/harness/adapters/process/identity.ts
|
|
496
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
497
|
+
function processIdentity(pid) {
|
|
498
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
499
|
+
throw new Error("invalid_process_id");
|
|
500
|
+
const stat = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
501
|
+
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
502
|
+
if (!/^\d+$/.test(fields[19] ?? ""))
|
|
503
|
+
throw new Error("process_identity_unknown");
|
|
504
|
+
return `${pid}:${fields[19]}`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/harness/adapters/docker/observation.ts
|
|
508
|
+
function inspectResource(resource, enforceConfiguration = true) {
|
|
509
|
+
const result = JSON.parse(command("docker", ["inspect", resource.id]));
|
|
510
|
+
if (!Array.isArray(result) || result.length !== 1)
|
|
511
|
+
throw new Error("invalid_container_inspection");
|
|
512
|
+
const actual = object(result[0]);
|
|
513
|
+
assertResourceOwner(resource, actual);
|
|
514
|
+
if (enforceConfiguration && configurationDigest(actual) !== resource.configurationDigest)
|
|
515
|
+
throw new Error("container_configuration_changed");
|
|
516
|
+
return actual;
|
|
517
|
+
}
|
|
518
|
+
function resourceAbsent(error, id2) {
|
|
519
|
+
const failure = error;
|
|
520
|
+
const message = String(failure.stderr).toLowerCase();
|
|
521
|
+
return ["object", "container"].some(
|
|
522
|
+
(kind) => message.includes(`no such ${kind}: ${id2}`)
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
function assertResourceFresh(resource) {
|
|
526
|
+
const state = object(inspectResource(resource).State);
|
|
527
|
+
if (state.Status !== "created" || state.Running !== false || state.Pid !== 0)
|
|
528
|
+
throw new Error("container_requires_fresh_creation");
|
|
529
|
+
}
|
|
530
|
+
function resourceRunning(resource) {
|
|
531
|
+
let actual;
|
|
532
|
+
try {
|
|
533
|
+
actual = inspectResource(resource);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
if (resourceAbsent(error, resource.id)) return false;
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
const state = object(actual.State);
|
|
539
|
+
if (typeof state.Running !== "boolean")
|
|
540
|
+
throw new Error("unknown_container_lifecycle");
|
|
541
|
+
return state.Running;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/harness/adapters/tmux/connection.ts
|
|
545
|
+
var Connection = class {
|
|
546
|
+
constructor(records3) {
|
|
547
|
+
this.records = records3;
|
|
548
|
+
}
|
|
549
|
+
records;
|
|
550
|
+
tmux(args, options = {}) {
|
|
551
|
+
return command(
|
|
552
|
+
"tmux",
|
|
553
|
+
["-S", this.records.socket, "-f", this.records.configPath, ...args],
|
|
554
|
+
options
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
alive() {
|
|
558
|
+
try {
|
|
559
|
+
this.tmux(["list-sessions"]);
|
|
560
|
+
return true;
|
|
561
|
+
} catch (error) {
|
|
562
|
+
const failure = error;
|
|
563
|
+
if (/no server running|No such file or directory|no sessions/.test(
|
|
564
|
+
String(failure.stderr ?? failure.message)
|
|
565
|
+
))
|
|
566
|
+
return false;
|
|
567
|
+
throw error;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
inspect(id2) {
|
|
571
|
+
const record = this.records.read(id2);
|
|
572
|
+
const target = this.tmux([
|
|
573
|
+
"display-message",
|
|
574
|
+
"-p",
|
|
575
|
+
"-t",
|
|
576
|
+
record.paneId,
|
|
577
|
+
"#{pid}|#{pane_id}|#{pane_pid}|#{pane_dead}|#{@pmcp-owner}|#{@pmcp-session}|#{pane_width}|#{pane_height}|#{session_id}|#{window_id}"
|
|
578
|
+
]).trim().split("|");
|
|
579
|
+
const [
|
|
580
|
+
serverPid,
|
|
581
|
+
paneId,
|
|
582
|
+
panePid,
|
|
583
|
+
dead,
|
|
584
|
+
owner,
|
|
585
|
+
sessionId,
|
|
586
|
+
width,
|
|
587
|
+
height,
|
|
588
|
+
tmuxSessionId,
|
|
589
|
+
windowId
|
|
590
|
+
] = target;
|
|
591
|
+
if (processIdentity(Number(serverPid)) !== record.serverIdentity)
|
|
592
|
+
throw new Error("stale_server");
|
|
593
|
+
if (paneId !== record.paneId || Number(panePid) !== record.panePid || tmuxSessionId !== record.tmuxSessionId || windowId !== record.windowId)
|
|
594
|
+
throw new Error("stale_pane");
|
|
595
|
+
if (owner !== this.records.owner || sessionId !== id2)
|
|
596
|
+
throw new Error("foreign_pane");
|
|
597
|
+
return {
|
|
598
|
+
...record,
|
|
599
|
+
alive: dead === "0" && (!record.resource || resourceRunning(record.resource)),
|
|
600
|
+
width: Number(width),
|
|
601
|
+
height: Number(height)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// src/harness/adapters/tmux/launch-identity.ts
|
|
607
|
+
function createProof(connection2, intent2) {
|
|
608
|
+
const paneId = text(process.env.TMUX_PANE, "native_pane");
|
|
609
|
+
if (!/^%\d+$/.test(paneId)) throw new Error("invalid_native_pane");
|
|
610
|
+
const socket = text(process.env.TMUX, "native_tmux").split(",")[0];
|
|
611
|
+
if (socket !== intent2.socket) throw new Error("foreign_native_socket");
|
|
612
|
+
connection2.tmux([
|
|
613
|
+
"set-option",
|
|
614
|
+
"-p",
|
|
615
|
+
"-t",
|
|
616
|
+
paneId,
|
|
617
|
+
"@pmcp-owner",
|
|
618
|
+
intent2.owner
|
|
619
|
+
]);
|
|
620
|
+
connection2.tmux([
|
|
621
|
+
"set-option",
|
|
622
|
+
"-p",
|
|
623
|
+
"-t",
|
|
624
|
+
paneId,
|
|
625
|
+
"@pmcp-session",
|
|
626
|
+
intent2.id
|
|
627
|
+
]);
|
|
628
|
+
connection2.tmux([
|
|
629
|
+
"set-option",
|
|
630
|
+
"-p",
|
|
631
|
+
"-t",
|
|
632
|
+
paneId,
|
|
633
|
+
"@pmcp-launch",
|
|
634
|
+
intent2.nonce
|
|
635
|
+
]);
|
|
636
|
+
const fields = connection2.tmux([
|
|
637
|
+
"display-message",
|
|
638
|
+
"-p",
|
|
639
|
+
"-t",
|
|
640
|
+
paneId,
|
|
641
|
+
"#{session_id}|#{window_id}|#{pane_id}|#{pane_pid}|#{pid}"
|
|
642
|
+
]).trim().split("|");
|
|
643
|
+
return {
|
|
644
|
+
nonce: intent2.nonce,
|
|
645
|
+
runnerIdentity: processIdentity(process.pid),
|
|
646
|
+
session: {
|
|
647
|
+
id: intent2.id,
|
|
648
|
+
owner: intent2.owner,
|
|
649
|
+
socket: intent2.socket,
|
|
650
|
+
runtime: intent2.runtime,
|
|
651
|
+
cwd: intent2.cwd,
|
|
652
|
+
memoryMb: intent2.memoryMb,
|
|
653
|
+
tmuxSessionId: text(fields[0], "session"),
|
|
654
|
+
windowId: text(fields[1], "window"),
|
|
655
|
+
paneId: text(fields[2], "pane"),
|
|
656
|
+
panePid: integer(
|
|
657
|
+
Number(fields[3]),
|
|
658
|
+
"pane_process",
|
|
659
|
+
1,
|
|
660
|
+
Number.MAX_SAFE_INTEGER
|
|
661
|
+
),
|
|
662
|
+
serverIdentity: processIdentity(Number(fields[4])),
|
|
663
|
+
unit: intent2.unit,
|
|
664
|
+
createdAt: intent2.createdAt,
|
|
665
|
+
...intent2.resource ? { resource: intent2.resource } : {}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function assertProof(connection2, proof2) {
|
|
670
|
+
const record = proof2.session;
|
|
671
|
+
const fields = connection2.tmux([
|
|
672
|
+
"display-message",
|
|
673
|
+
"-p",
|
|
674
|
+
"-t",
|
|
675
|
+
record.paneId,
|
|
676
|
+
"#{pid}|#{session_id}|#{window_id}|#{pane_id}|#{pane_pid}|#{@pmcp-owner}|#{@pmcp-session}|#{@pmcp-launch}"
|
|
677
|
+
]).trim().split("|");
|
|
678
|
+
if (processIdentity(Number(fields[0])) !== record.serverIdentity)
|
|
679
|
+
throw new Error("stale_launch_server");
|
|
680
|
+
if (fields[1] !== record.tmuxSessionId || fields[2] !== record.windowId || fields[3] !== record.paneId || Number(fields[4]) !== record.panePid)
|
|
681
|
+
throw new Error("stale_launch_pane");
|
|
682
|
+
if (fields[5] !== record.owner || fields[6] !== record.id || fields[7] !== proof2.nonce)
|
|
683
|
+
throw new Error("foreign_launch_pane");
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/harness/launch/journal.ts
|
|
687
|
+
import { createHash as createHash2 } from "crypto";
|
|
688
|
+
import { existsSync, readdirSync as readdirSync2 } from "fs";
|
|
689
|
+
import { join as join3 } from "path";
|
|
690
|
+
var LaunchJournal = class {
|
|
691
|
+
constructor(records3, id2) {
|
|
692
|
+
this.records = records3;
|
|
693
|
+
this.id = id2;
|
|
694
|
+
runtimeUnit(id2);
|
|
695
|
+
}
|
|
696
|
+
records;
|
|
697
|
+
id;
|
|
698
|
+
path(kind) {
|
|
699
|
+
if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind))
|
|
700
|
+
throw new Error("invalid_launch_artifact");
|
|
701
|
+
return join3(this.records.directory, `${this.id}.${kind}.json`);
|
|
702
|
+
}
|
|
703
|
+
intent() {
|
|
704
|
+
const value = object(readPrivateJson(this.path("intent")));
|
|
705
|
+
if (value.id !== this.id || value.owner !== this.records.owner || value.socket !== this.records.socket || value.unit !== runtimeUnit(this.id) || value.slice !== budgetSlice(this.records.owner))
|
|
706
|
+
throw new Error("foreign_launch_intent");
|
|
707
|
+
if (!/^\d+:\d+$/.test(text(value.controllerIdentity, "controller")))
|
|
708
|
+
throw new Error("invalid_launch_controller");
|
|
709
|
+
for (const field of ["groupId", "generation", "nonce"])
|
|
710
|
+
runtimeUnit(text(value[field], field));
|
|
711
|
+
if (value.role !== "main" && value.role !== "worker")
|
|
712
|
+
throw new Error("invalid_launch_role");
|
|
713
|
+
strings(value.argv, "argv");
|
|
714
|
+
text(value.cwd, "cwd");
|
|
715
|
+
text(value.runtime, "runtime");
|
|
716
|
+
text(value.createdAt, "created_at");
|
|
717
|
+
integer(value.memoryMb, "session_memory", 32, 1536);
|
|
718
|
+
if (value.resource !== void 0)
|
|
719
|
+
resourceClaim(value.resource, this.records.owner);
|
|
720
|
+
if (value.dockerCreation !== void 0) {
|
|
721
|
+
const creation2 = object(value.dockerCreation);
|
|
722
|
+
const config = object(creation2.configuration);
|
|
723
|
+
const labels = object(config.Labels);
|
|
724
|
+
if (creation2.egress !== void 0) egressPolicy(creation2.egress);
|
|
725
|
+
const bridge = creation2.egress === void 0 ? void 0 : egressPolicy(creation2.egress).bridgeExecutable;
|
|
726
|
+
if (bridge && !/^[a-f0-9]{64}$/.test(String(creation2.bridgeSha256)))
|
|
727
|
+
throw new Error("invalid_docker_bridge_digest");
|
|
728
|
+
if (!bridge && creation2.bridgeSha256 !== void 0)
|
|
729
|
+
throw new Error("ungranted_docker_bridge_digest");
|
|
730
|
+
const policy = new GroupStore(join3(this.records.directory, "group.json")).state.group.dockerPolicy;
|
|
731
|
+
const profile = selectedProfile(
|
|
732
|
+
policy,
|
|
733
|
+
creation2.privateProfile,
|
|
734
|
+
text(value.runtime, "runtime")
|
|
735
|
+
);
|
|
736
|
+
const expectedState = profile?.directory ?? join3(
|
|
737
|
+
this.records.directory,
|
|
738
|
+
"containers",
|
|
739
|
+
text(value.nonce, "nonce"),
|
|
740
|
+
"profile"
|
|
741
|
+
);
|
|
742
|
+
if (value.resource !== void 0 && object(value.resource).privateProfile !== creation2.privateProfile)
|
|
743
|
+
throw new Error("launch_private_profile_mismatch");
|
|
744
|
+
if (creation2.name !== containerName(this.records.owner, this.id) || creation2.nonce !== value.nonce || creation2.state !== expectedState || labels[containerLabels.owner] !== this.records.owner || labels[containerLabels.role] !== value.role || labels[containerLabels.nonce] !== value.nonce)
|
|
745
|
+
throw new Error("foreign_container_creation");
|
|
746
|
+
}
|
|
747
|
+
return value;
|
|
748
|
+
}
|
|
749
|
+
proof() {
|
|
750
|
+
const intent2 = this.intent();
|
|
751
|
+
const value = object(readPrivateJson(this.path("proof")));
|
|
752
|
+
const session = object(value.session);
|
|
753
|
+
if (value.nonce !== intent2.nonce || session.id !== intent2.id || session.owner !== intent2.owner || session.socket !== intent2.socket || session.unit !== intent2.unit || session.cwd !== intent2.cwd || session.memoryMb !== intent2.memoryMb || session.runtime !== intent2.runtime || session.createdAt !== intent2.createdAt || JSON.stringify(session.resource) !== JSON.stringify(intent2.resource))
|
|
754
|
+
throw new Error("foreign_launch_proof");
|
|
755
|
+
for (const [field, pattern] of [
|
|
756
|
+
["paneId", /^%\d+$/],
|
|
757
|
+
["windowId", /^@\d+$/],
|
|
758
|
+
["tmuxSessionId", /^\$\d+$/],
|
|
759
|
+
["serverIdentity", /^\d+:\d+$/]
|
|
760
|
+
]) {
|
|
761
|
+
if (!pattern.test(text(session[field], field)))
|
|
762
|
+
throw new Error("invalid_launch_identity");
|
|
763
|
+
}
|
|
764
|
+
integer(session.panePid, "pane_process", 1, Number.MAX_SAFE_INTEGER);
|
|
765
|
+
if (!/^\d+:\d+$/.test(text(value.runnerIdentity, "runner")))
|
|
766
|
+
throw new Error("invalid_launch_runner");
|
|
767
|
+
return value;
|
|
768
|
+
}
|
|
769
|
+
grant() {
|
|
770
|
+
const intent2 = this.intent();
|
|
771
|
+
return {
|
|
772
|
+
id: intent2.id,
|
|
773
|
+
owner: intent2.owner,
|
|
774
|
+
groupId: intent2.groupId,
|
|
775
|
+
generation: intent2.generation,
|
|
776
|
+
nonce: intent2.nonce,
|
|
777
|
+
proofDigest: createHash2("sha256").update(JSON.stringify(this.proof())).digest("hex")
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
assertRegistered() {
|
|
781
|
+
const intent2 = this.intent();
|
|
782
|
+
const group = new GroupStore(join3(this.records.directory, "group.json")).state.group;
|
|
783
|
+
if (group.id !== intent2.groupId || group.generation !== intent2.generation)
|
|
784
|
+
throw new Error("launch_group_generation_mismatch");
|
|
785
|
+
if (intent2.dockerCreation && JSON.stringify(intent2.dockerCreation.egress) !== JSON.stringify(group.dockerPolicy?.egress))
|
|
786
|
+
throw new Error("launch_egress_grant_mismatch");
|
|
787
|
+
if (group.state !== "ready" || !group.allowedRuntimes.includes(intent2.runtime))
|
|
788
|
+
throw new Error("launch_group_not_ready");
|
|
789
|
+
if (!group.sessions.some(
|
|
790
|
+
(session) => session.id === intent2.id && session.role === intent2.role && !session.stopped
|
|
791
|
+
))
|
|
792
|
+
throw new Error("launch_registration_missing");
|
|
793
|
+
const record = this.records.read(this.id);
|
|
794
|
+
if (JSON.stringify(record) !== JSON.stringify(this.proof().session))
|
|
795
|
+
throw new Error("launch_record_mismatch");
|
|
796
|
+
}
|
|
797
|
+
authorize() {
|
|
798
|
+
if (existsSync(this.path("aborted"))) throw new Error("launch_was_aborted");
|
|
799
|
+
this.assertRegistered();
|
|
800
|
+
privateJson(this.path("grant"), this.grant());
|
|
801
|
+
}
|
|
802
|
+
assertGrant() {
|
|
803
|
+
if (existsSync(this.path("aborted"))) throw new Error("launch_was_aborted");
|
|
804
|
+
const grant = object(readPrivateJson(this.path("grant")));
|
|
805
|
+
if (JSON.stringify(grant) !== JSON.stringify(this.grant()))
|
|
806
|
+
throw new Error("launch_grant_mismatch");
|
|
807
|
+
}
|
|
808
|
+
commit() {
|
|
809
|
+
this.assertRegistered();
|
|
810
|
+
this.assertGrant();
|
|
811
|
+
privateJson(this.path("committed"), this.grant());
|
|
812
|
+
}
|
|
813
|
+
assertCommitted() {
|
|
814
|
+
this.assertGrant();
|
|
815
|
+
const committed = object(readPrivateJson(this.path("committed")));
|
|
816
|
+
if (JSON.stringify(committed) !== JSON.stringify(this.grant()))
|
|
817
|
+
throw new Error("launch_commit_mismatch");
|
|
818
|
+
}
|
|
819
|
+
abortRecord() {
|
|
820
|
+
if (!existsSync(this.path("aborted"))) return;
|
|
821
|
+
const value = object(readPrivateJson(this.path("aborted")));
|
|
822
|
+
if (value.id !== this.id || value.owner !== this.records.owner || !["stopping", "stopped"].includes(String(value.state)))
|
|
823
|
+
throw new Error("foreign_launch_abort");
|
|
824
|
+
return value;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// src/harness/adapters/egress/server.ts
|
|
829
|
+
import { createServer } from "net";
|
|
830
|
+
|
|
831
|
+
// src/harness/adapters/egress/destination.ts
|
|
832
|
+
import { BlockList, isIP } from "net";
|
|
833
|
+
var denied = new BlockList();
|
|
834
|
+
for (const [network, prefix] of [
|
|
835
|
+
["0.0.0.0", 8],
|
|
836
|
+
["10.0.0.0", 8],
|
|
837
|
+
["100.64.0.0", 10],
|
|
838
|
+
["127.0.0.0", 8],
|
|
839
|
+
["169.254.0.0", 16],
|
|
840
|
+
["172.16.0.0", 12],
|
|
841
|
+
["192.0.0.0", 24],
|
|
842
|
+
["192.0.2.0", 24],
|
|
843
|
+
["192.88.99.0", 24],
|
|
844
|
+
["192.168.0.0", 16],
|
|
845
|
+
["198.18.0.0", 15],
|
|
846
|
+
["198.51.100.0", 24],
|
|
847
|
+
["203.0.113.0", 24],
|
|
848
|
+
["224.0.0.0", 4],
|
|
849
|
+
["240.0.0.0", 4]
|
|
850
|
+
])
|
|
851
|
+
denied.addSubnet(network, prefix, "ipv4");
|
|
852
|
+
function publicAddress(address) {
|
|
853
|
+
return isIP(address) === 4 && !denied.check(address, "ipv4");
|
|
854
|
+
}
|
|
855
|
+
function destination(header, hosts) {
|
|
856
|
+
if (header.some((value) => value > 127 || value === 0)) return;
|
|
857
|
+
const firstLine = header.toString("ascii").split("\r\n")[0] ?? "";
|
|
858
|
+
const match = /^CONNECT ([a-z0-9.-]+):443 HTTP\/1\.[01]$/i.exec(firstLine);
|
|
859
|
+
const host = match?.[1]?.toLowerCase();
|
|
860
|
+
if (!host || !hosts.includes(host)) return;
|
|
861
|
+
return host;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/harness/adapters/egress/socket.ts
|
|
865
|
+
import { chmodSync, closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2, openSync as openSync2 } from "fs";
|
|
866
|
+
import { join as join4 } from "path";
|
|
867
|
+
async function listenPrivateSocket(server, directory, name) {
|
|
868
|
+
if (!/^e[a-f0-9]{8}$/.test(name))
|
|
869
|
+
throw new Error("invalid_egress_socket_name");
|
|
870
|
+
const descriptor = openSync2(
|
|
871
|
+
directory,
|
|
872
|
+
constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW
|
|
873
|
+
);
|
|
874
|
+
const stat = fstatSync2(descriptor);
|
|
875
|
+
if (stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0) {
|
|
876
|
+
closeSync2(descriptor);
|
|
877
|
+
throw new Error("unsafe_egress_directory");
|
|
878
|
+
}
|
|
879
|
+
const path = join4(`/proc/self/fd/${descriptor}`, name);
|
|
880
|
+
try {
|
|
881
|
+
await new Promise((resolve, reject) => {
|
|
882
|
+
server.once("error", reject);
|
|
883
|
+
server.listen(path, resolve);
|
|
884
|
+
});
|
|
885
|
+
chmodSync(path, 384);
|
|
886
|
+
} catch (error) {
|
|
887
|
+
if (server.listening)
|
|
888
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
889
|
+
closeSync2(descriptor);
|
|
890
|
+
throw error;
|
|
891
|
+
}
|
|
892
|
+
return () => closeSync2(descriptor);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/harness/adapters/egress/tunnel.ts
|
|
896
|
+
import { lookup } from "dns/promises";
|
|
897
|
+
import { connect } from "net";
|
|
898
|
+
async function openTunnel(client, host, initial, sockets, record) {
|
|
899
|
+
let timer;
|
|
900
|
+
try {
|
|
901
|
+
const addresses = await Promise.race([
|
|
902
|
+
lookup(host, { family: 4, all: true }),
|
|
903
|
+
new Promise((_, reject) => {
|
|
904
|
+
timer = setTimeout(() => reject(new Error("egress_dns_timeout")), 5e3);
|
|
905
|
+
})
|
|
906
|
+
]);
|
|
907
|
+
if (client.destroyed) return;
|
|
908
|
+
if (!addresses.length || addresses.some((value) => !publicAddress(value.address)))
|
|
909
|
+
throw new Error("non_public_destination");
|
|
910
|
+
const upstream = connect({ host: addresses[0].address, port: 443 });
|
|
911
|
+
sockets.add(upstream);
|
|
912
|
+
upstream.once("close", () => sockets.delete(upstream));
|
|
913
|
+
upstream.setTimeout(6e4, () => upstream.destroy());
|
|
914
|
+
upstream.once("error", () => client.destroy());
|
|
915
|
+
client.once("close", () => upstream.destroy());
|
|
916
|
+
upstream.once("close", () => client.destroy());
|
|
917
|
+
upstream.once("connect", () => {
|
|
918
|
+
if (client.destroyed) {
|
|
919
|
+
upstream.destroy();
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
record({ host, allowed: true });
|
|
923
|
+
client.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
924
|
+
if (initial.length) upstream.write(initial);
|
|
925
|
+
client.pipe(upstream);
|
|
926
|
+
upstream.pipe(client);
|
|
927
|
+
});
|
|
928
|
+
} catch {
|
|
929
|
+
if (client.destroyed) return;
|
|
930
|
+
record({ host, allowed: false, reason: "destination_resolution_failed" });
|
|
931
|
+
client.end(
|
|
932
|
+
"HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n",
|
|
933
|
+
() => client.destroy()
|
|
934
|
+
);
|
|
935
|
+
} finally {
|
|
936
|
+
if (timer) clearTimeout(timer);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// src/harness/adapters/egress/server.ts
|
|
941
|
+
async function createEgressProxy(directory, name, hosts, onEvent) {
|
|
942
|
+
const policy = egressPolicy({ hosts });
|
|
943
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
944
|
+
const events = [];
|
|
945
|
+
const record = (event) => {
|
|
946
|
+
if (events.length >= 128) events.shift();
|
|
947
|
+
events.push(event);
|
|
948
|
+
onEvent([...events]);
|
|
949
|
+
};
|
|
950
|
+
const server = createServer((client) => {
|
|
951
|
+
if (sockets.size >= 16) {
|
|
952
|
+
client.destroy();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
sockets.add(client);
|
|
956
|
+
client.once("close", () => sockets.delete(client));
|
|
957
|
+
client.on("error", () => {
|
|
958
|
+
});
|
|
959
|
+
client.setTimeout(6e4, () => client.destroy());
|
|
960
|
+
let buffer = Buffer.alloc(0);
|
|
961
|
+
function headers(chunk) {
|
|
962
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
963
|
+
if (buffer.length > 16384) {
|
|
964
|
+
client.destroy();
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const boundary = buffer.indexOf("\r\n\r\n");
|
|
968
|
+
if (boundary < 0) return;
|
|
969
|
+
client.off("data", headers);
|
|
970
|
+
client.pause();
|
|
971
|
+
const host = destination(buffer.subarray(0, boundary), policy.hosts);
|
|
972
|
+
if (!host) {
|
|
973
|
+
record({ allowed: false, reason: "destination_not_allowed" });
|
|
974
|
+
client.end(
|
|
975
|
+
"HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n",
|
|
976
|
+
() => client.destroy()
|
|
977
|
+
);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
void openTunnel(
|
|
981
|
+
client,
|
|
982
|
+
host,
|
|
983
|
+
buffer.subarray(boundary + 4),
|
|
984
|
+
sockets,
|
|
985
|
+
record
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
client.on("data", headers);
|
|
989
|
+
});
|
|
990
|
+
const release = await listenPrivateSocket(server, directory, name);
|
|
991
|
+
return {
|
|
992
|
+
async close() {
|
|
993
|
+
for (const socket of sockets) socket.destroy();
|
|
994
|
+
await new Promise(
|
|
995
|
+
(resolve, reject) => server.close((error) => error ? reject(error) : resolve())
|
|
996
|
+
);
|
|
997
|
+
release();
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/harness/adapters/docker/bridge-artifact.ts
|
|
1003
|
+
import { createHash as createHash3 } from "crypto";
|
|
1004
|
+
import {
|
|
1005
|
+
closeSync as closeSync4,
|
|
1006
|
+
constants as constants4,
|
|
1007
|
+
fstatSync as fstatSync4,
|
|
1008
|
+
fsyncSync as fsyncSync2,
|
|
1009
|
+
openSync as openSync4,
|
|
1010
|
+
readFileSync as readFileSync3,
|
|
1011
|
+
writeFileSync as writeFileSync3
|
|
1012
|
+
} from "fs";
|
|
1013
|
+
import { join as join6 } from "path";
|
|
1014
|
+
|
|
1015
|
+
// src/harness/adapters/process/artifact.ts
|
|
1016
|
+
import { fileURLToPath } from "url";
|
|
1017
|
+
|
|
1018
|
+
// src/harness/adapters/docker/private-profile.ts
|
|
1019
|
+
import { homedir as homedir2 } from "os";
|
|
1020
|
+
import {
|
|
1021
|
+
closeSync as closeSync3,
|
|
1022
|
+
constants as constants3,
|
|
1023
|
+
existsSync as existsSync2,
|
|
1024
|
+
fstatSync as fstatSync3,
|
|
1025
|
+
lstatSync as lstatSync2,
|
|
1026
|
+
openSync as openSync3,
|
|
1027
|
+
realpathSync as realpathSync3,
|
|
1028
|
+
statfsSync
|
|
1029
|
+
} from "fs";
|
|
1030
|
+
import { join as join5, relative } from "path";
|
|
1031
|
+
var privateProfileLock = "profile.lock";
|
|
1032
|
+
function overlap(left, right) {
|
|
1033
|
+
const scope = relative(left, right);
|
|
1034
|
+
return scope === "" || scope !== ".." && !scope.startsWith("../");
|
|
1035
|
+
}
|
|
1036
|
+
function profileIdentity(directory, projectRoot) {
|
|
1037
|
+
if (directory === "/" || directory === realpathSync3(homedir2()) || overlap(directory, projectRoot) || overlap(projectRoot, directory) || realpathSync3(directory) !== directory)
|
|
1038
|
+
throw new Error("private_profile_scope_not_admitted");
|
|
1039
|
+
const stat = lstatSync2(directory);
|
|
1040
|
+
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 511) !== 448)
|
|
1041
|
+
throw new Error("unsafe_private_profile_directory");
|
|
1042
|
+
if ([26985, 4283649346, 4266872130].includes(statfsSync(directory).type))
|
|
1043
|
+
throw new Error("remote_private_profile_not_admitted");
|
|
1044
|
+
const lock = openSync3(
|
|
1045
|
+
join5(directory, privateProfileLock),
|
|
1046
|
+
constants3.O_RDONLY | constants3.O_NOFOLLOW
|
|
1047
|
+
);
|
|
1048
|
+
try {
|
|
1049
|
+
const value = fstatSync3(lock);
|
|
1050
|
+
if (!value.isFile() || value.nlink !== 1 || value.uid !== process.getuid?.() || (value.mode & 511) !== 384)
|
|
1051
|
+
throw new Error("unsafe_private_profile_lock");
|
|
1052
|
+
return `${stat.dev}:${stat.ino}:${value.dev}:${value.ino}`;
|
|
1053
|
+
} finally {
|
|
1054
|
+
closeSync3(lock);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function assertProfileIdentity(grant, projectRoot) {
|
|
1058
|
+
if (!grant.identity || profileIdentity(grant.directory, projectRoot) !== grant.identity)
|
|
1059
|
+
throw new Error("private_profile_identity_changed");
|
|
1060
|
+
}
|
|
1061
|
+
function assertProfileAvailable(grant, projectRoot) {
|
|
1062
|
+
assertProfileIdentity(grant, projectRoot);
|
|
1063
|
+
try {
|
|
1064
|
+
command("flock", [
|
|
1065
|
+
"--exclusive",
|
|
1066
|
+
"--nonblock",
|
|
1067
|
+
"--conflict-exit-code",
|
|
1068
|
+
"73",
|
|
1069
|
+
join5(grant.directory, privateProfileLock),
|
|
1070
|
+
"true"
|
|
1071
|
+
]);
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
if (error.status === 73)
|
|
1074
|
+
throw new Error("private_profile_busy");
|
|
1075
|
+
throw error;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/harness/adapters/docker/entrypoint.ts
|
|
1080
|
+
function bridgeName(nonce) {
|
|
1081
|
+
return `.pmcp-egress-${nonce.slice(0, 8)}.mjs`;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/harness/adapters/docker/bridge-artifact.ts
|
|
1085
|
+
function digest(value) {
|
|
1086
|
+
return createHash3("sha256").update(value).digest("hex");
|
|
1087
|
+
}
|
|
1088
|
+
function assertBridge(creation2) {
|
|
1089
|
+
if (!creation2.egress?.bridgeExecutable) return;
|
|
1090
|
+
const descriptor = openSync4(
|
|
1091
|
+
join6(creation2.state, bridgeName(creation2.nonce)),
|
|
1092
|
+
constants4.O_RDONLY | constants4.O_NOFOLLOW
|
|
1093
|
+
);
|
|
1094
|
+
try {
|
|
1095
|
+
const stat = fstatSync4(descriptor);
|
|
1096
|
+
if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.nlink !== 1 || (stat.mode & 63) !== 0 || stat.size > 2097152 || digest(readFileSync3(descriptor)) !== creation2.bridgeSha256)
|
|
1097
|
+
throw new Error("docker_bridge_artifact_changed");
|
|
1098
|
+
} finally {
|
|
1099
|
+
closeSync4(descriptor);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/harness/adapters/docker/private-creation.ts
|
|
1104
|
+
function assertPrivateCreation(creation2, group, runtime) {
|
|
1105
|
+
const profile = selectedProfile(
|
|
1106
|
+
group.dockerPolicy,
|
|
1107
|
+
creation2.privateProfile,
|
|
1108
|
+
runtime
|
|
1109
|
+
);
|
|
1110
|
+
if (!profile) return;
|
|
1111
|
+
if (creation2.state !== profile.directory)
|
|
1112
|
+
throw new Error("foreign_container_state_directory");
|
|
1113
|
+
assertProfileAvailable(profile, group.projectRoot);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// src/harness/adapters/process/launch-gate.ts
|
|
1117
|
+
var records2 = new RecordStore(text(process.argv[2], "directory"));
|
|
1118
|
+
var journal = new LaunchJournal(records2, text(process.argv[3], "session_id"));
|
|
1119
|
+
var connection = new Connection(records2);
|
|
1120
|
+
var intent = journal.intent();
|
|
1121
|
+
var proof = createProof(connection, intent);
|
|
1122
|
+
privateJson(journal.path("proof"), proof);
|
|
1123
|
+
var deadline = Date.now() + 15e3;
|
|
1124
|
+
while (!existsSync3(journal.path("grant"))) {
|
|
1125
|
+
if (existsSync3(journal.path("aborted")))
|
|
1126
|
+
throw new Error("launch_was_aborted");
|
|
1127
|
+
if (Date.now() >= deadline) throw new Error("launch_grant_timeout");
|
|
1128
|
+
await setTimeout2(25);
|
|
1129
|
+
}
|
|
1130
|
+
journal.assertRegistered();
|
|
1131
|
+
journal.assertGrant();
|
|
1132
|
+
assertProof(connection, proof);
|
|
1133
|
+
if (intent.resource) assertResourceFresh(intent.resource);
|
|
1134
|
+
var creation = intent.dockerCreation;
|
|
1135
|
+
if (creation) assertBridge(creation);
|
|
1136
|
+
if (creation)
|
|
1137
|
+
assertPrivateCreation(
|
|
1138
|
+
creation,
|
|
1139
|
+
new GroupStore(join7(records2.directory, "group.json")).state.group,
|
|
1140
|
+
intent.runtime
|
|
1141
|
+
);
|
|
1142
|
+
var proxy = creation?.egress ? await createEgressProxy(
|
|
1143
|
+
creation.state,
|
|
1144
|
+
`e${intent.nonce.slice(0, 8)}`,
|
|
1145
|
+
creation.egress.hosts,
|
|
1146
|
+
(events) => privateJson(join7(records2.directory, `${intent.id}.egress.json`), {
|
|
1147
|
+
id: intent.id,
|
|
1148
|
+
events
|
|
1149
|
+
})
|
|
1150
|
+
) : void 0;
|
|
1151
|
+
var witness = join7(records2.directory, `${intent.id}.process.json`);
|
|
1152
|
+
var startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1153
|
+
privateJson(witness, { id: intent.id, pid: process.pid, startedAt });
|
|
1154
|
+
var child = spawn(
|
|
1155
|
+
text(intent.argv[0], "runtime_executable"),
|
|
1156
|
+
intent.argv.slice(1),
|
|
1157
|
+
{
|
|
1158
|
+
cwd: intent.cwd,
|
|
1159
|
+
stdio: "inherit",
|
|
1160
|
+
env: process.env
|
|
1161
|
+
}
|
|
1162
|
+
);
|
|
1163
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
1164
|
+
process.on("SIGINT", () => {
|
|
1165
|
+
});
|
|
1166
|
+
child.on("error", async (error) => {
|
|
1167
|
+
await proxy?.close();
|
|
1168
|
+
privateJson(witness, {
|
|
1169
|
+
id: intent.id,
|
|
1170
|
+
startedAt,
|
|
1171
|
+
exitedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1172
|
+
error: error.code
|
|
1173
|
+
});
|
|
1174
|
+
process.exitCode = 1;
|
|
1175
|
+
});
|
|
1176
|
+
child.on("exit", async (code, signal) => {
|
|
1177
|
+
await proxy?.close();
|
|
1178
|
+
privateJson(witness, {
|
|
1179
|
+
id: intent.id,
|
|
1180
|
+
startedAt,
|
|
1181
|
+
exitedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1182
|
+
code,
|
|
1183
|
+
signal
|
|
1184
|
+
});
|
|
1185
|
+
process.exitCode = code ?? 1;
|
|
1186
|
+
});
|
|
1187
|
+
//# sourceMappingURL=harness-launch-gate.js.map
|