@stage-labs/metro 0.1.0-beta.234 → 0.1.0-beta.235

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage-labs/metro",
3
- "version": "0.1.0-beta.234",
3
+ "version": "0.1.0-beta.235",
4
4
  "description": "The metro command line: run your agent on this machine, relaying its chat channels and connectors to Claude Code.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metro",
3
- "version": "0.1.0-beta.234",
3
+ "version": "0.1.0-beta.235",
4
4
  "description": "Metro for Claude Code, installed by the metro daemon on this machine: every connector the agent holds as its own MCP server relayed through the daemon, the orchestrator-only guard on the main thread, and the standing rules loaded at each session start. No vendor credential ever sits in a config file.",
5
5
  "author": {
6
6
  "name": "Bonustrack",
@@ -1,4 +1,4 @@
1
- import { IdentifierKind } from '@xmtp/node-sdk';
1
+ import { IdentifierKind, type DecodedMessage } from '@xmtp/node-sdk';
2
2
  import { accountForCall, convOf, lineOf, parseLine } from './accounts.js';
3
3
  import { respond } from '@metro-labs/core/stations/station-runtime';
4
4
  import { resolveMsgId } from './wire.js';
@@ -14,6 +14,7 @@ import {
14
14
  groupRemoveMembers,
15
15
  } from './group.js';
16
16
  import { updateChannelMeta } from './actions-meta.js';
17
+ import { deletedByRequests, isDeleteRequest, superAdminCheck } from './delete-requests.js';
17
18
 
18
19
  type Args = Record<string, unknown>;
19
20
  type Handler = (id: string, args: Args) => Promise<void>;
@@ -42,6 +43,17 @@ function upTo<T extends { id: string }>(all: T[], before: string | undefined): T
42
43
  return all.slice(0, at);
43
44
  }
44
45
 
