@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/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 = path3.join(directory, "control.sock");
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((resolve, reject) => {
1128
+ await new Promise((resolve2, reject) => {
1100
1129
  server.once("error", reject);
1101
- server.listen(filename, resolve);
1130
+ server.listen(filename, resolve2);
1102
1131
  });
1103
1132
  await chmod(filename, 384);
1104
1133
  return async () => {
1105
1134
  await new Promise(
1106
- (resolve, reject) => server.close((error) => error ? reject(error) : resolve())
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.1",
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) => createHash("sha256").update(value).digest("hex");
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((resolve) => setTimeout(resolve, 50));
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 lstat(workspace).catch((error) => {
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 () => {