@stage-labs/metro 0.1.0-beta.280 → 0.1.0-beta.282
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 +1 -1
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/github-skills.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +1 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +55 -15
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +3 -2
- package/runtime/runtime.json +1 -1
- package/runtime/sdk-runner/runner.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.282",
|
|
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",
|
|
@@ -158,7 +158,7 @@ export class InboundRelay {
|
|
|
158
158
|
await this.notify('notifications/claude/channel', {
|
|
159
159
|
content:
|
|
160
160
|
(e.text ? `${e.text}\n` : '') +
|
|
161
|
-
`[attachment(s)
|
|
161
|
+
`[attachment(s) still downloading: ${names}; they follow in a separate note]`,
|
|
162
162
|
meta: {
|
|
163
163
|
line: e.line,
|
|
164
164
|
from: e.from,
|
|
@@ -124,11 +124,13 @@ export class GitHubSkills {
|
|
|
124
124
|
|
|
125
125
|
rows(activation = this.activation(), loaded = this.loaded(activation)): ClaudeSkill[] {
|
|
126
126
|
if (loaded === null) return [];
|
|
127
|
+
const origin = loaded.source;
|
|
127
128
|
const shadowed = activation.shadowed;
|
|
128
129
|
return loaded.skills.map((skill) => ({
|
|
129
130
|
id: `github:${skill.name}`, name: skill.name, title: skill.name, description: skill.description,
|
|
130
131
|
path: join(skillRelease(this.root, loaded.id), 'skills', skill.name, 'SKILL.md'), editable: false,
|
|
131
132
|
updatedAt: loaded.createdAt, managed: true, shadowed: shadowed.includes(skill.name),
|
|
133
|
+
...(origin === null ? {} : { github: { repository: origin.repository, commit: origin.commit, folder: origin.folder } }),
|
|
132
134
|
}));
|
|
133
135
|
}
|
|
134
136
|
|
|
@@ -12,8 +12,15 @@ import { errMsg } from '@metro-labs/core/log';
|
|
|
12
12
|
|
|
13
13
|
export type { SavedAttachment };
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
const
|
|
15
|
+
const READ_RETRY_DELAYS_MS: readonly number[] = [400, 800];
|
|
16
|
+
export const REMOTE_FETCH_ATTEMPTS = READ_RETRY_DELAYS_MS.length + 1;
|
|
17
|
+
export const INBOUND_RETRY_DELAYS_MS: readonly number[] = [
|
|
18
|
+
2_000, 4_000, 8_000, 15_000, 30_000, 30_000,
|
|
19
|
+
60_000, 60_000, 60_000, 60_000, 60_000, 60_000,
|
|
20
|
+
];
|
|
21
|
+
const REMOTE_FETCH_TIMEOUT_MS = 120_000;
|
|
22
|
+
const SWARM_FALLBACK_GATEWAY = 'https://download.gateway.ethswarm.org/bzz/';
|
|
23
|
+
const SWARM_BZZ_URL = /^https:\/\/[^/]+\/bzz\/([0-9a-f]{64}(?:[0-9a-f]{64})?)\/?$/i;
|
|
17
24
|
|
|
18
25
|
const sleep = (ms: number): Promise<void> =>
|
|
19
26
|
new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -74,22 +81,54 @@ interface DecodedAttachment {
|
|
|
74
81
|
data: Uint8Array;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
function remoteUrls(url: string): string[] {
|
|
85
|
+
const ref = SWARM_BZZ_URL.exec(url)?.[1];
|
|
86
|
+
const fallback = ref === undefined ? url : `${SWARM_FALLBACK_GATEWAY}${ref}/`;
|
|
87
|
+
return fallback === url ? [url] : [url, fallback];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function withTimeout<T>(work: Promise<T>, ms: number): Promise<T> {
|
|
91
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
92
|
+
const expired = new Promise<never>((_resolve, reject) => {
|
|
93
|
+
timer = setTimeout(() => {
|
|
94
|
+
reject(new Error(`no answer within ${ms / 1000}s`));
|
|
95
|
+
}, ms);
|
|
96
|
+
});
|
|
97
|
+
return Promise.race([work, expired]).finally(() => {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function loadFrom(r: RemoteEntry, url: string): Promise<DecodedAttachment> {
|
|
103
|
+
return withTimeout(
|
|
104
|
+
RemoteAttachmentCodec.load<DecodedAttachment>(
|
|
105
|
+
{ ...toRemoteDescriptor(r), url },
|
|
106
|
+
loadRegistry,
|
|
107
|
+
),
|
|
108
|
+
REMOTE_FETCH_TIMEOUT_MS,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function loadRemote(
|
|
113
|
+
r: RemoteEntry,
|
|
114
|
+
retryDelaysMs: readonly number[],
|
|
115
|
+
): Promise<DecodedAttachment> {
|
|
116
|
+
const urls = remoteUrls(r.url);
|
|
78
117
|
let last = '';
|
|
79
|
-
for (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await sleep(REMOTE_FETCH_BACKOFF_MS * attempt);
|
|
118
|
+
for (const delay of [0, ...retryDelaysMs]) {
|
|
119
|
+
if (delay > 0) await sleep(delay);
|
|
120
|
+
const errors: string[] = [];
|
|
121
|
+
for (const url of urls) {
|
|
122
|
+
try {
|
|
123
|
+
return await loadFrom(r, url);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
errors.push(errMsg(err));
|
|
126
|
+
}
|
|
89
127
|
}
|
|
128
|
+
last = errors.join('; ');
|
|
90
129
|
}
|
|
91
130
|
throw new Error(
|
|
92
|
-
`xmtp remote attachment fetch failed after ${
|
|
131
|
+
`xmtp remote attachment fetch failed after ${retryDelaysMs.length + 1} attempts: ${last}`,
|
|
93
132
|
);
|
|
94
133
|
}
|
|
95
134
|
|
|
@@ -97,9 +136,10 @@ export async function saveRemoteAttachment(
|
|
|
97
136
|
r: RemoteEntry,
|
|
98
137
|
messageId: string,
|
|
99
138
|
index = 0,
|
|
139
|
+
retryDelaysMs: readonly number[] = READ_RETRY_DELAYS_MS,
|
|
100
140
|
): Promise<SavedAttachment> {
|
|
101
141
|
if (r.contentLength) assertAttachmentSize(r.contentLength);
|
|
102
|
-
const decoded = await loadRemote(r);
|
|
142
|
+
const decoded = await loadRemote(r, retryDelaysMs);
|
|
103
143
|
return saveBufferToCache(decoded.data, messageId, index, {
|
|
104
144
|
mime: decoded.mimeType,
|
|
105
145
|
name: decoded.filename ?? r.filename,
|
|
@@ -3,6 +3,7 @@ import type { Reply } from '@xmtp/content-type-reply';
|
|
|
3
3
|
import { errMsg } from '@metro-labs/core/log';
|
|
4
4
|
import { transcribeAndEmit } from './transcribe.js';
|
|
5
5
|
import {
|
|
6
|
+
INBOUND_RETRY_DELAYS_MS,
|
|
6
7
|
saveInlineAttachment,
|
|
7
8
|
saveRemoteAttachment,
|
|
8
9
|
type RemoteEntry,
|
|
@@ -207,7 +208,7 @@ function remoteStaticPayload(
|
|
|
207
208
|
ctx: EnvelopeCtx,
|
|
208
209
|
): Record<string, unknown> {
|
|
209
210
|
const kind = IMG_RE.test(c.url) ? 'image' : 'file';
|
|
210
|
-
reportAttachment(saveRemoteAttachment(c, ctx.msgId, 0), { station: 'xmtp', account: ctx.accountId, line: ctx.line, forId: ctx.baseId, index: 0 });
|
|
211
|
+
reportAttachment(saveRemoteAttachment(c, ctx.msgId, 0, INBOUND_RETRY_DELAYS_MS), { station: 'xmtp', account: ctx.accountId, line: ctx.line, forId: ctx.baseId, index: 0 });
|
|
211
212
|
return {
|
|
212
213
|
...base,
|
|
213
214
|
text: `[${kind}: ${c.filename ?? c.url}]`,
|
|
@@ -223,7 +224,7 @@ function multiRemoteEnvelope(
|
|
|
223
224
|
): Record<string, unknown> {
|
|
224
225
|
const m = c as { attachments?: RemoteEntry[] };
|
|
225
226
|
(Array.isArray(m.attachments) ? m.attachments : []).forEach((r, i) => {
|
|
226
|
-
reportAttachment(saveRemoteAttachment(r, ctx.msgId, i), { station: 'xmtp', account: ctx.accountId, line: ctx.line, forId: ctx.baseId, index: i });
|
|
227
|
+
reportAttachment(saveRemoteAttachment(r, ctx.msgId, i, INBOUND_RETRY_DELAYS_MS), { station: 'xmtp', account: ctx.accountId, line: ctx.line, forId: ctx.baseId, index: i });
|
|
227
228
|
});
|
|
228
229
|
return multiRemotePayload(base, typeId, c);
|
|
229
230
|
}
|
package/runtime/runtime.json
CHANGED