@saadnvd1/agent-os 0.3.697 → 0.3.700
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/app/api/bus/send/route.ts +11 -1
- package/app/api/bus/wait/route.ts +22 -0
- package/app/api/peer/hello/reveal/route.ts +6 -0
- package/app/api/peer/hello/route.ts +9 -0
- package/app/api/peer/hello/status/route.ts +6 -0
- package/app/api/peer/ping/route.ts +10 -0
- package/app/api/peers/account/route.ts +9 -0
- package/app/api/peers/gate.test.ts +111 -0
- package/app/api/peers/link/[id]/confirm/route.ts +27 -0
- package/app/api/peers/link/[id]/route.ts +20 -0
- package/app/api/peers/link/route.ts +23 -0
- package/app/api/peers/pending/[id]/accept/route.ts +31 -0
- package/app/api/peers/pending/[id]/decline/route.ts +13 -0
- package/app/api/peers/pending/route.ts +7 -0
- package/app/api/sessions/route.ts +18 -1
- package/app/page.tsx +2 -0
- package/components/Hosts/AccountMachines.tsx +180 -0
- package/components/Hosts/HostRow.tsx +44 -35
- package/components/Hosts/HostsDialog.tsx +49 -37
- package/components/Hosts/IncomingLinkSheet.tsx +228 -0
- package/components/Hosts/LinkCodeSheet.tsx +186 -0
- package/components/Hosts/sheet.ts +3 -0
- package/components/SessionList/RowSubtitle.tsx +11 -1
- package/components/Welcome/ConnectBlock.tsx +1 -1
- package/data/peers/index.ts +2 -0
- package/data/peers/keys.ts +6 -0
- package/data/peers/queries.ts +141 -0
- package/lib/__fixtures__/connect-transport.ts +93 -0
- package/lib/__fixtures__/peer-machine.ts +45 -0
- package/lib/__fixtures__/peer-rpc.ts +224 -0
- package/lib/agents/brief.ts +3 -1
- package/lib/bus/format.ts +25 -4
- package/lib/bus/index.ts +113 -106
- package/lib/bus/resolve.ts +16 -2
- package/lib/bus/targets.ts +122 -0
- package/lib/bus/waits/kick.ts +12 -0
- package/lib/bus/waits/lines.ts +69 -0
- package/lib/bus/waits/register.ts +83 -0
- package/lib/bus/waits/store.ts +196 -0
- package/lib/bus/waits/tick.ts +192 -0
- package/lib/connect/control-api.ts +108 -2
- package/lib/connect/heartbeat.ts +11 -3
- package/lib/connect/serve.ts +2 -0
- package/lib/db/migrations/20261020T1258_peer_links.ts +49 -0
- package/lib/db/migrations/20261020T1345_peer_nonces.ts +18 -0
- package/lib/db/migrations/20261020T1600_bus_waits.ts +35 -0
- package/lib/db/types.ts +6 -0
- package/lib/done/archive.ts +2 -0
- package/lib/hosts/index.ts +28 -7
- package/lib/hosts/peer-socket.ts +3 -1
- package/lib/hosts/read-capped-bytes.ts +23 -0
- package/lib/hosts/remote-api.ts +112 -41
- package/lib/orchestrator/describe.ts +13 -0
- package/lib/orchestrator/facts.ts +17 -0
- package/lib/peer/account.ts +198 -0
- package/lib/peer/allowlist.ts +136 -0
- package/lib/peer/audit.ts +144 -0
- package/lib/peer/cli.ts +24 -0
- package/lib/peer/client.ts +188 -0
- package/lib/peer/code.ts +3 -0
- package/lib/peer/envelope.ts +424 -0
- package/lib/peer/hello-route.ts +32 -0
- package/lib/peer/hello.ts +346 -0
- package/lib/peer/internal.ts +22 -0
- package/lib/peer/keys.ts +122 -0
- package/lib/peer/link-cli.ts +233 -0
- package/lib/peer/links.ts +96 -0
- package/lib/peer/machines.ts +72 -0
- package/lib/peer/nonce-store.ts +23 -0
- package/lib/peer/notify.ts +32 -0
- package/lib/peer/pairing-out.ts +382 -0
- package/lib/peer/pairing.ts +403 -0
- package/lib/peer/pin.ts +55 -0
- package/lib/peer/replay.ts +81 -0
- package/lib/peer/rpc-intake.ts +258 -0
- package/lib/peer/rpc-server.ts +369 -0
- package/lib/schedules/when.ts +1 -1
- package/lib/security/auth.ts +51 -6
- package/lib/security/gate.ts +38 -12
- package/lib/security/route-guard.ts +3 -1
- package/lib/session-artifacts/peer.ts +12 -1
- package/lib/sessions/end.ts +3 -0
- package/lib/tasks/contract/authorize.ts +4 -0
- package/lib/worktree-sweep/testTemp.ts +2 -0
- package/package.json +1 -1
- package/scripts/agent-os +2 -0
- package/scripts/lib/commands.sh +2 -0
- package/server.ts +21 -0
package/lib/security/auth.ts
CHANGED
|
@@ -10,20 +10,36 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { IncomingHttpHeaders } from "http";
|
|
13
|
+
import { peerTargetAllowed } from "../peer/allowlist";
|
|
14
|
+
import { secretMatches } from "../peer/internal";
|
|
13
15
|
|
|
14
16
|
export const DEVICE_COOKIE = "aos_device";
|
|
15
17
|
// Set by server.ts on every request after stripping any client-sent copy.
|
|
16
18
|
export const DEVICE_HEADER = "x-agentos-device";
|
|
17
19
|
export const TRUST_HEADER = "x-agentos-trust";
|
|
18
20
|
export const REMOTE_HEADER = "x-agentos-remote";
|
|
21
|
+
// Who sent it, for per-sender limits: the visitor's address on a Connect
|
|
22
|
+
// request (lib/peer/rpc-intake sourceOf), an IPv6 address by its /64.
|
|
23
|
+
export const SOURCE_HEADER = "x-agentos-source";
|
|
24
|
+
// The rpc server's loopback hop (lib/peer/rpc-server): the per-boot secret,
|
|
25
|
+
// and the linked machine it verified. Never trusted from anywhere else.
|
|
26
|
+
export const PEER_INTERNAL_HEADER = "x-agentos-peer-internal";
|
|
27
|
+
export const PEER_HEADER = "x-agentos-peer";
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
/**
|
|
30
|
+
* `peer`: a linked machine's verified call, re-issued by this machine. It is
|
|
31
|
+
* never local trust and never an approver, and only reaches its allowlist.
|
|
32
|
+
*/
|
|
33
|
+
export type Trust = "loopback" | "tailnet" | "device" | "open" | "peer";
|
|
21
34
|
|
|
22
35
|
export type AuthResult =
|
|
23
|
-
{ ok: true; via: Trust; deviceId?: string
|
|
36
|
+
| { ok: true; via: Trust; deviceId?: string; peerHost?: string }
|
|
37
|
+
// hard: refused outright, even on a path that is otherwise public.
|
|
38
|
+
| { ok: false; hard?: true };
|
|
24
39
|
|
|
25
40
|
export interface AuthRequest {
|
|
26
41
|
url?: string;
|
|
42
|
+
method?: string;
|
|
27
43
|
headers: IncomingHttpHeaders;
|
|
28
44
|
remoteAddress?: string;
|
|
29
45
|
localAddress?: string;
|
|
@@ -47,11 +63,14 @@ export interface AuthPolicy {
|
|
|
47
63
|
/** AGENTOS_AUTH=off: someone else's proxy does the login. */
|
|
48
64
|
off?: boolean;
|
|
49
65
|
lookup: (token: string) => { id: string } | null;
|
|
66
|
+
/** The rpc server's per-boot secret; without it no request is a peer. */
|
|
67
|
+
peerSecret?: () => string;
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
const TAILSCALE_V4 = /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./;
|
|
53
71
|
const LOOPBACK = new Set(["127.0.0.1", "::1"]);
|
|
54
72
|
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
|
|
73
|
+
const PEER_HOST_ID = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
55
74
|
// A proxy must send one of these, or it looks like this machine itself.
|
|
56
75
|
// Proxies that send none need AGENTOS_AUTH, see docs/reference/security.md.
|
|
57
76
|
const FORWARDING = new Set([
|
|
@@ -64,12 +83,18 @@ const FORWARDING = new Set([
|
|
|
64
83
|
]);
|
|
65
84
|
const FORWARDING_PREFIXES = ["x-forwarded-", "cf-", "tailscale-"];
|
|
66
85
|
|
|
67
|
-
// Reached without a token: the pairing page, what it needs to render,
|
|
68
|
-
//
|
|
69
|
-
//
|
|
86
|
+
// Reached without a token: the pairing page, what it needs to render, the
|
|
87
|
+
// claim it posts, a linked machine's rpc (its envelope is the proof) and
|
|
88
|
+
// the pairing hellos (signed by a key the account lists). Exact paths, plus
|
|
89
|
+
// a few static asset folders whose paths may only use plain characters (no
|
|
90
|
+
// traversal, no encoding).
|
|
70
91
|
const PUBLIC_EXACT = new Set([
|
|
71
92
|
"/pair",
|
|
72
93
|
"/api/pair/claim",
|
|
94
|
+
"/api/peer/rpc",
|
|
95
|
+
"/api/peer/hello",
|
|
96
|
+
"/api/peer/hello/reveal",
|
|
97
|
+
"/api/peer/hello/status",
|
|
73
98
|
"/manifest.webmanifest",
|
|
74
99
|
"/manifest.json",
|
|
75
100
|
"/favicon.ico",
|
|
@@ -155,11 +180,31 @@ export function authorize(req: AuthRequest, policy: AuthPolicy): AuthResult {
|
|
|
155
180
|
try {
|
|
156
181
|
return decide(req, policy);
|
|
157
182
|
} catch {
|
|
158
|
-
return
|
|
183
|
+
return req.headers[PEER_INTERNAL_HEADER] !== undefined
|
|
184
|
+
? { ok: false, hard: true }
|
|
185
|
+
: { ok: false };
|
|
159
186
|
}
|
|
160
187
|
}
|
|
161
188
|
|
|
189
|
+
/**
|
|
190
|
+
* A request carrying the internal header is a peer call or nothing: it
|
|
191
|
+
* never falls back to loopback trust, whatever its address says.
|
|
192
|
+
*/
|
|
193
|
+
function peerTrust(req: AuthRequest, policy: AuthPolicy): AuthResult {
|
|
194
|
+
const refuse = { ok: false, hard: true } as const;
|
|
195
|
+
if (req.tunnel || !policy.peerSecret || proxied(req.headers)) return refuse;
|
|
196
|
+
if (!LOOPBACK.has(plainAddress(req.remoteAddress))) return refuse;
|
|
197
|
+
if (!secretMatches(req.headers[PEER_INTERNAL_HEADER], policy.peerSecret()))
|
|
198
|
+
return refuse;
|
|
199
|
+
const host = req.headers[PEER_HEADER];
|
|
200
|
+
if (typeof host !== "string" || !PEER_HOST_ID.test(host)) return refuse;
|
|
201
|
+
if (!peerTargetAllowed(req.method, req.url)) return refuse;
|
|
202
|
+
return { ok: true, via: "peer", deviceId: `peer:${host}`, peerHost: host };
|
|
203
|
+
}
|
|
204
|
+
|
|
162
205
|
function decide(req: AuthRequest, policy: AuthPolicy): AuthResult {
|
|
206
|
+
if (req.headers[PEER_INTERNAL_HEADER] !== undefined)
|
|
207
|
+
return peerTrust(req, policy);
|
|
163
208
|
const trusted = req.tunnel ? null : networkTrust(req, policy);
|
|
164
209
|
const token = readToken(req.headers);
|
|
165
210
|
if (trusted) {
|
package/lib/security/gate.ts
CHANGED
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
DEVICE_HEADER,
|
|
15
15
|
TRUST_HEADER,
|
|
16
16
|
REMOTE_HEADER,
|
|
17
|
+
SOURCE_HEADER,
|
|
18
|
+
PEER_HEADER,
|
|
19
|
+
PEER_INTERNAL_HEADER,
|
|
17
20
|
type AuthPolicy,
|
|
18
21
|
type AuthResult,
|
|
19
22
|
} from "./auth";
|
|
@@ -22,6 +25,8 @@ import { deviceForToken, touchDevice, trackDeviceSocket } from "./devices";
|
|
|
22
25
|
import { tailscaleAddresses } from "./net";
|
|
23
26
|
import { networkSetting } from "./network-settings";
|
|
24
27
|
import { pairLocation } from "../session-url";
|
|
28
|
+
import { peerSecret } from "../peer/internal";
|
|
29
|
+
import { sourceOf } from "../peer/rpc-intake";
|
|
25
30
|
|
|
26
31
|
// Interface addresses change rarely; don't re-read them on every request.
|
|
27
32
|
let tailnetCache = { at: 0, ips: [] as string[] };
|
|
@@ -42,6 +47,7 @@ export function authPolicy(env = process.env): AuthPolicy {
|
|
|
42
47
|
},
|
|
43
48
|
off: env.AGENTOS_AUTH === "off",
|
|
44
49
|
lookup: deviceForToken,
|
|
50
|
+
peerSecret,
|
|
45
51
|
};
|
|
46
52
|
}
|
|
47
53
|
|
|
@@ -54,21 +60,37 @@ function decide(req: IncomingMessage, policy: AuthPolicy): AuthResult {
|
|
|
54
60
|
// Never trust a client-supplied verdict.
|
|
55
61
|
delete req.headers[DEVICE_HEADER];
|
|
56
62
|
delete req.headers[TRUST_HEADER];
|
|
63
|
+
// A peer's identity only ever comes with the internal secret.
|
|
64
|
+
if (req.headers[PEER_INTERNAL_HEADER] === undefined)
|
|
65
|
+
delete req.headers[PEER_HEADER];
|
|
57
66
|
const tunnel = isTunnelSocket(req.socket);
|
|
58
67
|
req.headers[REMOTE_HEADER] = sourceLabel(req.socket.remoteAddress, tunnel);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
req.headers[SOURCE_HEADER] = sourceOf(req);
|
|
69
|
+
let result: AuthResult;
|
|
70
|
+
try {
|
|
71
|
+
result = authorize(
|
|
72
|
+
{
|
|
73
|
+
url: req.url,
|
|
74
|
+
method: req.method,
|
|
75
|
+
headers: req.headers,
|
|
76
|
+
remoteAddress: req.socket.remoteAddress,
|
|
77
|
+
localAddress: req.socket.localAddress,
|
|
78
|
+
tunnel,
|
|
79
|
+
},
|
|
80
|
+
policy
|
|
81
|
+
);
|
|
82
|
+
} finally {
|
|
83
|
+
// No route ever sees the secret.
|
|
84
|
+
delete req.headers[PEER_INTERNAL_HEADER];
|
|
85
|
+
}
|
|
86
|
+
delete req.headers[PEER_HEADER];
|
|
69
87
|
if (result.ok) {
|
|
70
88
|
req.headers[TRUST_HEADER] = result.via;
|
|
71
89
|
if (result.deviceId) req.headers[DEVICE_HEADER] = result.deviceId;
|
|
90
|
+
if (result.peerHost) {
|
|
91
|
+
req.headers[PEER_HEADER] = result.peerHost;
|
|
92
|
+
req.headers[REMOTE_HEADER] = "peer";
|
|
93
|
+
}
|
|
72
94
|
}
|
|
73
95
|
return result;
|
|
74
96
|
}
|
|
@@ -81,6 +103,8 @@ function check(req: IncomingMessage, policy: AuthPolicy): AuthResult {
|
|
|
81
103
|
console.error("access check failed:", err);
|
|
82
104
|
delete req.headers[TRUST_HEADER];
|
|
83
105
|
delete req.headers[DEVICE_HEADER];
|
|
106
|
+
delete req.headers[PEER_HEADER];
|
|
107
|
+
delete req.headers[PEER_INTERNAL_HEADER];
|
|
84
108
|
return { ok: false };
|
|
85
109
|
}
|
|
86
110
|
}
|
|
@@ -102,6 +126,7 @@ export function gateRequest(
|
|
|
102
126
|
): boolean {
|
|
103
127
|
const result = check(req, policy);
|
|
104
128
|
if (result.ok) {
|
|
129
|
+
if (result.via === "peer") return true;
|
|
105
130
|
// Slide the cookie's expiry forward, at most once a minute per device.
|
|
106
131
|
const cookie = result.deviceId ? cookieToken(req.headers) : null;
|
|
107
132
|
if (result.deviceId && touch(req, result.deviceId) && cookie) {
|
|
@@ -114,7 +139,7 @@ export function gateRequest(
|
|
|
114
139
|
}
|
|
115
140
|
return true;
|
|
116
141
|
}
|
|
117
|
-
if (isPublicPath(req.url)) return true;
|
|
142
|
+
if (!result.hard && isPublicPath(req.url)) return true;
|
|
118
143
|
const path = (req.url ?? "/").split("?")[0];
|
|
119
144
|
const wantsPage =
|
|
120
145
|
!path.startsWith("/api/") &&
|
|
@@ -139,7 +164,8 @@ export function gateUpgrade(
|
|
|
139
164
|
policy: AuthPolicy
|
|
140
165
|
): boolean {
|
|
141
166
|
const result = check(req, policy);
|
|
142
|
-
|
|
167
|
+
// A peer reaches its allowlisted routes and never a socket.
|
|
168
|
+
if (!result.ok || result.via === "peer") {
|
|
143
169
|
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
144
170
|
socket.destroy();
|
|
145
171
|
return false;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* For actions a stolen device token must not be able to take (adding
|
|
3
|
-
* devices, opening up the network): only this machine or the tailnet.
|
|
3
|
+
* devices, opening up the network): only this machine or the tailnet. A
|
|
4
|
+
* linked machine's call (`peer`) never passes, even though it arrives on
|
|
5
|
+
* loopback.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
import { NextResponse, type NextRequest } from "next/server";
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
import { ARTIFACT_HEADERS } from "../artifacts/serve";
|
|
9
9
|
import { IMAGE_TYPES } from "../artifacts/image-paths";
|
|
10
10
|
import { IMAGE_CSP } from "../artifacts/serve";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
cleanRemoteText,
|
|
13
|
+
hostApi,
|
|
14
|
+
isConnectLink,
|
|
15
|
+
type HostLink,
|
|
16
|
+
} from "../hosts/remote-api";
|
|
12
17
|
import type {
|
|
13
18
|
ArtifactKind,
|
|
14
19
|
ArtifactPreview,
|
|
@@ -148,6 +153,12 @@ export async function peerContent(
|
|
|
148
153
|
): Promise<Content> {
|
|
149
154
|
if (!ID.test(artifactId))
|
|
150
155
|
return { ok: false, status: 404, error: "Not found" };
|
|
156
|
+
if (isConnectLink(link))
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
status: 409,
|
|
160
|
+
error: `${link.hostName} is linked through Connect: artifacts need a Tailscale/ssh link for now`,
|
|
161
|
+
};
|
|
151
162
|
let res: Response;
|
|
152
163
|
try {
|
|
153
164
|
res = await fetch(
|
package/lib/sessions/end.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { waitForExit } from "../chat/worker/client";
|
|
|
4
4
|
import { endTmuxSession } from "../tmux/end";
|
|
5
5
|
import { releaseOnEnd, shutTaskSimulators } from "../simulator";
|
|
6
6
|
import { forgetPlanWait } from "../models/plan-step";
|
|
7
|
+
import { kickWaits } from "../bus/waits/kick";
|
|
7
8
|
|
|
8
9
|
type Agent = Pick<Session, "id" | "host_id" | "tmux_name"> &
|
|
9
10
|
Partial<Pick<Session, "worktree_path" | "branch_name">>;
|
|
@@ -20,4 +21,6 @@ export async function endSessionAgent(s: Agent): Promise<void> {
|
|
|
20
21
|
shutTaskSimulators(s);
|
|
21
22
|
void waitForExit(s.id).catch(() => {});
|
|
22
23
|
await endTmuxSession(s.host_id, s.tmux_name);
|
|
24
|
+
// Whoever waits on it is told now, not at the next sweep.
|
|
25
|
+
kickWaits();
|
|
23
26
|
}
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* (which sends no device) is never refused on them.
|
|
10
10
|
* - plan: open, and a plan another machine placed here is read and approved
|
|
11
11
|
* by that machine only.
|
|
12
|
+
*
|
|
13
|
+
* The caller is the gate's device id. A machine linked through Connect is
|
|
14
|
+
* `peer:<host id>` (lib/security/gate.ts), with no Can approve: the same
|
|
15
|
+
* standing a linked machine's device token has.
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
18
|
import { NextRequest, NextResponse } from "next/server";
|
package/package.json
CHANGED
package/scripts/agent-os
CHANGED
|
@@ -83,6 +83,8 @@ case "${1:-}" in
|
|
|
83
83
|
pair) node "$SCRIPT_DIR/pair.mjs" "$PORT" ;;
|
|
84
84
|
connect) shift; (cd "$(dirname "$SCRIPT_DIR")" && npx tsx lib/connect/cli.ts "$@") ;;
|
|
85
85
|
disconnect) (cd "$(dirname "$SCRIPT_DIR")" && npx tsx lib/connect/cli.ts --off) ;;
|
|
86
|
+
link) shift; (cd "$(dirname "$SCRIPT_DIR")" && npx tsx lib/peer/cli.ts "$PORT" "$@") ;;
|
|
87
|
+
unlink) shift; (cd "$(dirname "$SCRIPT_DIR")" && npx tsx lib/peer/cli.ts "$PORT" --unlink "$@") ;;
|
|
86
88
|
passkeys) shift; (cd "$(dirname "$SCRIPT_DIR")" && npx tsx lib/security/passkeys-cli.ts "$@") ;;
|
|
87
89
|
logs) cmd_logs ;;
|
|
88
90
|
update) cmd_update ;;
|
package/scripts/lib/commands.sh
CHANGED
|
@@ -558,6 +558,8 @@ cmd_help() {
|
|
|
558
558
|
echo " pair Add a phone, tablet or laptop (prints a QR code)"
|
|
559
559
|
echo " connect Turn on AgentOS Connect for this machine (in development)"
|
|
560
560
|
echo " disconnect Turn Connect off; the tunnel closes within seconds"
|
|
561
|
+
echo " link Link machines on your Connect account (link <name>, link accept <code>)"
|
|
562
|
+
echo " unlink Unlink a machine linked through Connect"
|
|
561
563
|
echo " passkeys reset Revoke every passkey (recovery; asks you to confirm)"
|
|
562
564
|
echo " logs Tail server logs"
|
|
563
565
|
echo " update Update to latest version"
|
package/server.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { setStartGate } from "./lib/stacks/tick";
|
|
|
14
14
|
import { stackStartGate } from "./lib/orchestrator/brakes";
|
|
15
15
|
import { buildId } from "./lib/build";
|
|
16
16
|
import { startOrchestratorWatcher } from "./lib/orchestrator/watcher";
|
|
17
|
+
import { startWaitWatcher } from "./lib/bus/waits/tick";
|
|
17
18
|
import { installErrorCounter } from "./lib/health/errors";
|
|
18
19
|
import { recordBoot } from "./lib/health/boots";
|
|
19
20
|
import { realDeps, schedulesEnabled, startScheduler } from "./lib/schedules";
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
refuseNextInternal,
|
|
48
49
|
} from "./lib/security/next-internals";
|
|
49
50
|
import { lanEnabled } from "./lib/security/network-settings";
|
|
51
|
+
import { handlePeerRpc, isPeerRpc, rpcDeps } from "./lib/peer/rpc-server";
|
|
50
52
|
import { startConnect } from "./lib/connect/serve";
|
|
51
53
|
import { serverPort } from "./lib/connect/config";
|
|
52
54
|
import { startUpdateChecks, updateChecksEnabled } from "./lib/updates/check";
|
|
@@ -150,6 +152,7 @@ app.prepare().then(async () => {
|
|
|
150
152
|
.map((h) => h.trim().toLowerCase())
|
|
151
153
|
.filter(Boolean);
|
|
152
154
|
const auth = authPolicy();
|
|
155
|
+
const peerRpc = rpcDeps(port);
|
|
153
156
|
const header = (v: string | string[] | undefined) =>
|
|
154
157
|
Array.isArray(v) ? v[0] : v;
|
|
155
158
|
|
|
@@ -177,6 +180,22 @@ app.prepare().then(async () => {
|
|
|
177
180
|
if (refuseNextInternal(req, res)) return;
|
|
178
181
|
if (!gateRequest(req, res, auth)) return;
|
|
179
182
|
if (demo && !gateDemoRequest(req, res)) return;
|
|
183
|
+
// A linked machine's envelope: verified, then re-issued to loopback.
|
|
184
|
+
if (isPeerRpc(req)) {
|
|
185
|
+
if (demo) {
|
|
186
|
+
res.statusCode = 404;
|
|
187
|
+
res.end();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
await handlePeerRpc(req, res, peerRpc);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error("peer rpc failed:", err);
|
|
194
|
+
if (!res.headersSent) res.statusCode = 500;
|
|
195
|
+
res.end();
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
180
199
|
try {
|
|
181
200
|
await compressJson(req, res);
|
|
182
201
|
const parsedUrl = parse(req.url!, true);
|
|
@@ -458,6 +477,8 @@ app.prepare().then(async () => {
|
|
|
458
477
|
if (process.env.AGENTOS_STACKS !== "off") startStackWatcher();
|
|
459
478
|
// Each workspace's orchestrator hears about its sessions as events.
|
|
460
479
|
if (process.env.AGENTOS_ORCHESTRATOR !== "off") startOrchestratorWatcher();
|
|
480
|
+
// Sessions waiting on each other (aos send --await, aos wait).
|
|
481
|
+
startWaitWatcher();
|
|
461
482
|
// Schedules tick here, once a minute, and nowhere else.
|
|
462
483
|
if (schedulesEnabled(process.env)) startScheduler(realDeps);
|
|
463
484
|
// Looking for a newer version, and running an update only when one is
|