@stage-labs/metro 0.1.0-beta.325 → 0.1.0-beta.327
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 +2 -2
- package/runtime/node_modules/@metro-labs/core/package.json +3 -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/brands.ts +100 -0
- package/runtime/node_modules/@metro-labs/core/src/favicon.ts +38 -0
- 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 +6 -6
- 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/package.json +1 -1
- 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/github-fetch.ts +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/claude/github-skills.ts +2 -2
- 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/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/package.json +1 -1
- 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/package.json +1 -1
- 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/package.json +1 -1
- 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 +2 -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 +43 -13
- 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 +2 -1
- 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 +1 -1
- 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/package.json +1 -1
- 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/package.json +1 -1
- 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/package.json +1 -1
- 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/package.json +1 -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/webhook/package.json +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +1 -1
- 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/package.json +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/package.json +3 -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/brands.ts +100 -0
- package/runtime/sdk-runner/node_modules/@metro-labs/core/src/favicon.ts +38 -0
- 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 +6 -6
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
4
|
+
import { stringOf } from '@metro-labs/http/api-http';
|
|
4
5
|
import type { AgentUser } from './user.js';
|
|
5
6
|
import { helperRun, type RunResult } from './unit-files.js';
|
|
6
7
|
|
|
@@ -64,22 +65,21 @@ function listTimers(runner: Runner): Record<string, unknown>[] {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
const text = (v: unknown): string => (typeof v === 'string' ? v : '');
|
|
68
68
|
const orRoot = (user: string | undefined): string => (user === undefined || user === '' ? 'root' : user);
|
|
69
69
|
|
|
70
70
|
function timerJob(runner: Runner, t: Record<string, unknown>): ScheduledJob | null {
|
|
71
|
-
const unit =
|
|
72
|
-
const service =
|
|
71
|
+
const unit = stringOf(t.unit);
|
|
72
|
+
const service = stringOf(t.activates);
|
|
73
73
|
if (unit === '' || service === '' || OWN_UNITS.test(service)) return null;
|
|
74
74
|
const timer = showProps(runner, unit, ['FragmentPath', 'TimersCalendar', 'TimersMonotonic']);
|
|
75
|
-
if (!
|
|
75
|
+
if (!stringOf(timer.FragmentPath).startsWith(`${UNIT_DIR}/`)) return null;
|
|
76
76
|
const svc = showProps(runner, service, ['ExecStart', 'User', 'Result']);
|
|
77
|
-
const command = execArgv(
|
|
77
|
+
const command = execArgv(stringOf(svc.ExecStart)).join(' ');
|
|
78
78
|
return {
|
|
79
79
|
id: `timer:${unit}`,
|
|
80
80
|
kind: 'timer',
|
|
81
81
|
name: unit.replace(/\.timer$/, ''),
|
|
82
|
-
schedule: calendarOf(`${
|
|
82
|
+
schedule: calendarOf(`${stringOf(timer.TimersCalendar)} ${stringOf(timer.TimersMonotonic)}`),
|
|
83
83
|
command,
|
|
84
84
|
runsAs: orRoot(svc.User),
|
|
85
85
|
next: stamp(t.next),
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
2
|
import { join } from 'node:path';
|
|
5
3
|
import { runningAsMetro } from '../metro-user/privilege.js';
|
|
6
4
|
|
|
@@ -74,28 +72,6 @@ export const agentCommand = (command: readonly string[], extra: Record<string, s
|
|
|
74
72
|
return [bin, ...argv];
|
|
75
73
|
};
|
|
76
74
|
|
|
77
|
-
const SDK_PACKAGE = '@anthropic-ai/claude-agent-sdk';
|
|
78
|
-
|
|
79
|
-
export const CLAUDE_ENV: Readonly<Record<string, string>> = { DISABLE_AUTOUPDATER: '1' };
|
|
80
|
-
|
|
81
|
-
let musl: boolean | null = null;
|
|
82
|
-
|
|
83
|
-
function muslFirst(): boolean {
|
|
84
|
-
if (process.platform !== 'linux') return false;
|
|
85
|
-
if (musl === null) {
|
|
86
|
-
const report = process.report.getReport() as { header?: { glibcVersionRuntime?: string } };
|
|
87
|
-
musl = report.header?.glibcVersionRuntime === undefined;
|
|
88
|
-
}
|
|
89
|
-
return musl;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function claudeBin(user = agentUser()): string {
|
|
93
|
-
const name = `${SDK_PACKAGE}-${process.platform}-${process.arch}`;
|
|
94
|
-
const packages = muslFirst() ? [`${name}-musl`, name] : [name, `${name}-musl`];
|
|
95
|
-
const [first = '', other = ''] = packages.map((pkg) => join(user?.home ?? homedir(), '.metro', 'sdk-runner', 'node_modules', pkg, 'claude'));
|
|
96
|
-
return existsSync(first) || !existsSync(other) ? first : other;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
75
|
export const claudeHome = (user = agentUser()): string | null => (user === null ? null : user.home);
|
|
100
76
|
|
|
101
77
|
export const agentViewDir = (user: AgentUser): string => join(user.home, '.metro', 'agents');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { errMsg, log } from '@metro-labs/core/log';
|
|
4
4
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
@@ -45,7 +45,7 @@ export function viewFiles(dir = agentsDir(), model: () => string | null = () =>
|
|
|
45
45
|
['agent.json', agentFile(dir)],
|
|
46
46
|
['model.json', model()],
|
|
47
47
|
]);
|
|
48
|
-
for (const name of COPIED) files.set(name,
|
|
48
|
+
for (const name of COPIED) files.set(name, readText(join(dir, name)));
|
|
49
49
|
for (const name of NEVER_COPIED) files.set(name, null);
|
|
50
50
|
return files;
|
|
51
51
|
}
|
|
@@ -58,7 +58,7 @@ export function syncAgentView(user: AgentUser | null = agentUser(), dir = agents
|
|
|
58
58
|
let changed = 0;
|
|
59
59
|
for (const [name, text] of viewFiles(dir)) {
|
|
60
60
|
const path = join(target, name);
|
|
61
|
-
if (written.
|
|
61
|
+
if (written.get(path) === text) continue;
|
|
62
62
|
if (text === null) removeHome(path, false, user);
|
|
63
63
|
else writeHomeText(path, text, 0o600, undefined, user);
|
|
64
64
|
written.set(path, text);
|
|
@@ -6,13 +6,12 @@ import { ApiError } from '@metro-labs/http/api-error';
|
|
|
6
6
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
7
7
|
import { parseId } from '@metro-labs/core/ids';
|
|
8
8
|
import type { LoadedAccount, LoadedAgent } from '../stations/materialize.js';
|
|
9
|
-
import { STATIONS } from '@metro-labs/core/station-names';
|
|
10
9
|
import { approversOf } from './files.js';
|
|
10
|
+
import { isStationName } from './account-attach.js';
|
|
11
11
|
|
|
12
12
|
const AGENTS = '/api/agents';
|
|
13
13
|
const RESTORE_PATH = `${AGENTS}/restore`;
|
|
14
14
|
const BUNDLE_MAX = 2 * 1024 * 1024;
|
|
15
|
-
const STATION_NAMES = new Set<string>(STATIONS);
|
|
16
15
|
|
|
17
16
|
export interface AgentBundle {
|
|
18
17
|
version: 1;
|
|
@@ -50,7 +49,7 @@ function parseMode(raw: unknown): ImportMode {
|
|
|
50
49
|
const bad = (what: string): ApiError => new ApiError(`bundle: ${what}`, 400);
|
|
51
50
|
|
|
52
51
|
function stationOf(raw: unknown): LoadedAccount {
|
|
53
|
-
if (!isRecord(raw) ||
|
|
52
|
+
if (!isRecord(raw) || !isStationName(raw.station))
|
|
54
53
|
throw bad('a station is not a known station');
|
|
55
54
|
if (typeof raw.id !== 'string' || !isRecord(raw.config)) throw bad('a station has no id or config');
|
|
56
55
|
const allowlist = Array.isArray(raw.allowlist)
|
|
@@ -58,7 +57,7 @@ function stationOf(raw: unknown): LoadedAccount {
|
|
|
58
57
|
: null;
|
|
59
58
|
const policy = parsePolicy(raw.policy, `bundle ${raw.station}/${raw.id}`);
|
|
60
59
|
return {
|
|
61
|
-
station: raw.station
|
|
60
|
+
station: raw.station,
|
|
62
61
|
id: raw.id,
|
|
63
62
|
allowlist,
|
|
64
63
|
...approversOf(raw.approvers),
|
|
@@ -33,8 +33,6 @@ interface Stored {
|
|
|
33
33
|
file: AgentFile;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const missing = (): AgentAdminError => new AgentAdminError('no such agent', 404);
|
|
37
|
-
|
|
38
36
|
function parseOwner(raw: string): string | null {
|
|
39
37
|
const text = raw.trim();
|
|
40
38
|
return isOrganizationId(text) ? text : null;
|
|
@@ -66,7 +64,7 @@ function save(stored: Stored): void {
|
|
|
66
64
|
|
|
67
65
|
function agentOrThrow(id: string, dir: string): Stored {
|
|
68
66
|
const found = storedAgents(dir).find((s) => s.file.id === id);
|
|
69
|
-
if (found === undefined) throw
|
|
67
|
+
if (found === undefined) throw new AgentAdminError('no such agent', 404);
|
|
70
68
|
return found;
|
|
71
69
|
}
|
|
72
70
|
|
|
@@ -84,9 +82,8 @@ function freshId(taken: Set<string>): string {
|
|
|
84
82
|
|
|
85
83
|
export async function localCreateAgent(rawName?: string, dir = agentsDir()): Promise<CreatedAgent> {
|
|
86
84
|
const name = rawName === undefined ? null : normalizeAgentName(rawName);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const id = freshId(new Set());
|
|
85
|
+
if (storedAgents(dir).length > 0) throw new AgentAdminError('this box already has its agent', 409);
|
|
86
|
+
const id = newId();
|
|
90
87
|
const key = newApiKey();
|
|
91
88
|
ensureSecureDir(dir);
|
|
92
89
|
save({
|
|
@@ -228,9 +225,7 @@ export function localPairAgain(agentId: string, station: StationName, accountId:
|
|
|
228
225
|
}
|
|
229
226
|
|
|
230
227
|
export function readLocalAgentFile(agentId: string, dir = agentsDir()): AgentFile {
|
|
231
|
-
|
|
232
|
-
if (found === undefined) throw missing();
|
|
233
|
-
return found.file;
|
|
228
|
+
return agentOrThrow(agentId, dir).file;
|
|
234
229
|
}
|
|
235
230
|
|
|
236
231
|
const UNIQUE_CREDENTIALS: Record<string, string> = {
|
|
@@ -2,15 +2,14 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { AGENT_NAME_RE, ID_RE } from '@metro-labs/core/ids';
|
|
5
|
-
import { STATIONS, type StationName } from '@metro-labs/core/station-names';
|
|
6
5
|
import type { LoadedAccount, LoadedAgent, StationSource } from '../stations/materialize.js';
|
|
7
6
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
8
7
|
import { parsePolicy } from '../policy/policy.js';
|
|
8
|
+
import { isStationName } from './account-attach.js';
|
|
9
9
|
|
|
10
10
|
const AGENT_FILE = 'agent.json';
|
|
11
11
|
|
|
12
12
|
const KEY_RE = /^[A-Za-z0-9_-]{16,128}$/;
|
|
13
|
-
const STATION_NAMES = new Set<string>(STATIONS);
|
|
14
13
|
|
|
15
14
|
export class AgentFileError extends Error {}
|
|
16
15
|
|
|
@@ -35,10 +34,6 @@ function fail(path: string, reason: string): never {
|
|
|
35
34
|
throw new AgentFileError(`${path}: ${reason}`);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
function isStationName(value: unknown): value is StationName {
|
|
39
|
-
return typeof value === 'string' && STATION_NAMES.has(value);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
37
|
function allowlistOf(raw: unknown, path: string, where: string): string[] | null {
|
|
43
38
|
if (raw === null || raw === undefined) return null;
|
|
44
39
|
if (!Array.isArray(raw)) fail(path, `${where}.allowlist is not a list of strings`);
|
|
@@ -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),
|
|
@@ -142,7 +142,7 @@ function immutableCommit(data: unknown): { sha: string; tree: string } {
|
|
|
142
142
|
|
|
143
143
|
function regularFile(entry: Entry): void {
|
|
144
144
|
if (entry.type !== 'blob' || !['100644', '100755'].includes(entry.mode)) throw new GitHubFailure('Symlinks, submodules and special files are not supported in skills.');
|
|
145
|
-
if (!skillPath(entry.path) || entry.size < 0 || entry.size > SKILL_FILE_MAX || !Number.isInteger(entry.size)) throw new GitHubFailure(
|
|
145
|
+
if (!skillPath(entry.path) || entry.size < 0 || entry.size > SKILL_FILE_MAX || !Number.isInteger(entry.size)) throw new GitHubFailure(`A skills path or file size is not supported. Use skill-name/SKILL.md and regular resources, up to ${SKILL_FILE_MAX / 1024} KiB each.`);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
function checkedEntries(entries: Entry[]): Entry[] {
|
|
@@ -154,7 +154,7 @@ function checkedEntries(entries: Entry[]): Entry[] {
|
|
|
154
154
|
total += entry.size;
|
|
155
155
|
files.push(entry);
|
|
156
156
|
}
|
|
157
|
-
if (files.length > SKILL_FILES_MAX || total > SKILL_TOTAL_MAX) throw new GitHubFailure(
|
|
157
|
+
if (files.length > SKILL_FILES_MAX || total > SKILL_TOTAL_MAX) throw new GitHubFailure(`Skills exceed ${SKILL_FILES_MAX} files or ${SKILL_TOTAL_MAX / 1024 / 1024} MiB.`);
|
|
158
158
|
if (new Set(files.map((entry) => entry.path.toLowerCase())).size !== files.length) throw new GitHubFailure('Skills contain duplicate file paths.');
|
|
159
159
|
return files;
|
|
160
160
|
}
|
|
@@ -4,7 +4,7 @@ import { Readable } from 'node:stream';
|
|
|
4
4
|
import { log } from '@metro-labs/core/log';
|
|
5
5
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
6
6
|
import { writeSecure } from '@metro-labs/core/secure-fs';
|
|
7
|
-
import { SKILL_HASH, SKILL_MANIFEST_MAX, skillGeneration, skillRelease, skillSourceRoot, type SkillGeneration } from '@metro-labs/core/skill-source';
|
|
7
|
+
import { SKILL_FILE_MAX, SKILL_HASH, SKILL_MANIFEST_MAX, skillGeneration, skillRelease, skillSourceRoot, type SkillGeneration } from '@metro-labs/core/skill-source';
|
|
8
8
|
import { ApiError } from '@metro-labs/http/api-error';
|
|
9
9
|
import { readFileSync, statSync } from '../agent-user/agent-fs.js';
|
|
10
10
|
import { receiveHomeFile, writeHomeText } from '../agent-user/home-fs.js';
|
|
@@ -135,7 +135,7 @@ export class GitHubSkills {
|
|
|
135
135
|
read(id: string): ClaudeSkill & { text: string } {
|
|
136
136
|
const row = this.rows().find((skill) => skill.id === id);
|
|
137
137
|
if (row === undefined) throw new ApiError('no such skill', 404);
|
|
138
|
-
if (statSync(row.path).size >
|
|
138
|
+
if (statSync(row.path).size > SKILL_FILE_MAX) throw new ApiError('The managed skill changed on disk. Sync it again.', 409);
|
|
139
139
|
return { ...row, text: readFileSync(row.path, 'utf8') };
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -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(
|
|
@@ -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
|
);
|