stoops 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -4,22 +4,22 @@ import {
4
4
  Room,
5
5
  randomName,
6
6
  randomRoomName
7
- } from "../chunk-LC5WPWR2.js";
7
+ } from "../chunk-KY524S5X.js";
8
8
  import {
9
9
  EventProcessor,
10
10
  RemoteRoomDataSource,
11
11
  SseMultiplexer
12
- } from "../chunk-SS5NGUJM.js";
13
- import "../chunk-5ADJGMXQ.js";
12
+ } from "../chunk-ZO6SITQN.js";
13
+ import "../chunk-QUSAD4P5.js";
14
14
  import {
15
15
  buildCatchUpLines,
16
16
  contentPartsToString,
17
17
  createRuntimeMcpServer,
18
18
  formatTimestamp
19
- } from "../chunk-BLGV3QN4.js";
19
+ } from "../chunk-TODXZFII.js";
20
20
  import {
21
21
  createEvent
22
- } from "../chunk-HQS7HBZR.js";
22
+ } from "../chunk-YGROOQFE.js";
23
23
 
24
24
  // src/cli/serve.ts
25
25
  import { createServer } from "http";
@@ -79,8 +79,8 @@ var TokenManager = class {
79
79
  };
80
80
  var TIER_ORDER = {
81
81
  admin: 2,
82
- participant: 1,
83
- observer: 0
82
+ member: 1,
83
+ guest: 0
84
84
  };
