@gorlitzer-labs/apiary 1.13.8 → 1.14.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 +15 -3
- package/dist/agent/index.d.ts +7 -0
- package/dist/agent/index.js +2 -2
- package/dist/chunk-36WX2UAF.js +663 -0
- package/dist/chunk-36WX2UAF.js.map +1 -0
- package/dist/{chunk-3O76FESR.js → chunk-LNSDH2V5.js} +7 -7
- package/dist/{chunk-FTN3D6WM.js → chunk-LOONM4G3.js} +8 -177
- package/dist/chunk-LOONM4G3.js.map +1 -0
- package/dist/{chunk-63Q3KJ4W.js → chunk-N2LPKMPO.js} +186 -3
- package/dist/chunk-N2LPKMPO.js.map +1 -0
- package/dist/{chunk-YP4K2A7G.js → chunk-TMXQDGMN.js} +30 -30
- package/dist/chunk-TMXQDGMN.js.map +1 -0
- package/dist/{chunk-LN6ATWE3.js → chunk-WJD5EKO6.js} +158 -158
- package/dist/chunk-WJD5EKO6.js.map +1 -0
- package/dist/{chunk-J3T2PS3M.js → chunk-XP5F5NCQ.js} +2 -2
- package/dist/cli/index.js +38 -380
- package/dist/cli/index.js.map +1 -1
- package/dist/{invites-YZWCUC67.js → invites-ID5SRM3D.js} +5 -5
- package/dist/{join-FSKOS5BO.js → join-W53OPEHR.js} +5 -5
- package/dist/{room-MAUAV2XD.js → room-XC546NTX.js} +11 -11
- package/dist/run-6JRFWMAQ.js +164 -0
- package/dist/run-6JRFWMAQ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-63Q3KJ4W.js.map +0 -1
- package/dist/chunk-FTN3D6WM.js.map +0 -1
- package/dist/chunk-LN6ATWE3.js.map +0 -1
- package/dist/chunk-XKFWEK7W.js +0 -288
- package/dist/chunk-XKFWEK7W.js.map +0 -1
- package/dist/chunk-YP4K2A7G.js.map +0 -1
- package/dist/run-4EJB2KC3.js +0 -90
- package/dist/run-4EJB2KC3.js.map +0 -1
- /package/dist/{chunk-3O76FESR.js.map → chunk-LNSDH2V5.js.map} +0 -0
- /package/dist/{chunk-J3T2PS3M.js.map → chunk-XP5F5NCQ.js.map} +0 -0
- /package/dist/{invites-YZWCUC67.js.map → invites-ID5SRM3D.js.map} +0 -0
- /package/dist/{join-FSKOS5BO.js.map → join-W53OPEHR.js.map} +0 -0
- /package/dist/{room-MAUAV2XD.js.map → room-XC546NTX.js.map} +0 -0
package/dist/chunk-XKFWEK7W.js
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RemoteRoomDataSource,
|
|
3
|
-
buildCatchUpLines
|
|
4
|
-
} from "./chunk-LN6ATWE3.js";
|
|
5
|
-
import {
|
|
6
|
-
extractToken
|
|
7
|
-
} from "./chunk-63Q3KJ4W.js";
|
|
8
|
-
import {
|
|
9
|
-
can
|
|
10
|
-
} from "./chunk-YP4K2A7G.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-XKFWEK7W.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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/authority.ts","../src/agent/prompts.ts"],"sourcesContent":["/**\n * Centralized authority tier model.\n *\n * Four ordered tiers — admin > product_owner > member > guest — gate every\n * privileged operation. Historically each call site had its own\n * `authority === \"admin\" || authority === \"product_owner\"` chain. That style\n * drifted (product_owner was silently excluded from /mute autocomplete; the\n * MCP runtime gated admin tools on a static CLI flag instead of the\n * server-granted tier; etc.) and is exactly the kind of bug that recurs when\n * a 5th tier eventually lands.\n *\n * One table here. Every call site reads from it. Adding an operation = one\n * row. Adding a tier = one entry in `TIER_ORDER`. No more scattered string\n * comparisons.\n */\n\nimport type { AuthorityLevel } from \"./types.js\";\n\n/** Every operation the system gates. Keep this list narrow — it's the surface. */\nexport type Operation =\n // Member-and-up — basic participation\n | \"send_message\"\n | \"set_own_mode\"\n | \"ping\"\n | \"create_share_link\"\n // Product owner-and-up — room management\n | \"mute\"\n | \"unmute\"\n | \"set_mode_for\"\n // Admin-only — destructive or escalation\n | \"kick\"\n | \"clear_history\"\n | \"start_tunnel\"\n | \"promote\"\n | \"demote\";\n\n/**\n * The single source of truth. Adding a new operation = one row here; the gate\n * is automatically enforceable everywhere via `can()`.\n */\nconst MIN_AUTHORITY: Record<Operation, AuthorityLevel> = {\n send_message: \"member\",\n set_own_mode: \"member\",\n ping: \"member\",\n create_share_link: \"member\",\n\n mute: \"product_owner\",\n unmute: \"product_owner\",\n set_mode_for: \"product_owner\",\n\n kick: \"admin\",\n clear_history: \"admin\",\n start_tunnel: \"admin\",\n promote: \"admin\",\n demote: \"admin\",\n};\n\n/**\n * Numeric ordering. Authority strings MUST NOT be compared with `<`/`>`\n * directly — lexical ordering \"p\" > \"m\" makes `\"product_owner\" > \"member\"`\n * accidentally true and silently breaks the moment any string changes. Always\n * go through `TIER_ORDER`.\n */\nexport const TIER_ORDER: Record<AuthorityLevel, number> = {\n guest: 0,\n member: 1,\n product_owner: 2,\n admin: 3,\n};\n\n/** Can the bearer of `authority` perform `op`? The check everyone calls. */\nexport function can(authority: AuthorityLevel | undefined, op: Operation): boolean {\n if (!authority) return false;\n return TIER_ORDER[authority] >= TIER_ORDER[MIN_AUTHORITY[op]];\n}\n","/** Event formatting and mode descriptions for apiary agents. */\n\nimport type { Participant } from \"../core/types.js\";\nimport type { RoomEvent } from \"../core/events.js\";\nimport type { ContentPart } from \"./types.js\";\n\n// ── Mode descriptions ────────────────────────────────────────────────────────\n\n/** One-liner mode descriptions used in join_room responses and set_mode. */\nexport const MODE_DESCRIPTIONS: Record<string, string> = {\n \"everyone\": \"All messages are pushed to you.\",\n \"people\": \"Human messages are pushed to you. Agent messages are delivered as context.\",\n \"agents\": \"Agent messages are pushed to you. Human messages are delivered as context.\",\n \"fight\": \"⚔️ FIGHT MODE. All messages trigger. Gloves are off. Talk trash, roast, clap back. No mercy.\",\n \"standby-everyone\": \"Only @mentions are pushed to you.\",\n \"standby-people\": \"Only human @mentions are pushed to you.\",\n \"standby-agents\": \"Only agent @mentions are pushed to you.\",\n};\n\n// ── Formatting ────────────────────────────────────────────────────────────────\n\n/** Short 4-char ref for a message ID, used in transcripts. */\nexport function messageRef(messageId: string): string {\n return messageId.replace(/-/g, \"\").slice(0, 4);\n}\n\n/** Format a participant as a labeled name: \"[human] Alice\" or \"[agent] Quinn\". */\nexport function participantLabel(p: Participant | null, fallback?: string): string {\n if (!p) return fallback ?? \"someone\";\n return `[${p.type}] ${p.name}`;\n}\n\n/** Resolve participant name, with fallback. */\nfunction resolveName(resolveParticipant: (id: string) => Participant | null, id: string, fallback?: string): string {\n return resolveParticipant(id)?.name ?? fallback ?? \"someone\";\n}\n\n/** Format a Date as UTC HH:MM:SS for display in agent transcripts. */\nexport function formatTimestamp(date: Date): string {\n return date.toISOString().slice(11, 19);\n}\n\n/** Convert ContentPart[] back to a plain string (for trace logs and stats). */\nexport function contentPartsToString(parts: ContentPart[]): string {\n return parts.map(p => p.type === \"text\" ? p.text : ` [image: ${p.url}]`).join(\"\");\n}\n\n/** Count visual character width (grapheme clusters) for padding alignment. */\nfunction visualLength(s: string): number {\n // Use Intl.Segmenter if available (Node 16+), otherwise fall back to spread\n if (typeof Intl !== \"undefined\" && Intl.Segmenter) {\n const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n let count = 0;\n for (const _ of segmenter.segment(s)) count++;\n return count;\n }\n return [...s].length;\n}\n\n/**\n * Format multiline content with room label continuation.\n * First line is returned as-is. Subsequent lines get [room] prefix aligned.\n */\nfunction formatMultilineContent(content: string, roomLabel: string | undefined, prefix: string): string {\n const lines = content.split(\"\\n\");\n if (lines.length <= 1) return content;\n const continuation = roomLabel ? `[${roomLabel}] ` : \"\";\n // Pad continuation to align under the content start (grapheme-aware)\n const pad = \" \".repeat(visualLength(prefix));\n return lines[0] + \"\\n\" + lines.slice(1).map(l => `${pad}${continuation}${l}`).join(\"\\n\");\n}\n\n/**\n * Render `prefix` followed by content whose wrapped continuation lines align\n * under the text after `prefix`. Passing `prefix` in one place keeps the visible\n * prefix and the alignment padding from silently drifting apart.\n */\nfunction renderLine(prefix: string, content: string, roomLabel: string | undefined): string {\n return `${prefix}${formatMultilineContent(content, roomLabel, prefix)}`;\n}\n\n/**\n * Format a typed event as ContentPart[] for the LLM session.\n * Returns null for events that shouldn't be sent to the LLM (noise).\n *\n * Compact one-liner format:\n * Messages: [14:23:01] #3847 [lobby] Alice: hey everyone\n * Replies: [14:23:01] #9102 [lobby] Alice (→ #3847 Bob): good point\n * Mentions: [14:23:01] #5521 [lobby] ⚡ Alice: @bot what do you think?\n * Joined: [14:23:01] [lobby] + Alice joined\n * Left: [14:23:15] [lobby] - Bob left\n * Reactions: [14:23:20] [lobby] Alice reacted ❤️ to #3847\n */\nexport function formatEvent(\n event: RoomEvent,\n resolveParticipant: (id: string) => Participant | null,\n replyContext?: { senderName: string; content: string } | null,\n roomLabel?: string,\n reactionTarget?: { senderName: string; content: string; isSelf: boolean } | null,\n assignRef?: (messageId: string) => string,\n): ContentPart[] | null {\n const r = roomLabel ? `[${roomLabel}] ` : \"\";\n const ts = `[${formatTimestamp(\"timestamp\" in event ? new Date(event.timestamp as Date) : new Date())}] `;\n const mkRef = (id: string) => `#${assignRef ? assignRef(id) : messageRef(id)}`;\n\n switch (event.type) {\n case \"MessageSent\": {\n const msg = event.message;\n const name = resolveName(resolveParticipant, msg.sender_id, msg.sender_name);\n const ref = mkRef(msg.id);\n const linePrefix = `${ts}${ref} ${r}`;\n let text: string;\n if (msg.reply_to_id && replyContext) {\n const rRef = assignRef ? mkRef(msg.reply_to_id) : ref;\n text = renderLine(`${linePrefix}${name} (→ ${rRef} ${replyContext.senderName}): `, msg.content, roomLabel);\n } else {\n text = renderLine(`${linePrefix}${name}: `, msg.content, roomLabel);\n }\n const parts: ContentPart[] = [{ type: \"text\", text }];\n if (msg.image_url) parts.push({ type: \"image\", url: msg.image_url });\n return parts;\n }\n case \"Mentioned\": {\n const msg = event.message;\n const name = resolveName(resolveParticipant, msg.sender_id, msg.sender_name);\n const ref = mkRef(msg.id);\n const linePrefix = `${ts}${ref} ${r}⚡ `;\n const text = renderLine(`${linePrefix}${name}: `, msg.content, roomLabel);\n const parts: ContentPart[] = [{ type: \"text\", text }];\n if (msg.image_url) parts.push({ type: \"image\", url: msg.image_url });\n return parts;\n }\n case \"Pinged\": {\n const pingerName = event.pinger_name ?? \"someone\";\n return [{ type: \"text\", text: `${ts}${r}🔔 ${pingerName} pinged you — respond NOW with a one-line status of what you're working on. Keep it short, don't stop your current task.` }];\n }\n case \"ToolUse\":\n return null;\n case \"Activity\":\n return null;\n case \"ReactionAdded\": {\n const name = resolveName(resolveParticipant, event.participant_id);\n const targetRef = reactionTarget ? ` to ${mkRef(event.message_id)}` : \"\";\n return [{ type: \"text\", text: `${ts}${r}${name} reacted ${event.emoji}${targetRef}` }];\n }\n case \"ReactionRemoved\":\n return null;\n case \"ParticipantJoined\": {\n const name = event.participant?.name ?? \"someone\";\n return [{ type: \"text\", text: `${ts}${r}+ ${name} joined` }];\n }\n case \"ParticipantLeft\": {\n const name = event.participant?.name ?? \"someone\";\n return [{ type: \"text\", text: `${ts}${r}- ${name} left` }];\n }\n case \"ParticipantKicked\": {\n const name = event.participant?.name ?? \"someone\";\n return [{ type: \"text\", text: `${ts}${r}${name} was kicked` }];\n }\n case \"AuthorityChanged\": {\n const name = event.participant?.name ?? \"someone\";\n if (event.new_authority === \"guest\") {\n return [{ type: \"text\", text: `${ts}${r}${name} was muted` }];\n }\n if (event.new_authority === \"member\") {\n return [{ type: \"text\", text: `${ts}${r}${name} was unmuted` }];\n }\n if (event.new_authority === \"product_owner\") {\n return [{ type: \"text\", text: `${ts}${r}${name} was promoted to product owner` }];\n }\n return [{ type: \"text\", text: `${ts}${r}${name} → ${event.new_authority}` }];\n }\n case \"ContextCompacted\":\n return null;\n default:\n return null;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/run-4EJB2KC3.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildRuntimeMcpOptions
|
|
3
|
-
} from "./chunk-XKFWEK7W.js";
|
|
4
|
-
import {
|
|
5
|
-
EventProcessor,
|
|
6
|
-
SseMultiplexer,
|
|
7
|
-
createStdioRuntimeMcpServer
|
|
8
|
-
} from "./chunk-LN6ATWE3.js";
|
|
9
|
-
import "./chunk-63Q3KJ4W.js";
|
|
10
|
-
import {
|
|
11
|
-
randomName
|
|
12
|
-
} from "./chunk-D7DXZQWT.js";
|
|
13
|
-
import "./chunk-YP4K2A7G.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
|
-
});
|
|
25
|
-
const wrappedSource = {
|
|
26
|
-
[Symbol.asyncIterator]() {
|
|
27
|
-
const inner = sseMux[Symbol.asyncIterator]();
|
|
28
|
-
return {
|
|
29
|
-
async next() {
|
|
30
|
-
const result = await inner.next();
|
|
31
|
-
if (!result.done) {
|
|
32
|
-
const { roomId, event } = result.value;
|
|
33
|
-
const jr = joinResults.find((j) => j.roomId === roomId);
|
|
34
|
-
if (jr) {
|
|
35
|
-
if (event.type === "ParticipantJoined") {
|
|
36
|
-
jr.dataSource.addParticipant(event.participant);
|
|
37
|
-
} else if (event.type === "ParticipantLeft") {
|
|
38
|
-
jr.dataSource.removeParticipant(event.participant_id);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const mcpServer = await createStdioRuntimeMcpServer(
|
|
48
|
-
buildRuntimeMcpOptions({ processor, sseMux, joinResults, agentName, authority })
|
|
49
|
-
);
|
|
50
|
-
const deliver = async (_parts) => {
|
|
51
|
-
};
|
|
52
|
-
const eventLoopPromise = processor.run(deliver, wrappedSource).catch(() => {
|
|
53
|
-
});
|
|
54
|
-
process.stderr.write(`apiary mcp server running as "${agentName}"
|
|
55
|
-
`);
|
|
56
|
-
if (options.joinUrls && options.joinUrls.length > 0) {
|
|
57
|
-
process.stderr.write(`pending rooms: ${options.joinUrls.join(", ")}
|
|
58
|
-
`);
|
|
59
|
-
}
|
|
60
|
-
process.stderr.write(`use join_room(url) to connect to a room
|
|
61
|
-
`);
|
|
62
|
-
async function cleanup() {
|
|
63
|
-
await processor.stop();
|
|
64
|
-
sseMux.close();
|
|
65
|
-
await mcpServer.stop();
|
|
66
|
-
for (const jr of joinResults) {
|
|
67
|
-
try {
|
|
68
|
-
await fetch(`${jr.serverUrl}/disconnect`, {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: { "Content-Type": "application/json" },
|
|
71
|
-
body: JSON.stringify({ token: jr.sessionToken })
|
|
72
|
-
});
|
|
73
|
-
} catch {
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
await Promise.race([
|
|
78
|
-
mcpServer.closed,
|
|
79
|
-
new Promise((resolve) => {
|
|
80
|
-
process.on("SIGINT", resolve);
|
|
81
|
-
process.on("SIGTERM", resolve);
|
|
82
|
-
})
|
|
83
|
-
]);
|
|
84
|
-
await cleanup();
|
|
85
|
-
await eventLoopPromise;
|
|
86
|
-
}
|
|
87
|
-
export {
|
|
88
|
-
runMcpServer
|
|
89
|
-
};
|
|
90
|
-
//# sourceMappingURL=run-4EJB2KC3.js.map
|
package/dist/run-4EJB2KC3.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 });\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,EACf,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
|