46
+ function textOf(m: DecodedMessage): string {
47
+ try {
48
+ const cc: unknown = m.content;
49
+ return typeof cc === 'string' ? cc : `[${m.contentType?.typeId ?? 'unknown'}]`;
50
+ } catch {
51
+ return `[${m.contentType?.typeId ?? 'unknown'}]`;
52
+ }
53
+ }
54
+
55
+ const DELETED = { text: '[deletedMessage]', contentType: 'deletedMessage' };
56
+
45
57
  async function read(id: string, args: Args): Promise<void> {
46
58
  const { line, limit, before } = args as { line: string; limit?: number; before?: string };
47
59
  const { conv } = await convOf(line);
@@ -50,28 +62,18 @@ async function read(id: string, args: Args): Promise<void> {
50
62
  const lim = Math.min(Math.max(1, limit ?? 20), 200);
51
63
  await conv.sync().catch(() => undefined);
52
64
  const all = await conv.messages();
53
- const slice = upTo(all, before).slice(-lim);
65
+ const deleted = deletedByRequests(all, superAdminCheck(conv));
66
+ const slice = upTo(all.filter((m) => !isDeleteRequest(m)), before).slice(-lim);
54
67
  const parsed = parseLine(line);
55
68
  if (!parsed)
56
69
  throw new TrainError('NOT_FOUND', `could not parse line ${line}`);
57
70
  const acctId = parsed.accountId;
58
- const messages = slice.map((m) => {
59
- let text = '';
60
- try {
61
- const cc: unknown = m.content;
62
- text =
63
- typeof cc === 'string' ? cc : `[${m.contentType?.typeId ?? 'unknown'}]`;
64
- } catch {
65
- text = `[${m.contentType?.typeId ?? 'unknown'}]`;
66
- }
67
- return {
68
- id: m.id,
69
- ts: new Date(Number(m.sentAtNs / 1_000_000n)).toISOString(),
70
- from: `metro://xmtp/${acctId}/user/${m.senderInboxId}`,
71
- text,
72
- contentType: m.contentType?.typeId ?? 'unknown',
73
- };
74
- });
71
+ const messages = slice.map((m) => ({
72
+ id: m.id,
73
+ ts: new Date(Number(m.sentAtNs / 1_000_000n)).toISOString(),
74
+ from: `metro://xmtp/${acctId}/user/${m.senderInboxId}`,
75
+ ...(deleted.has(m.id) ? DELETED : { text: textOf(m), contentType: m.contentType?.typeId ?? 'unknown' }),
76
+ }));
75
77
  respond(id, { result: { line, count: messages.length, messages } });
76
78
  }
77
79
 
@@ -200,6 +200,14 @@ export const frameActionText = (c: FrameActionContent): string => {
200
200
  };
201
201
  const FrameActionCodec = makeJsonCodec<FrameActionContent>(ContentTypeFrameAction, frameActionText);
202
202
 
203
+ export const ContentTypeDeleteRequest: ContentTypeId = {
204
+ authorityId: 'stage.box',
205
+ typeId: 'deleteRequest',
206
+ versionMajor: 1,
207
+ versionMinor: 0,
208
+ };
209
+ const DeleteRequestCodec = makeJsonCodec<{ messageId: string }>(ContentTypeDeleteRequest, () => 'Message deleted');
210
+
203
211
  const ContentTypeDeleteMessage: ContentTypeId = {
204
212
  authorityId: 'xmtp.org',
205
213
  typeId: 'deleteMessage',
@@ -233,4 +241,5 @@ export const CODECS = (): ContentCodec[] => [
233
241
  new SignatureReferenceCodec(),
234
242
  new FrameCodec(),
235
243
  new FrameActionCodec(),
244
+ new DeleteRequestCodec(),
236
245
  ];
@@ -0,0 +1,34 @@
1
+ import type { DecodedMessage } from '@xmtp/node-sdk';
2
+ import { ContentTypeDeleteRequest } from './codecs.js';
3
+
4
+ type Listed = Pick<DecodedMessage, 'id' | 'senderInboxId' | 'contentType' | 'content'>;
5
+
6
+ export const isDeleteRequest = (m: Pick<Listed, 'contentType'>): boolean =>
7
+ m.contentType?.authorityId === ContentTypeDeleteRequest.authorityId &&
8
+ m.contentType.typeId === ContentTypeDeleteRequest.typeId;
9
+
10
+ function targetOf(m: Listed): string | undefined {
11
+ if (!isDeleteRequest(m)) return undefined;
12
+ const id = (m.content as { messageId?: unknown } | undefined)?.messageId;
13
+ return typeof id === 'string' && id ? id : undefined;
14
+ }
15
+
16
+ export function superAdminCheck(conv: unknown): (inboxId: string) => boolean {
17
+ const group = conv as { isSuperAdmin?: (inboxId: string) => boolean };
18
+ return (inboxId) => typeof group.isSuperAdmin === 'function' && group.isSuperAdmin(inboxId);
19
+ }
20
+
21
+ export function deletedByRequests(
22
+ messages: Listed[],
23
+ isSuperAdmin: (inboxId: string) => boolean,
24
+ ): Set<string> {
25
+ const senders = new Map(messages.map((m) => [m.id, m.senderInboxId]));
26
+ const deleted = new Set<string>();
27
+ for (const m of messages) {
28
+ const target = targetOf(m);
29
+ const sender = target === undefined ? undefined : senders.get(target);
30
+ if (target !== undefined && sender !== undefined && (sender === m.senderInboxId || isSuperAdmin(m.senderInboxId)))
31
+ deleted.add(target);
32
+ }
33
+ return deleted;
34
+ }
@@ -23,6 +23,7 @@ const SILENT_TYPES = new Set([
23
23
  'walletSendCalls',
24
24
  'groupUpdated',
25
25
  'group_updated',
26
+ 'deleteRequest',
26
27
  ]);
27
28
 
28
29
  async function bootSync(acct: Account): Promise<void> {
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.234"
2
+ "version": "0.1.0-beta.235"
3
3
  }