85
85
  function canGrant(callerLevel, targetLevel) {
86
86
  return TIER_ORDER[callerLevel] >= TIER_ORDER[targetLevel];
@@ -128,7 +128,7 @@ async function serve(options) {
128
128
  const room = new Room(roomName, storage);
129
129
  const tokens = new TokenManager();
130
130
  const participants = /* @__PURE__ */ new Map();
131
- const observers = /* @__PURE__ */ new Map();
131
+ const guests = /* @__PURE__ */ new Map();
132
132
  const idToSession = /* @__PURE__ */ new Map();
133
133
  const sseConnections = /* @__PURE__ */ new Map();
134
134
  async function parseBody(req) {
@@ -144,8 +144,8 @@ async function serve(options) {
144
144
  if (!token) return null;
145
145
  const p = participants.get(token);
146
146
  if (p) return { ...p, kind: "participant" };
147
- const o = observers.get(token);
148
- if (o) return { ...o, kind: "observer" };
147
+ const g = guests.get(token);
148
+ if (g) return { ...g, kind: "guest" };
149
149
  return null;
150
150
  }
151
151
  function jsonError(res, status, error) {
@@ -201,7 +201,7 @@ async function serve(options) {
201
201
  id: p.id,
202
202
  name: p.name,
203
203
  type: p.type,
204
- authority: p.authority ?? "participant"
204
+ authority: p.authority ?? "member"
205
205
  }));
206
206
  res.writeHead(200, { "Content-Type": "application/json" });
207
207
  res.end(JSON.stringify({ participants: list }));
@@ -258,25 +258,25 @@ async function serve(options) {
258
258
  if (!tokenAuthority) return jsonError(res, 403, "Invalid share token");
259
259
  authority = tokenAuthority;
260
260
  } else if (legacyType === "guest") {
261
- authority = "observer";
261
+ authority = "guest";
262
262
  } else if (legacyType === "human") {
263
- authority = "participant";
263
+ authority = "member";
264
264
  } else {
265
- authority = "participant";
265
+ authority = "member";
266
266
  }
267
267
  const participantType = String(body.type ?? "human");
268
268
  const name = String(body.name ?? randomName());
269
- if (authority === "observer") {
269
+ if (authority === "guest") {
270
270
  const id2 = `obs_${randomUUID().slice(0, 8)}`;
271
271
  const channel2 = room.observe();
272
- const sessionToken3 = tokens.createSessionToken(id2, "observer");
273
- observers.set(sessionToken3, { id: id2, authority: "observer", channel: channel2, sessionToken: sessionToken3 });
272
+ const sessionToken3 = tokens.createSessionToken(id2, "guest");
273
+ guests.set(sessionToken3, { id: id2, authority: "guest", channel: channel2, sessionToken: sessionToken3 });
274
274
  idToSession.set(id2, sessionToken3);
275
275
  const participantList2 = room.listParticipants().map((p) => ({
276
276
  id: p.id,
277
277
  name: p.name,
278
278
  type: p.type,
279
- authority: p.authority ?? "participant"
279
+ authority: p.authority ?? "member"
280
280
  }));
281
281
  res.writeHead(200, { "Content-Type": "application/json" });
282
282
  res.end(JSON.stringify({
@@ -285,7 +285,7 @@ async function serve(options) {
285
285
  roomName,
286
286
  roomId: room.roomId,
287
287
  participants: participantList2,
288
- authority: "observer"
288
+ authority: "guest"
289
289
  }));
290
290
  return;
291
291
  }
@@ -298,7 +298,7 @@ async function serve(options) {
298
298
  id: p.id,
299
299
  name: p.name,
300
300
  type: p.type,
301
- authority: p.authority ?? "participant"
301
+ authority: p.authority ?? "member"
302
302
  }));
303
303
  log(`${name} joined (${authority})`);
304
304
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -316,7 +316,7 @@ async function serve(options) {
316
316
  const session = getSession(sessionToken);
317
317
  if (url.pathname === "/message") {
318
318
  if (!session) return jsonError(res, 401, "Invalid session token");
319
- if (session.authority === "observer") return jsonError(res, 403, "Observers cannot send messages");
319
+ if (session.authority === "guest") return jsonError(res, 403, "Guests cannot send messages");
320
320
  const content = String(body.content ?? "");
321
321
  const replyTo = body.replyTo ? String(body.replyTo) : void 0;
322
322
  if (!content) return jsonError(res, 400, "Empty message");
@@ -328,7 +328,7 @@ async function serve(options) {
328
328
  }
329
329
  if (url.pathname === "/event") {
330
330
  if (!session) return jsonError(res, 401, "Invalid session token");
331
- if (session.authority === "observer") return jsonError(res, 403, "Observers cannot emit events");
331
+ if (session.authority === "guest") return jsonError(res, 403, "Guests cannot emit events");
332
332
  const event = body.event;
333
333
  if (!event) return jsonError(res, 400, "Missing event");
334
334
  const p = participants.get(sessionToken);
@@ -364,8 +364,8 @@ async function serve(options) {
364
364
  const targetId = String(body.participantId ?? "");
365
365
  const newAuthority = String(body.authority ?? "");
366
366
  if (!targetId) return jsonError(res, 400, "Missing participantId");
367
- if (!["admin", "participant", "observer"].includes(newAuthority)) {
368
- return jsonError(res, 400, "Invalid authority. Must be admin, participant, or observer.");
367
+ if (!["admin", "member", "guest"].includes(newAuthority)) {
368
+ return jsonError(res, 400, "Invalid authority. Must be admin, member, or guest.");
369
369
  }
370
370
  if (targetId === session.id) return jsonError(res, 400, "Cannot change own authority");
371
371
  const targetSession = idToSession.get(targetId);
@@ -375,15 +375,17 @@ async function serve(options) {
375
375
  target.authority = newAuthority;
376
376
  tokens.updateSessionAuthority(targetSession, newAuthority);
377
377
  room.setParticipantAuthority(targetId, newAuthority);
378
- const p = participants.get(sessionToken);
379
- if (p) {
380
- await p.channel.emit(createEvent({
381
- type: "Activity",
382
- category: "ACTIVITY",
378
+ const adminP = participants.get(sessionToken);
379
+ const targetParticipant = room.listParticipants().find((p) => p.id === targetId);
380
+ if (adminP && targetParticipant) {
381
+ await adminP.channel.emit(createEvent({
382
+ type: "AuthorityChanged",
383
+ category: "PRESENCE",
383
384
  room_id: room.roomId,
384
385
  participant_id: targetId,
385
- action: "authority_changed",
386
- detail: { authority: newAuthority }
386
+ participant: targetParticipant,
387
+ new_authority: newAuthority,
388
+ changed_by: adminP.name
387
389
  }));
388
390
  }
389
391
  log(`${target.name} authority \u2192 ${newAuthority}`);
@@ -397,11 +399,23 @@ async function serve(options) {
397
399
  if (!targetId) return jsonError(res, 400, "Missing participantId");
398
400
  const targetSession = idToSession.get(targetId);
399
401
  if (targetSession) {
400
- const target = participants.get(targetSession) ?? observers.get(targetSession);
402
+ const target = participants.get(targetSession) ?? guests.get(targetSession);
401
403
  if (target) {
402
- await target.channel.disconnect();
404
+ const adminP = participants.get(sessionToken);
405
+ const targetParticipant = room.listParticipants().find((p) => p.id === targetId);
406
+ if (adminP && targetParticipant) {
407
+ await adminP.channel.emit(createEvent({
408
+ type: "ParticipantKicked",
409
+ category: "PRESENCE",
410
+ room_id: room.roomId,
411
+ participant_id: targetId,
412
+ participant: targetParticipant,
413
+ kicked_by: adminP.name
414
+ }));
415
+ }
416
+ await target.channel.disconnect(true);
403
417
  participants.delete(targetSession);
404
- observers.delete(targetSession);
418
+ guests.delete(targetSession);
405
419
  idToSession.delete(targetId);
406
420
  tokens.revokeSessionToken(targetSession);
407
421
  const sse = sseConnections.get(targetId);
@@ -417,7 +431,7 @@ async function serve(options) {
417
431
  }
418
432
  if (url.pathname === "/share") {
419
433
  if (!session) return jsonError(res, 401, "Invalid session token");
420
- if (session.authority === "observer") return jsonError(res, 403, "Observers cannot create share links");
434
+ if (session.authority === "guest") return jsonError(res, 403, "Guests cannot create share links");
421
435
  const targetAuthority = body.authority ?? void 0;
422
436
  const links = {};
423
437
  if (targetAuthority) {
@@ -425,7 +439,7 @@ async function serve(options) {
425
439
  if (!token) return jsonError(res, 403, `Cannot generate ${targetAuthority} link`);
426
440
  links[targetAuthority] = buildShareUrl(publicUrl, token);
427
441
  } else {
428
- const tiers = ["admin", "participant", "observer"];
442
+ const tiers = ["admin", "member", "guest"];
429
443
  for (const tier of tiers) {
430
444
  const token = tokens.generateShareToken(session.authority, tier);
431
445
  if (token) links[tier] = buildShareUrl(publicUrl, token);
@@ -456,16 +470,16 @@ async function serve(options) {
456
470
  }
457
471
  log(`${p.name} disconnected`);
458
472
  }
459
- const o = observers.get(targetToken);
460
- if (o) {
461
- await o.channel.disconnect();
462
- observers.delete(targetToken);
463
- idToSession.delete(o.id);
473
+ const g = guests.get(targetToken);
474
+ if (g) {
475
+ await g.channel.disconnect();
476
+ guests.delete(targetToken);
477
+ idToSession.delete(g.id);
464
478
  tokens.revokeSessionToken(targetToken);
465
- const sse = sseConnections.get(o.id);
479
+ const sse = sseConnections.get(g.id);
466
480
  if (sse) {
467
481
  sse.end();
468
- sseConnections.delete(o.id);
482
+ sseConnections.delete(g.id);
469
483
  }
470
484
  }
471
485
  }
@@ -497,9 +511,9 @@ Port ${port} is already in use. Another stoops instance may be running.`);
497
511
  }
498
512
  }
499
513
  const adminToken = tokens.generateShareToken("admin", "admin");
500
- const participantToken = tokens.generateShareToken("admin", "participant");
514
+ const memberToken = tokens.generateShareToken("admin", "member");
501
515
  if (options.headless) {
502
- process.stdout.write(JSON.stringify({ serverUrl, publicUrl, roomName, adminToken, participantToken }) + "\n");
516
+ process.stdout.write(JSON.stringify({ serverUrl, publicUrl, roomName, adminToken, memberToken }) + "\n");
503
517
  } else if (!options.quiet) {
504
518
  let version = process.env.npm_package_version ?? "";
505
519
  if (!version) {
@@ -512,7 +526,7 @@ Port ${port} is already in use. Another stoops instance may be running.`);
512
526
  }
513
527
  }
514
528
  const adminUrl = buildShareUrl(publicUrl, adminToken);
515
- const joinUrl = buildShareUrl(publicUrl, participantToken);
529
+ const joinUrl = buildShareUrl(publicUrl, memberToken);
516
530
  console.log(`
517
531
  stoops v${version}
518
532
 
@@ -539,8 +553,8 @@ Port ${port} is already in use. Another stoops instance may be running.`);
539
553
  await p.channel.disconnect().catch(() => {
540
554
  });
541
555
  }
542
- for (const o of observers.values()) {
543
- await o.channel.disconnect().catch(() => {
556
+ for (const g of guests.values()) {
557
+ await g.channel.disconnect().catch(() => {
544
558
  });
545
559
  }
546
560
  await new Promise((resolve, reject) => {
@@ -550,7 +564,7 @@ Port ${port} is already in use. Another stoops instance may be running.`);
550
564
  };
551
565
  process.on("SIGINT", shutdown);
552
566
  process.on("SIGTERM", shutdown);
553
- return { serverUrl, publicUrl, roomName, adminToken, participantToken };
567
+ return { serverUrl, publicUrl, roomName, adminToken, memberToken };
554
568
  }
555
569
  function logServer(message) {
556
570
  console.log(` [${formatTimestamp(/* @__PURE__ */ new Date())}] ${message}`);
@@ -654,10 +668,10 @@ var SLASH_COMMANDS = [
654
668
  { name: "/kick", description: "Remove a participant", adminOnly: true, params: [
655
669
  { label: "name", completions: "participants" }
656
670
  ] },
657
- { name: "/mute", description: "Make read-only (observer)", adminOnly: true, params: [
671
+ { name: "/mute", description: "Make read-only (guest)", adminOnly: true, params: [
658
672
  { label: "name", completions: "participants" }
659
673
  ] },
660
- { name: "/unmute", description: "Restore to participant", adminOnly: true, params: [
674
+ { name: "/unmute", description: "Restore to member", adminOnly: true, params: [
661
675
  { label: "name", completions: "participants" }
662
676
  ] },
663
677
  { name: "/setmode", description: "Set engagement mode", adminOnly: true, params: [
@@ -1039,7 +1053,7 @@ async function join(options) {
1039
1053
  sessionToken = String(data.sessionToken ?? "");
1040
1054
  participantId = String(data.participantId);
1041
1055
  roomName = String(data.roomName);
1042
- authority = data.authority ?? "participant";
1056
+ authority = data.authority ?? "member";
1043
1057
  participants = data.participants ?? [];
1044
1058
  } catch {
1045
1059
  console.error(`Cannot reach stoops server at ${serverUrl}. Is it running?`);
@@ -1077,7 +1091,7 @@ async function join(options) {
1077
1091
  const rl = createInterface({ input: process.stdin, terminal: false });
1078
1092
  rl.on("line", async (line) => {
1079
1093
  const content = line.trim();
1080
- if (!content || authority === "observer") return;
1094
+ if (!content || authority === "guest") return;
1081
1095
  try {
1082
1096
  await fetch(`${serverUrl}/message`, {
1083
1097
  method: "POST",
@@ -1124,7 +1138,7 @@ async function join(options) {
1124
1138
  }
1125
1139
  process.exit(0);
1126
1140
  }
1127
- const isReadOnly = authority === "observer" || isGuest;
1141
+ const isReadOnly = authority === "guest" || isGuest;
1128
1142
  function systemEvent(content) {
1129
1143
  tui.push({
1130
1144
  id: randomUUID2(),
@@ -1148,7 +1162,7 @@ async function join(options) {
1148
1162
  }
1149
1163
  const data = await res.json();
1150
1164
  const lines = data.participants.map((p) => {
1151
- const auth = p.authority ?? "participant";
1165
+ const auth = p.authority ?? "member";
1152
1166
  return ` ${p.type === "agent" ? "agent" : "human"} ${p.name} (${auth})`;
1153
1167
  });
1154
1168
  systemEvent(`Participants:
@@ -1229,13 +1243,13 @@ ${lines.join("\n")}`);
1229
1243
  const authRes = await fetch(`${serverUrl}/set-authority`, {
1230
1244
  method: "POST",
1231
1245
  headers: { "Content-Type": "application/json" },
1232
- body: JSON.stringify({ token: sessionToken, participantId: target.id, authority: "observer" })
1246
+ body: JSON.stringify({ token: sessionToken, participantId: target.id, authority: "guest" })
1233
1247
  });
1234
1248
  if (!authRes.ok) {
1235
1249
  systemEvent(`Failed to mute: ${await authRes.text()}`);
1236
1250
  return;
1237
1251
  }
1238
- systemEvent(`Muted ${targetName} (observer).`);
1252
+ systemEvent(`Muted ${targetName} (guest).`);
1239
1253
  } catch {
1240
1254
  systemEvent("Failed to reach server.");
1241
1255
  }
@@ -1267,13 +1281,13 @@ ${lines.join("\n")}`);
1267
1281
  const authRes = await fetch(`${serverUrl}/set-authority`, {
1268
1282
  method: "POST",
1269
1283
  headers: { "Content-Type": "application/json" },
1270
- body: JSON.stringify({ token: sessionToken, participantId: target.id, authority: "participant" })
1284
+ body: JSON.stringify({ token: sessionToken, participantId: target.id, authority: "member" })
1271
1285
  });
1272
1286
  if (!authRes.ok) {
1273
1287
  systemEvent(`Failed to unmute: ${await authRes.text()}`);
1274
1288
  return;
1275
1289
  }
1276
- systemEvent(`Unmuted ${targetName} (participant).`);
1290
+ systemEvent(`Unmuted ${targetName} (member).`);
1277
1291
  } catch {
1278
1292
  systemEvent("Failed to reach server.");
1279
1293
  }
@@ -1320,8 +1334,8 @@ ${lines.join("\n")}`);
1320
1334
  }
1321
1335
  // ── /share [--as <tier>] ──────────────────────────────────────
1322
1336
  case "share": {
1323
- if (authority === "observer") {
1324
- systemEvent("Observers cannot create share links.");
1337
+ if (authority === "guest") {
1338
+ systemEvent("Guests cannot create share links.");
1325
1339
  return;
1326
1340
  }
1327
1341
  let targetAuthority;
@@ -1525,6 +1539,23 @@ function toDisplayEvent(event, selfId, participantTypes) {
1525
1539
  name: event.participant.name,
1526
1540
  participantType: event.participant.type
1527
1541
  };
1542
+ case "ParticipantKicked":
1543
+ return {
1544
+ id: randomUUID2(),
1545
+ ts,
1546
+ kind: "system",
1547
+ content: `${event.participant.name} was kicked`
1548
+ };
1549
+ case "AuthorityChanged": {
1550
+ const name = event.participant.name;
1551
+ if (event.new_authority === "guest") {
1552
+ return { id: randomUUID2(), ts, kind: "system", content: `${name} was muted` };
1553
+ }
1554
+ if (event.new_authority === "member") {
1555
+ return { id: randomUUID2(), ts, kind: "system", content: `${name} was unmuted` };
1556
+ }
1557
+ return { id: randomUUID2(), ts, kind: "system", content: `${name} \u2192 ${event.new_authority}` };
1558
+ }
1528
1559
  case "Activity":
1529
1560
  if (event.action === "mode_changed") {
1530
1561
  return {
@@ -1534,15 +1565,6 @@ function toDisplayEvent(event, selfId, participantTypes) {
1534
1565
  mode: String(event.detail?.mode ?? "")
1535
1566
  };
1536
1567
  }
1537
- if (event.action === "authority_changed") {
1538
- const newAuth = String(event.detail?.authority ?? "");
1539
- return {
1540
- id: randomUUID2(),
1541
- ts,
1542
- kind: "system",
1543
- content: `authority \u2192 ${newAuth}`
1544
- };
1545
- }
1546
1568
  return null;
1547
1569
  default:
1548
1570
  return null;
@@ -1903,7 +1925,7 @@ async function setupAgentRuntime(options) {
1903
1925
  const sessionToken = String(data.sessionToken ?? "");
1904
1926
  const roomName = alias ?? String(data.roomName ?? "");
1905
1927
  const roomId = String(data.roomId ?? "");
1906
- const authority = String(data.authority ?? "participant");
1928
+ const authority = String(data.authority ?? "member");
1907
1929
  const participants = data.participants ?? [];
1908
1930
  const newParticipantId = String(data.participantId ?? "");
1909
1931
  const dataSource = new RemoteRoomDataSource(serverUrl, sessionToken, roomId);
@@ -1943,7 +1965,7 @@ async function setupAgentRuntime(options) {
1943
1965
  agentName,
1944
1966
  authority,
1945
1967
  mode,
1946
- participants: participants.filter((p) => p.id !== newParticipantId).map((p) => ({ name: p.name, authority: p.authority ?? "participant" })),
1968
+ participants: participants.filter((p) => p.id !== newParticipantId).map((p) => ({ name: p.name, authority: p.authority ?? "member" })),
1947
1969
  recentLines
1948
1970
  };
1949
1971
  } catch (err) {
@@ -2000,7 +2022,7 @@ async function setupAgentRuntime(options) {
2000
2022
  const res = await fetch(`${ds.serverUrl}/set-authority`, {
2001
2023
  method: "POST",
2002
2024
  headers: { "Content-Type": "application/json" },
2003
- body: JSON.stringify({ token: ds.sessionToken, participantId: p.id, authority: "observer" })
2025
+ body: JSON.stringify({ token: ds.sessionToken, participantId: p.id, authority: "guest" })
2004
2026
  });
2005
2027
  if (!res.ok) return { success: false, error: await res.text() };
2006
2028
  return { success: true };
@@ -2018,7 +2040,7 @@ async function setupAgentRuntime(options) {
2018
2040
  const res = await fetch(`${ds.serverUrl}/set-authority`, {
2019
2041
  method: "POST",
2020
2042
  headers: { "Content-Type": "application/json" },
2021
- body: JSON.stringify({ token: ds.sessionToken, participantId: p.id, authority: "participant" })
2043
+ body: JSON.stringify({ token: ds.sessionToken, participantId: p.id, authority: "member" })
2022
2044
  });
2023
2045
  if (!res.ok) return { success: false, error: await res.text() };
2024
2046
  return { success: true };
@@ -2447,7 +2469,7 @@ async function main() {
2447
2469
  const adminJoinUrl = buildShareUrl(result.serverUrl, result.adminToken);
2448
2470
  const participantShareUrl = buildShareUrl(
2449
2471
  result.publicUrl !== result.serverUrl ? result.publicUrl : result.serverUrl,
2450
- result.participantToken
2472
+ result.memberToken
2451
2473
  );
2452
2474
  await join({
2453
2475
  server: adminJoinUrl,