remote-pi 0.1.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/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +1285 -0
- package/dist/index.js.map +1 -0
- package/dist/pairing/crypto.d.ts +8 -0
- package/dist/pairing/crypto.js +22 -0
- package/dist/pairing/crypto.js.map +1 -0
- package/dist/pairing/handshake.d.ts +28 -0
- package/dist/pairing/handshake.js +113 -0
- package/dist/pairing/handshake.js.map +1 -0
- package/dist/pairing/noise-sha256.d.ts +16 -0
- package/dist/pairing/noise-sha256.js +103 -0
- package/dist/pairing/noise-sha256.js.map +1 -0
- package/dist/pairing/qr.d.ts +41 -0
- package/dist/pairing/qr.js +96 -0
- package/dist/pairing/qr.js.map +1 -0
- package/dist/pairing/storage.d.ts +10 -0
- package/dist/pairing/storage.js +65 -0
- package/dist/pairing/storage.js.map +1 -0
- package/dist/protocol/codec.d.ts +7 -0
- package/dist/protocol/codec.js +46 -0
- package/dist/protocol/codec.js.map +1 -0
- package/dist/protocol/types.d.ts +119 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/rooms.d.ts +9 -0
- package/dist/rooms.js +22 -0
- package/dist/rooms.js.map +1 -0
- package/dist/session/agent_bridge.d.ts +55 -0
- package/dist/session/agent_bridge.js +146 -0
- package/dist/session/agent_bridge.js.map +1 -0
- package/dist/session/broker.d.ts +37 -0
- package/dist/session/broker.js +206 -0
- package/dist/session/broker.js.map +1 -0
- package/dist/session/envelope.d.ts +24 -0
- package/dist/session/envelope.js +89 -0
- package/dist/session/envelope.js.map +1 -0
- package/dist/session/global_config.d.ts +14 -0
- package/dist/session/global_config.js +51 -0
- package/dist/session/global_config.js.map +1 -0
- package/dist/session/leader_election.d.ts +16 -0
- package/dist/session/leader_election.js +78 -0
- package/dist/session/leader_election.js.map +1 -0
- package/dist/session/local_config.d.ts +18 -0
- package/dist/session/local_config.js +47 -0
- package/dist/session/local_config.js.map +1 -0
- package/dist/session/peer.d.ts +80 -0
- package/dist/session/peer.js +268 -0
- package/dist/session/peer.js.map +1 -0
- package/dist/session/setup_wizard.d.ts +32 -0
- package/dist/session/setup_wizard.js +60 -0
- package/dist/session/setup_wizard.js.map +1 -0
- package/dist/session/tool_gate.d.ts +5 -0
- package/dist/session/tool_gate.js +11 -0
- package/dist/session/tool_gate.js.map +1 -0
- package/dist/session/tools.d.ts +16 -0
- package/dist/session/tools.js +123 -0
- package/dist/session/tools.js.map +1 -0
- package/dist/session/wizard.d.ts +13 -0
- package/dist/session/wizard.js +20 -0
- package/dist/session/wizard.js.map +1 -0
- package/dist/settings.d.ts +15 -0
- package/dist/settings.js +52 -0
- package/dist/settings.js.map +1 -0
- package/dist/transport/peer_channel.d.ts +37 -0
- package/dist/transport/peer_channel.js +85 -0
- package/dist/transport/peer_channel.js.map +1 -0
- package/dist/transport/relay_client.d.ts +81 -0
- package/dist/transport/relay_client.js +154 -0
- package/dist/transport/relay_client.js.map +1 -0
- package/dist/ui/footer.d.ts +32 -0
- package/dist/ui/footer.js +32 -0
- package/dist/ui/footer.js.map +1 -0
- package/package.json +77 -0
- package/skills/agent-network/SKILL.md +429 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { parse, serialize, uuidv7, EnvelopeError } from "./envelope.js";
|
|
4
|
+
const BROKER_NAME = "broker";
|
|
5
|
+
export class Broker {
|
|
6
|
+
peers = new Map();
|
|
7
|
+
auditPath;
|
|
8
|
+
onRouted;
|
|
9
|
+
server;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.server = opts.server;
|
|
12
|
+
this.auditPath = opts.auditPath;
|
|
13
|
+
this.onRouted = opts.onRouted;
|
|
14
|
+
this.server.on("connection", (socket) => this._handleConnection(socket));
|
|
15
|
+
}
|
|
16
|
+
/** Peers currently registered. Snapshot, safe to read. */
|
|
17
|
+
peerNames() {
|
|
18
|
+
return [...this.peers.keys()];
|
|
19
|
+
}
|
|
20
|
+
async close() {
|
|
21
|
+
for (const p of this.peers.values())
|
|
22
|
+
p.socket.destroy();
|
|
23
|
+
this.peers.clear();
|
|
24
|
+
await new Promise((resolve) => this.server.close(() => resolve()));
|
|
25
|
+
}
|
|
26
|
+
// ── connection lifecycle ──────────────────────────────────────────────────
|
|
27
|
+
_handleConnection(socket) {
|
|
28
|
+
const conn = { name: "", socket, buf: "" };
|
|
29
|
+
socket.setEncoding("utf8");
|
|
30
|
+
socket.on("data", (chunk) => this._onData(conn, chunk));
|
|
31
|
+
socket.on("close", () => this._onClose(conn));
|
|
32
|
+
socket.on("error", () => { });
|
|
33
|
+
}
|
|
34
|
+
_onData(conn, chunk) {
|
|
35
|
+
conn.buf += chunk;
|
|
36
|
+
let nl;
|
|
37
|
+
while ((nl = conn.buf.indexOf("\n")) >= 0) {
|
|
38
|
+
const line = conn.buf.slice(0, nl);
|
|
39
|
+
conn.buf = conn.buf.slice(nl + 1);
|
|
40
|
+
if (!line)
|
|
41
|
+
continue;
|
|
42
|
+
void this._handleLine(conn, line);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async _handleLine(conn, line) {
|
|
46
|
+
// Unregistered conn must send a `register` control message first.
|
|
47
|
+
if (!conn.name) {
|
|
48
|
+
this._handleRegister(conn, line);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Already registered — must be a regular envelope.
|
|
52
|
+
let env;
|
|
53
|
+
try {
|
|
54
|
+
env = parse(line);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
if (e instanceof EnvelopeError)
|
|
58
|
+
return; // malformed; drop silently
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
// Force `from` to the registered name (security: peer can't spoof).
|
|
62
|
+
env.from = conn.name;
|
|
63
|
+
await this._route(env);
|
|
64
|
+
}
|
|
65
|
+
_handleRegister(conn, line) {
|
|
66
|
+
let req;
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(line);
|
|
69
|
+
if (!parsed ||
|
|
70
|
+
typeof parsed !== "object" ||
|
|
71
|
+
parsed.type !== "register" ||
|
|
72
|
+
typeof parsed.name !== "string") {
|
|
73
|
+
conn.socket.destroy();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
req = parsed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
conn.socket.destroy();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const assigned = this._uniqueName(req.name);
|
|
83
|
+
conn.name = assigned;
|
|
84
|
+
this.peers.set(assigned, conn);
|
|
85
|
+
const ack = { type: "register_ack", name_assigned: assigned };
|
|
86
|
+
try {
|
|
87
|
+
conn.socket.write(JSON.stringify(ack) + "\n");
|
|
88
|
+
}
|
|
89
|
+
catch { /* peer hung up */ }
|
|
90
|
+
// Notify others (peer_joined broadcast).
|
|
91
|
+
this._broadcastSystem({ type: "peer_joined", name: assigned }, assigned);
|
|
92
|
+
}
|
|
93
|
+
_uniqueName(requested) {
|
|
94
|
+
if (!this.peers.has(requested))
|
|
95
|
+
return requested;
|
|
96
|
+
for (let n = 2; n < 1000; n++) {
|
|
97
|
+
const candidate = `${requested}#${n}`;
|
|
98
|
+
if (!this.peers.has(candidate))
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`name space exhausted for ${requested}`);
|
|
102
|
+
}
|
|
103
|
+
_onClose(conn) {
|
|
104
|
+
if (!conn.name)
|
|
105
|
+
return;
|
|
106
|
+
this.peers.delete(conn.name);
|
|
107
|
+
this._broadcastSystem({ type: "peer_left", name: conn.name }, conn.name);
|
|
108
|
+
}
|
|
109
|
+
// ── routing ───────────────────────────────────────────────────────────────
|
|
110
|
+
async _route(env) {
|
|
111
|
+
// Special handling for messages addressed to the broker itself.
|
|
112
|
+
if (env.to === BROKER_NAME) {
|
|
113
|
+
this._handleBrokerMessage(env);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const targets = this._resolveTargets(env);
|
|
117
|
+
const delivered = [];
|
|
118
|
+
const line = serialize(env);
|
|
119
|
+
for (const targetName of targets) {
|
|
120
|
+
const peer = this.peers.get(targetName);
|
|
121
|
+
if (!peer)
|
|
122
|
+
continue;
|
|
123
|
+
try {
|
|
124
|
+
peer.socket.write(line);
|
|
125
|
+
delivered.push(targetName);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// peer dropped mid-write — close handler will fire
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (this.auditPath)
|
|
132
|
+
await this._appendAudit(env, delivered);
|
|
133
|
+
this.onRouted?.(env, delivered);
|
|
134
|
+
}
|
|
135
|
+
_resolveTargets(env) {
|
|
136
|
+
if (env.to === "broadcast") {
|
|
137
|
+
return this.peerNames().filter((n) => n !== env.from);
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(env.to)) {
|
|
140
|
+
return env.to.filter((n) => n !== env.from);
|
|
141
|
+
}
|
|
142
|
+
// Unicast: drop self-loops too. The skill warns "useless" but the LLM
|
|
143
|
+
// might still try (especially with deceiving `re` reply chains). A
|
|
144
|
+
// self-loop has no upside and risks unbounded message ↔ inject ↔ message
|
|
145
|
+
// cycles when the inbound injector tells the LLM "reply with re=…".
|
|
146
|
+
if (env.to === env.from)
|
|
147
|
+
return [];
|
|
148
|
+
return [env.to];
|
|
149
|
+
}
|
|
150
|
+
_handleBrokerMessage(env) {
|
|
151
|
+
const body = env.body;
|
|
152
|
+
if (!body || typeof body !== "object")
|
|
153
|
+
return;
|
|
154
|
+
if (body.type === "list_peers") {
|
|
155
|
+
const reply = {
|
|
156
|
+
from: BROKER_NAME,
|
|
157
|
+
to: env.from,
|
|
158
|
+
id: uuidv7(),
|
|
159
|
+
re: env.id,
|
|
160
|
+
body: { type: "list_peers_reply", peers: this.peerNames() },
|
|
161
|
+
};
|
|
162
|
+
const peer = this.peers.get(env.from);
|
|
163
|
+
if (peer) {
|
|
164
|
+
try {
|
|
165
|
+
peer.socket.write(serialize(reply));
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignored */ }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
_broadcastSystem(body, excludeName) {
|
|
172
|
+
for (const [name, peer] of this.peers) {
|
|
173
|
+
if (name === excludeName)
|
|
174
|
+
continue;
|
|
175
|
+
const env = {
|
|
176
|
+
from: BROKER_NAME,
|
|
177
|
+
to: name,
|
|
178
|
+
id: uuidv7(),
|
|
179
|
+
re: null,
|
|
180
|
+
body,
|
|
181
|
+
};
|
|
182
|
+
try {
|
|
183
|
+
peer.socket.write(serialize(env));
|
|
184
|
+
}
|
|
185
|
+
catch { /* ignored */ }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async _appendAudit(env, delivered) {
|
|
189
|
+
if (!this.auditPath)
|
|
190
|
+
return;
|
|
191
|
+
const line = JSON.stringify({
|
|
192
|
+
ts: Date.now(),
|
|
193
|
+
from: env.from,
|
|
194
|
+
to: env.to,
|
|
195
|
+
id: env.id,
|
|
196
|
+
re: env.re,
|
|
197
|
+
delivered,
|
|
198
|
+
}) + "\n";
|
|
199
|
+
try {
|
|
200
|
+
await mkdir(dirname(this.auditPath), { recursive: true });
|
|
201
|
+
await appendFile(this.auditPath, line, "utf8");
|
|
202
|
+
}
|
|
203
|
+
catch { /* audit best-effort */ }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=broker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker.js","sourceRoot":"","sources":["../../src/session/broker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 5-field envelope for the agent-network local protocol (plano 19).
|
|
3
|
+
* Serialized as JSONL (one JSON object per line) over UDS streams.
|
|
4
|
+
*/
|
|
5
|
+
export interface Envelope {
|
|
6
|
+
from: string;
|
|
7
|
+
to: string | string[];
|
|
8
|
+
id: string;
|
|
9
|
+
re: string | null;
|
|
10
|
+
body: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generates a UUID v7 — time-ordered, monotonically increasing within the
|
|
14
|
+
* same millisecond. Format:
|
|
15
|
+
* <48-bit ts ms><4-bit ver=7><12-bit rand><2-bit var=10><62-bit rand>
|
|
16
|
+
*/
|
|
17
|
+
export declare function uuidv7(): string;
|
|
18
|
+
export declare class EnvelopeError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
export declare function serialize(env: Envelope): string;
|
|
22
|
+
export declare function parse(line: string): Envelope;
|
|
23
|
+
/** Convenience: builds an envelope with id auto-generated. */
|
|
24
|
+
export declare function envelope(from: string, to: string | string[], body: unknown, re?: string | null): Envelope;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
/**
|
|
4
|
+
* Generates a UUID v7 — time-ordered, monotonically increasing within the
|
|
5
|
+
* same millisecond. Format:
|
|
6
|
+
* <48-bit ts ms><4-bit ver=7><12-bit rand><2-bit var=10><62-bit rand>
|
|
7
|
+
*/
|
|
8
|
+
export function uuidv7() {
|
|
9
|
+
const ts = Date.now();
|
|
10
|
+
const rand = randomBytes(10);
|
|
11
|
+
// Encode 48-bit timestamp into bytes 0-5.
|
|
12
|
+
const buf = Buffer.alloc(16);
|
|
13
|
+
buf[0] = (ts / 2 ** 40) & 0xff;
|
|
14
|
+
buf[1] = (ts / 2 ** 32) & 0xff;
|
|
15
|
+
buf[2] = (ts / 2 ** 24) & 0xff;
|
|
16
|
+
buf[3] = (ts / 2 ** 16) & 0xff;
|
|
17
|
+
buf[4] = (ts / 2 ** 8) & 0xff;
|
|
18
|
+
buf[5] = ts & 0xff;
|
|
19
|
+
rand.copy(buf, 6);
|
|
20
|
+
// Set version (7) in upper nibble of byte 6.
|
|
21
|
+
buf[6] = (buf[6] & 0x0f) | 0x70;
|
|
22
|
+
// Set variant (10) in upper two bits of byte 8.
|
|
23
|
+
buf[8] = (buf[8] & 0x3f) | 0x80;
|
|
24
|
+
const hex = buf.toString("hex");
|
|
25
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
26
|
+
}
|
|
27
|
+
export class EnvelopeError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "EnvelopeError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function serialize(env) {
|
|
34
|
+
return JSON.stringify(env) + "\n";
|
|
35
|
+
}
|
|
36
|
+
export function parse(line) {
|
|
37
|
+
let raw;
|
|
38
|
+
try {
|
|
39
|
+
raw = JSON.parse(line);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
throw new EnvelopeError(`not JSON: ${e.message}`);
|
|
43
|
+
}
|
|
44
|
+
if (!raw || typeof raw !== "object") {
|
|
45
|
+
throw new EnvelopeError("not an object");
|
|
46
|
+
}
|
|
47
|
+
const o = raw;
|
|
48
|
+
if (typeof o["from"] !== "string" || o["from"].length === 0) {
|
|
49
|
+
throw new EnvelopeError("from must be non-empty string");
|
|
50
|
+
}
|
|
51
|
+
const to = o["to"];
|
|
52
|
+
if (typeof to !== "string" && !Array.isArray(to)) {
|
|
53
|
+
throw new EnvelopeError("to must be string or array");
|
|
54
|
+
}
|
|
55
|
+
if (typeof to === "string" && to.length === 0) {
|
|
56
|
+
throw new EnvelopeError("to must be non-empty");
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(to)) {
|
|
59
|
+
if (to.length === 0)
|
|
60
|
+
throw new EnvelopeError("to[] must not be empty");
|
|
61
|
+
for (const t of to) {
|
|
62
|
+
if (typeof t !== "string" || t.length === 0) {
|
|
63
|
+
throw new EnvelopeError("to[] entries must be non-empty strings");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (typeof o["id"] !== "string" || !UUID_RE.test(o["id"])) {
|
|
68
|
+
throw new EnvelopeError("id must be UUID");
|
|
69
|
+
}
|
|
70
|
+
const re = o["re"];
|
|
71
|
+
if (re !== null && (typeof re !== "string" || !UUID_RE.test(re))) {
|
|
72
|
+
throw new EnvelopeError("re must be null or UUID");
|
|
73
|
+
}
|
|
74
|
+
if (!("body" in o)) {
|
|
75
|
+
throw new EnvelopeError("body required");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
from: o["from"],
|
|
79
|
+
to: to,
|
|
80
|
+
id: o["id"],
|
|
81
|
+
re: re,
|
|
82
|
+
body: o["body"],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Convenience: builds an envelope with id auto-generated. */
|
|
86
|
+
export function envelope(from, to, body, re = null) {
|
|
87
|
+
return { from, to, id: uuidv7(), re, body };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/session/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAc1C,MAAM,OAAO,GACX,iEAAiE,CAAC;AAEpE;;;;GAIG;AACH,MAAM,UAAU,MAAM;IACpB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACtB,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,0CAA0C;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7B,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAC/B,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAC/B,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAC/B,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAC/B,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC9B,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IACnB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAClB,6CAA6C;IAC7C,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjC,gDAAgD;IAChD,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjC,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;AAC7G,CAAC;AAED,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,UAAU,SAAS,CAAC,GAAa;IACrC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,aAAa,CAAC,aAAc,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,aAAa,CAAC,eAAe,CAAC,CAAC;IAC3C,CAAC;IACD,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAK,CAAC,CAAC,MAAM,CAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,aAAa,CAAC,+BAA+B,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACnB,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,aAAa,CAAC,4BAA4B,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;QACtB,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,aAAa,CAAC,wBAAwB,CAAC,CAAC;QACvE,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;YACnB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5C,MAAM,IAAI,aAAa,CAAC,wCAAwC,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAW,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,aAAa,CAAC,iBAAiB,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACnB,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAY,CAAC,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,aAAa,CAAC,yBAAyB,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,aAAa,CAAC,eAAe,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,MAAM,CAAW;QACzB,EAAE,EAAE,EAAuB;QAC3B,EAAE,EAAE,CAAC,CAAC,IAAI,CAAW;QACrB,EAAE,EAAE,EAAmB;QACvB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;KAChB,CAAC;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,QAAQ,CACtB,IAAY,EACZ,EAAqB,EACrB,IAAa,EACb,KAAoB,IAAI;IAExB,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Ensures the new subdirs exist inside the existing ~/.pi/remote/. */
|
|
2
|
+
export declare function ensureGlobalDirs(): void;
|
|
3
|
+
/** Path to the UDS socket for a named session. */
|
|
4
|
+
export declare function sessionSockPath(name: string): string;
|
|
5
|
+
/** Path to the audit log for a named session. */
|
|
6
|
+
export declare function sessionAuditPath(name: string): string;
|
|
7
|
+
/** Path to the session metadata JSON. */
|
|
8
|
+
export declare function sessionMetaPath(name: string): string;
|
|
9
|
+
export declare function sessionsDir(): string;
|
|
10
|
+
export declare function skillsDir(): string;
|
|
11
|
+
/** Lists discovered session names from disk. */
|
|
12
|
+
export declare function listSessions(): string[];
|
|
13
|
+
/** Heuristic: a session has an existing broker.sock file. */
|
|
14
|
+
export declare function sessionHasSock(name: string): boolean;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const HOME_PI_REMOTE = join(homedir(), ".pi", "remote");
|
|
5
|
+
const SESSIONS_DIR = join(HOME_PI_REMOTE, "sessions");
|
|
6
|
+
const SKILLS_DIR = join(HOME_PI_REMOTE, "skills");
|
|
7
|
+
/** Ensures the new subdirs exist inside the existing ~/.pi/remote/. */
|
|
8
|
+
export function ensureGlobalDirs() {
|
|
9
|
+
mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
10
|
+
mkdirSync(SKILLS_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
/** Path to the UDS socket for a named session. */
|
|
13
|
+
export function sessionSockPath(name) {
|
|
14
|
+
return join(SESSIONS_DIR, name, "broker.sock");
|
|
15
|
+
}
|
|
16
|
+
/** Path to the audit log for a named session. */
|
|
17
|
+
export function sessionAuditPath(name) {
|
|
18
|
+
return join(SESSIONS_DIR, name, "audit.jsonl");
|
|
19
|
+
}
|
|
20
|
+
/** Path to the session metadata JSON. */
|
|
21
|
+
export function sessionMetaPath(name) {
|
|
22
|
+
return join(SESSIONS_DIR, name, "session.json");
|
|
23
|
+
}
|
|
24
|
+
export function sessionsDir() {
|
|
25
|
+
return SESSIONS_DIR;
|
|
26
|
+
}
|
|
27
|
+
export function skillsDir() {
|
|
28
|
+
return SKILLS_DIR;
|
|
29
|
+
}
|
|
30
|
+
/** Lists discovered session names from disk. */
|
|
31
|
+
export function listSessions() {
|
|
32
|
+
ensureGlobalDirs();
|
|
33
|
+
try {
|
|
34
|
+
return readdirSync(SESSIONS_DIR).filter((entry) => {
|
|
35
|
+
try {
|
|
36
|
+
return statSync(join(SESSIONS_DIR, entry)).isDirectory();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Heuristic: a session has an existing broker.sock file. */
|
|
48
|
+
export function sessionHasSock(name) {
|
|
49
|
+
return existsSync(sessionSockPath(name));
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=global_config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global_config.js","sourceRoot":"","sources":["../../src/session/global_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AACxD,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;AACtD,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;AAElD,uEAAuE;AACvE,MAAM,UAAU,gBAAgB;IAC9B,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,YAAY;IAC1B,gBAAgB,EAAE,CAAC;IACnB,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YAChD,IAAI,CAAC;gBACH,OAAO,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAC3D,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,KAAK,CAAC;YAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Server, Socket } from "node:net";
|
|
2
|
+
export type ElectionResult = {
|
|
3
|
+
role: "leader";
|
|
4
|
+
server: Server;
|
|
5
|
+
} | {
|
|
6
|
+
role: "follower";
|
|
7
|
+
socket: Socket;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* UDS-based leader election. Tries to connect to `sockPath`; on failure, tries
|
|
11
|
+
* to bind. If both lose the race (EADDRINUSE on bind, ECONNREFUSED on connect),
|
|
12
|
+
* cleans up stale sock file and retries with jittered backoff.
|
|
13
|
+
*
|
|
14
|
+
* Returns the role + the live handle (server for leader, socket for follower).
|
|
15
|
+
*/
|
|
16
|
+
export declare function joinOrLead(sockPath: string): Promise<ElectionResult>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createConnection, createServer } from "node:net";
|
|
2
|
+
import { existsSync, lstatSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
const MAX_ATTEMPTS = 20;
|
|
5
|
+
const BASE_BACKOFF_MS = 30;
|
|
6
|
+
const JITTER_MS = 70;
|
|
7
|
+
const PROBE_TIMEOUT_MS = 500;
|
|
8
|
+
/**
|
|
9
|
+
* UDS-based leader election. Tries to connect to `sockPath`; on failure, tries
|
|
10
|
+
* to bind. If both lose the race (EADDRINUSE on bind, ECONNREFUSED on connect),
|
|
11
|
+
* cleans up stale sock file and retries with jittered backoff.
|
|
12
|
+
*
|
|
13
|
+
* Returns the role + the live handle (server for leader, socket for follower).
|
|
14
|
+
*/
|
|
15
|
+
export async function joinOrLead(sockPath) {
|
|
16
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
17
|
+
if (existsSync(sockPath)) {
|
|
18
|
+
const sock = await _tryConnect(sockPath);
|
|
19
|
+
if (sock)
|
|
20
|
+
return { role: "follower", socket: sock };
|
|
21
|
+
_removeStaleSock(sockPath);
|
|
22
|
+
}
|
|
23
|
+
const server = await _tryBind(sockPath);
|
|
24
|
+
if (server)
|
|
25
|
+
return { role: "leader", server };
|
|
26
|
+
await delay(BASE_BACKOFF_MS + Math.random() * JITTER_MS);
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`leader election failed after ${MAX_ATTEMPTS} attempts: ${sockPath}`);
|
|
29
|
+
}
|
|
30
|
+
function _tryConnect(sockPath) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
const sock = createConnection({ path: sockPath });
|
|
33
|
+
let settled = false;
|
|
34
|
+
const settle = (val) => {
|
|
35
|
+
if (settled)
|
|
36
|
+
return;
|
|
37
|
+
settled = true;
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
if (!val)
|
|
40
|
+
sock.destroy();
|
|
41
|
+
resolve(val);
|
|
42
|
+
};
|
|
43
|
+
const timer = setTimeout(() => settle(null), PROBE_TIMEOUT_MS);
|
|
44
|
+
sock.once("connect", () => settle(sock));
|
|
45
|
+
sock.once("error", () => settle(null));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function _tryBind(sockPath) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const server = createServer();
|
|
51
|
+
let settled = false;
|
|
52
|
+
const settle = (val) => {
|
|
53
|
+
if (settled)
|
|
54
|
+
return;
|
|
55
|
+
settled = true;
|
|
56
|
+
if (!val) {
|
|
57
|
+
try {
|
|
58
|
+
server.close();
|
|
59
|
+
}
|
|
60
|
+
catch { /* ignored */ }
|
|
61
|
+
}
|
|
62
|
+
resolve(val);
|
|
63
|
+
};
|
|
64
|
+
server.once("error", () => settle(null));
|
|
65
|
+
server.listen(sockPath, () => settle(server));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function _removeStaleSock(sockPath) {
|
|
69
|
+
try {
|
|
70
|
+
const stat = lstatSync(sockPath);
|
|
71
|
+
if (stat.isSocket())
|
|
72
|
+
unlinkSync(sockPath);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// ENOENT or not-a-socket — nothing to clean up.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=leader_election.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leader_election.js","sourceRoot":"","sources":["../../src/session/leader_election.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface LocalConfig {
|
|
2
|
+
agent_name?: string;
|
|
3
|
+
session_name?: string;
|
|
4
|
+
/**
|
|
5
|
+
* If true (default), `/remote-pi` with no args auto-joins the session and
|
|
6
|
+
* starts the relay on a fresh terminal. Added in plano 21. Legacy configs
|
|
7
|
+
* without this field are treated as `true` for backward compatibility.
|
|
8
|
+
*/
|
|
9
|
+
auto_start_relay?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Returns true when `<cwd>/.pi/remote-pi/config.json` exists on disk. */
|
|
12
|
+
export declare function localConfigExists(cwd: string): boolean;
|
|
13
|
+
export declare function loadLocalConfig(cwd: string): LocalConfig;
|
|
14
|
+
export declare function saveLocalConfig(cwd: string, patch: Partial<LocalConfig>): void;
|
|
15
|
+
/** Default agent name when none is configured: basename of cwd. */
|
|
16
|
+
export declare function defaultAgentName(cwd: string): string;
|
|
17
|
+
/** Resolves auto_start_relay with backward-compat (undefined → true). */
|
|
18
|
+
export declare function effectiveAutoStartRelay(cfg: LocalConfig): boolean;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
const LOCAL_DIR = ".pi/remote-pi";
|
|
4
|
+
const LOCAL_FILE = "config.json";
|
|
5
|
+
function pathFor(cwd) {
|
|
6
|
+
return join(cwd, LOCAL_DIR, LOCAL_FILE);
|
|
7
|
+
}
|
|
8
|
+
/** Returns true when `<cwd>/.pi/remote-pi/config.json` exists on disk. */
|
|
9
|
+
export function localConfigExists(cwd) {
|
|
10
|
+
return existsSync(pathFor(cwd));
|
|
11
|
+
}
|
|
12
|
+
export function loadLocalConfig(cwd) {
|
|
13
|
+
const p = pathFor(cwd);
|
|
14
|
+
if (!existsSync(p))
|
|
15
|
+
return {};
|
|
16
|
+
try {
|
|
17
|
+
const raw = readFileSync(p, "utf8");
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
if (!parsed || typeof parsed !== "object")
|
|
20
|
+
return {};
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function saveLocalConfig(cwd, patch) {
|
|
28
|
+
const p = pathFor(cwd);
|
|
29
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
30
|
+
const current = loadLocalConfig(cwd);
|
|
31
|
+
const next = { ...current, ...patch };
|
|
32
|
+
// Always persist auto_start_relay explicitly (default true) so future reads
|
|
33
|
+
// never need to guess. Backward-compat: legacy files without the field
|
|
34
|
+
// are treated as true on read; we lock that intent in on first save.
|
|
35
|
+
if (typeof next.auto_start_relay !== "boolean")
|
|
36
|
+
next.auto_start_relay = true;
|
|
37
|
+
writeFileSync(p, JSON.stringify(next, null, 2));
|
|
38
|
+
}
|
|
39
|
+
/** Default agent name when none is configured: basename of cwd. */
|
|
40
|
+
export function defaultAgentName(cwd) {
|
|
41
|
+
return basename(cwd) || "agent";
|
|
42
|
+
}
|
|
43
|
+
/** Resolves auto_start_relay with backward-compat (undefined → true). */
|
|
44
|
+
export function effectiveAutoStartRelay(cfg) {
|
|
45
|
+
return cfg.auto_start_relay !== false;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=local_config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local_config.js","sourceRoot":"","sources":["../../src/session/local_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,SAAS,GAAG,eAAe,CAAC;AAClC,MAAM,UAAU,GAAG,aAAa,CAAC;AAajC,SAAS,OAAO,CAAC,GAAW;IAC1B,OAAO,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACrD,OAAO,MAAqB,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,KAA2B;IACtE,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,IAAI,GAAgB,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACnD,4EAA4E;IAC5E,uEAAuE;IACvE,qEAAqE;IACrE,IAAI,OAAO,IAAI,CAAC,gBAAgB,KAAK,SAAS;QAAE,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAC7E,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC;AAClC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,uBAAuB,CAAC,GAAgB;IACtD,OAAO,GAAG,CAAC,gBAAgB,KAAK,KAAK,CAAC;AACxC,CAAC"}
|