@stage-labs/metro 0.1.0-beta.8 → 0.1.0-beta.80
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/api.js +14 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +107 -0
- package/dist/cli.js +101 -0
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +93 -0
- package/dist/node-name.js +71 -0
- package/dist/plugin.js +80 -0
- package/dist/provider-flags.js +9 -0
- package/dist/runtime-install.js +87 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +146 -0
- package/dist/service.js +242 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -25
- package/runtime/node_modules/@metro-labs/core/package.json +43 -0
- package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +82 -0
- package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +12 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +107 -0
- package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +311 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +302 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +329 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +84 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +176 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +148 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +329 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +167 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +307 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +144 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +117 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +157 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +127 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +295 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +277 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +330 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +252 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +220 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +182 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +197 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +195 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +198 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +40 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +137 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +390 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +187 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +184 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +248 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +35 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +71 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/http/package.json +33 -0
- package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
- package/runtime/runtime.json +3 -0
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +36 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -249
- package/dist/cache.js +0 -75
- package/dist/cli/actions.js +0 -139
- package/dist/cli/config.js +0 -185
- package/dist/cli/index.js +0 -177
- package/dist/cli/skill.js +0 -62
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -83
- package/dist/codex-rc.js +0 -245
- package/dist/dispatcher.js +0 -122
- package/dist/history.js +0 -99
- package/dist/ipc.js +0 -72
- package/dist/log.js +0 -15
- package/dist/paths.js +0 -68
- package/dist/registry.js +0 -48
- package/dist/stations/claude.js +0 -45
- package/dist/stations/codex.js +0 -68
- package/dist/stations/discord.js +0 -200
- package/dist/stations/index.js +0 -133
- package/dist/stations/telegram-md.js +0 -34
- package/dist/stations/telegram-upload.js +0 -113
- package/dist/stations/telegram.js +0 -213
- package/dist/stations/webhook.js +0 -100
- package/dist/tunnel.js +0 -49
- package/dist/webhooks.js +0 -40
- package/docs/agents.md +0 -216
- package/docs/uri-scheme.md +0 -125
- package/skills/metro/SKILL.md +0 -249
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type AgentMap = Record<string, string>;
|
|
2
|
+
export type AgentNameMap = Record<string, string>;
|
|
3
|
+
export type AllowlistMap = Record<string, string[]>;
|
|
4
|
+
|
|
5
|
+
const mapKey = (station: string, accountId: string): string =>
|
|
6
|
+
`${station}/${accountId}`;
|
|
7
|
+
|
|
8
|
+
let agentMap: AgentMap = {};
|
|
9
|
+
let agentNames: AgentNameMap = {};
|
|
10
|
+
let allowlistMap: AllowlistMap = {};
|
|
11
|
+
|
|
12
|
+
export function setAgentMap(map: AgentMap, names: AgentNameMap): void {
|
|
13
|
+
agentMap = map;
|
|
14
|
+
agentNames = names;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function setAllowlistMap(map: AllowlistMap): void {
|
|
18
|
+
allowlistMap = map;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function accountFromLine(
|
|
22
|
+
line: string,
|
|
23
|
+
): { station: string; accountId: string } | undefined {
|
|
24
|
+
const parts = line.split('/');
|
|
25
|
+
const station = parts[2];
|
|
26
|
+
const accountId = parts[3];
|
|
27
|
+
if (!station || !accountId) return undefined;
|
|
28
|
+
return { station, accountId };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function agentIdForAccount(
|
|
32
|
+
station: string,
|
|
33
|
+
accountId: string,
|
|
34
|
+
): string | undefined {
|
|
35
|
+
return agentMap[mapKey(station, accountId)];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function agentIdForLine(line: string): string | undefined {
|
|
39
|
+
const a = accountFromLine(line);
|
|
40
|
+
return a ? agentIdForAccount(a.station, a.accountId) : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function agentForLine(line: string): string | undefined {
|
|
44
|
+
const id = agentIdForLine(line);
|
|
45
|
+
return id === undefined ? undefined : agentNames[id];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface KnownAccount {
|
|
49
|
+
station: string;
|
|
50
|
+
id: string;
|
|
51
|
+
agentId: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function knownAccounts(): KnownAccount[] {
|
|
55
|
+
return Object.entries(agentMap).flatMap(([key, agentId]) => {
|
|
56
|
+
const cut = key.indexOf('/');
|
|
57
|
+
return cut === -1 ? [] : [{ station: key.slice(0, cut), id: key.slice(cut + 1), agentId }];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function stationAgentIds(station: string): string[] {
|
|
62
|
+
const prefix = `${station}/`;
|
|
63
|
+
return Object.entries(agentMap)
|
|
64
|
+
.filter(([key]) => key.startsWith(prefix))
|
|
65
|
+
.map(([, id]) => id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function allowlistForLine(line: string): string[] | undefined {
|
|
69
|
+
const a = accountFromLine(line);
|
|
70
|
+
return a ? allowlistMap[mapKey(a.station, a.accountId)] : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function senderMatchesAllowlist(
|
|
74
|
+
allowlist: string[],
|
|
75
|
+
from: string,
|
|
76
|
+
): boolean {
|
|
77
|
+
if (allowlist.length === 0 || allowlist.includes('*')) return true;
|
|
78
|
+
const f = (from ?? '').toLowerCase();
|
|
79
|
+
const id = f.split('/').pop() ?? f;
|
|
80
|
+
return allowlist.some((a) => {
|
|
81
|
+
const v = a.toLowerCase();
|
|
82
|
+
return v === f || v === id;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
accountFromLine,
|
|
3
|
+
agentIdForAccount,
|
|
4
|
+
stationAgentIds,
|
|
5
|
+
} from './map.js';
|
|
6
|
+
import { STATIONS } from '@metro-labs/core/station-names';
|
|
7
|
+
|
|
8
|
+
const ACCOUNT_STATIONS = new Set<string>(STATIONS);
|
|
9
|
+
|
|
10
|
+
const stationHasAccounts = (station: string): boolean =>
|
|
11
|
+
ACCOUNT_STATIONS.has(station);
|
|
12
|
+
|
|
13
|
+
const accountInScope = (
|
|
14
|
+
allowed: Set<string>,
|
|
15
|
+
station: string,
|
|
16
|
+
accountId: string,
|
|
17
|
+
): boolean => {
|
|
18
|
+
const agentId = agentIdForAccount(station, accountId);
|
|
19
|
+
return agentId !== undefined && allowed.has(agentId);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function lineTargetDenied(
|
|
23
|
+
allowed: Set<string>,
|
|
24
|
+
args: Record<string, unknown>,
|
|
25
|
+
station?: string,
|
|
26
|
+
): boolean {
|
|
27
|
+
const line = typeof args.line === 'string' ? args.line : undefined;
|
|
28
|
+
if (line === undefined) return false;
|
|
29
|
+
const acct = accountFromLine(line);
|
|
30
|
+
if (!acct) return true;
|
|
31
|
+
if (station !== undefined && acct.station !== station) return true;
|
|
32
|
+
if (!accountInScope(allowed, acct.station, acct.accountId)) return true;
|
|
33
|
+
const override = typeof args.account === 'string' ? args.account : undefined;
|
|
34
|
+
return override !== undefined
|
|
35
|
+
? !accountInScope(allowed, acct.station, override)
|
|
36
|
+
: false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function stationFullyScoped(
|
|
40
|
+
allowed: Set<string>,
|
|
41
|
+
station: string,
|
|
42
|
+
): boolean {
|
|
43
|
+
const ids = stationAgentIds(station);
|
|
44
|
+
return ids.length > 0 && ids.every((id) => allowed.has(id));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function callTargetDenied(
|
|
48
|
+
allowed: Set<string>,
|
|
49
|
+
station: string,
|
|
50
|
+
args: Record<string, unknown>,
|
|
51
|
+
): boolean {
|
|
52
|
+
if (typeof args.line === 'string')
|
|
53
|
+
return lineTargetDenied(allowed, args, station);
|
|
54
|
+
if (typeof args.account === 'string')
|
|
55
|
+
return !accountInScope(allowed, station, args.account);
|
|
56
|
+
return !stationFullyScoped(allowed, station);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function eventInScope(allowed: Set<string>, line: string): boolean {
|
|
60
|
+
const acct = accountFromLine(line);
|
|
61
|
+
if (!acct) return false;
|
|
62
|
+
if (!stationHasAccounts(acct.station)) return true;
|
|
63
|
+
return accountInScope(allowed, acct.station, acct.accountId);
|
|
64
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { type Server } from 'node:http';
|
|
3
|
+
import { userSelf } from '@metro-labs/core/events';
|
|
4
|
+
import { setTrainCallBackend } from '../stations/train-call.js';
|
|
5
|
+
import { errMsg, log, logFatalSync } from '@metro-labs/core/log';
|
|
6
|
+
import { acquireLock, STATE_DIR, trainsDir } from './paths.js';
|
|
7
|
+
import { installCrashGuard, markDaemonReady } from './crash-guard.js';
|
|
8
|
+
import { METRO_VERSION } from '@metro-labs/core/version';
|
|
9
|
+
import { funnelDriver, Tunnel, tunnelWanted, webhookPort } from '../net/tunnel.js';
|
|
10
|
+
import {
|
|
11
|
+
localConnectHint,
|
|
12
|
+
publicConnectHint,
|
|
13
|
+
tunnelPendingHint,
|
|
14
|
+
} from './connect-hint.js';
|
|
15
|
+
import { TrainSupervisor } from '../stations/supervisor.js';
|
|
16
|
+
import {
|
|
17
|
+
makeEmit,
|
|
18
|
+
startWebhookServer,
|
|
19
|
+
trainEventToMetroEvent,
|
|
20
|
+
} from '../routes/http.js';
|
|
21
|
+
import { localAgentKey } from '../stations/materialize.js';
|
|
22
|
+
import { fileSource } from '../agents/files.js';
|
|
23
|
+
import { applyLocalOwner } from './local-owner.js';
|
|
24
|
+
import { localOwner } from '../agents/file-admin.js';
|
|
25
|
+
import { ensureStationDeps } from '../stations/runtime-deps.js';
|
|
26
|
+
import { localSessionApis } from '../routes/local-mode.js';
|
|
27
|
+
import type { SessionApis } from '../routes/session-apis.js';
|
|
28
|
+
import {
|
|
29
|
+
agentLiveness,
|
|
30
|
+
closeAgentSession,
|
|
31
|
+
createMetroMcp,
|
|
32
|
+
} from '../mcp/index.js';
|
|
33
|
+
import { metroCall } from '../mcp/ctx.js';
|
|
34
|
+
import { gatherAccountsForAgents } from '../mcp/accounts.js';
|
|
35
|
+
import {
|
|
36
|
+
accountStationCapabilities,
|
|
37
|
+
stationByName,
|
|
38
|
+
} from '../stations/registry.js';
|
|
39
|
+
import { prepareAccount } from '../stations/attach.js';
|
|
40
|
+
import { materializeFrom, reloadFrom } from '../stations/materialize.js';
|
|
41
|
+
import type { StationName } from '@metro-labs/core/station-names';
|
|
42
|
+
import { startUploadReaper } from '../files/upload-store.js';
|
|
43
|
+
|
|
44
|
+
installCrashGuard();
|
|
45
|
+
acquireLock(join(STATE_DIR, '.tail-lock'));
|
|
46
|
+
|
|
47
|
+
log.info({ self: userSelf() }, 'user identity');
|
|
48
|
+
|
|
49
|
+
process.stdout.on('error', (err) => {
|
|
50
|
+
if ((err as NodeJS.ErrnoException).code !== 'EPIPE')
|
|
51
|
+
log.warn({ err: errMsg(err) }, 'stdout error');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const supervisor = new TrainSupervisor();
|
|
55
|
+
const emit = makeEmit();
|
|
56
|
+
|
|
57
|
+
supervisor.onTrainEvent((env, train) => {
|
|
58
|
+
const entry = trainEventToMetroEvent(env, train);
|
|
59
|
+
if (entry) emit(entry);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let webhookServer: Server | null = null;
|
|
63
|
+
const tunnel = tunnelWanted() ? new Tunnel(funnelDriver(webhookPort()), announcePublic) : null;
|
|
64
|
+
|
|
65
|
+
function announcePublic(url: string): void {
|
|
66
|
+
process.stderr.write(`\n${publicConnectHint(url, localOwner())}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setTrainCallBackend((train, action, args) =>
|
|
70
|
+
supervisor.call(train, action, args),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
function announceLocalEndpoint(): void {
|
|
74
|
+
process.stderr.write(
|
|
75
|
+
`\n${tunnel === null ? localConnectHint(webhookPort(), localOwner()) : tunnelPendingHint()}`,
|
|
76
|
+
);
|
|
77
|
+
const key = localAgentKey();
|
|
78
|
+
if (key === null) {
|
|
79
|
+
log.info('no agent on this machine yet');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const url = `http://127.0.0.1:${String(webhookPort())}/mcp?token=${key}`;
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`\nConnect an agent on this machine:\n\n claude mcp add --transport http metro "${url}"\n\n`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function syncStations(station: StationName): Promise<void> {
|
|
89
|
+
const { removed } = await reloadFrom(fileSource);
|
|
90
|
+
if (stationByName(station)?.hasTrain === false) return;
|
|
91
|
+
if (removed.includes(station)) await supervisor.stopTrain(station);
|
|
92
|
+
else {
|
|
93
|
+
ensureStationDeps(station);
|
|
94
|
+
supervisor.requestReload(station);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sessionApis(): SessionApis {
|
|
99
|
+
return localSessionApis({
|
|
100
|
+
syncStations,
|
|
101
|
+
restart: () => {
|
|
102
|
+
exitCode = RESTART_EXIT;
|
|
103
|
+
onShutdown();
|
|
104
|
+
},
|
|
105
|
+
stop: () => {
|
|
106
|
+
exitCode = HOLD_EXIT;
|
|
107
|
+
onShutdown();
|
|
108
|
+
},
|
|
109
|
+
closeAgentSession,
|
|
110
|
+
gatherAccounts: gatherAccountsForAgents,
|
|
111
|
+
capabilities: accountStationCapabilities,
|
|
112
|
+
liveness: agentLiveness,
|
|
113
|
+
prepareAccount,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function main(): Promise<void> {
|
|
118
|
+
applyLocalOwner();
|
|
119
|
+
await materializeFrom(fileSource, { allowEmpty: true });
|
|
120
|
+
supervisor.start();
|
|
121
|
+
const metroMcp = await createMetroMcp();
|
|
122
|
+
webhookServer = await startWebhookServer(
|
|
123
|
+
emit,
|
|
124
|
+
sessionApis(),
|
|
125
|
+
metroMcp.httpHandler,
|
|
126
|
+
metroCall,
|
|
127
|
+
);
|
|
128
|
+
metroMcp.startInbound();
|
|
129
|
+
startUploadReaper();
|
|
130
|
+
announceLocalEndpoint();
|
|
131
|
+
tunnel?.start();
|
|
132
|
+
log.info(
|
|
133
|
+
{ tunnel: !!tunnel, trainsDir: trainsDir(), mcp: '/', version: METRO_VERSION },
|
|
134
|
+
'dispatcher ready',
|
|
135
|
+
);
|
|
136
|
+
markDaemonReady();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const SHUTDOWN_TIMEOUT_MS = 3_000;
|
|
140
|
+
const RESTART_EXIT = 75;
|
|
141
|
+
const HOLD_EXIT = 76;
|
|
142
|
+
let exitCode = 0;
|
|
143
|
+
let shuttingDown = false;
|
|
144
|
+
async function shutdown(): Promise<void> {
|
|
145
|
+
if (shuttingDown) return;
|
|
146
|
+
shuttingDown = true;
|
|
147
|
+
log.info('dispatcher shutting down');
|
|
148
|
+
tunnel?.stop();
|
|
149
|
+
if (webhookServer) {
|
|
150
|
+
const server = webhookServer;
|
|
151
|
+
await Promise.race([
|
|
152
|
+
new Promise<void>((r) => {
|
|
153
|
+
server.close(() => {
|
|
154
|
+
r();
|
|
155
|
+
});
|
|
156
|
+
}),
|
|
157
|
+
new Promise<void>((r) => {
|
|
158
|
+
setTimeout(r, SHUTDOWN_TIMEOUT_MS).unref();
|
|
159
|
+
}),
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
await supervisor.stop();
|
|
163
|
+
process.exit(exitCode);
|
|
164
|
+
}
|
|
165
|
+
const onShutdown = (): void => {
|
|
166
|
+
shutdown().catch((err: unknown) => {
|
|
167
|
+
log.error({ err: errMsg(err) }, 'dispatcher: shutdown failed');
|
|
168
|
+
process.exit(1);
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
for (const sig of ['SIGINT', 'SIGTERM'] as const) process.on(sig, onShutdown);
|
|
172
|
+
|
|
173
|
+
await main().catch((err: unknown) => {
|
|
174
|
+
logFatalSync({ err: errMsg(err) }, 'dispatcher failed to start');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { hostname } from 'node:os';
|
|
2
|
+
|
|
3
|
+
const uiBase = (): string => process.env.METRO_UI_URL ?? 'https://metro.box';
|
|
4
|
+
|
|
5
|
+
const connectLink = (base: string): string =>
|
|
6
|
+
`${uiBase()}/#/${new URL(base).host}`;
|
|
7
|
+
|
|
8
|
+
const ownerLine = (owner: string | null): string =>
|
|
9
|
+
owner === null
|
|
10
|
+
? 'No owner is set, so no wallet can sign in. Restart with: metro serve --owner <address>'
|
|
11
|
+
: `Only ${owner} can sign in.`;
|
|
12
|
+
|
|
13
|
+
export function localConnectHint(port: number, owner: string | null): string {
|
|
14
|
+
const here = `http://127.0.0.1:${String(port)}`;
|
|
15
|
+
const forward = `ssh -L ${String(port)}:127.0.0.1:${String(port)} ${hostname()}`;
|
|
16
|
+
return [
|
|
17
|
+
'Manage this machine from the web UI:',
|
|
18
|
+
'',
|
|
19
|
+
` ${connectLink(here)}`,
|
|
20
|
+
'',
|
|
21
|
+
`From another computer, forward the port first: ${forward}`,
|
|
22
|
+
ownerLine(owner),
|
|
23
|
+
'',
|
|
24
|
+
].join('\n');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function publicConnectHint(url: string, owner: string | null): string {
|
|
28
|
+
return [
|
|
29
|
+
'Manage this machine from the web UI, from anywhere:',
|
|
30
|
+
'',
|
|
31
|
+
` ${connectLink(url)}`,
|
|
32
|
+
'',
|
|
33
|
+
ownerLine(owner),
|
|
34
|
+
'',
|
|
35
|
+
].join('\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const tunnelPendingHint = (): string =>
|
|
39
|
+
'Publishing this daemon through Tailscale Funnel… (if Tailscale asks to enable Funnel, its link is in the log)\n';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { errMsg, log, logFatalSync } from '@metro-labs/core/log';
|
|
2
|
+
|
|
3
|
+
export type CrashKind = 'unhandledRejection' | 'uncaughtException';
|
|
4
|
+
|
|
5
|
+
const seen: Record<CrashKind, number> = {
|
|
6
|
+
unhandledRejection: 0,
|
|
7
|
+
uncaughtException: 0,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let ready = false;
|
|
11
|
+
let installed = false;
|
|
12
|
+
|
|
13
|
+
export function markDaemonReady(): void {
|
|
14
|
+
ready = true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function crashCount(kind: CrashKind): number {
|
|
18
|
+
return seen[kind];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fields(kind: CrashKind, err: unknown): Record<string, unknown> {
|
|
22
|
+
return {
|
|
23
|
+
crash: kind,
|
|
24
|
+
seen: seen[kind],
|
|
25
|
+
err: errMsg(err),
|
|
26
|
+
errType: err instanceof Error ? err.name : typeof err,
|
|
27
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function reportCrash(kind: CrashKind, err: unknown): void {
|
|
32
|
+
seen[kind] += 1;
|
|
33
|
+
if (!ready) {
|
|
34
|
+
logFatalSync(fields(kind, err), 'daemon: failed before it was ready');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
log.error(fields(kind, err), 'daemon: kept running through a crash');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function installCrashGuard(): void {
|
|
41
|
+
if (installed) return;
|
|
42
|
+
installed = true;
|
|
43
|
+
process.on('unhandledRejection', (reason: unknown) => {
|
|
44
|
+
reportCrash('unhandledRejection', reason);
|
|
45
|
+
});
|
|
46
|
+
process.on('uncaughtException', (err: unknown) => {
|
|
47
|
+
reportCrash('uncaughtException', err);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { localOwner, setLocalOwner } from '../agents/file-admin.js';
|
|
2
|
+
import { log } from '@metro-labs/core/log';
|
|
3
|
+
|
|
4
|
+
export function applyLocalOwner(): string | null {
|
|
5
|
+
const wanted = process.env.METRO_OWNER?.trim() ?? '';
|
|
6
|
+
if (wanted !== '') {
|
|
7
|
+
const address = setLocalOwner(wanted);
|
|
8
|
+
log.info({ owner: address }, 'local daemon: owner set from METRO_OWNER');
|
|
9
|
+
return address;
|
|
10
|
+
}
|
|
11
|
+
const stored = localOwner();
|
|
12
|
+
if (stored === null) log.warn('local daemon: no owner set, so no wallet can sign in; restart with metro serve --owner <address>');
|
|
13
|
+
return stored;
|
|
14
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
openSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
writeSync,
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { log } from '@metro-labs/core/log';
|
|
14
|
+
|
|
15
|
+
export const STATE_DIR =
|
|
16
|
+
process.env.METRO_STATE_DIR ?? join(homedir(), '.cache', 'metro');
|
|
17
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export function trainsDir(): string {
|
|
21
|
+
const env = process.env.METRO_TRAINS_DIR?.trim();
|
|
22
|
+
if (env !== undefined && env !== '') return env;
|
|
23
|
+
return fileURLToPath(new URL('../../trains', import.meta.url));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readLockPid(lockFile: string): number {
|
|
27
|
+
try {
|
|
28
|
+
return Number(readFileSync(lockFile, 'utf8').trim());
|
|
29
|
+
} catch {
|
|
30
|
+
return NaN;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function namesMetro(text: string): boolean {
|
|
35
|
+
return text.includes('server.ts') || text.includes('metro');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function holderIsMetro(pid: number): boolean {
|
|
39
|
+
try {
|
|
40
|
+
return namesMetro(readFileSync(`/proc/${pid}/cmdline`, 'utf8'));
|
|
41
|
+
} catch {
|
|
42
|
+
return holderCommand(pid);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function holderCommand(pid: number): boolean {
|
|
47
|
+
try {
|
|
48
|
+
return namesMetro(
|
|
49
|
+
execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function lockHeldByLiveDaemon(pid: number): boolean {
|
|
60
|
+
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 0);
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return holderIsMetro(pid);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writeOwnLock(lockFile: string): void {
|
|
70
|
+
const fd = openSync(lockFile, 'wx');
|
|
71
|
+
writeSync(fd, String(process.pid));
|
|
72
|
+
closeSync(fd);
|
|
73
|
+
process.on('exit', () => {
|
|
74
|
+
try {
|
|
75
|
+
if (readFileSync(lockFile, 'utf8').trim() === String(process.pid))
|
|
76
|
+
unlinkSync(lockFile);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function acquireLock(lockFile: string): void {
|
|
83
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
84
|
+
try {
|
|
85
|
+
writeOwnLock(lockFile);
|
|
86
|
+
return;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;
|
|
89
|
+
const pid = readLockPid(lockFile);
|
|
90
|
+
if (lockHeldByLiveDaemon(pid)) {
|
|
91
|
+
log.info(
|
|
92
|
+
{ pid },
|
|
93
|
+
'a healthy `metro` daemon is already running; exiting (no second dispatcher)',
|
|
94
|
+
);
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
unlinkSync(lockFile);
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new Error(
|
|
104
|
+
`metro: could not acquire dispatcher lock (${lockFile}) after retries`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { str } from '@metro-labs/core/str';
|
|
2
|
+
|
|
3
|
+
export type Addressed = 'direct' | 'mention' | 'reply';
|
|
4
|
+
|
|
5
|
+
interface AddressFacts {
|
|
6
|
+
isPrivate?: unknown;
|
|
7
|
+
mentionsSelf?: unknown;
|
|
8
|
+
replyToSelf?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function addressedBy(facts: AddressFacts, replyTo: string, sent: ReadonlySet<string>): Addressed | undefined {
|
|
12
|
+
if (facts.isPrivate === true) return 'direct';
|
|
13
|
+
if (facts.mentionsSelf === true) return 'mention';
|
|
14
|
+
if (facts.replyToSelf === true || (replyTo !== '' && sent.has(replyTo))) return 'reply';
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function replyMeta(ev: Record<string, unknown>, sent: ReadonlySet<string>): Record<string, string> {
|
|
19
|
+
const replyTo = str(ev.replyTo);
|
|
20
|
+
const addressed = addressedBy(ev, replyTo, sent);
|
|
21
|
+
return {
|
|
22
|
+
...(replyTo ? { reply_to: replyTo } : {}),
|
|
23
|
+
...(addressed === undefined ? {} : { addressed }),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { accountFromLine, agentIdForAccount } from '../agents/map.js';
|
|
2
|
+
|
|
3
|
+
const accountStrippedLine = (line: string): string => {
|
|
4
|
+
const parts = line.split('/');
|
|
5
|
+
if (parts.length < 5) return line;
|
|
6
|
+
return [parts[0], parts[1], parts[2], ...parts.slice(4)].join('/');
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const dedupeOwner = (line: string): string => {
|
|
10
|
+
const acct = accountFromLine(line);
|
|
11
|
+
if (!acct) return 'unowned';
|
|
12
|
+
const agentId = agentIdForAccount(acct.station, acct.accountId);
|
|
13
|
+
return agentId === undefined
|
|
14
|
+
? `account:${acct.accountId}`
|
|
15
|
+
: `agent:${agentId}`;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const dedupeKey = (
|
|
19
|
+
station: string,
|
|
20
|
+
line: string,
|
|
21
|
+
kind: string,
|
|
22
|
+
messageId: string,
|
|
23
|
+
): string =>
|
|
24
|
+
`${dedupeOwner(line)} ${station} ${accountStrippedLine(line)} ${kind} ${messageId}`;
|