@gorlitzer-labs/apiary 1.13.9 → 1.15.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/README.md +17 -3
- package/dist/agent/index.d.ts +73 -0
- package/dist/agent/index.js +1 -1
- package/dist/{chunk-BCQQJR5E.js → chunk-3UQBSELW.js} +7 -178
- package/dist/chunk-3UQBSELW.js.map +1 -0
- package/dist/{chunk-7JXPJO3R.js → chunk-74XU7HHN.js} +6 -6
- package/dist/{chunk-4A7MREYX.js → chunk-N2LPKMPO.js} +185 -2
- package/dist/chunk-N2LPKMPO.js.map +1 -0
- package/dist/chunk-OEAHTZCA.js +994 -0
- package/dist/chunk-OEAHTZCA.js.map +1 -0
- package/dist/{chunk-MEWZT5KA.js → chunk-W4KWQKPP.js} +2 -2
- package/dist/{chunk-WJD5EKO6.js → chunk-Z4J6TRJY.js} +154 -41
- package/dist/chunk-Z4J6TRJY.js.map +1 -0
- package/dist/cli/index.js +39 -386
- package/dist/cli/index.js.map +1 -1
- package/dist/{invites-GRROUSCE.js → invites-SJ4ZVKVX.js} +4 -4
- package/dist/{join-H62VB4HU.js → join-5UFFMSLS.js} +4 -4
- package/dist/{room-ID4DQDPT.js → room-S6ULVAEH.js} +10 -10
- package/dist/run-5PSUQJF4.js +270 -0
- package/dist/run-5PSUQJF4.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-4A7MREYX.js.map +0 -1
- package/dist/chunk-BCQQJR5E.js.map +0 -1
- package/dist/chunk-WJD5EKO6.js.map +0 -1
- package/dist/chunk-XLUTCAY6.js +0 -288
- package/dist/chunk-XLUTCAY6.js.map +0 -1
- package/dist/run-C22WQZVK.js +0 -91
- package/dist/run-C22WQZVK.js.map +0 -1
- /package/dist/{chunk-7JXPJO3R.js.map → chunk-74XU7HHN.js.map} +0 -0
- /package/dist/{chunk-MEWZT5KA.js.map → chunk-W4KWQKPP.js.map} +0 -0
- /package/dist/{invites-GRROUSCE.js.map → invites-SJ4ZVKVX.js.map} +0 -0
- /package/dist/{join-H62VB4HU.js.map → join-5UFFMSLS.js.map} +0 -0
- /package/dist/{room-ID4DQDPT.js.map → room-S6ULVAEH.js.map} +0 -0
package/dist/chunk-XLUTCAY6.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RemoteRoomDataSource,
|
|
3
|
-
buildCatchUpLines
|
|
4
|
-
} from "./chunk-WJD5EKO6.js";
|
|
5
|
-
import {
|
|
6
|
-
extractToken
|
|
7
|
-
} from "./chunk-4A7MREYX.js";
|
|
8
|
-
import {
|
|
9
|
-
can
|
|
10
|
-
} from "./chunk-TMXQDGMN.js";
|
|
11
|
-
|
|
12
|
-
// src/agent/wait-for-agent.ts
|
|
13
|
-
var DEFAULT_WAIT_MS = 6e4;
|
|
14
|
-
var MAX_WAIT_MS = 12e4;
|
|
15
|
-
var DEFAULT_POLL_MS = 2e3;
|
|
16
|
-
function resolveBudgetMs(timeoutMs, maxWaitMs = MAX_WAIT_MS) {
|
|
17
|
-
return Math.min(Math.max(timeoutMs ?? DEFAULT_WAIT_MS, 0), maxWaitMs);
|
|
18
|
-
}
|
|
19
|
-
function satisfies(state, until, initial) {
|
|
20
|
-
if (state === "unknown") return false;
|
|
21
|
-
if (until === "change") return state !== initial;
|
|
22
|
-
return state === until;
|
|
23
|
-
}
|
|
24
|
-
async function waitForAgent(opts) {
|
|
25
|
-
const {
|
|
26
|
-
readState,
|
|
27
|
-
until = "idle",
|
|
28
|
-
maxWaitMs = MAX_WAIT_MS,
|
|
29
|
-
pollIntervalMs = DEFAULT_POLL_MS,
|
|
30
|
-
sleep = (ms) => new Promise((r) => {
|
|
31
|
-
setTimeout(r, ms).unref?.();
|
|
32
|
-
}),
|
|
33
|
-
now = () => Date.now()
|
|
34
|
-
} = opts;
|
|
35
|
-
const budget = resolveBudgetMs(opts.timeoutMs, maxWaitMs);
|
|
36
|
-
const started = now();
|
|
37
|
-
const initial = await readState();
|
|
38
|
-
if (initial === "unknown") {
|
|
39
|
-
return { result: "not-reporting", state: "unknown", waitedMs: now() - started };
|
|
40
|
-
}
|
|
41
|
-
if (satisfies(initial, until, initial)) {
|
|
42
|
-
return { result: "satisfied", state: initial, waitedMs: now() - started };
|
|
43
|
-
}
|
|
44
|
-
let latest = initial;
|
|
45
|
-
const steps = Math.max(1, Math.ceil(budget / pollIntervalMs));
|
|
46
|
-
for (let step = 0; step < steps; step++) {
|
|
47
|
-
await sleep(pollIntervalMs);
|
|
48
|
-
latest = await readState();
|
|
49
|
-
if (satisfies(latest, until, initial)) {
|
|
50
|
-
return { result: "satisfied", state: latest, waitedMs: now() - started };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return { result: "timeout", state: latest, waitedMs: now() - started };
|
|
54
|
-
}
|
|
55
|
-
function describeOutcome(participant, until, outcome) {
|
|
56
|
-
const secs = Math.round(outcome.waitedMs / 1e3);
|
|
57
|
-
switch (outcome.result) {
|
|
58
|
-
case "satisfied":
|
|
59
|
-
return `${participant} is now ${outcome.state} (waited ${secs}s).`;
|
|
60
|
-
case "timeout":
|
|
61
|
-
return `${participant} is still ${outcome.state} after ${secs}s \u2014 not ${until} yet. Call again to keep waiting, or get on with something else.`;
|
|
62
|
-
case "not-reporting":
|
|
63
|
-
return `${participant} does not report its state, so there is nothing to wait for. Only agents started by apiary with a Claude Code or Codex runtime report it. Ask them directly instead.`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/cli/runtime-handlers.ts
|
|
68
|
-
function buildRuntimeMcpOptions(ctx) {
|
|
69
|
-
const { processor, sseMux, joinResults, agentName, authority, onRoomJoined } = ctx;
|
|
70
|
-
const adminAction = (build) => async (room, participant) => {
|
|
71
|
-
const conn = processor.resolve(room);
|
|
72
|
-
if (!conn) return { success: false, error: `Unknown room "${room}".` };
|
|
73
|
-
const ds = conn.dataSource;
|
|
74
|
-
const p = conn.dataSource.listParticipants().find((pp) => pp.name === participant);
|
|
75
|
-
if (!p) return { success: false, error: `Unknown participant "${participant}".` };
|
|
76
|
-
const { path, body } = build(p.id);
|
|
77
|
-
try {
|
|
78
|
-
const res = await fetch(`${ds.serverUrl}${path}`, {
|
|
79
|
-
method: "POST",
|
|
80
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${ds.sessionToken}` },
|
|
81
|
-
body: JSON.stringify(body)
|
|
82
|
-
});
|
|
83
|
-
if (!res.ok) return { success: false, error: await res.text() };
|
|
84
|
-
return { success: true };
|
|
85
|
-
} catch {
|
|
86
|
-
return { success: false, error: "Server unreachable." };
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
return {
|
|
90
|
-
resolver: processor,
|
|
91
|
-
toolOptions: {
|
|
92
|
-
isEventSeen: (id) => processor.isEventSeen(id),
|
|
93
|
-
markEventsSeen: (ids) => processor.markEventsSeen(ids),
|
|
94
|
-
assignRef: (id) => processor.assignRef(id),
|
|
95
|
-
resolveRef: (ref) => processor.resolveRef(ref)
|
|
96
|
-
},
|
|
97
|
-
authority,
|
|
98
|
-
onSetMode: async (room, mode) => {
|
|
99
|
-
const conn = processor.resolve(room);
|
|
100
|
-
if (!conn) return { success: false, error: `Unknown room "${room}".` };
|
|
101
|
-
processor.setModeForRoom(conn.dataSource.roomId, mode, false);
|
|
102
|
-
try {
|
|
103
|
-
const ds = conn.dataSource;
|
|
104
|
-
const res = await fetch(`${ds.serverUrl}/set-mode`, {
|
|
105
|
-
method: "POST",
|
|
106
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${ds.sessionToken}` },
|
|
107
|
-
body: JSON.stringify({ mode })
|
|
108
|
-
});
|
|
109
|
-
if (!res.ok) return { success: false, error: `Server rejected: ${await res.text()}` };
|
|
110
|
-
} catch {
|
|
111
|
-
}
|
|
112
|
-
return { success: true };
|
|
113
|
-
},
|
|
114
|
-
/**
|
|
115
|
-
* Wait for another agent to reach a state, by polling the room server's
|
|
116
|
-
* participant list. Two seconds of HTTP is far cheaper than the LLM turn
|
|
117
|
-
* the agent would otherwise spend re-reading the room to check.
|
|
118
|
-
*/
|
|
119
|
-
onWaitForAgent: async (room, participant, until, timeoutSec) => {
|
|
120
|
-
const conn = processor.resolve(room);
|
|
121
|
-
if (!conn) return { success: false, error: `Unknown room "${room}".` };
|
|
122
|
-
const ds = conn.dataSource;
|
|
123
|
-
const target = conn.dataSource.listParticipants().find((pp) => pp.name === participant);
|
|
124
|
-
if (!target) return { success: false, error: `Unknown participant "${participant}".` };
|
|
125
|
-
if (target.id === conn.dataSource.selfId) {
|
|
126
|
-
return { success: false, error: "You cannot wait for yourself." };
|
|
127
|
-
}
|
|
128
|
-
const readState = async () => {
|
|
129
|
-
try {
|
|
130
|
-
const res = await fetch(`${ds.serverUrl}/participants`, {
|
|
131
|
-
headers: { Authorization: `Bearer ${ds.sessionToken}` },
|
|
132
|
-
signal: AbortSignal.timeout(1e4)
|
|
133
|
-
});
|
|
134
|
-
if (!res.ok) return "unknown";
|
|
135
|
-
const data = await res.json();
|
|
136
|
-
const found = data.participants?.find((pp) => pp.id === target.id);
|
|
137
|
-
const state = found?.agentState;
|
|
138
|
-
return state === "idle" || state === "working" || state === "blocked" ? state : "unknown";
|
|
139
|
-
} catch {
|
|
140
|
-
return "unknown";
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
const outcome = await waitForAgent({
|
|
144
|
-
readState,
|
|
145
|
-
until,
|
|
146
|
-
timeoutMs: timeoutSec === void 0 ? void 0 : timeoutSec * 1e3
|
|
147
|
-
});
|
|
148
|
-
return { success: true, message: describeOutcome(participant, until ?? "idle", outcome) };
|
|
149
|
-
},
|
|
150
|
-
onPing: async (room, participant) => {
|
|
151
|
-
const conn = processor.resolve(room);
|
|
152
|
-
if (!conn) return { success: false, error: `Unknown room "${room}".` };
|
|
153
|
-
const ds = conn.dataSource;
|
|
154
|
-
const p = conn.dataSource.listParticipants().find((pp) => pp.name === participant);
|
|
155
|
-
if (!p) return { success: false, error: `Unknown participant "${participant}".` };
|
|
156
|
-
try {
|
|
157
|
-
const res = await fetch(`${ds.serverUrl}/ping`, {
|
|
158
|
-
method: "POST",
|
|
159
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${ds.sessionToken}` },
|
|
160
|
-
body: JSON.stringify({ participantId: p.id })
|
|
161
|
-
});
|
|
162
|
-
if (!res.ok) return { success: false, error: await res.text() };
|
|
163
|
-
return { success: true };
|
|
164
|
-
} catch {
|
|
165
|
-
return { success: false, error: "Server unreachable." };
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
onJoinRoom: async (url, alias, nameOverride) => {
|
|
169
|
-
const token = extractToken(url);
|
|
170
|
-
let serverUrl;
|
|
171
|
-
try {
|
|
172
|
-
const parsed = new URL(url);
|
|
173
|
-
parsed.search = "";
|
|
174
|
-
serverUrl = parsed.toString().replace(/\/$/, "");
|
|
175
|
-
} catch {
|
|
176
|
-
serverUrl = url.replace(/\/$/, "");
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
const joinName = nameOverride ?? agentName;
|
|
180
|
-
const joinBody = { type: "agent", name: joinName };
|
|
181
|
-
if (token) joinBody.token = token;
|
|
182
|
-
const res = await fetch(`${serverUrl}/join`, {
|
|
183
|
-
method: "POST",
|
|
184
|
-
headers: { "Content-Type": "application/json" },
|
|
185
|
-
body: JSON.stringify(joinBody),
|
|
186
|
-
signal: AbortSignal.timeout(15e3)
|
|
187
|
-
});
|
|
188
|
-
if (!res.ok) return { success: false, error: `Failed to join: ${await res.text()}` };
|
|
189
|
-
const data = await res.json();
|
|
190
|
-
const sessionToken = String(data.sessionToken ?? "");
|
|
191
|
-
const roomName = alias ?? String(data.roomName ?? "");
|
|
192
|
-
const roomId = String(data.roomId ?? "");
|
|
193
|
-
const roomAuthority = String(data.authority ?? "member");
|
|
194
|
-
const participants = data.participants ?? [];
|
|
195
|
-
const newParticipantId = String(data.participantId ?? "");
|
|
196
|
-
const dataSource = new RemoteRoomDataSource(serverUrl, sessionToken, roomId);
|
|
197
|
-
dataSource.setParticipants(participants);
|
|
198
|
-
dataSource.setSelf(newParticipantId, joinName);
|
|
199
|
-
if (joinResults.length === 0) {
|
|
200
|
-
processor.participantId = newParticipantId;
|
|
201
|
-
}
|
|
202
|
-
processor.setRoomParticipantId(roomId, newParticipantId);
|
|
203
|
-
const mode = processor.getModeForRoom(roomId) ?? "everyone";
|
|
204
|
-
processor.connectRemoteRoom(dataSource, roomName);
|
|
205
|
-
sseMux.addConnection(serverUrl, sessionToken, roomName, roomId);
|
|
206
|
-
const jr = {
|
|
207
|
-
serverUrl,
|
|
208
|
-
sessionToken,
|
|
209
|
-
participantId: newParticipantId,
|
|
210
|
-
roomName,
|
|
211
|
-
roomId,
|
|
212
|
-
authority: roomAuthority,
|
|
213
|
-
participants,
|
|
214
|
-
dataSource
|
|
215
|
-
};
|
|
216
|
-
joinResults.push(jr);
|
|
217
|
-
const conn = processor.resolve(roomName);
|
|
218
|
-
let recentLines = [];
|
|
219
|
-
if (conn) {
|
|
220
|
-
const catchUp = await buildCatchUpLines(conn, {
|
|
221
|
-
isEventSeen: (id) => processor.isEventSeen(id),
|
|
222
|
-
markEventsSeen: (ids) => processor.markEventsSeen(ids),
|
|
223
|
-
assignRef: (id) => processor.assignRef(id)
|
|
224
|
-
});
|
|
225
|
-
recentLines = catchUp.lines;
|
|
226
|
-
}
|
|
227
|
-
await onRoomJoined?.();
|
|
228
|
-
let rules;
|
|
229
|
-
try {
|
|
230
|
-
const rulesRes = await fetch(`${serverUrl}/rules`, {
|
|
231
|
-
headers: { Authorization: `Bearer ${sessionToken}` }
|
|
232
|
-
});
|
|
233
|
-
if (rulesRes.ok) {
|
|
234
|
-
const rulesData = await rulesRes.json();
|
|
235
|
-
if (Array.isArray(rulesData.rules) && rulesData.rules.length > 0) rules = rulesData.rules;
|
|
236
|
-
}
|
|
237
|
-
} catch {
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
success: true,
|
|
241
|
-
roomName,
|
|
242
|
-
agentName: joinName,
|
|
243
|
-
authority: roomAuthority,
|
|
244
|
-
mode,
|
|
245
|
-
participants: participants.filter((p) => p.id !== newParticipantId).map((p) => ({ name: p.name, authority: p.authority ?? "member" })),
|
|
246
|
-
recentLines,
|
|
247
|
-
rules
|
|
248
|
-
};
|
|
249
|
-
} catch (err) {
|
|
250
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
251
|
-
return { success: false, error: `Unable to connect. Is the server running? (${serverUrl}) \u2014 ${msg}` };
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
onLeaveRoom: async (room) => {
|
|
255
|
-
const conn = processor.resolve(room);
|
|
256
|
-
if (!conn) return { success: false, error: `Unknown room "${room}".` };
|
|
257
|
-
const roomId = conn.dataSource.roomId;
|
|
258
|
-
const idx = joinResults.findIndex((jr) => jr.roomId === roomId);
|
|
259
|
-
if (idx >= 0) {
|
|
260
|
-
const jr = joinResults[idx];
|
|
261
|
-
sseMux.removeConnection(roomId);
|
|
262
|
-
processor.disconnectRemoteRoom(roomId);
|
|
263
|
-
try {
|
|
264
|
-
await fetch(`${jr.serverUrl}/disconnect`, {
|
|
265
|
-
method: "POST",
|
|
266
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${jr.sessionToken}` },
|
|
267
|
-
body: JSON.stringify({})
|
|
268
|
-
});
|
|
269
|
-
} catch {
|
|
270
|
-
}
|
|
271
|
-
joinResults.splice(idx, 1);
|
|
272
|
-
}
|
|
273
|
-
return { success: true };
|
|
274
|
-
},
|
|
275
|
-
// Admin actions differ only by endpoint + target tier — one helper covers all.
|
|
276
|
-
onAdminSetModeFor: can(authority, "set_mode_for") ? (room, participant, mode) => adminAction((id) => ({ path: "/set-mode", body: { participantId: id, mode } }))(room, participant) : void 0,
|
|
277
|
-
onAdminMute: can(authority, "mute") ? adminAction((id) => ({ path: "/set-authority", body: { participantId: id, authority: "guest" } })) : void 0,
|
|
278
|
-
onAdminUnmute: can(authority, "unmute") ? adminAction((id) => ({ path: "/set-authority", body: { participantId: id, authority: "member" } })) : void 0,
|
|
279
|
-
onAdminKick: can(authority, "kick") ? adminAction((id) => ({ path: "/kick", body: { participantId: id } })) : void 0,
|
|
280
|
-
onAdminPromote: can(authority, "promote") ? adminAction((id) => ({ path: "/set-authority", body: { participantId: id, authority: "product_owner" } })) : void 0,
|
|
281
|
-
onAdminDemote: can(authority, "demote") ? adminAction((id) => ({ path: "/set-authority", body: { participantId: id, authority: "member" } })) : void 0
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export {
|
|
286
|
-
buildRuntimeMcpOptions
|
|
287
|
-
};
|
|
288
|
-
//# sourceMappingURL=chunk-XLUTCAY6.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent/wait-for-agent.ts","../src/cli/runtime-handlers.ts"],"sourcesContent":["/**\n * Wait for another agent to reach a state.\n *\n * Without this, an agent that needs to act after another one finishes has two\n * options, both bad: re-read the room on a loop (a full LLM turn per check,\n * which is the expensive thing), or guess and collide.\n *\n * It became buildable once runtimes started reporting the state they read off\n * their own CLI's screen, because \"working\" and \"blocked\" look identical from\n * the room's side and only one of them ever resolves on its own.\n *\n * Deliberately bounded rather than open-ended: the call has to return inside\n * the MCP client's tool timeout, so it reports the state it last saw and lets\n * the caller decide whether to wait again. A tool that hangs past the client's\n * timeout fails the turn, which is worse than one that says \"still working\".\n */\n\n/** The states a runtime reports. `unknown` means it reports nothing. */\nexport type AgentWaitState = \"idle\" | \"working\" | \"blocked\" | \"unknown\";\n\n/** What the caller is waiting for. */\nexport type WaitTarget = \"idle\" | \"blocked\" | \"working\" | \"change\";\n\nexport interface WaitForAgentOptions {\n /** Read the target's current state. `unknown` if it isn't reporting. */\n readState: () => Promise<AgentWaitState>;\n /** What to wait for. Default \"idle\" — \"they finished, my turn\". */\n until?: WaitTarget;\n /** Give up after this long. Default 60s, capped by `maxWaitMs`. */\n timeoutMs?: number;\n /** Hard ceiling, to stay inside the MCP client's tool timeout. */\n maxWaitMs?: number;\n /** Gap between reads. Default 2s — this is cheap, unlike an LLM turn. */\n pollIntervalMs?: number;\n sleep?: (ms: number) => Promise<void>;\n now?: () => number;\n}\n\nexport type WaitOutcome =\n | { result: \"satisfied\"; state: AgentWaitState; waitedMs: number }\n | { result: \"timeout\"; state: AgentWaitState; waitedMs: number }\n /** The target reports no state at all — waiting would never end. */\n | { result: \"not-reporting\"; state: \"unknown\"; waitedMs: number };\n\nexport const DEFAULT_WAIT_MS = 60_000;\nexport const MAX_WAIT_MS = 120_000;\nconst DEFAULT_POLL_MS = 2_000;\n\n/**\n * Clamp a requested wait to something that returns inside the MCP client's\n * tool timeout. Exported so the clamp is testable without actually waiting.\n */\nexport function resolveBudgetMs(timeoutMs: number | undefined, maxWaitMs: number = MAX_WAIT_MS): number {\n return Math.min(Math.max(timeoutMs ?? DEFAULT_WAIT_MS, 0), maxWaitMs);\n}\n\n/** Is `state` what the caller asked for? */\nexport function satisfies(state: AgentWaitState, until: WaitTarget, initial: AgentWaitState): boolean {\n if (state === \"unknown\") return false;\n if (until === \"change\") return state !== initial;\n return state === until;\n}\n\n/**\n * Poll until the target agent reaches `until`, or the deadline passes.\n *\n * Returns `not-reporting` immediately if the agent reports no state, rather\n * than burning the whole timeout on something that cannot change: a runtime\n * with no heartbeat (or one that has died) would otherwise look exactly like\n * an agent that is simply taking a long time.\n */\nexport async function waitForAgent(opts: WaitForAgentOptions): Promise<WaitOutcome> {\n const {\n readState,\n until = \"idle\",\n maxWaitMs = MAX_WAIT_MS,\n pollIntervalMs = DEFAULT_POLL_MS,\n sleep = (ms: number) => new Promise<void>((r) => { setTimeout(r, ms).unref?.(); }),\n now = () => Date.now(),\n } = opts;\n\n const budget = resolveBudgetMs(opts.timeoutMs, maxWaitMs);\n const started = now();\n\n const initial = await readState();\n if (initial === \"unknown\") {\n return { result: \"not-reporting\", state: \"unknown\", waitedMs: now() - started };\n }\n if (satisfies(initial, until, initial)) {\n return { result: \"satisfied\", state: initial, waitedMs: now() - started };\n }\n\n let latest: AgentWaitState = initial;\n // Counted rather than wall-clocked so an injected instant sleep cannot spin.\n const steps = Math.max(1, Math.ceil(budget / pollIntervalMs));\n for (let step = 0; step < steps; step++) {\n await sleep(pollIntervalMs);\n latest = await readState();\n if (satisfies(latest, until, initial)) {\n return { result: \"satisfied\", state: latest, waitedMs: now() - started };\n }\n }\n\n return { result: \"timeout\", state: latest, waitedMs: now() - started };\n}\n\n/** One line for the agent, saying what happened and what to do next. */\nexport function describeOutcome(\n participant: string,\n until: WaitTarget,\n outcome: WaitOutcome,\n): string {\n const secs = Math.round(outcome.waitedMs / 1000);\n switch (outcome.result) {\n case \"satisfied\":\n return `${participant} is now ${outcome.state} (waited ${secs}s).`;\n case \"timeout\":\n return `${participant} is still ${outcome.state} after ${secs}s — not ${until} yet. `\n + `Call again to keep waiting, or get on with something else.`;\n case \"not-reporting\":\n return `${participant} does not report its state, so there is nothing to wait for. `\n + `Only agents started by apiary with a Claude Code or Codex runtime report it. `\n + `Ask them directly instead.`;\n }\n}\n","/**\n * Shared MCP-server option builder for the CLI runtime.\n *\n * Both entry points wire the SAME set of local MCP tool handlers against an\n * EventProcessor + SseMultiplexer:\n * - `setupAgentRuntime` (apiary claude / codex / opencode — tmux/HTTP delivery)\n * - `runMcpServer` (apiary mcp — stdio, pull-based)\n *\n * The handlers proxy tool calls (join/leave/set-mode/ping + admin\n * set-mode-for/mute/unmute/kick/promote/demote) to the apiary server over HTTP.\n * Keeping them here stops the two entry points from drifting — which they had:\n * the stdio path was missing admin promote/demote and the room-rules fetch on\n * join. This builder is the single source for both.\n */\n\nimport { extractToken } from \"./auth.js\";\nimport { can } from \"../core/authority.js\";\nimport { RemoteRoomDataSource } from \"../agent/remote-room-data-source.js\";\nimport { EventProcessor } from \"../agent/event-processor.js\";\nimport { SseMultiplexer } from \"../agent/sse-multiplexer.js\";\nimport { buildCatchUpLines } from \"../agent/tool-handlers.js\";\nimport {\n waitForAgent,\n describeOutcome,\n type AgentWaitState,\n type WaitTarget,\n} from \"../agent/wait-for-agent.js\";\nimport { type EngagementMode } from \"../agent/engagement.js\";\nimport type { Participant, AuthorityLevel } from \"../core/types.js\";\nimport type { RuntimeMcpServerOptions, JoinRoomResult } from \"../agent/mcp/runtime.js\";\n\n/** A room the agent has joined, tracked for cache updates + cleanup. */\nexport interface JoinResult {\n serverUrl: string;\n sessionToken: string;\n participantId: string;\n roomName: string;\n roomId: string;\n authority: string;\n participants: Participant[];\n dataSource: RemoteRoomDataSource;\n}\n\nexport interface RuntimeHandlerContext {\n processor: EventProcessor;\n sseMux: SseMultiplexer;\n /** Mutable — join/leave handlers push and splice as the agent moves rooms. */\n joinResults: JoinResult[];\n agentName: string;\n authority: AuthorityLevel | undefined;\n /** Fired after a successful join (the tmux/HTTP path repaints its UI). */\n onRoomJoined?: () => void | Promise<void>;\n}\n\n/**\n * Build the `RuntimeMcpServerOptions` shared by every CLI runtime entry point.\n * Pass the result straight to `createRuntimeMcpServer` / `createStdioRuntimeMcpServer`.\n */\nexport function buildRuntimeMcpOptions(ctx: RuntimeHandlerContext): RuntimeMcpServerOptions {\n const { processor, sseMux, joinResults, agentName, authority, onRoomJoined } = ctx;\n\n /** Resolve a room + named participant, then POST to the apiary server. */\n const adminAction = (build: (id: string) => { path: string; body: Record<string, unknown> }) =>\n async (room: string, participant: string) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const ds = conn.dataSource as RemoteRoomDataSource;\n const p = conn.dataSource.listParticipants().find((pp) => pp.name === participant);\n if (!p) return { success: false, error: `Unknown participant \"${participant}\".` };\n const { path, body } = build(p.id);\n try {\n const res = await fetch(`${ds.serverUrl}${path}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${ds.sessionToken}` },\n body: JSON.stringify(body),\n });\n if (!res.ok) return { success: false, error: await res.text() };\n return { success: true };\n } catch {\n return { success: false, error: \"Server unreachable.\" };\n }\n };\n\n return {\n resolver: processor,\n toolOptions: {\n isEventSeen: (id) => processor.isEventSeen(id),\n markEventsSeen: (ids) => processor.markEventsSeen(ids),\n assignRef: (id) => processor.assignRef(id),\n resolveRef: (ref) => processor.resolveRef(ref),\n },\n authority,\n\n onSetMode: async (room, mode) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n processor.setModeForRoom(conn.dataSource.roomId, mode as EngagementMode, false);\n try {\n const ds = conn.dataSource as RemoteRoomDataSource;\n const res = await fetch(`${ds.serverUrl}/set-mode`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${ds.sessionToken}` },\n body: JSON.stringify({ mode }),\n });\n if (!res.ok) return { success: false, error: `Server rejected: ${await res.text()}` };\n } catch {\n // Server unreachable, local mode still set\n }\n return { success: true };\n },\n\n /**\n * Wait for another agent to reach a state, by polling the room server's\n * participant list. Two seconds of HTTP is far cheaper than the LLM turn\n * the agent would otherwise spend re-reading the room to check.\n */\n onWaitForAgent: async (room, participant, until, timeoutSec) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const ds = conn.dataSource as RemoteRoomDataSource;\n const target = conn.dataSource.listParticipants().find((pp) => pp.name === participant);\n if (!target) return { success: false, error: `Unknown participant \"${participant}\".` };\n if (target.id === conn.dataSource.selfId) {\n return { success: false, error: \"You cannot wait for yourself.\" };\n }\n\n const readState = async (): Promise<AgentWaitState> => {\n try {\n const res = await fetch(`${ds.serverUrl}/participants`, {\n headers: { Authorization: `Bearer ${ds.sessionToken}` },\n signal: AbortSignal.timeout(10_000),\n });\n if (!res.ok) return \"unknown\";\n const data = (await res.json()) as {\n participants?: Array<{ id: string; agentState?: string }>;\n };\n const found = data.participants?.find((pp) => pp.id === target.id);\n const state = found?.agentState;\n return state === \"idle\" || state === \"working\" || state === \"blocked\" ? state : \"unknown\";\n } catch {\n // A transient failure must not read as a state change.\n return \"unknown\";\n }\n };\n\n const outcome = await waitForAgent({\n readState,\n until,\n timeoutMs: timeoutSec === undefined ? undefined : timeoutSec * 1000,\n });\n return { success: true, message: describeOutcome(participant, until ?? \"idle\", outcome) };\n },\n\n onPing: async (room, participant) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const ds = conn.dataSource as RemoteRoomDataSource;\n const p = conn.dataSource.listParticipants().find((pp) => pp.name === participant);\n if (!p) return { success: false, error: `Unknown participant \"${participant}\".` };\n try {\n const res = await fetch(`${ds.serverUrl}/ping`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${ds.sessionToken}` },\n body: JSON.stringify({ participantId: p.id }),\n });\n if (!res.ok) return { success: false, error: await res.text() };\n return { success: true };\n } catch {\n return { success: false, error: \"Server unreachable.\" };\n }\n },\n\n onJoinRoom: async (url, alias, nameOverride) => {\n const token = extractToken(url);\n let serverUrl: string;\n try {\n const parsed = new URL(url);\n parsed.search = \"\";\n serverUrl = parsed.toString().replace(/\\/$/, \"\");\n } catch {\n serverUrl = url.replace(/\\/$/, \"\");\n }\n\n try {\n const joinName = nameOverride ?? agentName;\n const joinBody: Record<string, unknown> = { type: \"agent\", name: joinName };\n if (token) joinBody.token = token;\n\n const res = await fetch(`${serverUrl}/join`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(joinBody),\n signal: AbortSignal.timeout(15_000),\n });\n if (!res.ok) return { success: false, error: `Failed to join: ${await res.text()}` };\n\n const data = await res.json() as Record<string, unknown>;\n const sessionToken = String(data.sessionToken ?? \"\");\n const roomName = alias ?? String(data.roomName ?? \"\");\n const roomId = String(data.roomId ?? \"\");\n const roomAuthority = String(data.authority ?? \"member\");\n const participants = (data.participants as Participant[]) ?? [];\n const newParticipantId = String(data.participantId ?? \"\");\n\n const dataSource = new RemoteRoomDataSource(serverUrl, sessionToken, roomId);\n dataSource.setParticipants(participants);\n dataSource.setSelf(newParticipantId, joinName);\n\n // Set global selfId on first join; always set per-room selfId\n if (joinResults.length === 0) {\n processor.participantId = newParticipantId;\n }\n processor.setRoomParticipantId(roomId, newParticipantId);\n\n // Register in EventProcessor and SSE multiplexer\n const mode = processor.getModeForRoom(roomId) ?? \"everyone\";\n processor.connectRemoteRoom(dataSource, roomName);\n sseMux.addConnection(serverUrl, sessionToken, roomName, roomId);\n\n // Track for cleanup\n const jr: JoinResult = {\n serverUrl,\n sessionToken,\n participantId: newParticipantId,\n roomName,\n roomId,\n authority: roomAuthority,\n participants,\n dataSource,\n };\n joinResults.push(jr);\n\n // Build recent activity lines for the response\n const conn = processor.resolve(roomName);\n let recentLines: string[] = [];\n if (conn) {\n const catchUp = await buildCatchUpLines(conn, {\n isEventSeen: (id) => processor.isEventSeen(id),\n markEventsSeen: (ids) => processor.markEventsSeen(ids),\n assignRef: (id) => processor.assignRef(id),\n });\n recentLines = catchUp.lines;\n // TODO: catchUp.imageBlocks not surfaced here — the runtime injection\n // path doesn't support image content blocks yet. Wire up when adding\n // image delivery to the tmux/HTTP agent path.\n }\n\n await onRoomJoined?.();\n\n // Fetch this room's rules so they show up in the join response. Best\n // effort — older servers without /rules return 404 and we just skip.\n let rules: string[] | undefined;\n try {\n const rulesRes = await fetch(`${serverUrl}/rules`, {\n headers: { Authorization: `Bearer ${sessionToken}` },\n });\n if (rulesRes.ok) {\n const rulesData = (await rulesRes.json()) as { rules?: string[] };\n if (Array.isArray(rulesData.rules) && rulesData.rules.length > 0) rules = rulesData.rules;\n }\n } catch {\n /* old server or transient — skip */\n }\n\n return {\n success: true,\n roomName,\n agentName: joinName,\n authority: roomAuthority,\n mode,\n participants: participants\n .filter((p) => p.id !== newParticipantId)\n .map((p) => ({ name: p.name, authority: (p as any).authority ?? \"member\" })),\n recentLines,\n rules,\n } as JoinRoomResult;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { success: false, error: `Unable to connect. Is the server running? (${serverUrl}) — ${msg}` };\n }\n },\n\n onLeaveRoom: async (room) => {\n const conn = processor.resolve(room);\n if (!conn) return { success: false, error: `Unknown room \"${room}\".` };\n const roomId = conn.dataSource.roomId;\n\n const idx = joinResults.findIndex((jr) => jr.roomId === roomId);\n if (idx >= 0) {\n const jr = joinResults[idx];\n sseMux.removeConnection(roomId);\n processor.disconnectRemoteRoom(roomId);\n\n try {\n await fetch(`${jr.serverUrl}/disconnect`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: `Bearer ${jr.sessionToken}` },\n body: JSON.stringify({}),\n });\n } catch {\n // Server may be down\n }\n\n joinResults.splice(idx, 1);\n }\n return { success: true };\n },\n\n // Admin actions differ only by endpoint + target tier — one helper covers all.\n onAdminSetModeFor: can(authority, \"set_mode_for\")\n ? (room, participant, mode) =>\n adminAction((id) => ({ path: \"/set-mode\", body: { participantId: id, mode } }))(room, participant)\n : undefined,\n onAdminMute: can(authority, \"mute\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"guest\" } }))\n : undefined,\n onAdminUnmute: can(authority, \"unmute\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"member\" } }))\n : undefined,\n onAdminKick: can(authority, \"kick\")\n ? adminAction((id) => ({ path: \"/kick\", body: { participantId: id } }))\n : undefined,\n onAdminPromote: can(authority, \"promote\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"product_owner\" } }))\n : undefined,\n onAdminDemote: can(authority, \"demote\")\n ? adminAction((id) => ({ path: \"/set-authority\", body: { participantId: id, authority: \"member\" } }))\n : undefined,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/run-C22WQZVK.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildRuntimeMcpOptions
|
|
3
|
-
} from "./chunk-XLUTCAY6.js";
|
|
4
|
-
import {
|
|
5
|
-
EventProcessor,
|
|
6
|
-
SseMultiplexer,
|
|
7
|
-
createStdioRuntimeMcpServer
|
|
8
|
-
} from "./chunk-WJD5EKO6.js";
|
|
9
|
-
import "./chunk-4A7MREYX.js";
|
|
10
|
-
import {
|
|
11
|
-
randomName
|
|
12
|
-
} from "./chunk-D7DXZQWT.js";
|
|
13
|
-
import "./chunk-TMXQDGMN.js";
|
|
14
|
-
import "./chunk-BZ4VDWC2.js";
|
|
15
|
-
|
|
16
|
-
// src/cli/mcp/run.ts
|
|
17
|
-
async function runMcpServer(options) {
|
|
18
|
-
const agentName = options.name ?? randomName();
|
|
19
|
-
const authority = options.authority ?? (options.admin ? "admin" : void 0);
|
|
20
|
-
const joinResults = [];
|
|
21
|
-
const sseMux = new SseMultiplexer();
|
|
22
|
-
const processor = new EventProcessor("", agentName, {
|
|
23
|
-
defaultMode: "everyone",
|
|
24
|
-
pullOnly: true
|
|
25
|
-
});
|
|
26
|
-
const wrappedSource = {
|
|
27
|
-
[Symbol.asyncIterator]() {
|
|
28
|
-
const inner = sseMux[Symbol.asyncIterator]();
|
|
29
|
-
return {
|
|
30
|
-
async next() {
|
|
31
|
-
const result = await inner.next();
|
|
32
|
-
if (!result.done) {
|
|
33
|
-
const { roomId, event } = result.value;
|
|
34
|
-
const jr = joinResults.find((j) => j.roomId === roomId);
|
|
35
|
-
if (jr) {
|
|
36
|
-
if (event.type === "ParticipantJoined") {
|
|
37
|
-
jr.dataSource.addParticipant(event.participant);
|
|
38
|
-
} else if (event.type === "ParticipantLeft") {
|
|
39
|
-
jr.dataSource.removeParticipant(event.participant_id);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const mcpServer = await createStdioRuntimeMcpServer(
|
|
49
|
-
buildRuntimeMcpOptions({ processor, sseMux, joinResults, agentName, authority })
|
|
50
|
-
);
|
|
51
|
-
const deliver = async (_parts) => {
|
|
52
|
-
};
|
|
53
|
-
const eventLoopPromise = processor.run(deliver, wrappedSource).catch(() => {
|
|
54
|
-
});
|
|
55
|
-
process.stderr.write(`apiary mcp server running as "${agentName}"
|
|
56
|
-
`);
|
|
57
|
-
if (options.joinUrls && options.joinUrls.length > 0) {
|
|
58
|
-
process.stderr.write(`pending rooms: ${options.joinUrls.join(", ")}
|
|
59
|
-
`);
|
|
60
|
-
}
|
|
61
|
-
process.stderr.write(`use join_room(url) to connect to a room
|
|
62
|
-
`);
|
|
63
|
-
async function cleanup() {
|
|
64
|
-
await processor.stop();
|
|
65
|
-
sseMux.close();
|
|
66
|
-
await mcpServer.stop();
|
|
67
|
-
for (const jr of joinResults) {
|
|
68
|
-
try {
|
|
69
|
-
await fetch(`${jr.serverUrl}/disconnect`, {
|
|
70
|
-
method: "POST",
|
|
71
|
-
headers: { "Content-Type": "application/json" },
|
|
72
|
-
body: JSON.stringify({ token: jr.sessionToken })
|
|
73
|
-
});
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
await Promise.race([
|
|
79
|
-
mcpServer.closed,
|
|
80
|
-
new Promise((resolve) => {
|
|
81
|
-
process.on("SIGINT", resolve);
|
|
82
|
-
process.on("SIGTERM", resolve);
|
|
83
|
-
})
|
|
84
|
-
]);
|
|
85
|
-
await cleanup();
|
|
86
|
-
await eventLoopPromise;
|
|
87
|
-
}
|
|
88
|
-
export {
|
|
89
|
-
runMcpServer
|
|
90
|
-
};
|
|
91
|
-
//# sourceMappingURL=run-C22WQZVK.js.map
|
package/dist/run-C22WQZVK.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/mcp/run.ts"],"sourcesContent":["/**\n * apiary mcp — standalone MCP server for any MCP client.\n *\n * Unlike `apiary claude` (which wraps Claude Code in tmux), this exposes\n * apiary tools as a stdio MCP server. Any MCP client (Claude Code, Cursor,\n * Windsurf, etc.) can add it to their MCP config and call join_room()\n * to participate in apiary rooms on the fly.\n *\n * No tmux. No wrapper. Just tools.\n *\n * Event delivery is pull-based: the agent calls catch_up() to get new events.\n * The EventProcessor still runs in the background, classifying events via the\n * engagement model and buffering content — catch_up() returns the formatted,\n * classified result.\n *\n * The MCP tool handlers are the same ones the tmux/HTTP runtimes use; they are\n * built by the shared runtime-handlers factory so this path can't drift.\n */\n\nimport { randomName } from \"../../core/names.js\";\nimport { EventProcessor } from \"../../agent/event-processor.js\";\nimport { SseMultiplexer } from \"../../agent/sse-multiplexer.js\";\nimport { createStdioRuntimeMcpServer } from \"../../agent/mcp/runtime.js\";\nimport type { AuthorityLevel } from \"../../core/types.js\";\nimport type { LabeledEvent } from \"../../agent/multiplexer.js\";\nimport type { ContentPart } from \"../../agent/types.js\";\nimport { buildRuntimeMcpOptions, type JoinResult } from \"../runtime-handlers.js\";\n\nexport interface McpServerOptions {\n name?: string;\n /** Legacy CLI surface flag. Equivalent to `authority: \"admin\"`. */\n admin?: boolean;\n /** Pre-declared authority for tool exposure. See AgentRuntimeOptions. */\n authority?: AuthorityLevel;\n joinUrls?: string[];\n}\n\nexport async function runMcpServer(options: McpServerOptions): Promise<void> {\n const agentName = options.name ?? randomName();\n const authority: AuthorityLevel | undefined = options.authority\n ?? (options.admin ? \"admin\" : undefined);\n const joinResults: JoinResult[] = [];\n\n // ── SSE multiplexer (starts empty, grows as agent joins rooms) ────────\n const sseMux = new SseMultiplexer();\n\n // ── EventProcessor (selfId set on first join) ─────────────────────────\n const processor = new EventProcessor(\"\", agentName, {\n defaultMode: \"everyone\",\n pullOnly: true,\n });\n\n // ── Wrap SSE source for participant cache updates ─────────────────────\n const wrappedSource: AsyncIterable<LabeledEvent> = {\n [Symbol.asyncIterator]() {\n const inner = sseMux[Symbol.asyncIterator]();\n return {\n async next() {\n const result = await inner.next();\n if (!result.done) {\n const { roomId, event } = result.value;\n const jr = joinResults.find((j) => j.roomId === roomId);\n if (jr) {\n if (event.type === \"ParticipantJoined\") {\n jr.dataSource.addParticipant(event.participant);\n } else if (event.type === \"ParticipantLeft\") {\n jr.dataSource.removeParticipant(event.participant_id);\n }\n }\n }\n return result;\n },\n };\n },\n };\n\n // ── Create stdio MCP server (shared handler set) ──────────────────────\n const mcpServer = await createStdioRuntimeMcpServer(\n buildRuntimeMcpOptions({ processor, sseMux, joinResults, agentName, authority }),\n );\n\n // ── Start event loop in background ────────────────────────────────────\n // Delivery is a no-op: events are buffered in the EventProcessor and\n // returned via catch_up(). We still run the loop so engagement\n // classification, buffering, and seen-event tracking happen in real-time.\n const deliver = async (_parts: ContentPart[]) => {\n // No-op — standalone MCP is pull-based.\n // Events are classified and buffered by EventProcessor.\n // Agent pulls them via catch_up().\n };\n\n const eventLoopPromise = processor\n .run(deliver, wrappedSource)\n .catch(() => {});\n\n process.stderr.write(`apiary mcp server running as \"${agentName}\"\\n`);\n if (options.joinUrls && options.joinUrls.length > 0) {\n process.stderr.write(`pending rooms: ${options.joinUrls.join(\", \")}\\n`);\n }\n process.stderr.write(`use join_room(url) to connect to a room\\n`);\n\n // ── Cleanup ───────────────────────────────────────────────────────────\n async function cleanup(): Promise<void> {\n await processor.stop();\n sseMux.close();\n await mcpServer.stop();\n\n for (const jr of joinResults) {\n try {\n await fetch(`${jr.serverUrl}/disconnect`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token: jr.sessionToken }),\n });\n } catch {\n // Server may be down\n }\n }\n }\n\n // Wait for stdio transport to close (MCP client disconnected)\n // or process signal\n await Promise.race([\n mcpServer.closed,\n new Promise<void>((resolve) => {\n process.on(\"SIGINT\", resolve);\n process.on(\"SIGTERM\", resolve);\n }),\n ]);\n\n await cleanup();\n await eventLoopPromise;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqCA,eAAsB,aAAa,SAA0C;AAC3E,QAAM,YAAY,QAAQ,QAAQ,WAAW;AAC7C,QAAM,YAAwC,QAAQ,cAChD,QAAQ,QAAQ,UAAU;AAChC,QAAM,cAA4B,CAAC;AAGnC,QAAM,SAAS,IAAI,eAAe;AAGlC,QAAM,YAAY,IAAI,eAAe,IAAI,WAAW;AAAA,IAClD,aAAa;AAAA,IACb,UAAU;AAAA,EACZ,CAAC;AAGD,QAAM,gBAA6C;AAAA,IACjD,CAAC,OAAO,aAAa,IAAI;AACvB,YAAM,QAAQ,OAAO,OAAO,aAAa,EAAE;AAC3C,aAAO;AAAA,QACL,MAAM,OAAO;AACX,gBAAM,SAAS,MAAM,MAAM,KAAK;AAChC,cAAI,CAAC,OAAO,MAAM;AAChB,kBAAM,EAAE,QAAQ,MAAM,IAAI,OAAO;AACjC,kBAAM,KAAK,YAAY,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM;AACtD,gBAAI,IAAI;AACN,kBAAI,MAAM,SAAS,qBAAqB;AACtC,mBAAG,WAAW,eAAe,MAAM,WAAW;AAAA,cAChD,WAAW,MAAM,SAAS,mBAAmB;AAC3C,mBAAG,WAAW,kBAAkB,MAAM,cAAc;AAAA,cACtD;AAAA,YACF;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,MAAM;AAAA,IACtB,uBAAuB,EAAE,WAAW,QAAQ,aAAa,WAAW,UAAU,CAAC;AAAA,EACjF;AAMA,QAAM,UAAU,OAAO,WAA0B;AAAA,EAIjD;AAEA,QAAM,mBAAmB,UACtB,IAAI,SAAS,aAAa,EAC1B,MAAM,MAAM;AAAA,EAAC,CAAC;AAEjB,UAAQ,OAAO,MAAM,iCAAiC,SAAS;AAAA,CAAK;AACpE,MAAI,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAAG;AACnD,YAAQ,OAAO,MAAM,kBAAkB,QAAQ,SAAS,KAAK,IAAI,CAAC;AAAA,CAAI;AAAA,EACxE;AACA,UAAQ,OAAO,MAAM;AAAA,CAA2C;AAGhE,iBAAe,UAAyB;AACtC,UAAM,UAAU,KAAK;AACrB,WAAO,MAAM;AACb,UAAM,UAAU,KAAK;AAErB,eAAW,MAAM,aAAa;AAC5B,UAAI;AACF,cAAM,MAAM,GAAG,GAAG,SAAS,eAAe;AAAA,UACxC,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,GAAG,aAAa,CAAC;AAAA,QACjD,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAIA,QAAM,QAAQ,KAAK;AAAA,IACjB,UAAU;AAAA,IACV,IAAI,QAAc,CAAC,YAAY;AAC7B,cAAQ,GAAG,UAAU,OAAO;AAC5B,cAAQ,GAAG,WAAW,OAAO;AAAA,IAC/B,CAAC;AAAA,EACH,CAAC;AAED,QAAM,QAAQ;AACd,QAAM;AACR;","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|