@stage-labs/metro 0.1.0-beta.324 → 0.1.0-beta.326

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.
Files changed (176) hide show
  1. package/dist/agent.js +1 -1
  2. package/dist/cli.js +1 -1
  3. package/dist/lock.js +15 -3
  4. package/dist/root-release-io.js +1 -2
  5. package/dist/root-release.js +2 -3
  6. package/dist/route.js +1 -1
  7. package/dist/service.js +4 -4
  8. package/dist/version.js +1 -1
  9. package/package.json +1 -1
  10. package/runtime/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
  11. package/runtime/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
  12. package/runtime/node_modules/@metro-labs/core/src/lines.ts +2 -8
  13. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +0 -6
  14. package/runtime/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
  15. package/runtime/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
  16. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
  17. package/runtime/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
  18. package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
  19. package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
  20. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
  21. package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
  22. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
  23. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
  24. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +4 -4
  25. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +2 -6
  26. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +6 -6
  27. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +0 -24
  28. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +3 -3
  29. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +3 -4
  30. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +4 -9
  31. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +1 -6
  32. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +3 -3
  33. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +2 -2
  34. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +1 -1
  35. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +1 -1
  36. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +3 -2
  37. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +1 -1
  38. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +3 -7
  39. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +2 -2
  40. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +10 -19
  41. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +1 -5
  42. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +3 -4
  43. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +1 -2
  44. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +2 -4
  45. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +5 -6
  46. package/runtime/node_modules/@metro-labs/daemon/src/mcp/server-approvals.ts +2 -5
  47. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +1 -5
  48. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +2 -4
  49. package/runtime/node_modules/@metro-labs/daemon/src/mcp-server/announced.ts +1 -1
  50. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +0 -1
  51. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +2 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/tailscale-guard.ts +26 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +1 -3
  54. package/runtime/node_modules/@metro-labs/daemon/src/routes/gmail-attach.ts +3 -3
  55. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +6 -21
  56. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +1 -5
  57. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +6 -6
  58. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +0 -1
  59. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +12 -61
  60. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +13 -25
  61. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +5 -5
  62. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +2 -7
  63. package/runtime/node_modules/@metro-labs/daemon/src/voice/call.ts +2 -6
  64. package/runtime/node_modules/@metro-labs/daemon/src/voice/calls.ts +12 -12
  65. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +5 -23
  66. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +1 -7
  67. package/runtime/node_modules/@metro-labs/discord-bot/src/channels.ts +2 -2
  68. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +10 -35
  69. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +5 -11
  70. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +6 -34
  71. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +11 -14
  72. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +1 -3
  73. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +2 -3
  74. package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +3 -3
  75. package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +8 -8
  76. package/runtime/node_modules/@metro-labs/gmail/src/managed.ts +3 -2
  77. package/runtime/node_modules/@metro-labs/http/src/agent-signature.ts +1 -1
  78. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +3 -4
  79. package/runtime/node_modules/@metro-labs/http/src/connector-discovery.ts +0 -113
  80. package/runtime/node_modules/@metro-labs/http/src/connector-input.ts +1 -1
  81. package/runtime/node_modules/@metro-labs/http/src/connector-mcp.ts +12 -13
  82. package/runtime/node_modules/@metro-labs/http/src/connector-oauth.ts +1 -1
  83. package/runtime/node_modules/@metro-labs/http/src/connector-policy.ts +1 -6
  84. package/runtime/node_modules/@metro-labs/http/src/connector-public-url.ts +4 -2
  85. package/runtime/node_modules/@metro-labs/http/src/connector-tools.ts +1 -11
  86. package/runtime/node_modules/@metro-labs/http/src/connector-url.ts +0 -6
  87. package/runtime/node_modules/@metro-labs/http/src/mcp-server-protocol.ts +1 -1
  88. package/runtime/node_modules/@metro-labs/http/src/mcp-server-tools.ts +0 -2
  89. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +1 -1
  90. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +2 -2
  91. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +6 -7
  92. package/runtime/node_modules/@metro-labs/inference/package.json +1 -0
  93. package/runtime/node_modules/@metro-labs/inference/src/attempts.ts +3 -3
  94. package/runtime/node_modules/@metro-labs/inference/src/bedrock.ts +1 -1
  95. package/runtime/node_modules/@metro-labs/inference/src/codex-translate.ts +2 -2
  96. package/runtime/node_modules/@metro-labs/inference/src/delivered.ts +1 -1
  97. package/runtime/node_modules/@metro-labs/inference/src/effort.ts +0 -6
  98. package/runtime/node_modules/@metro-labs/inference/src/fallback.ts +7 -5
  99. package/runtime/node_modules/@metro-labs/inference/src/forward.ts +5 -5
  100. package/runtime/node_modules/@metro-labs/inference/src/measure.ts +2 -3
  101. package/runtime/node_modules/@metro-labs/inference/src/named.ts +3 -3
  102. package/runtime/node_modules/@metro-labs/inference/src/probes.ts +98 -0
  103. package/runtime/node_modules/@metro-labs/inference/src/usage.ts +5 -5
  104. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +6 -8
  105. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +2 -1
  106. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +3 -5
  107. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +6 -6
  108. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +10 -13
  109. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +7 -26
  110. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
  111. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +3 -6
  112. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +3 -2
  113. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +6 -20
  114. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +1 -7
  115. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +0 -2
  116. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +1 -16
  117. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -8
  118. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +2 -1
  119. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +1 -8
  120. package/runtime/node_modules/@metro-labs/threema/src/chats.ts +4 -3
  121. package/runtime/node_modules/@metro-labs/threema/src/format.ts +15 -17
  122. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +2 -4
  123. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +2 -1
  124. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +2 -1
  125. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +7 -7
  126. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +1 -3
  127. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +0 -2
  128. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +1 -1
  129. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
  130. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +7 -0
  131. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +4 -10
  132. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -5
  133. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +14 -26
  134. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +1 -7
  135. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +17 -58
  136. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +1 -6
  137. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +10 -31
  138. package/runtime/node_modules/@metro-labs/xmtp/src/frames.ts +12 -14
  139. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -3
  140. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +3 -9
  141. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +8 -9
  142. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +4 -2
  143. package/runtime/node_modules/@metro-labs/xmtp/src/network.ts +2 -3
  144. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +2 -1
  145. package/runtime/node_modules/@metro-labs/xmtp/src/read-attachments.ts +2 -2
  146. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +2 -1
  147. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +23 -46
  148. package/runtime/node_modules/@metro-labs/xmtp/src/wallet.ts +5 -4
  149. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +12 -15
  150. package/runtime/runtime.json +1 -1
  151. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
  152. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
  153. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/lines.ts +2 -8
  154. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/protocol.ts +0 -6
  155. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
  156. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
  157. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
  158. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
  159. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
  160. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
  161. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
  162. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
  163. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
  164. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
  165. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/train-error.ts +4 -4
  166. package/runtime/sdk-runner/runner.json +1 -1
  167. package/runtime/sdk-runner/src/approvals.ts +2 -2
  168. package/runtime/sdk-runner/src/automation-command.ts +2 -2
  169. package/runtime/sdk-runner/src/calls.ts +0 -6
  170. package/runtime/sdk-runner/src/errors.ts +1 -1
  171. package/runtime/sdk-runner/src/link.ts +1 -1
  172. package/runtime/sdk-runner/src/runner.ts +2 -2
  173. package/runtime/sdk-runner/src/session-watch.ts +5 -34
  174. package/runtime/sdk-runner/src/task-state.ts +1 -1
  175. package/runtime/sdk-runner/src/tool-proxy.ts +1 -1
  176. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +0 -40
