@agentrific/server 0.5.0 → 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 +14 -0
- package/cli.js +80 -27
- package/cli.js.map +4 -4
- package/index.js +82 -35
- package/index.js.map +4 -4
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,27 +1,7 @@
|
|
|
1
|
-
// src/warnings.ts
|
|
2
|
-
var handlers = process.rawListeners("warning");
|
|
3
|
-
process.removeAllListeners("warning");
|
|
4
|
-
process.on("warning", (warning) => {
|
|
5
|
-
if (warning.name === "ExperimentalWarning" && warning.message === "SQLite is an experimental feature and might change at any time")
|
|
6
|
-
return;
|
|
7
|
-
for (const handler of handlers) handler.call(process, warning);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
// src/push.ts
|
|
11
|
-
import {
|
|
12
|
-
generateKeyPairSync,
|
|
13
|
-
createPrivateKey,
|
|
14
|
-
createPublicKey,
|
|
15
|
-
sign,
|
|
16
|
-
randomUUID
|
|
17
|
-
} from "node:crypto";
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
|
-
import path from "node:path";
|
|
20
|
-
import { Effect } from "effect";
|
|
21
|
-
|
|
22
1
|
// ../../packages/contracts/src/push.ts
|
|
23
2
|
import { z } from "zod";
|
|
24
3
|
var PUSH_API_URL = "https://api.agentrific.com";
|
|
4
|
+
var PUSH_SANDBOX_API_URL = "https://sandbox-api.agentrific.com";
|
|
25
5
|
var PushKindSchema = z.enum([
|
|
26
6
|
"started",
|
|
27
7
|
"completed",
|
|
@@ -85,7 +65,26 @@ function pushSigningText(installationId, operation, request) {
|
|
|
85
65
|
].join("\n");
|
|
86
66
|
}
|
|
87
67
|
|
|
68
|
+
// src/warnings.ts
|
|
69
|
+
var handlers = process.rawListeners("warning");
|
|
70
|
+
process.removeAllListeners("warning");
|
|
71
|
+
process.on("warning", (warning) => {
|
|
72
|
+
if (warning.name === "ExperimentalWarning" && warning.message === "SQLite is an experimental feature and might change at any time")
|
|
73
|
+
return;
|
|
74
|
+
for (const handler of handlers) handler.call(process, warning);
|
|
75
|
+
});
|
|
76
|
+
|
|
88
77
|
// src/push.ts
|
|
78
|
+
import {
|
|
79
|
+
generateKeyPairSync,
|
|
80
|
+
createPrivateKey,
|
|
81
|
+
createPublicKey,
|
|
82
|
+
sign,
|
|
83
|
+
randomUUID
|
|
84
|
+
} from "node:crypto";
|
|
85
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
86
|
+
import path from "node:path";
|
|
87
|
+
import { Effect } from "effect";
|
|
89
88
|
function pushKind(session, previous) {
|
|
90
89
|
if (session.gitDelivery?.status === "pr_opened" && previous?.gitDelivery?.status !== "pr_opened")
|
|
91
90
|
return "pr_created";
|
|
@@ -957,7 +956,7 @@ function attachEventServer(app2, security2, origins, snapshot) {
|
|
|
957
956
|
}
|
|
958
957
|
|
|
959
958
|
// src/security.ts
|
|
960
|
-
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";
|
|
961
960
|
import { Effect as Effect4 } from "effect";
|
|
962
961
|
import { z as z6 } from "zod";
|
|
963
962
|
|
|
@@ -967,10 +966,23 @@ import qrcode from "qrcode-terminal";
|
|
|
967
966
|
|
|
968
967
|
// src/control.ts
|
|
969
968
|
import { createServer, createConnection } from "node:net";
|
|
970
|
-
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";
|
|
971
970
|
import path3 from "node:path";
|
|
972
971
|
import { Effect as Effect3 } from "effect";
|
|
973
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
|
|
974
986
|
var MaintenanceSchema = z4.object({
|
|
975
987
|
active: z4.boolean(),
|
|
976
988
|
drained: z4.boolean(),
|
|
@@ -979,6 +991,7 @@ var MaintenanceSchema = z4.object({
|
|
|
979
991
|
var ControlStatusSchema = z4.object({
|
|
980
992
|
serverId: z4.string().uuid(),
|
|
981
993
|
publicUrl: z4.string().optional(),
|
|
994
|
+
localUrl: z4.string().optional(),
|
|
982
995
|
serverVersion: z4.string().optional(),
|
|
983
996
|
connectedDevices: z4.number().optional(),
|
|
984
997
|
connectedSockets: z4.number().optional(),
|
|
@@ -996,12 +1009,14 @@ var ControlResultSchema = z4.union([
|
|
|
996
1009
|
DeviceSchema.array(),
|
|
997
1010
|
ControlStatusSchema,
|
|
998
1011
|
z4.object({ revoked: z4.boolean() }),
|
|
1012
|
+
z4.object({ shutdown: z4.literal(true) }),
|
|
999
1013
|
MaintenanceSchema
|
|
1000
1014
|
]);
|
|
1001
1015
|
var ControlRequest = z4.discriminatedUnion("command", [
|
|
1002
1016
|
z4.object({ command: z4.literal("pair") }),
|
|
1003
1017
|
z4.object({ command: z4.literal("devices") }),
|
|
1004
1018
|
z4.object({ command: z4.literal("status") }),
|
|
1019
|
+
z4.object({ command: z4.literal("shutdown") }),
|
|
1005
1020
|
z4.object({ command: z4.literal("revoke"), id: z4.string().uuid() }),
|
|
1006
1021
|
z4.object({
|
|
1007
1022
|
command: z4.literal("maintenance"),
|
|
@@ -1050,8 +1065,14 @@ async function acquireServerLock(directory) {
|
|
|
1050
1065
|
await unlink(filename);
|
|
1051
1066
|
};
|
|
1052
1067
|
}
|
|
1053
|
-
async function startControl(directory, security2, status, maintenance2) {
|
|
1054
|
-
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);
|
|
1055
1076
|
await unlink(filename).catch((error) => {
|
|
1056
1077
|
if (!z4.object({ code: z4.literal("ENOENT") }).safeParse(error).success) throw error;
|
|
1057
1078
|
});
|
|
@@ -1071,6 +1092,13 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1071
1092
|
const request = ControlRequest.parse(JSON.parse(buffer.split("\n")[0]));
|
|
1072
1093
|
let result;
|
|
1073
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;
|
|
1074
1102
|
case "pair":
|
|
1075
1103
|
result = security2.createPairing();
|
|
1076
1104
|
break;
|
|
@@ -1097,14 +1125,14 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1097
1125
|
});
|
|
1098
1126
|
socket.on("error", () => socket.destroy());
|
|
1099
1127
|
});
|
|
1100
|
-
await new Promise((
|
|
1128
|
+
await new Promise((resolve2, reject) => {
|
|
1101
1129
|
server.once("error", reject);
|
|
1102
|
-
server.listen(filename,
|
|
1130
|
+
server.listen(filename, resolve2);
|
|
1103
1131
|
});
|
|
1104
1132
|
await chmod(filename, 384);
|
|
1105
1133
|
return async () => {
|
|
1106
1134
|
await new Promise(
|
|
1107
|
-
(
|
|
1135
|
+
(resolve2, reject) => server.close((error) => error ? reject(error) : resolve2())
|
|
1108
1136
|
);
|
|
1109
1137
|
};
|
|
1110
1138
|
}
|
|
@@ -1112,7 +1140,7 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1112
1140
|
// package.json
|
|
1113
1141
|
var package_default = {
|
|
1114
1142
|
name: "@agentrific/server",
|
|
1115
|
-
version: "0.5.
|
|
1143
|
+
version: "0.5.2",
|
|
1116
1144
|
description: "Self-hosted control plane for long-running coding agents",
|
|
1117
1145
|
keywords: [
|
|
1118
1146
|
"agentrific",
|
|
@@ -1188,7 +1216,7 @@ function pairingOrigin(url) {
|
|
|
1188
1216
|
|
|
1189
1217
|
// src/security.ts
|
|
1190
1218
|
var secret = () => randomBytes(32).toString("hex");
|
|
1191
|
-
var digest = (value) =>
|
|
1219
|
+
var digest = (value) => createHash2("sha256").update(value).digest("hex");
|
|
1192
1220
|
var ServerSecurity = class {
|
|
1193
1221
|
constructor(store2, serverId) {
|
|
1194
1222
|
this.store = store2;
|
|
@@ -1386,7 +1414,7 @@ var Maintenance = class {
|
|
|
1386
1414
|
this.enter();
|
|
1387
1415
|
const deadline = Date.now() + timeoutMs;
|
|
1388
1416
|
while (!this.status().drained && Date.now() < deadline)
|
|
1389
|
-
await new Promise((
|
|
1417
|
+
await new Promise((resolve2) => setTimeout(resolve2, 50));
|
|
1390
1418
|
return this.status();
|
|
1391
1419
|
}
|
|
1392
1420
|
attach(app2) {
|
|
@@ -3797,7 +3825,7 @@ var SessionStore = class extends Context3.Service()(
|
|
|
3797
3825
|
};
|
|
3798
3826
|
|
|
3799
3827
|
// src/worktree-cleanup.ts
|
|
3800
|
-
import { lstat, rm as rm2 } from "node:fs/promises";
|
|
3828
|
+
import { lstat as lstat2, rm as rm2 } from "node:fs/promises";
|
|
3801
3829
|
import path8 from "node:path";
|
|
3802
3830
|
import { Effect as Effect15 } from "effect";
|
|
3803
3831
|
var WorktreeCleanup = class {
|
|
@@ -3812,7 +3840,7 @@ var WorktreeCleanup = class {
|
|
|
3812
3840
|
async remove(session, discardChanges = true) {
|
|
3813
3841
|
if (session.gitFlow?.workspace !== "worktree" || !session.workspacePath) return;
|
|
3814
3842
|
const workspace = path8.resolve(session.workspacePath);
|
|
3815
|
-
const info = await
|
|
3843
|
+
const info = await lstat2(workspace).catch((error) => {
|
|
3816
3844
|
if (error.code === "ENOENT") return void 0;
|
|
3817
3845
|
throw error;
|
|
3818
3846
|
});
|
|
@@ -3924,13 +3952,23 @@ var releaseLock = await acquireServerLock(dataRoot);
|
|
|
3924
3952
|
var effects = ManagedRuntime.make(SessionStore.layer(path9.join(dataRoot, "sessions.sqlite")));
|
|
3925
3953
|
var store = await effects.runPromise(SessionStore);
|
|
3926
3954
|
var security = new ServerSecurity(store, await Effect17.runPromise(store.serverIdentity()));
|
|
3955
|
+
var sandboxPushUrl = process.env.AGENTRIFIC_PUSH_SANDBOX === "1" ? PUSH_SANDBOX_API_URL : void 0;
|
|
3956
|
+
if (sandboxPushUrl && process.env.AGENTRIFIC_TEST_MODE !== "1")
|
|
3957
|
+
throw new Error("Sandbox push requires explicit test mode.");
|
|
3927
3958
|
var testPushUrl = process.env.AGENTRIFIC_PUSH_TEST_URL;
|
|
3928
3959
|
if (testPushUrl && (process.env.AGENTRIFIC_TEST_MODE !== "1" || !new URL(testPushUrl).hostname.endsWith(".ts.net") || new URL(testPushUrl).protocol !== "https:"))
|
|
3929
3960
|
throw new Error("Test relay requires explicit test mode and Tailscale HTTPS.");
|
|
3930
|
-
var push = new ServerPush(
|
|
3961
|
+
var push = new ServerPush(
|
|
3962
|
+
store,
|
|
3963
|
+
security.serverId,
|
|
3964
|
+
dataRoot,
|
|
3965
|
+
fetch,
|
|
3966
|
+
testPushUrl ?? sandboxPushUrl
|
|
3967
|
+
);
|
|
3931
3968
|
var allowedOrigins = [
|
|
3932
3969
|
.../* @__PURE__ */ new Set([
|
|
3933
3970
|
"agentrific-desktop://app",
|
|
3971
|
+
"https://app.agentrific.com",
|
|
3934
3972
|
...(process.env.AGENTRIFIC_ALLOWED_ORIGINS ?? "http://localhost:8081,http://127.0.0.1:8081").split(","),
|
|
3935
3973
|
`http://${host === "::1" ? "[::1]" : host}:${port}`,
|
|
3936
3974
|
...process.env.AGENTRIFIC_PUBLIC_URL ? [new URL(process.env.AGENTRIFIC_PUBLIC_URL).origin] : []
|
|
@@ -4662,12 +4700,17 @@ attachEventServer(app, security, allowedOrigins, () => ({
|
|
|
4662
4700
|
type: "snapshot",
|
|
4663
4701
|
sessions: Effect17.runSync(store.list())
|
|
4664
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
|
+
}
|
|
4665
4707
|
var closeControl = await startControl(
|
|
4666
4708
|
dataRoot,
|
|
4667
4709
|
security,
|
|
4668
4710
|
() => ({
|
|
4669
4711
|
serverId: security.serverId,
|
|
4670
4712
|
publicUrl: process.env.AGENTRIFIC_PUBLIC_URL,
|
|
4713
|
+
localUrl: localServerUrl(),
|
|
4671
4714
|
serverVersion,
|
|
4672
4715
|
connectedDevices: new Set(security.sockets.values()).size,
|
|
4673
4716
|
connectedSockets: security.sockets.size,
|
|
@@ -4675,7 +4718,11 @@ var closeControl = await startControl(
|
|
|
4675
4718
|
activeRuns: activeAgents(Effect17.runSync(store.list())),
|
|
4676
4719
|
maintenance: maintenance.status()
|
|
4677
4720
|
}),
|
|
4678
|
-
maintenance
|
|
4721
|
+
maintenance,
|
|
4722
|
+
async () => {
|
|
4723
|
+
await app.close();
|
|
4724
|
+
process.exit(0);
|
|
4725
|
+
}
|
|
4679
4726
|
);
|
|
4680
4727
|
push.start();
|
|
4681
4728
|
app.addHook("onClose", async () => {
|