@stage-labs/metro 0.1.0-beta.320 → 0.1.0-beta.321

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.320",
3
+ "version": "0.1.0-beta.321",
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": {
@@ -154,6 +154,7 @@ const PRIOR_STAGE: ReadonlySet<string> = new Set([
154
154
  'f2be51c5290d2e61b47f10c379fa45d051a59a6b3f06d99174c2a170074bc8f5',
155
155
  '4c727aa110c9023371cfb364f3304ad9db22cdf2501208ef1e42e6ccc245b00f',
156
156
  'f3901d8a4537441b1d7fe8e4073322a97b779472df6b4a8e0f2091ab5ab0ae09',
157
+ 'af22c346df478fd249cc6931d4cf1dab4e091682e422f1bd27af0a08781e189d',
157
158
  ]);
158
159
  const PRIOR_MEMORY: ReadonlySet<string> = new Set(['08795399635c8cdec2b50cb4e336f89ae880f3997ae52560ef647a9cdc8f1b08']);
159
160
 
@@ -25,7 +25,11 @@ async function applyNameAndDescription(
25
25
  group: GroupLike,
26
26
  name: string | undefined,
27
27
  description: string | undefined,
28
+ verb: string,
28
29
  ): Promise<void> {
30
+ if (description !== undefined && typeof group.updateDescription !== 'function') {
31
+ throw new TrainError('INVALID_ARGS', `${verb} target does not support native group descriptions`);
32
+ }
29
33
  if (
30
34
  typeof name === 'string' &&
31
35
  name &&
@@ -88,7 +92,7 @@ async function applyChannelMeta(
88
92
  }
89
93
  }
90
94
 
91
- await applyNameAndDescription(group, name, description);
95
+ await applyNameAndDescription(group, name, description, verb);
92
96
  const merged = await applyMergedAppData(
93
97
  group as GroupLike & { updateAppData: (s: string) => Promise<void> },
94
98
  appData,
@@ -139,6 +143,9 @@ export async function updateChannelMeta(id: string, args: Args): Promise<void> {
139
143
  if (name !== undefined && typeof name !== 'string') {
140
144
  throw new TrainError('INVALID_ARGS', 'name must be a string');
141
145
  }
146
+ if (description !== undefined && typeof description !== 'string') {
147
+ throw new TrainError('INVALID_ARGS', 'description must be a string');
148
+ }
142
149
  const task = () => applyChannelMeta({ line, name, description, appData }, 'updateChannelMeta');
143
150
  const previous = metadataWrites.get(key);
144
151
  const pending = previous ? previous.then(task, task) : task();
@@ -100,6 +100,7 @@ export async function buildGroupInfo(
100
100
  account: acct.cfg.id,
101
101
  version: isDm ? 'dm' : 'group',
102
102
  name: resolvedName ?? '',
103
+ description: 'description' in conv ? conv.description : '',
103
104
  memberCount: inboxIds.length,
104
105
  labels,
105
106
  category,
@@ -12,6 +12,9 @@ export const str = (v: unknown): string => (typeof v === 'string' ? v : '');
12
12
 
13
13
  function channelAppData(a: Record<string, unknown>): Record<string, unknown> {
14
14
  const appData = a.metadata === undefined ? {} : { ...metadataPatch(a.metadata) };
15
+ if (Object.hasOwn(appData, 'description')) {
16
+ throw new TrainError('INVALID_ARGS', 'Use top-level description for the native XMTP group description, not metadata.description');
17
+ }
15
18
  for (const key of ['labels', 'github', 'preview']) {
16
19
  if (!Object.hasOwn(a, key)) continue;
17
20
  if (Object.hasOwn(appData, key)) {
@@ -29,18 +32,23 @@ export async function setChannelMetadata(
29
32
  ) {
30
33
  const line = str(a.line);
31
34
  if (!line) return ctx.err('set_channel_metadata requires `line`');
32
- if (Object.hasOwn(a, 'name') && typeof a.name !== 'string') {
33
- return ctx.err('set_channel_metadata name must be a string');
34
- }
35
+ const invalidField = ['name', 'description'].find(
36
+ (field) => Object.hasOwn(a, field) && typeof a[field] !== 'string',
37
+ );
38
+ if (invalidField) return ctx.err(`set_channel_metadata ${invalidField} must be a string`);
35
39
  const appData = channelAppData(a);
36
40
  const metaName = a.name;
37
41
  const hasName = typeof metaName === 'string' && metaName.length > 0;
38
- if (!hasName && Object.keys(appData).length === 0)
42
+ const hasDescription = typeof a.description === 'string';
43
+ const hasAppData = Object.keys(appData).length > 0;
44
+ if (!hasName && !hasDescription && !hasAppData)
39
45
  return ctx.err(
40
- 'set_channel_metadata requires at least one of `metadata`, `github`, `preview`, `name`, `labels`',
46
+ 'set_channel_metadata requires at least one of `metadata`, `github`, `preview`, `name`, `description`, `labels`',
41
47
  );
42
- const callArgs: Record<string, unknown> = { line, appData };
48
+ const callArgs: Record<string, unknown> = { line };
49
+ if (hasAppData) callArgs.appData = appData;
43
50
  if (hasName) callArgs.name = metaName;
51
+ if (hasDescription) callArgs.description = a.description;
44
52
  return ctx.okJson(await ctx.call('updateChannelMeta', callArgs));
45
53
  }
46
54
 
@@ -115,7 +115,7 @@ export const XMTP_TOOLS: StationTool[] = [
115
115
  group: 'read',
116
116
  description:
117
117
  "Read an XMTP channel's current metadata + membership. Args: line (required). Returns " +
118
- '{line, id, account, version (dm|group), name, memberCount, labels, category, status, priority, github, preview, ' +
118
+ '{line, id, account, version (dm|group), name, description (native XMTP group description, empty for DMs), memberCount, labels, category, status, priority, github, preview, ' +
119
119
  'assigned, appData (stored JSON object), rawAppData (stored JSON string), ' +
120
120
  'members:[{inboxId, address}]}. Syncs first and fails if metadata cannot be read. ' +
121
121
  'xmtp-only (daemon `groupInfo`). Use before ' +
@@ -170,7 +170,9 @@ export const XMTP_TOOLS: StationTool[] = [
170
170
  group: 'write',
171
171
  description:
172
172
  "Update an existing channel's metadata. Args: line (required, the metro:// line), and " +
173
- 'any of metadata? (JSON object), github? (url), preview? (url), name? (string). ' +
173
+ 'any of metadata? (JSON object), github? (url), preview? (url), name? (string), description? (string). ' +
174
+ 'Top-level description updates the standard XMTP group description shown in Stage; "" clears it. ' +
175
+ 'Use group_info.description for native readback. metadata.description is refused; it is not the visible description. ' +
174
176
  'metadata is a shallow appData patch: omitted keys are preserved, supplied nested ' +
175
177
  'values replace that key, and null deletes custom keys. v is reserved. Put labels, ' +
176
178
  'assigned, category, status and priority inside metadata. assigned replaces the array with normalized lowercase ' +
@@ -226,6 +228,10 @@ export const XMTP_TOOLS: StationTool[] = [
226
228
  description: 'Linked preview URL ("" to clear).',
227
229
  },
228
230
  name: { type: 'string', description: 'New channel name.' },
231
+ description: {
232
+ type: 'string',
233
+ description: 'Standard XMTP group description shown in Stage. "" clears it. Omit to preserve it; never put it inside metadata.',
234
+ },
229
235
  },
230
236
  required: ['line'],
231
237
  },
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.320"
2
+ "version": "0.1.0-beta.321"
3
3
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0-beta.320",
2
+ "version": "0.1.0-beta.321",
3
3
  "dependencies": {
4
4
  "@anthropic-ai/claude-agent-sdk": "0.3.287",
5
5
  "@modelcontextprotocol/sdk": "^1.29.0",
package/skills/STAGE.md CHANGED
@@ -11,10 +11,11 @@ Stage (https://stage.box) is a messenger built on XMTP. A Stage channel is an XM
11
11
 
12
12
  **Channels.**
13
13
  - Create: `create_group {"station": "xmtp", "name": "Launch plan", "members": ["0x…"]}`. Members are 0x addresses or XMTP inbox ids. It returns the new `line` and `id`.
14
- - Rename: `set_channel_metadata {"line": "…", "name": "New title"}`. No tool sets the description or the picture.
14
+ - Rename: `set_channel_metadata {"line": "…", "name": "New title"}`. No tool sets the picture.
15
+ - Description: `set_channel_metadata {"line": "…", "description": "What this channel is for"}` sets the standard XMTP group description shown in Stage. `""` clears it. Read `group_info.description` to check it saved. Never put it in `metadata.description`: that is not the visible description and is refused.
15
16
  - Add or remove people: `add_members {"line": "…", "members": ["0x…"]}`. `remove_members` takes the same shape.
16
17
  - Leave: `close_channel {"line": "…", "removeSelf": true}`.
17
- - Read: `group_info {"line": "…"}` gives the name, `labels`, `assigned`, `category`, `status`, `priority`, `appData` and the members (`inboxId`, `address`). `list_members {"line": "…"}` gives the members only.
18
+ - Read: `group_info {"line": "…"}` gives the name, native `description`, `labels`, `assigned`, `category`, `status`, `priority`, `appData` and the members (`inboxId`, `address`). `list_members {"line": "…"}` gives the members only.
18
19
  - Direct chat: `dm {"address": "0x…"}` opens a 1:1 chat and returns its line.
19
20
  - With several XMTP accounts, add `"account": "<id>"` to `create_group` and `dm`.
20
21