@@ -242,16 +242,6 @@ function pollPayload(
242
242
  };
243
243
  }
244
244
 
245
- function labeledPayload(
246
- base: Record<string, unknown>,
247
- typeId: string,
248
- text: string,
249
- key: string,
250
- c: unknown,
251
- ): Record<string, unknown> {
252
- return { ...base, text, payload: { contentType: typeId, [key]: c } };
253
- }
254
-
255
245
  function attachmentEnvelope(
256
246
  base: Record<string, unknown>,
257
247
  typeId: string,
@@ -275,32 +265,21 @@ function attachmentEnvelope(
275
265
  return undefined;
276
266
  }
277
267
 
268
+ const REF_TEXT = new Map([
269
+ ['walletSendCalls', 'Payment request'],
270
+ ['transactionReference', 'Transaction'],
271
+ ['signatureRequest', 'Signature request'],
272
+ ['signatureReference', 'Signature'],
273
+ ]);
274
+
278
275
  function refEnvelope(
279
276
  base: Record<string, unknown>,
280
277
  typeId: string | undefined,
281
278
  c: object,
282
279
  ): Record<string, unknown> | undefined {
283
- if (typeId === 'walletSendCalls')
284
- return labeledPayload(
285
- base,
286
- typeId,
287
- 'Payment request',
288
- 'walletSendCalls',
289
- c,
290
- );
291
- if (typeId === 'transactionReference')
292
- return labeledPayload(
293
- base,
294
- typeId,
295
- 'Transaction',
296
- 'transactionReference',
297
- c,
298
- );
299
- if (typeId === 'signatureRequest')
300
- return labeledPayload(base, typeId, 'Signature request', 'signatureRequest', c);
301
- if (typeId === 'signatureReference')
302
- return labeledPayload(base, typeId, 'Signature', 'signatureReference', c);
303
- return undefined;
280
+ if (typeId === undefined) return undefined;
281
+ const text = REF_TEXT.get(typeId);
282
+ return text === undefined ? undefined : { ...base, text, payload: { contentType: typeId, [typeId]: c } };
304
283
  }
305
284
 
306
285
  function frameEnvelope(
@@ -1,3 +1,4 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
1
2
  import { TrainError } from '@metro-labs/core/train-error';
2
3
  import type { FrameActionContent, FrameContent } from './codecs.js';
3
4
 
@@ -13,9 +14,6 @@ const TEXT_TYPES = new Set(['Text', 'Caption', 'Markdown']);
13
14
 
14
15
  type Node = Record<string, unknown>;
15
16
 
16
- export const isNode = (v: unknown): v is Node =>
17
- typeof v === 'object' && v !== null && !Array.isArray(v);
18
-
19
17
  export const bad = (message: string): TrainError => new TrainError('INVALID_ARGS', message);
20
18
 
21
19
  function plain(value: unknown): string {
@@ -30,8 +28,8 @@ function plain(value: unknown): string {
30
28
 
31
29
  function childrenOf(node: Node): Node[] {
32
30
  const c = node.children;
33
- if (Array.isArray(c)) return c.filter(isNode);
34
- return isNode(c) ? [c] : [];
31
+ if (Array.isArray(c)) return c.filter(isRecord);
32
+ return isRecord(c) ? [c] : [];
35
33
  }
36
34
 
37
35
  export function frameSummary(widget: Node): { title?: string; description?: string } {
@@ -73,7 +71,7 @@ function checkSize(value: unknown, name: string): void {
73
71
 
74
72
  function parseWidget(raw: unknown, name = 'frame widget'): Node {
75
73
  const widget = parseJson(raw, name);
76
- if (!isNode(widget) || typeof widget.type !== 'string' || widget.type === '')
74
+ if (!isRecord(widget) || typeof widget.type !== 'string' || widget.type === '')
77
75
  throw bad(`${name} must be a ChatKit widget object with a \`type\` (Card, ListView or Basic)`);
78
76
  checkSize(widget, name);
79
77
  return widget;
@@ -82,7 +80,7 @@ function parseWidget(raw: unknown, name = 'frame widget'): Node {
82
80
  function parseScreen(id: string, raw: unknown): { screen: Node; widget: Node } {
83
81
  const name = `frame screen "${id}"`;
84
82
  const given = parseJson(raw, name);
85
- if (!isNode(given) || given.type !== undefined || given.widget === undefined) {
83
+ if (!isRecord(given) || given.type !== undefined || given.widget === undefined) {
86
84
  const widget = parseWidget(given, name);
87
85
  return { screen: widget, widget };
88
86
  }
@@ -97,9 +95,9 @@ function openTargets(root: unknown): string[] {
97
95
  while (stack.length > 0) {
98
96
  const value = stack.pop();
99
97
  if (Array.isArray(value)) stack.push(...(value as unknown[]));
100
- else if (isNode(value)) {
98
+ else if (isRecord(value)) {
101
99
  if (value.type === FRAME_OPEN)
102
- targets.push(isNode(value.payload) && typeof value.payload.screen === 'string' ? value.payload.screen : '');
100
+ targets.push(isRecord(value.payload) && typeof value.payload.screen === 'string' ? value.payload.screen : '');
103
101
  stack.push(...Object.values(value));
104
102
  }
105
103
  }
@@ -130,7 +128,7 @@ interface FrameBody {
130
128
 
131
129
  function parseScreens(raw: unknown, start: unknown): FrameBody {
132
130
  const given = parseJson(raw, 'frame screens');
133
- if (!isNode(given))
131
+ if (!isRecord(given))
134
132
  throw bad('frame screens must be an object of screen id to widget: {"home": {…}, "story": {"title": "…", "widget": {…}}}');
135
133
  const ids = screenIds(given);
136
134
  const parsed = ids.map((id) => [id, parseScreen(id, given[id])] as const);
@@ -153,7 +151,7 @@ function frameBody(args: Node): FrameBody {
153
151
 
154
152
  function sourcePart(raw: unknown): Pick<FrameContent, 'source'> {
155
153
  if (raw === undefined) return {};
156
- const url = isNode(raw) && typeof raw.url === 'string' ? raw.url.trim() : '';
154
+ const url = isRecord(raw) && typeof raw.url === 'string' ? raw.url.trim() : '';
157
155
  if (url.length > MAX_SOURCE_URL)
158
156
  throw bad(`frame source url is ${url.length} characters; the limit is ${MAX_SOURCE_URL}`);
159
157
  if (!URL.canParse(url) || new URL(url).protocol !== 'https:')
@@ -163,7 +161,7 @@ function sourcePart(raw: unknown): Pick<FrameContent, 'source'> {
163
161
 
164
162
  export function buildFrameContent(raw: unknown): { frame: FrameContent; title: string } {
165
163
  const args = parseJson(raw, 'frame');
166
- if (!isNode(args))
164
+ if (!isRecord(args))
167
165
  throw bad('frame must be an object: {widget, title?, description?} or {screens, start, title?, description?}');
168
166
  const body = frameBody(args);
169
167
  const derived = frameSummary(body.first);
@@ -180,8 +178,8 @@ export function buildFrameContent(raw: unknown): { frame: FrameContent; title: s
180
178
 
181
179
  export function asFrameAction(c: object): FrameActionContent | undefined {
182
180
  const v = c as Partial<FrameActionContent>;
183
- if (typeof v.frameId !== 'string' || !isNode(v.action) || typeof v.action.type !== 'string') return undefined;
184
- const payload = isNode(v.action.payload) ? v.action.payload : undefined;
181
+ if (typeof v.frameId !== 'string' || !isRecord(v.action) || typeof v.action.type !== 'string') return undefined;
182
+ const payload = isRecord(v.action.payload) ? v.action.payload : undefined;
185
183
  return {
186
184
  frameId: v.frameId,
187
185
  action: payload ? { type: v.action.type, payload } : { type: v.action.type },
@@ -1,6 +1,6 @@
1
1
  import { TrainError } from '@metro-labs/core/train-error';
2
2
  import type { GroupResult } from '@metro-labs/core/stations/types';
3
- import { accountForCall, convOf, lineOf } from './accounts.js';
3
+ import { accountForCall, lineOf, openConv } from './accounts.js';
4
4
  import {
5
5
  applyMemberOp,
6
6
  createGroupWithMembers,
@@ -38,8 +38,7 @@ async function memberOp(
38
38
  const { addrs, inboxes } = splitMembers(args);
39
39
  if (!addrs.length && !inboxes.length)
40
40
  throw new TrainError('INVALID_ARGS', `${verb} requires members`);
41
- const { conv } = await convOf(line);
42
- if (!conv) throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
41
+ const { conv } = await openConv(line);
43
42
  await applyMemberOp(conv, addrs, inboxes, mode);
44
43
  return {
45
44
  capability: { supported: true },
@@ -1,4 +1,5 @@
1
1
  import { IdentifierKind, type Signer } from '@xmtp/node-sdk';
2
+ import { hexToBytes } from 'viem';
2
3
  import { privateKeyToAccount } from 'viem/accounts';
3
4
  import { homedir } from 'node:os';
4
5
  import { join } from 'node:path';
@@ -15,19 +16,12 @@ export interface Identity {
15
16
  smart: SmartAccount | null;
16
17
  }
17
18
 
18
- const bytesOf = (sig: string): Uint8Array => {
19
- const hex = sig.slice(2);
20
- const out = new Uint8Array(hex.length / 2);
21
- for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
22
- return out;
23
- };
24
-
25
19
  export function signerFor(privateKey: string): Identity {
26
20
  const acct = privateKeyToAccount(privateKey as `0x${string}`);
27
21
  const signer: Signer = {
28
22
  type: 'EOA',
29
23
  getIdentifier: () => Promise.resolve({ identifier: acct.address, identifierKind: IdentifierKind.Ethereum }),
30
- signMessage: async (msg: string) => bytesOf(await acct.signMessage({ message: msg })),
24
+ signMessage: async (msg: string) => hexToBytes(await acct.signMessage({ message: msg })),
31
25
  };
32
26
  return { signer, address: acct.address, smart: null };
33
27
  }
@@ -37,7 +31,7 @@ export async function smartSignerFor(privateKey: string, rpc?: string): Promise<
37
31
  const signer: Signer = {
38
32
  type: 'SCW',
39
33
  getIdentifier: () => Promise.resolve({ identifier: smart.address, identifierKind: IdentifierKind.Ethereum }),
40
- signMessage: async (msg: string) => bytesOf(await smart.signMessage(msg)),
34
+ signMessage: async (msg: string) => hexToBytes(await smart.signMessage(msg)),
41
35
  getChainId: () => BigInt(SMART_CHAIN_ID),
42
36
  };
43
37
  return { signer, address: smart.address, smart };
@@ -1,3 +1,4 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
1
2
  import { TrainError } from '@metro-labs/core/train-error';
2
3
 
3
4
  export interface GroupLike {
@@ -41,10 +42,10 @@ export function readAppDataObject(
41
42
  ): Record<string, unknown> {
42
43
  if (!existingAppData?.trim()) return {};
43
44
  const parsed: unknown = JSON.parse(existingAppData);
44
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
45
+ if (!isRecord(parsed)) {
45
46
  throw new TrainError('INVALID_ARGS', 'Channel appData must be a JSON object');
46
47
  }
47
- return parsed as Record<string, unknown>;
48
+ return parsed;
48
49
  }
49
50
 
50
51
  export function normalizeAssigned(value: unknown): string[] {
@@ -62,14 +63,13 @@ export function normalizeAssigned(value: unknown): string[] {
62
63
  }
63
64
 
64
65
  export function metadataPatch(value: unknown): Record<string, unknown> {
65
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
66
+ if (!isRecord(value)) {
66
67
  throw new TrainError('INVALID_ARGS', 'metadata must be a JSON object');
67
68
  }
68
- const patch = value as Record<string, unknown>;
69
- if (Object.hasOwn(patch, 'v')) {
69
+ if (Object.hasOwn(value, 'v')) {
70
70
  throw new TrainError('INVALID_ARGS', 'metadata cannot change the reserved v field');
71
71
  }
72
- return patch;
72
+ return value;
73
73
  }
74
74
 
75
75
  function trimmedString(v: unknown): string | undefined {
@@ -94,9 +94,8 @@ export function readAppData(appData: string | undefined): {
94
94
  } {
95
95
  if (!appData?.trim()) return { labels: [] };
96
96
  try {
97
- const p: unknown = JSON.parse(appData);
98
- if (!p || typeof p !== 'object' || Array.isArray(p)) return { labels: [] };
99
- const rec = p as Record<string, unknown>;
97
+ const rec: unknown = JSON.parse(appData);
98
+ if (!isRecord(rec)) return { labels: [] };
100
99
  return {
101
100
  labels: cleanLabels(rec.labels),
102
101
  category: cleanMetadataText(rec.category),
@@ -1,3 +1,5 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { errMsg } from '@metro-labs/core/log';
1
3
  import { TrainError } from '@metro-labs/core/train-error';
2
4
 
3
5
  export const STAGE_NAMES_PARENT = 'stage.base.eth';
@@ -27,10 +29,10 @@ async function ask(path: string, init: RequestInit, fetchImpl: typeof fetch): Pr
27
29
  try {
28
30
  res = await fetchImpl(`${proxyBase()}/names/${path}`, { ...init, headers: HEADERS, signal: AbortSignal.timeout(30_000) });
29
31
  } catch (err) {
30
- throw new TrainError('names_unreachable', `could not reach the stage name service: ${err instanceof Error ? err.message : String(err)}`);
32
+ throw new TrainError('names_unreachable', `could not reach the stage name service: ${errMsg(err)}`);
31
33
  }
32
34
  const body: unknown = await res.json().catch(() => null);
33
- const record = typeof body === 'object' && body !== null ? (body as Record<string, unknown>) : {};
35
+ const record = isRecord(body) ? body : {};
34
36
  if (!res.ok) throw new TrainError('names_refused', typeof record.error === 'string' ? record.error : `the stage name service answered ${String(res.status)}`);
35
37
  return record;
36
38
  }
@@ -1,12 +1,11 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
1
2
  import { errMsg } from '@metro-labs/core/log';
2
3
  import { TrainError } from '@metro-labs/core/train-error';
3
4
 
4
5
  const RATE_DELAY_MS = 60_000;
5
6
  const MAX_DELAY_MS = 300_000;
6
7
 
7
- function recordOf(value: unknown): Record<string, unknown> {
8
- return typeof value === 'object' && value !== null ? value as Record<string, unknown> : {};
9
- }
8
+ const recordOf = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
10
9
 
11
10
  function retryAfter(error: unknown, now: number): number {
12
11
  const fields = recordOf(error);
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { encodeFunctionData, encodePacked, keccak256, namehash, stringToBytes, type Hex } from 'viem';
3
3
  import { normalize } from 'viem/ens';
4
+ import { errMsg } from '@metro-labs/core/log';
4
5
  import { TrainError } from '@metro-labs/core/train-error';
5
6
  import {
6
7
  assertImage,
@@ -93,7 +94,7 @@ export async function setUpName(smart: SmartAccount, name: string): Promise<bool
93
94
  await sendSponsoredCalls(smart, nameSetupCalls(name, smart.address, resolver));
94
95
  return true;
95
96
  } catch (err) {
96
- process.stderr.write(`xmtp: the name ${name} is claimed but its address and primary name are not set for ${smart.address}: ${err instanceof Error ? err.message : String(err)}\n`);
97
+ process.stderr.write(`xmtp: the name ${name} is claimed but its address and primary name are not set for ${smart.address}: ${errMsg(err)}\n`);
97
98
  return false;
98
99
  }
99
100
  }
@@ -1,6 +1,6 @@
1
1
  import type { DecodedMessage } from '@xmtp/node-sdk';
2
- import { kindOf } from '@metro-labs/core/stations/attachments';
3
- import { saveInlineAttachment, saveRemoteAttachment, type RemoteEntry, type SavedAttachment } from './attachments.js';
2
+ import { kindOf, type SavedAttachment } from '@metro-labs/core/stations/attachments';
3
+ import { saveInlineAttachment, saveRemoteAttachment, type RemoteEntry } from './attachments.js';
4
4
 
5
5
  interface ReadAttachment {
6
6
  name?: string;
@@ -3,6 +3,7 @@ import { base } from 'viem/chains';
3
3
  import { normalize } from 'viem/ens';
4
4
  import { makeProfileCache, type SenderProfile as Profile } from '@metro-labs/core/stations/sender-profile';
5
5
  import { respond } from '@metro-labs/core/stations/station-runtime';
6
+ import { errMsg } from '@metro-labs/core/log';
6
7
  import { TrainError } from '@metro-labs/core/train-error';
7
8
  import { accountForCall, accounts, type Account } from './accounts.js';
8
9
  import { resolveAddresses } from './conv-helpers.js';
@@ -85,7 +86,7 @@ const senders = makeProfileCache<SenderProfile>(
85
86
  const address = (await resolveAddresses(acct, [inboxId]))[inboxId];
86
87
  return address === undefined ? null : profileOf(address);
87
88
  },
88
- { onError: (k, err) => process.stderr.write(`xmtp: could not resolve the sender ${k}: ${err instanceof Error ? err.message : String(err)}\n`) },
89
+ { onError: (k, err) => process.stderr.write(`xmtp: could not resolve the sender ${k}: ${errMsg(err)}\n`) },
89
90
  );
90
91
 
91
92
  export const senderFields = (p: SenderProfile | null): Record<string, string> =>
@@ -1,3 +1,7 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdtempSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
1
5
  import { errMsg } from '@metro-labs/core/log';
2
6
  import { emitInbound, selfUri } from '@metro-labs/core/stations/train-events';
3
7
  import { mintId } from '@metro-labs/core/stations/station-runtime';
@@ -8,59 +12,32 @@ const WHISPER_MODEL =
8
12
  `${process.env.HOME}/.cache/whisper-cpp/ggml-base.bin`;
9
13
  const FFMPEG_BIN = process.env.METRO_FFMPEG_BIN ?? 'ffmpeg';
10
14
 
15
+ const run = (bin: string, args: string[]): Promise<void> =>
16
+ new Promise((res, rej) => {
17
+ const p = spawn(bin, args, { stdio: 'ignore' });
18
+ p.on('error', rej);
19
+ p.on('exit', (c) => {
20
+ if (c === 0) res();
21
+ else rej(new Error(`${bin} ${String(c)}`));
22
+ });
23
+ });
24
+
11
25
  export async function transcribeAndEmit(
12
26
  audio: Uint8Array,
13
27
  line: string,
14
28
  accountId: string,
15
29
  sourceMsgId: string,
16
30
  ): Promise<void> {
17
- const {
18
- existsSync: ex,
19
- readFileSync: rf,
20
- writeFileSync: wf,
21
- unlinkSync,
22
- mkdtempSync,
23
- } = await import('node:fs');
24
- const { tmpdir } = await import('node:os');
25
- const path = await import('node:path');
26
- const j = (...parts: string[]): string => path.join(...parts);
27
- const { spawn } = await import('node:child_process');
28
- if (!ex(WHISPER_MODEL)) return;
29
- const dir = mkdtempSync(j(tmpdir(), 'xmtp-tx-'));
30
- const inFile = j(dir, 'in.m4a');
31
- const wav = j(dir, 'in.wav');
32
- const out = j(dir, 'in');
33
- const run = (bin: string, args: string[]): Promise<void> =>
34
- new Promise((res, rej) => {
35
- const p = spawn(bin, args, { stdio: 'ignore' });
36
- p.on('error', rej);
37
- p.on('exit', (c) => {
38
- if (c === 0) res();
39
- else rej(new Error(`${bin} ${String(c)}`));
40
- });
41
- });
31
+ if (!existsSync(WHISPER_MODEL)) return;
32
+ const dir = mkdtempSync(join(tmpdir(), 'xmtp-tx-'));
33
+ const inFile = join(dir, 'in.m4a');
34
+ const wav = join(dir, 'in.wav');
35
+ const out = join(dir, 'in');
42
36
  try {
43
- wf(inFile, audio);
44
- await run(FFMPEG_BIN, [
45
- '-y',
46
- '-i',
47
- inFile,
48
- '-ar',
49
- '16000',
50
- '-ac',
51
- '1',
52
- wav,
53
- ]);
54
- await run(WHISPER_BIN, [
55
- '-m',
56
- WHISPER_MODEL,
57
- '-f',
58
- wav,
59
- '--output-txt',
60
- '-of',
61
- out,
62
- ]);
63
- const text = rf(`${out}.txt`, 'utf8').trim();
37
+ writeFileSync(inFile, audio);
38
+ await run(FFMPEG_BIN, ['-y', '-i', inFile, '-ar', '16000', '-ac', '1', wav]);
39
+ await run(WHISPER_BIN, ['-m', WHISPER_MODEL, '-f', wav, '--output-txt', '-of', out]);
40
+ const text = readFileSync(`${out}.txt`, 'utf8').trim();
64
41
  if (!text) return;
65
42
  emitInbound(accountId, {
66
43
  id: mintId(),
@@ -5,7 +5,8 @@ import {
5
5
  TransactionReferenceCodec,
6
6
  WalletSendCallsCodec,
7
7
  } from './codecs.js';
8
- import { bad, isNode, parseJson } from './frames.js';
8
+ import { isRecord } from '@metro-labs/core/is-record';
9
+ import { bad, parseJson } from './frames.js';
9
10
 
10
11
  const WALLET_TYPES: Record<string, { codec: { encode(c: Record<string, unknown>): EncodedContent }; needs: string[] }> = {
11
12
  walletSendCalls: { codec: new WalletSendCallsCodec(), needs: ['version', 'chainId', 'from', 'calls'] },
@@ -16,11 +17,11 @@ const WALLET_TYPES: Record<string, { codec: { encode(c: Record<string, unknown>)
16
17
 
17
18
  export function buildWalletContent(raw: unknown): { encoded: EncodedContent; summary: string } {
18
19
  const args = parseJson(raw, 'wallet');
19
- const type = isNode(args) && typeof args.type === 'string' ? args.type : '';
20
+ const type = isRecord(args) && typeof args.type === 'string' ? args.type : '';
20
21
  const spec = Object.hasOwn(WALLET_TYPES, type) ? WALLET_TYPES[type] : undefined;
21
- if (!isNode(args) || !spec) throw bad(`wallet type must be one of ${Object.keys(WALLET_TYPES).join(', ')}`);
22
+ if (!isRecord(args) || !spec) throw bad(`wallet type must be one of ${Object.keys(WALLET_TYPES).join(', ')}`);
22
23
  const content = parseJson(args.content, 'wallet content');
23
- if (!isNode(content)) throw bad(`wallet content must be the ${type} JSON object`);
24
+ if (!isRecord(content)) throw bad(`wallet content must be the ${type} JSON object`);
24
25
  const missing = spec.needs.filter((key) => content[key] === undefined);
25
26
  if (missing.length > 0) throw bad(`${type} content needs ${missing.join(', ')}`);
26
27
  const encoded = spec.codec.encode(content);
@@ -4,12 +4,18 @@ const uidToXmtp = new Map<string, string>();
4
4
  const INBOX_ETH_CACHE_MAX = 5000;
5
5
  export const inboxEthCache = new Map<string, string>();
6
6
 
7
+ const SENT_MAX = 5000;
8
+ const sentIds = new Set<string>();
9
+
10
+ function dropOldest(held: Map<string, string> | Set<string>, max: number): void {
11
+ if (held.size <= max) return;
12
+ const oldest = held.keys().next().value;
13
+ if (oldest !== undefined) held.delete(oldest);
14
+ }
15
+
7
16
  export function cacheInboxEth(inboxId: string, eth: string): void {
8
17
  inboxEthCache.set(inboxId, eth);
9
- if (inboxEthCache.size > INBOX_ETH_CACHE_MAX) {
10
- const oldest = inboxEthCache.keys().next().value;
11
- if (oldest !== undefined) inboxEthCache.delete(oldest);
12
- }
18
+ dropOldest(inboxEthCache, INBOX_ETH_CACHE_MAX);
13
19
  }
14
20
 
15
21
  export function rememberUid(
@@ -18,10 +24,7 @@ export function rememberUid(
18
24
  ): void {
19
25
  if (!uid || !xmtpId || !uid.startsWith('msg_')) return;
20
26
  uidToXmtp.set(uid, xmtpId);
21
- if (uidToXmtp.size > UID_MAP_MAX) {
22
- const oldest = uidToXmtp.keys().next().value;
23
- if (oldest !== undefined) uidToXmtp.delete(oldest);
24
- }
27
+ dropOldest(uidToXmtp, UID_MAP_MAX);
25
28
  }
26
29
 
27
30
  export function resolveMsgId(rawId: string): string {
@@ -34,16 +37,10 @@ export function resolveMsgId(rawId: string): string {
34
37
  );
35
38
  }
36
39
 
37
- const SENT_MAX = 5000;
38
- const sentIds = new Set<string>();
39
-
40
40
  export function rememberSent(xmtpId: string | undefined): void {
41
41
  if (!xmtpId) return;
42
42
  sentIds.add(xmtpId);
43
- if (sentIds.size > SENT_MAX) {
44
- const oldest = sentIds.values().next().value;
45
- if (oldest !== undefined) sentIds.delete(oldest);
46
- }
43
+ dropOldest(sentIds, SENT_MAX);
47
44
  }
48
45
 
49
46
  export const sentByMe = (xmtpId: string): boolean => sentIds.has(xmtpId);
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.324"
2
+ "version": "0.1.0-beta.326"
3
3
  }
@@ -8,7 +8,7 @@ import {
8
8
  } from './automation-types.js';
9
9
  import { isRecord } from './is-record.js';
10
10
 
11
- export type { AutomationOutcome, AutomationRequest, AutomationResolution, AutomationStatus, AutomationState, AutomationUuid } from './automation-types.js';
11
+ export type { AutomationOutcome, AutomationRequest, AutomationResolution, AutomationStatus } from './automation-types.js';
12
12
 
13
13
  function payload<T extends { uuid: string }>(row: unknown, parse: (value: unknown) => T): T {
14
14
  try {
@@ -5,7 +5,7 @@ export const AUTOMATION_PROMPT_MAX = 64 * 1024;
5
5
  const PROMPT_JSON_MAX = 128 * 1024 + 2;
6
6
  export const AUTOMATION_REQUEST_MAX = 512;
7
7
  export const AUTOMATION_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
8
- export const AUTOMATION_ROUTINE_RE = /^[a-z][a-z0-9-]{0,63}$/;
8
+ const AUTOMATION_ROUTINE_RE = /^[a-z][a-z0-9-]{0,63}$/;
9
9
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
10
10
  const STATES = ['dispatched', 'consumed', 'awaiting-completion', 'completed', 'failed', 'interrupted', 'coalesced', 'cancelled'] as const;
11
11
  const DETAILS = ['sdk-completed', 'sdk-failed', 'runner-interrupted', 'newer-slot', 'owner-cancelled', 'worker-blocked', 'worker-completed'] as const;
@@ -29,7 +29,7 @@ const ERRORS = {
29
29
 
30
30
  export type AutomationUuid = `${string}-${string}-${string}-${string}-${string}`;
31
31
  export type AutomationState = (typeof STATES)[number];
32
- export type AutomationDetail = (typeof DETAILS)[number];
32
+ type AutomationDetail = (typeof DETAILS)[number];
33
33
  export type AutomationOutcome = 'completed' | 'blocked';
34
34
  export type AutomationErrorCode = keyof typeof ERRORS;
35
35
 
@@ -2,8 +2,6 @@ export type Line = string & { readonly __line: unique symbol };
2
2
  export const asLine = (s: string): Line => s as Line;
3
3
 
4
4
  const PREFIX = 'metro://';
5
- const build = (station: string, ...seg: (string | number)[]): Line =>
6
- asLine(`${PREFIX}${station}/${seg.map(String).join('/')}`);
7
5
 
8
6
  function parseAccountScoped(
9
7
  line: Line | string,
@@ -42,11 +40,7 @@ function parseTelegramLine(
42
40
  }
43
41
 
44
42
  export const Line = {
45
- claude: (orgId: string, sessionId: string): Line =>
46
- build('claude', orgId, sessionId),
47
- webhook: (endpointId: string): Line => build('webhook', endpointId),
48
- user: (station: string, id: string | number): Line =>
49
- build(station, 'user', id),
43
+ webhook: (endpointId: string): Line => asLine(`${PREFIX}webhook/${endpointId}`),
50
44
 
51
45
  parse(line: Line | string): { station: string; path: string[] } | null {
52
46
  if (!line.startsWith(PREFIX)) return null;
@@ -68,7 +62,7 @@ export const Line = {
68
62
  parseDiscord: (line: Line | string) =>
69
63
  parseAccountScoped(line, 'discord-bot', isSnowflake),
70
64
 
71
- parseTelegram: (line: Line | string) => parseTelegramLine(line),
65
+ parseTelegram: parseTelegramLine,
72
66
 
73
67
  parseThreema: (line: Line | string) =>
74
68
  parseAccountScoped(line, 'threema', isThreemaResource),
@@ -5,12 +5,6 @@ import { coerceErrorInfo, type TrainErrorInfo } from './train-error.js';
5
5
  import type { StructuredEvent } from './events.js';
6
6
  import type { CallMsg } from './stations/station-runtime.js';
7
7
 
8
- export {
9
- TrainError,
10
- serializeTrainError,
11
- type TrainErrorInfo,
12
- } from './train-error.js';
13
-
14
8
  const CALL_TIMEOUT_MS = 60_000;
15
9
 
16
10
  export interface Pending {
@@ -15,7 +15,7 @@ export interface SkillSource {
15
15
  folder: string;
16
16
  }
17
17
 
18
- export interface SkillFile {
18
+ interface SkillFile {
19
19
  path: string;
20
20
  size: number;
21
21
  sha256: string;
@@ -1,6 +1,6 @@
1
1
  import { isRecord } from '../is-record.js';
2
2
 
3
- export const ACCOUNT_STATES = ['connected', 'connecting', 'logged_out', 'disconnected'] as const;
3
+ const ACCOUNT_STATES = ['connected', 'connecting', 'logged_out', 'disconnected'] as const;
4
4
 
5
5
  export type AccountState = (typeof ACCOUNT_STATES)[number];
6
6
 
@@ -1,3 +1,7 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, open, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+
1
5
  export const attachDir = (): string =>
2
6
  process.env.METRO_XMTP_ATTACH_DIR ??
3
7
  `${process.env.HOME}/.cache/metro/messenger-uploads`;
@@ -89,14 +93,9 @@ const cachePathFor = async (
89
93
  index: number,
90
94
  meta: { mime?: string; name?: string },
91
95
  ): Promise<string> => {
92
- const { mkdir } = await import('node:fs/promises');
93
- const nodePath = await import('node:path');
94
96
  const dir = attachDir();
95
97
  await mkdir(dir, { recursive: true });
96
- return nodePath.join(
97
- dir,
98
- cacheFileName(messageId, index, meta.name, meta.mime),
99
- );
98
+ return join(dir, cacheFileName(messageId, index, meta.name, meta.mime));
100
99
  };
101
100
 
102
101
  export const saveBufferToCache = async (
@@ -106,8 +105,6 @@ export const saveBufferToCache = async (
106
105
  meta: { mime?: string; name?: string },
107
106
  ): Promise<SavedAttachment> => {
108
107
  assertAttachmentSize(data.length);
109
- const { writeFile, rename, rm } = await import('node:fs/promises');
110
- const { randomUUID } = await import('node:crypto');
111
108
  const path = await cachePathFor(messageId, index, meta);
112
109
  const part = `${await cachePathFor(randomUUID(), index, meta)}.part`;
113
110
  try {
@@ -125,7 +122,6 @@ export const saveStreamToCache = async (
125
122
  index: number,
126
123
  meta: { mime?: string; name?: string },
127
124
  ): Promise<SavedAttachment> => {
128
- const { open, rename, rm } = await import('node:fs/promises');
129
125
  const path = await cachePathFor(messageId, index, meta);
130
126
  const part = `${path}.part`;
131
127
  const handle = await open(part, 'w');
@@ -14,7 +14,7 @@ export interface ChannelEntry {
14
14
  kind: 'direct' | 'group' | 'channel' | 'thread';
15
15
  }
16
16
 
17
- export interface ChannelCapability {
17
+ interface ChannelCapability {
18
18
  supported: boolean;
19
19
  complete: boolean;
20
20
  source: 'remote' | 'local' | 'mixed' | 'unsupported';