@agentrific/server 0.5.1 → 0.5.2
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/CHANGELOG.md +8 -0
- package/cli.js +66 -24
- package/cli.js.map +4 -4
- package/index.js +52 -13
- package/index.js.map +4 -4
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -956,7 +956,7 @@ function attachEventServer(app2, security2, origins, snapshot) {
|
|
|
956
956
|
}
|
|
957
957
|
|
|
958
958
|
// src/security.ts
|
|
959
|
-
import { createHash, randomBytes, randomInt, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
|
|
959
|
+
import { createHash as createHash2, randomBytes, randomInt, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
|
|
960
960
|
import { Effect as Effect4 } from "effect";
|
|
961
961
|
import { z as z6 } from "zod";
|
|
962
962
|
|
|
@@ -966,10 +966,23 @@ import qrcode from "qrcode-terminal";
|
|
|
966
966
|
|
|
967
967
|
// src/control.ts
|
|
968
968
|
import { createServer, createConnection } from "node:net";
|
|
969
|
-
import { chmod, mkdir as mkdir2, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
|
|
969
|
+
import { chmod, mkdir as mkdir2, lstat, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
|
|
970
970
|
import path3 from "node:path";
|
|
971
971
|
import { Effect as Effect3 } from "effect";
|
|
972
972
|
import { z as z4 } from "zod";
|
|
973
|
+
|
|
974
|
+
// ../../packages/contracts/src/control-socket.ts
|
|
975
|
+
import { createHash } from "node:crypto";
|
|
976
|
+
import { join, resolve } from "node:path";
|
|
977
|
+
function controlSocketPath(directory, uid = process.getuid?.()) {
|
|
978
|
+
const direct = join(resolve(directory), "control.sock");
|
|
979
|
+
if (Buffer.byteLength(direct) < 100) return direct;
|
|
980
|
+
if (uid === void 0) throw new Error("Local control sockets require macOS or Linux.");
|
|
981
|
+
const digest2 = createHash("sha256").update(resolve(directory)).digest("hex").slice(0, 32);
|
|
982
|
+
return join("/tmp", `agentrific-${uid}`, `${digest2}.sock`);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// src/control.ts
|
|
973
986
|
var MaintenanceSchema = z4.object({
|
|
974
987
|
active: z4.boolean(),
|
|
975
988
|
drained: z4.boolean(),
|
|
@@ -978,6 +991,7 @@ var MaintenanceSchema = z4.object({
|
|
|
978
991
|
var ControlStatusSchema = z4.object({
|
|
979
992
|
serverId: z4.string().uuid(),
|
|
980
993
|
publicUrl: z4.string().optional(),
|
|
994
|
+
localUrl: z4.string().optional(),
|
|
981
995
|
serverVersion: z4.string().optional(),
|
|
982
996
|
connectedDevices: z4.number().optional(),
|
|
983
997
|
connectedSockets: z4.number().optional(),
|
|
@@ -995,12 +1009,14 @@ var ControlResultSchema = z4.union([
|
|
|
995
1009
|
DeviceSchema.array(),
|
|
996
1010
|
ControlStatusSchema,
|
|
997
1011
|
z4.object({ revoked: z4.boolean() }),
|
|
1012
|
+
z4.object({ shutdown: z4.literal(true) }),
|
|
998
1013
|
MaintenanceSchema
|
|
999
1014
|
]);
|
|
1000
1015
|
var ControlRequest = z4.discriminatedUnion("command", [
|
|
1001
1016
|
z4.object({ command: z4.literal("pair") }),
|
|
1002
1017
|
z4.object({ command: z4.literal("devices") }),
|
|
1003
1018
|
z4.object({ command: z4.literal("status") }),
|
|
1019
|
+
z4.object({ command: z4.literal("shutdown") }),
|
|
1004
1020
|
z4.object({ command: z4.literal("revoke"), id: z4.string().uuid() }),
|
|
1005
1021
|
z4.object({
|
|
1006
1022
|
command: z4.literal("maintenance"),
|
|
@@ -1049,8 +1065,14 @@ async function acquireServerLock(directory) {
|
|
|
1049
1065
|
await unlink(filename);
|
|
1050
1066
|
};
|
|
1051
1067
|
}
|
|
1052
|
-
async function startControl(directory, security2, status, maintenance2) {
|
|
1053
|
-
const filename =
|
|
1068
|
+
async function startControl(directory, security2, status, maintenance2, shutdown) {
|
|
1069
|
+
const filename = controlSocketPath(directory);
|
|
1070
|
+
const socketDirectory = path3.dirname(filename);
|
|
1071
|
+
await mkdir2(socketDirectory, { recursive: true, mode: 448 });
|
|
1072
|
+
const metadata = await lstat(socketDirectory);
|
|
1073
|
+
if (!metadata.isDirectory() || metadata.uid !== process.getuid?.())
|
|
1074
|
+
throw new Error("Local control directory must be private and owned by this user.");
|
|
1075
|
+
await chmod(socketDirectory, 448);
|
|
1054
1076
|
await unlink(filename).catch((error) => {
|
|
1055
1077
|
if (!z4.object({ code: z4.literal("ENOENT") }).safeParse(error).success) throw error;
|
|
1056
1078
|
});
|
|
@@ -1070,6 +1092,13 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1070
1092
|
const request = ControlRequest.parse(JSON.parse(buffer.split("\n")[0]));
|
|
1071
1093
|
let result;
|
|
1072
1094
|
switch (request.command) {
|
|
1095
|
+
case "shutdown":
|
|
1096
|
+
if (!shutdown || !maintenance2?.status().drained)
|
|
1097
|
+
throw new Error("Drain the server before shutdown");
|
|
1098
|
+
socket.end(JSON.stringify({ result: { shutdown: true } }) + "\n", () => {
|
|
1099
|
+
setImmediate(() => void shutdown());
|
|
1100
|
+
});
|
|
1101
|
+
return;
|
|
1073
1102
|
case "pair":
|
|
1074
1103
|
result = security2.createPairing();
|
|
1075
1104
|
break;
|
|
@@ -1096,14 +1125,14 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1096
1125
|
});
|
|
1097
1126
|
socket.on("error", () => socket.destroy());
|
|
1098
1127
|
});
|
|
1099
|
-
await new Promise((
|
|
1128
|
+
await new Promise((resolve2, reject) => {
|
|
1100
1129
|
server.once("error", reject);
|
|
1101
|
-
server.listen(filename,
|
|
1130
|
+
server.listen(filename, resolve2);
|
|
1102
1131
|
});
|
|
1103
1132
|
await chmod(filename, 384);
|
|
1104
1133
|
return async () => {
|
|
1105
1134
|
await new Promise(
|
|
1106
|
-
(
|
|
1135
|
+
(resolve2, reject) => server.close((error) => error ? reject(error) : resolve2())
|
|
1107
1136
|
);
|
|
1108
1137
|
};
|
|
1109
1138
|
}
|
|
@@ -1111,7 +1140,7 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1111
1140
|
// package.json
|
|
1112
1141
|
var package_default = {
|
|
1113
1142
|
name: "@agentrific/server",
|
|
1114
|
-
version: "0.5.
|
|
1143
|
+
version: "0.5.2",
|
|
1115
1144
|
description: "Self-hosted control plane for long-running coding agents",
|
|
1116
1145
|
keywords: [
|
|
1117
1146
|
"agentrific",
|
|
@@ -1187,7 +1216,7 @@ function pairingOrigin(url) {
|
|
|
1187
1216
|
|
|
1188
1217
|
// src/security.ts
|
|
1189
1218
|
var secret = () => randomBytes(32).toString("hex");
|
|
1190
|
-
var digest = (value) =>
|
|
1219
|
+
var digest = (value) => createHash2("sha256").update(value).digest("hex");
|
|
1191
1220
|
var ServerSecurity = class {
|
|
1192
1221
|
constructor(store2, serverId) {
|
|
1193
1222
|
this.store = store2;
|
|
@@ -1385,7 +1414,7 @@ var Maintenance = class {
|
|
|
1385
1414
|
this.enter();
|
|
1386
1415
|
const deadline = Date.now() + timeoutMs;
|
|
1387
1416
|
while (!this.status().drained && Date.now() < deadline)
|
|
1388
|
-
await new Promise((
|
|
1417
|
+
await new Promise((resolve2) => setTimeout(resolve2, 50));
|
|
1389
1418
|
return this.status();
|
|
1390
1419
|
}
|
|
1391
1420
|
attach(app2) {
|
|
@@ -3796,7 +3825,7 @@ var SessionStore = class extends Context3.Service()(
|
|
|
3796
3825
|
};
|
|
3797
3826
|
|
|
3798
3827
|
// src/worktree-cleanup.ts
|
|
3799
|
-
import { lstat, rm as rm2 } from "node:fs/promises";
|
|
3828
|
+
import { lstat as lstat2, rm as rm2 } from "node:fs/promises";
|
|
3800
3829
|
import path8 from "node:path";
|
|
3801
3830
|
import { Effect as Effect15 } from "effect";
|
|
3802
3831
|
var WorktreeCleanup = class {
|
|
@@ -3811,7 +3840,7 @@ var WorktreeCleanup = class {
|
|
|
3811
3840
|
async remove(session, discardChanges = true) {
|
|
3812
3841
|
if (session.gitFlow?.workspace !== "worktree" || !session.workspacePath) return;
|
|
3813
3842
|
const workspace = path8.resolve(session.workspacePath);
|
|
3814
|
-
const info = await
|
|
3843
|
+
const info = await lstat2(workspace).catch((error) => {
|
|
3815
3844
|
if (error.code === "ENOENT") return void 0;
|
|
3816
3845
|
throw error;
|
|
3817
3846
|
});
|
|
@@ -3939,6 +3968,7 @@ var push = new ServerPush(
|
|
|
3939
3968
|
var allowedOrigins = [
|
|
3940
3969
|
.../* @__PURE__ */ new Set([
|
|
3941
3970
|
"agentrific-desktop://app",
|
|
3971
|
+
"https://app.agentrific.com",
|
|
3942
3972
|
...(process.env.AGENTRIFIC_ALLOWED_ORIGINS ?? "http://localhost:8081,http://127.0.0.1:8081").split(","),
|
|
3943
3973
|
`http://${host === "::1" ? "[::1]" : host}:${port}`,
|
|
3944
3974
|
...process.env.AGENTRIFIC_PUBLIC_URL ? [new URL(process.env.AGENTRIFIC_PUBLIC_URL).origin] : []
|
|
@@ -4670,12 +4700,17 @@ attachEventServer(app, security, allowedOrigins, () => ({
|
|
|
4670
4700
|
type: "snapshot",
|
|
4671
4701
|
sessions: Effect17.runSync(store.list())
|
|
4672
4702
|
}));
|
|
4703
|
+
function localServerUrl() {
|
|
4704
|
+
const address = z17.object({ port: z17.number() }).safeParse(app.server.address());
|
|
4705
|
+
return address.success ? `http://${host}:${address.data.port}` : void 0;
|
|
4706
|
+
}
|
|
4673
4707
|
var closeControl = await startControl(
|
|
4674
4708
|
dataRoot,
|
|
4675
4709
|
security,
|
|
4676
4710
|
() => ({
|
|
4677
4711
|
serverId: security.serverId,
|
|
4678
4712
|
publicUrl: process.env.AGENTRIFIC_PUBLIC_URL,
|
|
4713
|
+
localUrl: localServerUrl(),
|
|
4679
4714
|
serverVersion,
|
|
4680
4715
|
connectedDevices: new Set(security.sockets.values()).size,
|
|
4681
4716
|
connectedSockets: security.sockets.size,
|
|
@@ -4683,7 +4718,11 @@ var closeControl = await startControl(
|
|
|
4683
4718
|
activeRuns: activeAgents(Effect17.runSync(store.list())),
|
|
4684
4719
|
maintenance: maintenance.status()
|
|
4685
4720
|
}),
|
|
4686
|
-
maintenance
|
|
4721
|
+
maintenance,
|
|
4722
|
+
async () => {
|
|
4723
|
+
await app.close();
|
|
4724
|
+
process.exit(0);
|
|
4725
|
+
}
|
|
4687
4726
|
);
|
|
4688
4727
|
push.start();
|
|
4689
4728
|
app.addHook("onClose", async () => {
|