@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.
- package/dist/agent.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/lock.js +15 -3
- package/dist/root-release-io.js +1 -2
- package/dist/root-release.js +2 -3
- package/dist/route.js +1 -1
- package/dist/service.js +4 -4
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runtime/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +2 -8
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +0 -6
- package/runtime/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
- package/runtime/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
- package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +2 -6
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +6 -6
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +0 -24
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +3 -4
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +4 -9
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +1 -6
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +3 -2
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +3 -7
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +10 -19
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +3 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +1 -2
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +2 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +5 -6
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/server-approvals.ts +2 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +2 -4
- package/runtime/node_modules/@metro-labs/daemon/src/mcp-server/announced.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/tailscale-guard.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +1 -3
- package/runtime/node_modules/@metro-labs/daemon/src/routes/gmail-attach.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +6 -21
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +6 -6
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +12 -61
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +13 -25
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +5 -5
- package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +2 -7
- package/runtime/node_modules/@metro-labs/daemon/src/voice/call.ts +2 -6
- package/runtime/node_modules/@metro-labs/daemon/src/voice/calls.ts +12 -12
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +5 -23
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/discord-bot/src/channels.ts +2 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +10 -35
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +5 -11
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +6 -34
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +11 -14
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +1 -3
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +2 -3
- package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +3 -3
- package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +8 -8
- package/runtime/node_modules/@metro-labs/gmail/src/managed.ts +3 -2
- package/runtime/node_modules/@metro-labs/http/src/agent-signature.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +3 -4
- package/runtime/node_modules/@metro-labs/http/src/connector-discovery.ts +0 -113
- package/runtime/node_modules/@metro-labs/http/src/connector-input.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/connector-mcp.ts +12 -13
- package/runtime/node_modules/@metro-labs/http/src/connector-oauth.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/connector-policy.ts +1 -6
- package/runtime/node_modules/@metro-labs/http/src/connector-public-url.ts +4 -2
- package/runtime/node_modules/@metro-labs/http/src/connector-tools.ts +1 -11
- package/runtime/node_modules/@metro-labs/http/src/connector-url.ts +0 -6
- package/runtime/node_modules/@metro-labs/http/src/mcp-server-protocol.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/mcp-server-tools.ts +0 -2
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +1 -1
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +2 -2
- package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +6 -7
- package/runtime/node_modules/@metro-labs/inference/package.json +1 -0
- package/runtime/node_modules/@metro-labs/inference/src/attempts.ts +3 -3
- package/runtime/node_modules/@metro-labs/inference/src/bedrock.ts +1 -1
- package/runtime/node_modules/@metro-labs/inference/src/codex-translate.ts +2 -2
- package/runtime/node_modules/@metro-labs/inference/src/delivered.ts +1 -1
- package/runtime/node_modules/@metro-labs/inference/src/effort.ts +0 -6
- package/runtime/node_modules/@metro-labs/inference/src/fallback.ts +7 -5
- package/runtime/node_modules/@metro-labs/inference/src/forward.ts +5 -5
- package/runtime/node_modules/@metro-labs/inference/src/measure.ts +2 -3
- package/runtime/node_modules/@metro-labs/inference/src/named.ts +3 -3
- package/runtime/node_modules/@metro-labs/inference/src/probes.ts +98 -0
- package/runtime/node_modules/@metro-labs/inference/src/usage.ts +5 -5
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +6 -8
- package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +2 -1
- package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +3 -5
- package/runtime/node_modules/@metro-labs/outlook/src/me.ts +6 -6
- package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +10 -13
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +7 -26
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +3 -6
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +3 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +6 -20
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +0 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +1 -16
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -8
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +1 -8
- package/runtime/node_modules/@metro-labs/threema/src/chats.ts +4 -3
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +15 -17
- package/runtime/node_modules/@metro-labs/threema/src/groups.ts +2 -4
- package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/messages.ts +2 -1
- package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +7 -7
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +1 -3
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +0 -2
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +7 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +4 -10
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +14 -26
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +1 -7
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +17 -58
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +1 -6
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +10 -31
- package/runtime/node_modules/@metro-labs/xmtp/src/frames.ts +12 -14
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +3 -9
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +8 -9
- package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +4 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/network.ts +2 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +2 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/read-attachments.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +2 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +23 -46
- package/runtime/node_modules/@metro-labs/xmtp/src/wallet.ts +5 -4
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +12 -15
- package/runtime/runtime.json +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/lines.ts +2 -8
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/protocol.ts +0 -6
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/train-error.ts +4 -4
- package/runtime/sdk-runner/runner.json +1 -1
- package/runtime/sdk-runner/src/approvals.ts +2 -2
- package/runtime/sdk-runner/src/automation-command.ts +2 -2
- package/runtime/sdk-runner/src/calls.ts +0 -6
- package/runtime/sdk-runner/src/errors.ts +1 -1
- package/runtime/sdk-runner/src/link.ts +1 -1
- package/runtime/sdk-runner/src/runner.ts +2 -2
- package/runtime/sdk-runner/src/session-watch.ts +5 -34
- package/runtime/sdk-runner/src/task-state.ts +1 -1
- package/runtime/sdk-runner/src/tool-proxy.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +0 -40
|
@@ -11,7 +11,7 @@ import { isMetroNotice } from './notice.js';
|
|
|
11
11
|
import { replyMeta } from './addressed.js';
|
|
12
12
|
import { reactContent, reactionEmoji } from './react-note.js';
|
|
13
13
|
import {
|
|
14
|
-
|
|
14
|
+
capSize,
|
|
15
15
|
displayNameMeta,
|
|
16
16
|
missingAttachmentNames,
|
|
17
17
|
senderMeta,
|
|
@@ -76,7 +76,7 @@ export class InboundRelay {
|
|
|
76
76
|
noteSent(messageId: string): void {
|
|
77
77
|
if (!messageId) return;
|
|
78
78
|
this.sentIds.add(messageId);
|
|
79
|
-
|
|
79
|
+
capSize(this.sentIds, SENT_IDS_MAX);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
private notify(method: string, params: Record<string, unknown>): Promise<void> {
|
|
@@ -283,7 +283,7 @@ export class InboundRelay {
|
|
|
283
283
|
}
|
|
284
284
|
if (!base.line) return;
|
|
285
285
|
this.allowedLines.add(base.line);
|
|
286
|
-
|
|
286
|
+
capSize(this.allowedLines, ALLOWED_LINES_MAX);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
private droppedSender(ev: Record<string, unknown>, from: string, line: string): boolean {
|
|
@@ -36,9 +36,9 @@ export interface MediaCtx {
|
|
|
36
36
|
callMeta?: () => Record<string, string>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function
|
|
39
|
+
export function capSize(set: Set<string> | Map<string, unknown>, max: number): void {
|
|
40
40
|
while (set.size > max) {
|
|
41
|
-
const oldest = set.
|
|
41
|
+
const oldest = set.keys().next();
|
|
42
42
|
if (oldest.done) break;
|
|
43
43
|
set.delete(oldest.value);
|
|
44
44
|
}
|
|
@@ -152,7 +152,7 @@ function sessionOf(path: string, id: string, mtime: number, size: number): Claud
|
|
|
152
152
|
return {
|
|
153
153
|
id,
|
|
154
154
|
title: (titled === undefined ? null : str(titled.aiTitle)) ?? firstPrompt(head) ?? 'Untitled session',
|
|
155
|
-
startedAt: str(head.find((l) => str(l.timestamp) !== null)?.timestamp)
|
|
155
|
+
startedAt: str(head.find((l) => str(l.timestamp) !== null)?.timestamp),
|
|
156
156
|
lastAt: new Date(mtime).toISOString(),
|
|
157
157
|
bytes: size,
|
|
158
158
|
gitBranch: str(meta.gitBranch),
|
|
@@ -104,7 +104,7 @@ const skillTemplate = (name: string): string =>
|
|
|
104
104
|
['---', `name: ${name}`, 'description: what this skill does, and when Claude should reach for it', '---', '', `# ${name}`, '', 'Write the instructions here.', ''].join('\n');
|
|
105
105
|
|
|
106
106
|
export function createClaudeSkill(name: string, text: string | undefined, dir = claudeDir()): ClaudeSkill {
|
|
107
|
-
if (
|
|
107
|
+
if (!SKILL_NAME_RE.test(name))
|
|
108
108
|
throw new ApiError('a skill name is lowercase letters, digits and dashes, up to 64 characters', 400);
|
|
109
109
|
const path = join(userSkillsRoot(dir), name, FILE);
|
|
110
110
|
if (existsSync(path)) throw new ApiError('a skill by that name already lives there', 409);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import { ID_RE } from '@metro-labs/core/ids';
|
|
3
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
3
4
|
import { readFileSync } from 'node:fs';
|
|
4
5
|
import { resolveCachedAttachment } from '@metro-labs/core/stations/attachments';
|
|
5
6
|
import { writeSecure } from '@metro-labs/core/secure-fs';
|
|
@@ -23,8 +24,8 @@ function parseGrant(raw: string): AttachmentGrant | undefined {
|
|
|
23
24
|
} catch {
|
|
24
25
|
return undefined;
|
|
25
26
|
}
|
|
26
|
-
if (
|
|
27
|
-
const { token, agentId, mintedAt } = parsed
|
|
27
|
+
if (!isRecord(parsed)) return undefined;
|
|
28
|
+
const { token, agentId, mintedAt } = parsed;
|
|
28
29
|
if (typeof token !== 'string' || token === '') return undefined;
|
|
29
30
|
if (typeof agentId !== 'string' || !ID_RE.test(agentId)) return undefined;
|
|
30
31
|
return {
|
|
@@ -40,7 +40,7 @@ export function attachmentUrl(
|
|
|
40
40
|
pathOrName: string,
|
|
41
41
|
agentId: string,
|
|
42
42
|
): string | null {
|
|
43
|
-
const base =
|
|
43
|
+
const base = publicBaseOrDefault();
|
|
44
44
|
if (!base) return null;
|
|
45
45
|
const name = pathOrName.split('/').pop();
|
|
46
46
|
if (!name || !resolveCachedAttachment(name)) return null;
|
|
@@ -37,11 +37,8 @@ const VAGUE_MIMES = new Set([
|
|
|
37
37
|
|
|
38
38
|
const mib = (n: number): string => `${Math.round(n / (1024 * 1024))} MiB`;
|
|
39
39
|
|
|
40
|
-
const uploadLimit = (): string =>
|
|
41
|
-
`${mib(MAX_UPLOAD_BYTES)} (${MAX_UPLOAD_BYTES} bytes)`;
|
|
42
|
-
|
|
43
40
|
const tooLarge = (): string =>
|
|
44
|
-
`upload exceeds the ${
|
|
41
|
+
`upload exceeds the ${mib(MAX_UPLOAD_BYTES)} (${MAX_UPLOAD_BYTES} bytes) per-file limit; ` +
|
|
45
42
|
'send a link in `text` for anything larger';
|
|
46
43
|
|
|
47
44
|
const noRoom = (): string =>
|
|
@@ -96,9 +93,8 @@ function declaredLength(req: IncomingMessage): number | undefined {
|
|
|
96
93
|
|
|
97
94
|
function assertRoomForDeclared(req: IncomingMessage, room: number): void {
|
|
98
95
|
const declared = declaredLength(req);
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
if (declared > room) throw new ApiError(noRoom(), 507);
|
|
96
|
+
const over = declared === undefined ? undefined : overflowAt(declared, room);
|
|
97
|
+
if (over !== undefined) throw over;
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
async function drainInto(
|
|
@@ -43,7 +43,7 @@ function helpersBody(setup: ModelSetup): Record<string, unknown> | null {
|
|
|
43
43
|
const helpers = helperSetup(setup);
|
|
44
44
|
if (helpers === null) return null;
|
|
45
45
|
const primary = primaryRoute(helpers);
|
|
46
|
-
return { route: primary?.connection.id ?? '', model: primary?.model ?? '', fallbacks: helpers.fallbacks, chain: chainStatus(helpers) };
|
|
46
|
+
return { route: primary?.connection.id ?? '', model: primary?.model ?? '', fallbacks: helpers.fallbacks, chain: chainStatus(helpers, Date.now(), setupNotReady) };
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export function settingsBody(deps: ModelApiDeps): Record<string, unknown> {
|
|
@@ -59,7 +59,7 @@ export function settingsBody(deps: ModelApiDeps): Record<string, unknown> {
|
|
|
59
59
|
reason,
|
|
60
60
|
connections: setup.connections.map(publicConnection),
|
|
61
61
|
fallbacks: setup.fallbacks,
|
|
62
|
-
chain: chainStatus(setup),
|
|
62
|
+
chain: chainStatus(setup, Date.now(), setupNotReady),
|
|
63
63
|
helpers: helpersBody(setup),
|
|
64
64
|
effort: { ...EFFORT_DEFAULTS, ...setup.effort },
|
|
65
65
|
};
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { listEndpoints } from '../stations/webhook-endpoints.js';
|
|
8
8
|
import { hookUrl, threemaCallbackUrl } from '../stations/attach.js';
|
|
9
9
|
import { accountStatusOf } from '@metro-labs/core/stations/account-status';
|
|
10
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
10
11
|
import { accountEnabled, agentIdForAccount, allowlistForAccount, approversForAccount, knownAccounts, type KnownAccount } from '../agents/map.js';
|
|
11
12
|
|
|
12
13
|
const accountId = (acc: unknown): string | undefined => {
|
|
@@ -14,16 +15,9 @@ const accountId = (acc: unknown): string | undefined => {
|
|
|
14
15
|
return typeof id === 'string' ? id : undefined;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
const asRecord = (acc: unknown): Record<string, unknown> | undefined =>
|
|
18
|
-
typeof acc === 'object' && acc !== null && !Array.isArray(acc)
|
|
19
|
-
? (acc as Record<string, unknown>)
|
|
20
|
-
: undefined;
|
|
21
|
-
|
|
22
18
|
function withAgentId(station: string, acc: unknown): unknown {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const id = typeof rec.id === 'string' ? rec.id : undefined;
|
|
26
|
-
if (id === undefined) return acc;
|
|
19
|
+
if (!isRecord(acc) || typeof acc.id !== 'string') return acc;
|
|
20
|
+
const id = acc.id;
|
|
27
21
|
const agentId = agentIdForAccount(station, id);
|
|
28
22
|
if (agentId === undefined) return acc;
|
|
29
23
|
const allowlist = allowlistForAccount(station, id);
|
|
@@ -31,7 +25,7 @@ function withAgentId(station: string, acc: unknown): unknown {
|
|
|
31
25
|
const policy = stored === undefined ? {} : { policy: stored };
|
|
32
26
|
const approvers = approversForAccount(station, id);
|
|
33
27
|
const extra = approvers.length === 0 ? policy : { ...policy, approvers };
|
|
34
|
-
const tagged = allowlist === undefined ? { ...
|
|
28
|
+
const tagged = allowlist === undefined ? { ...acc, agentId, ...extra } : { ...acc, agentId, allowlist, ...extra };
|
|
35
29
|
return accountEnabled(station, id) ? tagged : { ...tagged, enabled: false };
|
|
36
30
|
}
|
|
37
31
|
|
|
@@ -79,17 +73,15 @@ function inCoreAccounts(station: string): unknown[] {
|
|
|
79
73
|
}
|
|
80
74
|
|
|
81
75
|
function withCallbackUrl(acc: unknown): unknown {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const { callbackId, callbackToken, ...rest } = rec;
|
|
76
|
+
if (!isRecord(acc)) return acc;
|
|
77
|
+
const { callbackId, callbackToken, ...rest } = acc;
|
|
85
78
|
if (typeof callbackId !== 'string' || typeof callbackToken !== 'string') return rest;
|
|
86
79
|
return { ...rest, callback: threemaCallbackUrl(callbackId, callbackToken) };
|
|
87
80
|
}
|
|
88
81
|
|
|
89
82
|
function withStatus(reports: boolean, acc: unknown): unknown {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const { status, ...rest } = rec;
|
|
83
|
+
if (!isRecord(acc) || !('status' in acc)) return acc;
|
|
84
|
+
const { status, ...rest } = acc;
|
|
93
85
|
const live = reports ? accountStatusOf(status) : null;
|
|
94
86
|
return live === null ? rest : { ...rest, status: live };
|
|
95
87
|
}
|
|
@@ -168,9 +160,8 @@ async function loadStations(): Promise<ScopedAccounts> {
|
|
|
168
160
|
const URL_SECRETS = new Set(['endpoint', 'callback']);
|
|
169
161
|
|
|
170
162
|
function withoutUrlSecrets(acc: unknown): unknown {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return Object.fromEntries(Object.entries(rec).filter(([key]) => !URL_SECRETS.has(key)));
|
|
163
|
+
if (!isRecord(acc)) return acc;
|
|
164
|
+
return Object.fromEntries(Object.entries(acc).filter(([key]) => !URL_SECRETS.has(key)));
|
|
174
165
|
}
|
|
175
166
|
|
|
176
167
|
export async function gatherAccounts(
|
|
@@ -139,10 +139,6 @@ function refusal(m: MessageArgs, text: string | undefined, requested: CanonicalA
|
|
|
139
139
|
return undefined;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
function noteSent(m: MessageArgs, messageId: string | undefined): void {
|
|
143
|
-
if (messageId !== undefined) m.onSent?.(messageId);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
142
|
async function handleSend(m: MessageArgs): Promise<ToolResult> {
|
|
147
143
|
if (m.a.speech !== undefined) return sendSpeech(m.line, m.a);
|
|
148
144
|
const text = m.a.text as string | undefined;
|
|
@@ -161,7 +157,7 @@ async function handleSend(m: MessageArgs): Promise<ToolResult> {
|
|
|
161
157
|
? await sendNative(m, text, replyTo, atts)
|
|
162
158
|
: await sendForwarded(m, text, replyTo, atts);
|
|
163
159
|
if (!sent.labels.length) return errResult(NOTHING_TO_SEND);
|
|
164
|
-
|
|
160
|
+
if (sent.messageId !== undefined) m.onSent?.(sent.messageId);
|
|
165
161
|
const done = withId(`sent: ${sent.labels.join(', ')}`, sent.messageId);
|
|
166
162
|
return ok(sent.thread === undefined ? done : `${done} — new thread line: ${sent.thread}`);
|
|
167
163
|
} finally {
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
6
6
|
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
7
7
|
import { str } from '@metro-labs/core/str';
|
|
8
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
8
9
|
import { eventInScope } from '../agents/scope.js';
|
|
9
10
|
import { CALL_NOTICE, CALL_STATE } from '@metro-labs/core/call';
|
|
10
11
|
|
|
@@ -28,10 +29,8 @@ const SEP = '_';
|
|
|
28
29
|
|
|
29
30
|
function frameLine(message: JSONRPCMessage): string | undefined {
|
|
30
31
|
const params: unknown = (message as { params?: unknown }).params;
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
if (typeof meta !== 'object' || meta === null) return undefined;
|
|
34
|
-
return str((meta as { line?: unknown }).line) || undefined;
|
|
32
|
+
if (!isRecord(params) || !isRecord(params.meta)) return undefined;
|
|
33
|
+
return str(params.meta.line) || undefined;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
const isCallNotice = (message: JSONRPCMessage): boolean => 'method' in message && (message.method === CALL_NOTICE || message.method === CALL_STATE);
|
|
@@ -65,9 +65,8 @@ function forwardArgs(a: Record<string, unknown>): Record<string, unknown> {
|
|
|
65
65
|
const args: Record<string, unknown> = {};
|
|
66
66
|
const members = strList(a.members);
|
|
67
67
|
if (members.length) args.members = members;
|
|
68
|
-
for (const key of ['name', 'account', 'parent', 'addresses', 'inboxIds'])
|
|
68
|
+
for (const key of ['name', 'account', 'parent', 'addresses', 'inboxIds', 'private'])
|
|
69
69
|
if (a[key] !== undefined) args[key] = a[key];
|
|
70
|
-
if (a.private !== undefined) args.private = a.private;
|
|
71
70
|
return args;
|
|
72
71
|
}
|
|
73
72
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
serveChannelGet,
|
|
16
16
|
} from './raw-get-stream.js';
|
|
17
17
|
import { errMsg } from '@metro-labs/core/log';
|
|
18
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
18
19
|
import { channelLog, type McpSession } from './session.js';
|
|
19
20
|
import { SessionSlot } from './session-slot.js';
|
|
20
21
|
import { connectCallBridge } from './call-bridge.js';
|
|
@@ -22,10 +23,7 @@ import { connectServerApprovals } from './server-approvals.js';
|
|
|
22
23
|
import { handleIncomingDiagnostic } from './diagnostics.js';
|
|
23
24
|
import { onConnectorToolsChanged } from '../mcp-server/connector-tools.js';
|
|
24
25
|
|
|
25
|
-
const isInitialize = (b: unknown): boolean =>
|
|
26
|
-
!!b &&
|
|
27
|
-
typeof b === 'object' &&
|
|
28
|
-
(b as { method?: string }).method === 'initialize';
|
|
26
|
+
const isInitialize = (b: unknown): boolean => isRecord(b) && b.method === 'initialize';
|
|
29
27
|
|
|
30
28
|
const MCP_BODY_MAX = 32 * 1024 * 1024;
|
|
31
29
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
1
2
|
import { str } from '@metro-labs/core/str';
|
|
2
3
|
import type { ToolResult } from '@metro-labs/core/stations/types';
|
|
3
4
|
import { accountFromLine, knownAccounts } from '../agents/map.js';
|
|
@@ -22,7 +23,7 @@ function channelTargets(name: string, a: Record<string, unknown>): ChannelTarget
|
|
|
22
23
|
if (UNGATED.has(name)) return [];
|
|
23
24
|
if (name === 'get_profile') {
|
|
24
25
|
const line = profileScopeLine(a);
|
|
25
|
-
return line === undefined ? [] : fromLine(line
|
|
26
|
+
return line === undefined ? [] : fromLine(line);
|
|
26
27
|
}
|
|
27
28
|
const account = str(a.account);
|
|
28
29
|
if (name === 'list_channels') return accountTargets(name, { account });
|
|
@@ -79,10 +80,8 @@ function effectivePolicy(target: ChannelTarget): EffectivePolicy {
|
|
|
79
80
|
export function withEffectivePolicy(byStation: Record<string, unknown[]>): Record<string, unknown[]> {
|
|
80
81
|
const out: Record<string, unknown[]> = {};
|
|
81
82
|
for (const [station, rows] of Object.entries(byStation))
|
|
82
|
-
out[station] = rows.map((row) =>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return typeof id === 'string' ? { ...row, policy: effectivePolicy({ kind: 'channel', station, account: id }) } : row;
|
|
86
|
-
});
|
|
83
|
+
out[station] = rows.map((row) =>
|
|
84
|
+
isRecord(row) && typeof row.id === 'string' ? { ...row, policy: effectivePolicy({ kind: 'channel', station, account: row.id }) } : row,
|
|
85
|
+
);
|
|
87
86
|
return out;
|
|
88
87
|
}
|
|
@@ -4,6 +4,7 @@ import type { McpApproval, McpDecision } from '@metro-labs/http/mcp-server-proto
|
|
|
4
4
|
import { holdPrompt, isPromptHeld, type Behavior } from '../approvals/pending.js';
|
|
5
5
|
import { ownerLine } from '../approvals/owner-line.js';
|
|
6
6
|
import { metroNotice } from '../channels/notice.js';
|
|
7
|
+
import { capSize } from '../channels/pending.js';
|
|
7
8
|
import { announcedLine, noteAnnounced } from '../mcp-server/announced.js';
|
|
8
9
|
import type { AnswerOutcome } from '../mcp-server/link.js';
|
|
9
10
|
import { serverPromptBody } from './permission-prompt.js';
|
|
@@ -32,11 +33,7 @@ const noticeLineFor = (line: string | undefined): string | undefined => line ??
|
|
|
32
33
|
function remember(id: string, now = Date.now()): void {
|
|
33
34
|
for (const [seen, at] of recent) if (now - at >= RECENT_MS) recent.delete(seen);
|
|
34
35
|
recent.set(id, now);
|
|
35
|
-
|
|
36
|
-
const oldest = recent.keys().next();
|
|
37
|
-
if (oldest.done) break;
|
|
38
|
-
recent.delete(oldest.value);
|
|
39
|
-
}
|
|
36
|
+
capSize(recent, RECENT_MAX);
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
const answeredRecently = (id: string, now = Date.now()): boolean => now - (recent.get(id) ?? -Infinity) < RECENT_MS;
|
|
@@ -197,7 +197,7 @@ export class McpSession {
|
|
|
197
197
|
if (sink !== undefined) {
|
|
198
198
|
this.lastStreamAttachedAt = new Date().toISOString();
|
|
199
199
|
this.announceCallState();
|
|
200
|
-
this.
|
|
200
|
+
this.toolsChanged();
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -216,10 +216,6 @@ export class McpSession {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
toolsChanged(): void {
|
|
219
|
-
this.announceToolSchema();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private announceToolSchema(): void {
|
|
223
219
|
if (!this.streamAttached) return;
|
|
224
220
|
this.deliverSchemaNotice(
|
|
225
221
|
() => this.server.sendToolListChanged(),
|
|
@@ -41,21 +41,19 @@ export const declaredGroup = (name: string): ToolGroup | undefined => BY_NAME.ge
|
|
|
41
41
|
|
|
42
42
|
export const toolGroupOf = (name: string): ToolGroup => declaredGroup(name) ?? 'write';
|
|
43
43
|
|
|
44
|
-
export type ToolAnnotations = Record<string, unknown>;
|
|
45
|
-
|
|
46
44
|
export interface PublishedTool {
|
|
47
45
|
name: string;
|
|
48
46
|
title?: string;
|
|
49
47
|
description: string;
|
|
50
48
|
inputSchema: Record<string, unknown>;
|
|
51
|
-
annotations:
|
|
49
|
+
annotations: Record<string, unknown>;
|
|
52
50
|
_meta?: Record<string, boolean>;
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
const ALWAYS_LOADED = new Set(['react', 'typing', 'send', 'reply']);
|
|
56
54
|
const ALWAYS_LOAD = { 'anthropic/alwaysLoad': true };
|
|
57
55
|
|
|
58
|
-
const annotationsOf = (def: ToolDef):
|
|
56
|
+
const annotationsOf = (def: ToolDef): Record<string, unknown> =>
|
|
59
57
|
toolGroupOf(def.name) === 'read'
|
|
60
58
|
? { readOnlyHint: true }
|
|
61
59
|
: { readOnlyHint: false, destructiveHint: def.destructive === true };
|
|
@@ -37,7 +37,7 @@ function prune(now: number): void {
|
|
|
37
37
|
|
|
38
38
|
function save(): void {
|
|
39
39
|
if (file === undefined) return;
|
|
40
|
-
const announced = Object.fromEntries(
|
|
40
|
+
const announced = Object.fromEntries(known);
|
|
41
41
|
try {
|
|
42
42
|
writeSecure(file, `${JSON.stringify({ version: 1, announced })}\n`);
|
|
43
43
|
} catch (err) {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
upgradeUnitText,
|
|
15
15
|
} from './helper-script.js';
|
|
16
16
|
import { AGENT_NAME } from '../agent-user/user.js';
|
|
17
|
+
import { hardenTailscale } from './tailscale-guard.js';
|
|
17
18
|
|
|
18
19
|
interface InstallPaths {
|
|
19
20
|
helper: string;
|
|
@@ -72,6 +73,7 @@ if (import.meta.main) {
|
|
|
72
73
|
installRootHelper();
|
|
73
74
|
installImdsGuard();
|
|
74
75
|
installUpgradeUnit(node);
|
|
76
|
+
for (const note of hardenTailscale()) process.stderr.write(`metro: ${note}\n`);
|
|
75
77
|
process.stdout.write(
|
|
76
78
|
`metro: wrote ${HELPER_PATH}, ${SUDOERS_PATH}, ${IMDS_UNIT_PATH} and ${UPGRADE_UNIT_PATH}; only root and metro reach the instance metadata service, and the root side follows every update\n`,
|
|
77
79
|
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export const TAILSCALED_DEFAULTS = '/etc/default/tailscaled';
|
|
5
|
+
const NO_FORWARDING = 'TS_SSH_DISABLE_FORWARDING=1';
|
|
6
|
+
|
|
7
|
+
type Run = (file: string, args: string[]) => boolean;
|
|
8
|
+
|
|
9
|
+
const quiet: Run = (file, args) => spawnSync(file, args, { stdio: 'ignore' }).status === 0;
|
|
10
|
+
|
|
11
|
+
export function withoutForwarding(text: string): string | null {
|
|
12
|
+
if (text.split('\n').some((line) => line.trim() === NO_FORWARDING)) return null;
|
|
13
|
+
return `${text}${text === '' || text.endsWith('\n') ? '' : '\n'}${NO_FORWARDING}\n`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function hardenTailscale(run: Run = quiet, path: string = TAILSCALED_DEFAULTS): string[] {
|
|
17
|
+
if (!run('tailscale', ['version'])) return ['tailscale is not installed here, nothing to harden'];
|
|
18
|
+
const notes: string[] = [];
|
|
19
|
+
const next = withoutForwarding(existsSync(path) ? readFileSync(path, 'utf8') : '');
|
|
20
|
+
if (next !== null) {
|
|
21
|
+
writeFileSync(path, next, { mode: 0o644 });
|
|
22
|
+
notes.push(run('systemctl', ['restart', 'tailscaled']) ? 'Tailscale SSH forwarding is off' : `wrote ${NO_FORWARDING}, but tailscaled did not restart; it applies at its next start`);
|
|
23
|
+
}
|
|
24
|
+
if (!run('tailscale', ['set', '--auto-update'])) notes.push('tailscale set --auto-update failed; update Tailscale by hand');
|
|
25
|
+
return notes;
|
|
26
|
+
}
|
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
|
|
11
11
|
const KEEPALIVE_MS = 25_000;
|
|
12
12
|
|
|
13
|
-
const monitorEnabled = (): boolean => hasAnyKey();
|
|
14
|
-
|
|
15
13
|
function cors(req: IncomingMessage): Record<string, string> {
|
|
16
14
|
return {
|
|
17
15
|
'access-control-allow-origin': req.headers.origin ?? '*',
|
|
@@ -104,7 +102,7 @@ function routeApi(
|
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
function preflight(req: IncomingMessage, res: ServerResponse): boolean {
|
|
107
|
-
if (!
|
|
105
|
+
if (!hasAnyKey()) {
|
|
108
106
|
sendJson(res, req, 404, { error: 'not found' });
|
|
109
107
|
return true;
|
|
110
108
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { ApiError } from '@metro-labs/http/api-error';
|
|
3
3
|
import { stringOf } from '@metro-labs/http/api-http';
|
|
4
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
4
5
|
import { publicBaseUrl } from '../files/attach-serve.js';
|
|
5
6
|
import { localOwner, readLocalAgentFile, localRenewGmail } from '../agents/file-admin.js';
|
|
6
7
|
import { startManagedGmail } from '../stations/attach-gmail.js';
|
|
@@ -57,9 +58,8 @@ export const startGmailAttach: StartAttach = async (_station, input, hooks, owne
|
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
export function finishGmailUpgrade(owner: AttachOwner, config: Record<string, unknown>): ReturnType<typeof localRenewGmail> | null {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
const upgrade = value as Record<string, unknown>;
|
|
61
|
+
const upgrade = config.gmailUpgrade;
|
|
62
|
+
if (!isRecord(upgrade)) return null;
|
|
63
63
|
assertGmailNotDeleting(owner.agentId, stringOf(upgrade.accountId));
|
|
64
64
|
const saved = { ...config };
|
|
65
65
|
delete saved.gmailUpgrade;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from 'node:http';
|
|
12
12
|
import { Line } from '@metro-labs/core/lines';
|
|
13
13
|
import { MCP_STATUS_PATH } from '../mcp/diagnostics.js';
|
|
14
|
+
import { capSize } from '../channels/pending.js';
|
|
14
15
|
import { errMsg, log } from '@metro-labs/core/log';
|
|
15
16
|
import {
|
|
16
17
|
classifyEvent,
|
|
@@ -50,7 +51,7 @@ export interface DedupSeq {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export function makeDedupSeq(): DedupSeq {
|
|
53
|
-
const seen = new
|
|
54
|
+
const seen = new Set<string>();
|
|
54
55
|
const seqByLine = new Map<string, number>();
|
|
55
56
|
|
|
56
57
|
log.info('dedup+seq: live-from-boot (no persisted seed)');
|
|
@@ -72,20 +73,12 @@ export function makeDedupSeq(): DedupSeq {
|
|
|
72
73
|
);
|
|
73
74
|
return null;
|
|
74
75
|
}
|
|
75
|
-
seen.
|
|
76
|
-
|
|
77
|
-
const oldest = seen.keys().next();
|
|
78
|
-
if (oldest.done) break;
|
|
79
|
-
seen.delete(oldest.value);
|
|
80
|
-
}
|
|
76
|
+
seen.add(key);
|
|
77
|
+
capSize(seen, LRU_CAP);
|
|
81
78
|
}
|
|
82
79
|
const next = (seqByLine.get(entry.line) ?? 0) + 1;
|
|
83
80
|
seqByLine.set(entry.line, next);
|
|
84
|
-
|
|
85
|
-
const oldest = seqByLine.keys().next();
|
|
86
|
-
if (oldest.done) break;
|
|
87
|
-
seqByLine.delete(oldest.value);
|
|
88
|
-
}
|
|
81
|
+
capSize(seqByLine, LRU_CAP);
|
|
89
82
|
return next;
|
|
90
83
|
},
|
|
91
84
|
};
|
|
@@ -318,18 +311,10 @@ async function handleWebhookRoute(
|
|
|
318
311
|
return true;
|
|
319
312
|
}
|
|
320
313
|
|
|
321
|
-
function handleSignInRoutes(
|
|
322
|
-
req: IncomingMessage,
|
|
323
|
-
res: ServerResponse,
|
|
324
|
-
apis: SessionApis,
|
|
325
|
-
): boolean {
|
|
326
|
-
return apis.mode !== undefined && handleModeRequest(req, res, apis.mode);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
314
|
function handleEarlyRoutes(req: IncomingMessage, res: ServerResponse, apis: SessionApis): boolean {
|
|
330
315
|
if (handleHealth(req, res)) return true;
|
|
331
316
|
if (apis.gateway?.(req, res) === true) return true;
|
|
332
|
-
if (
|
|
317
|
+
if (apis.mode !== undefined && handleModeRequest(req, res, apis.mode)) return true;
|
|
333
318
|
return handleSessionApis(req, res, apis);
|
|
334
319
|
}
|
|
335
320
|
|
|
@@ -115,10 +115,6 @@ function bundleApi(deps: LocalModeDeps): BundleApiDeps {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function localModeInfo(): ModeInfo {
|
|
119
|
-
return { mode: 'local', owner: localOwner(), version: METRO_VERSION };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
118
|
async function accountCall(
|
|
123
119
|
station: StationName,
|
|
124
120
|
action: string,
|
|
@@ -150,6 +146,6 @@ export function localSessionApis(deps: LocalModeDeps): SessionApis {
|
|
|
150
146
|
modelApi: {},
|
|
151
147
|
gateway: boxGatewayHandler(),
|
|
152
148
|
terminalApi: {},
|
|
153
|
-
mode:
|
|
149
|
+
mode: (): ModeInfo => ({ mode: 'local', owner: localOwner(), version: METRO_VERSION }),
|
|
154
150
|
};
|
|
155
151
|
}
|
|
@@ -64,7 +64,7 @@ async function fromPathAs(
|
|
|
64
64
|
path: string,
|
|
65
65
|
): Promise<ResolvedAttachment> {
|
|
66
66
|
const proc = Bun.spawn(agentCommand(['cat', '--', path]), { stdout: 'pipe', stderr: 'pipe' });
|
|
67
|
-
const name = a.name ?? basenameOf(path)
|
|
67
|
+
const name = a.name ?? basenameOf(path);
|
|
68
68
|
let saved: Awaited<ReturnType<typeof streamToTemp>>;
|
|
69
69
|
try {
|
|
70
70
|
saved = await streamToTemp(proc.stdout, name);
|
|
@@ -101,7 +101,7 @@ async function fromPath(
|
|
|
101
101
|
return {
|
|
102
102
|
path,
|
|
103
103
|
mime: a.mime ?? guessMime(path),
|
|
104
|
-
name: a.name ?? basenameOf(path)
|
|
104
|
+
name: a.name ?? basenameOf(path),
|
|
105
105
|
bytes: file.size,
|
|
106
106
|
};
|
|
107
107
|
}
|
|
@@ -129,7 +129,7 @@ async function fromUrlAs(
|
|
|
129
129
|
url: string,
|
|
130
130
|
): Promise<ResolvedAttachment> {
|
|
131
131
|
const proc = Bun.spawn(agentCommand(curlArgs(url)), { stdout: 'pipe', stderr: 'pipe' });
|
|
132
|
-
const name = a.name ?? basenameOf(url)
|
|
132
|
+
const name = a.name ?? basenameOf(url);
|
|
133
133
|
const said = new Response(proc.stderr).text().catch((err: unknown) => {
|
|
134
134
|
log.debug({ err: String(err) }, 'send: could not read what curl said');
|
|
135
135
|
return '';
|
|
@@ -167,7 +167,7 @@ async function fromUrl(
|
|
|
167
167
|
assertContentLength(res.headers.get('content-length'));
|
|
168
168
|
const mime =
|
|
169
169
|
a.mime ?? res.headers.get('content-type')?.split(';')[0] ?? guessMime(url);
|
|
170
|
-
const name = a.name ?? basenameOf(url)
|
|
170
|
+
const name = a.name ?? basenameOf(url);
|
|
171
171
|
if (res.body === null) throw new Error(`attachment fetch returned no body for '${url}'`);
|
|
172
172
|
const { dir, path, bytes } = await streamToTemp(res.body, name);
|
|
173
173
|
return { path, mime, name, bytes, temp: dir };
|
|
@@ -223,8 +223,8 @@ const sourcesOf = (a: CanonicalAttachment): string[] =>
|
|
|
223
223
|
|
|
224
224
|
async function resolveAttachment(
|
|
225
225
|
a: CanonicalAttachment,
|
|
226
|
-
budget: InlineBudget
|
|
227
|
-
opts: ResolveOptions
|
|
226
|
+
budget: InlineBudget,
|
|
227
|
+
opts: ResolveOptions,
|
|
228
228
|
): Promise<ResolvedAttachment> {
|
|
229
229
|
const sources = sourcesOf(a);
|
|
230
230
|
if (sources.length === 0)
|