@stage-labs/metro 0.1.0-beta.172 → 0.1.0-beta.173
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +1 -1
- package/runtime/node_modules/@metro-labs/core/package.json +4 -1
- package/runtime/node_modules/@metro-labs/core/src/events.ts +1 -3
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +3 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +17 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +0 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +2 -5
- package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +89 -0
- package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +11 -28
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +0 -9
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +11 -91
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +7 -8
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +14 -90
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +1 -34
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +0 -5
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +4 -7
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +1 -13
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +5 -9
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +2 -7
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +1 -16
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +3 -20
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +15 -26
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +4 -10
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +26 -55
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +1 -8
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +1 -5
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +8 -21
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +20 -9
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +4 -14
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +75 -64
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +4 -32
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +2 -24
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +25 -20
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +3 -1
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +3 -19
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +15 -41
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +17 -40
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +5 -5
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +17 -132
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +9 -18
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +35 -140
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +43 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +5 -8
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +18 -40
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +15 -15
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +14 -26
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +12 -21
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +30 -133
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +0 -1
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +4 -4
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +5 -5
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +15 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +5 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +14 -15
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +22 -71
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +4 -5
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +5 -6
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +12 -31
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +1 -6
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +2 -75
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +3 -8
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +19 -29
- package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +15 -41
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +3 -25
- package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +3 -25
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +7 -34
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +1 -6
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +1 -13
- package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +8 -31
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +6 -31
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +1 -3
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +0 -1
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +0 -8
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +7 -66
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +5 -10
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +0 -4
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +39 -7
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +0 -1
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +0 -48
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +11 -33
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +4 -43
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +0 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +0 -1
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +0 -8
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +9 -50
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +7 -13
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +3 -2
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +0 -2
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +0 -1
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +0 -1
- package/runtime/node_modules/@metro-labs/threema/src/files.ts +8 -43
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +3 -16
- package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +5 -6
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +2 -7
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +0 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +1 -56
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +10 -27
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -4
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +1 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +0 -27
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +0 -10
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +4 -23
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +3 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +4 -8
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +0 -47
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +23 -43
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +3 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +0 -2
- package/runtime/runtime.json +1 -1
- package/runtime/stations.json +1 -3
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +0 -69
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +0 -65
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +0 -29
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +0 -21
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +0 -172
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +0 -43
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +0 -195
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +0 -292
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +0 -197
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -63
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stage-labs/metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.173",
|
|
4
4
|
"description": "The metro command line. Sign in once per machine, then hand your MCP connector list to Claude Code without the credentials touching disk, argv or shell history.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.173",
|
|
4
4
|
"description": "Metro for Claude Code, installed by the metro daemon on this machine: every connector the agent holds as its own MCP server relayed through the daemon, the orchestrator-only guard on the main thread, and the standing rules loaded at each session start. No vendor credential ever sits in a config file.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bonustrack",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"./train-error": "./src/train-error.ts",
|
|
15
15
|
"./trains/protocol": "./src/protocol.ts",
|
|
16
16
|
"./ids": "./src/ids.ts",
|
|
17
|
+
"./tickets": "./src/tickets.ts",
|
|
17
18
|
"./address": "./src/address.ts",
|
|
18
19
|
"./station-names": "./src/station-names.ts",
|
|
19
20
|
"./str": "./src/str.ts",
|
|
@@ -25,7 +26,9 @@
|
|
|
25
26
|
"./stations/attachments": "./src/stations/attachments.ts",
|
|
26
27
|
"./stations/messaging-normalize": "./src/stations/messaging-normalize.ts",
|
|
27
28
|
"./stations/profile": "./src/stations/profile.ts",
|
|
28
|
-
"./stations/sender-profile": "./src/stations/sender-profile.ts"
|
|
29
|
+
"./stations/sender-profile": "./src/stations/sender-profile.ts",
|
|
30
|
+
"./stations/train-events": "./src/stations/train-events.ts",
|
|
31
|
+
"./stations/train-boot": "./src/stations/train-boot.ts"
|
|
29
32
|
},
|
|
30
33
|
"scripts": {
|
|
31
34
|
"build": "tsc",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
1
|
import { Line } from './lines.js';
|
|
3
2
|
|
|
4
3
|
export { userSelf, daemonSelf } from './identity.js';
|
|
@@ -74,8 +73,7 @@ export function formatDisplay(e: MetroEvent): string {
|
|
|
74
73
|
return `${headerFor('📩', [e.station, e.fromName ?? e.from, e.lineName])}\n> ${body}`;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
export
|
|
78
|
-
`msg_${randomBytes(6).toString('base64url')}`;
|
|
76
|
+
export { mintId } from './ids.js';
|
|
79
77
|
|
|
80
78
|
export type BusListener = (event: MetroEvent, busSeq: number) => void;
|
|
81
79
|
|
|
@@ -5,6 +5,9 @@ const REROLLS = 16;
|
|
|
5
5
|
|
|
6
6
|
export const ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{10}$/;
|
|
7
7
|
|
|
8
|
+
export const mintId = (): string =>
|
|
9
|
+
`msg_${randomBytes(6).toString('base64url')}`;
|
|
10
|
+
|
|
8
11
|
export function newId(): string {
|
|
9
12
|
for (let attempt = 0; attempt < REROLLS; attempt += 1) {
|
|
10
13
|
const id = randomBytes(ID_BYTES).toString('base64url');
|
|
@@ -38,6 +38,23 @@ export function writeSecure(path: string, data: string): void {
|
|
|
38
38
|
chmodIfExists(path, 0o600);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function modeOf(path: string): number | null {
|
|
42
|
+
try {
|
|
43
|
+
return statSync(path).mode & 0o777;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function writeAtomic(path: string, data: string, mode?: number): void {
|
|
50
|
+
const chosen = mode ?? modeOf(path) ?? 0o644;
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
const tmp = `${path}.metro-${String(process.pid)}.tmp`;
|
|
53
|
+
writeFileSync(tmp, data, { mode: chosen });
|
|
54
|
+
chmodSync(tmp, chosen);
|
|
55
|
+
renameSync(tmp, path);
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
export function readJson<T>(
|
|
42
59
|
path: string,
|
|
43
60
|
fallback: T,
|
|
@@ -12,8 +12,7 @@ export const respond = (
|
|
|
12
12
|
JSON.stringify({ op: 'response', id, ...body }) + '\n',
|
|
13
13
|
);
|
|
14
14
|
|
|
15
|
-
export
|
|
16
|
-
`msg_${Math.random().toString(36).slice(2, 10)}`;
|
|
15
|
+
export { mintId } from '../ids.js';
|
|
17
16
|
|
|
18
17
|
type Args = Record<string, unknown>;
|
|
19
18
|
|
|
@@ -29,13 +28,12 @@ export type StationHandler = (id: string, args: Args) => void | Promise<void>;
|
|
|
29
28
|
export interface StationConfig {
|
|
30
29
|
handlers: Record<string, StationHandler>;
|
|
31
30
|
normalize: (action: string, args: Args) => Normalized;
|
|
32
|
-
preDispatch?: (id: string, action: string) => boolean;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
const lineTag = (args: Args): string =>
|
|
36
34
|
typeof args.line === 'string' ? args.line : '?';
|
|
37
35
|
|
|
38
|
-
export function makeStation({ handlers, normalize
|
|
36
|
+
export function makeStation({ handlers, normalize }: StationConfig) {
|
|
39
37
|
const known = Object.keys(handlers).join(', ');
|
|
40
38
|
return async function handleCall(msg: CallMsg): Promise<void> {
|
|
41
39
|
const { id } = msg;
|
|
@@ -44,7 +42,6 @@ export function makeStation({ handlers, normalize, preDispatch }: StationConfig)
|
|
|
44
42
|
try {
|
|
45
43
|
({ action, args } = normalize(msg.action, msg.args));
|
|
46
44
|
emit({ op: 'log', text: `call ${action} recv (line=${lineTag(args)})` });
|
|
47
|
-
if (preDispatch?.(id, action)) return;
|
|
48
45
|
const handler = handlers[action];
|
|
49
46
|
if (!handler) {
|
|
50
47
|
respond(id, { error: `unknown action '${action}' (have: ${known})` });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { errMsg } from '../log.js';
|
|
2
|
+
import { readCalls } from '../protocol.js';
|
|
3
|
+
import { TrainError } from '../train-error.js';
|
|
4
|
+
import type { CallMsg } from './station-runtime.js';
|
|
5
|
+
|
|
6
|
+
export function announceAccounts(station: string, ids: Iterable<string>): void {
|
|
7
|
+
const list = [...ids];
|
|
8
|
+
if (list.length === 0) {
|
|
9
|
+
process.stderr.write(`${station}: no accounts booted, exiting\n`);
|
|
10
|
+
process.exit(2);
|
|
11
|
+
}
|
|
12
|
+
process.stderr.write(`${station} train ready — ${String(list.length)} account(s): ${list.join(', ')}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClientTrain<A extends { id: string }, C> {
|
|
16
|
+
station: string;
|
|
17
|
+
accounts: Map<string, A>;
|
|
18
|
+
loadAccounts: () => A[];
|
|
19
|
+
createClient: (account: A) => C;
|
|
20
|
+
startInbound: (client: C) => Promise<void>;
|
|
21
|
+
makeHandleCall: (clientFor: (accountId: string) => C) => (msg: CallMsg) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function runClientTrain<A extends { id: string }, C>(train: ClientTrain<A, C>): void {
|
|
25
|
+
const clients = new Map<string, C>();
|
|
26
|
+
const clientFor = (accountId: string): C => {
|
|
27
|
+
const known = clients.get(accountId);
|
|
28
|
+
if (known !== undefined) return known;
|
|
29
|
+
const account = train.accounts.get(accountId);
|
|
30
|
+
if (!account) throw new TrainError('not_implemented', `unknown account '${accountId}'`);
|
|
31
|
+
const client = train.createClient(account);
|
|
32
|
+
clients.set(accountId, client);
|
|
33
|
+
return client;
|
|
34
|
+
};
|
|
35
|
+
readCalls(train.station, train.makeHandleCall(clientFor));
|
|
36
|
+
for (const cfg of train.loadAccounts()) train.accounts.set(cfg.id, cfg);
|
|
37
|
+
for (const id of train.accounts.keys())
|
|
38
|
+
train.startInbound(clientFor(id)).catch((err: unknown) => {
|
|
39
|
+
process.stderr.write(`${train.station}[${id}] inbound failed: ${errMsg(err)}\n`);
|
|
40
|
+
});
|
|
41
|
+
announceAccounts(train.station, train.accounts.keys());
|
|
42
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { mintId } from '../ids.js';
|
|
2
|
+
import { emit } from './station-runtime.js';
|
|
3
|
+
|
|
4
|
+
type Fields = Record<string, unknown>;
|
|
5
|
+
type Out = (e: unknown) => void;
|
|
6
|
+
|
|
7
|
+
export const selfUri = (station: string, account?: string): string =>
|
|
8
|
+
process.env.METRO_SELF_URI ??
|
|
9
|
+
(account === undefined ? `metro://${station}/self` : `metro://${station}/${account}/self`);
|
|
10
|
+
|
|
11
|
+
export function emitInbound(account: string, e: Fields, out: Out = emit): void {
|
|
12
|
+
out({
|
|
13
|
+
kind: 'inbound',
|
|
14
|
+
...e,
|
|
15
|
+
account,
|
|
16
|
+
payload: { ...(e.payload as Fields | undefined), account },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AttachmentAt {
|
|
21
|
+
station: string;
|
|
22
|
+
account: string;
|
|
23
|
+
line: string;
|
|
24
|
+
forId: string;
|
|
25
|
+
index: number;
|
|
26
|
+
from?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SavedFile {
|
|
30
|
+
path: string;
|
|
31
|
+
mime?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function attachmentEvent(at: AttachmentAt, text: string, payload: Fields): Fields {
|
|
36
|
+
return {
|
|
37
|
+
kind: 'inbound',
|
|
38
|
+
id: mintId(),
|
|
39
|
+
ts: new Date().toISOString(),
|
|
40
|
+
station: at.station,
|
|
41
|
+
line: at.line,
|
|
42
|
+
from: at.from ?? selfUri(at.station, at.account),
|
|
43
|
+
text,
|
|
44
|
+
account: at.account,
|
|
45
|
+
payload: { account: at.account, attachmentFor: at.forId, index: at.index, ...payload },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const attachmentSavedEvent = (at: AttachmentAt & { saved: SavedFile; extra?: Fields }): Fields =>
|
|
50
|
+
attachmentEvent(at, `📎 saved: ${at.saved.path}`, {
|
|
51
|
+
contentType: 'attachmentSaved',
|
|
52
|
+
...at.extra,
|
|
53
|
+
attachmentPath: at.saved.path,
|
|
54
|
+
localPath: at.saved.path,
|
|
55
|
+
mime: at.saved.mime,
|
|
56
|
+
name: at.saved.name,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const attachmentFailedEvent = (at: AttachmentAt & { reason: string; extra?: Fields }): Fields =>
|
|
60
|
+
attachmentEvent(at, `📎 not fetched: ${at.reason}`, {
|
|
61
|
+
contentType: 'attachmentFailed',
|
|
62
|
+
...at.extra,
|
|
63
|
+
reason: at.reason,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const reasonOf = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
67
|
+
|
|
68
|
+
export function reportAttachment(
|
|
69
|
+
save: Promise<SavedFile>,
|
|
70
|
+
at: AttachmentAt,
|
|
71
|
+
extra: { saved?: Fields; failed?: Fields } = {},
|
|
72
|
+
out: Out = emit,
|
|
73
|
+
): void {
|
|
74
|
+
const tag = `${at.station}[${at.account}]`;
|
|
75
|
+
save
|
|
76
|
+
.then(
|
|
77
|
+
(saved) => {
|
|
78
|
+
out(attachmentSavedEvent({ ...at, saved, extra: extra.saved }));
|
|
79
|
+
},
|
|
80
|
+
(err: unknown) => {
|
|
81
|
+
const reason = reasonOf(err);
|
|
82
|
+
process.stderr.write(`${tag} attachment save failed: ${reason}\n`);
|
|
83
|
+
out(attachmentFailedEvent({ ...at, reason, extra: extra.failed }));
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
.catch((err: unknown) => {
|
|
87
|
+
process.stderr.write(`${tag} attachment event not emitted: ${reasonOf(err)}\n`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export interface TicketStore<T> {
|
|
4
|
+
mint: (value: T, now?: number) => { ticket: string; expiresAt: number };
|
|
5
|
+
take: (ticket: string, now?: number) => T | undefined;
|
|
6
|
+
size: (now?: number) => number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ticketStore<T>(ttlMs: number, max: number): TicketStore<T> {
|
|
10
|
+
const open = new Map<string, { value: T; expiresAt: number }>();
|
|
11
|
+
const prune = (now: number): void => {
|
|
12
|
+
for (const [ticket, entry] of open) if (entry.expiresAt <= now) open.delete(ticket);
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
mint(value, now = Date.now()) {
|
|
16
|
+
prune(now);
|
|
17
|
+
while (open.size >= max) {
|
|
18
|
+
const oldest = open.keys().next();
|
|
19
|
+
if (oldest.done === true) break;
|
|
20
|
+
open.delete(oldest.value);
|
|
21
|
+
}
|
|
22
|
+
const ticket = randomBytes(32).toString('base64url');
|
|
23
|
+
const expiresAt = now + ttlMs;
|
|
24
|
+
open.set(ticket, { value, expiresAt });
|
|
25
|
+
return { ticket, expiresAt };
|
|
26
|
+
},
|
|
27
|
+
take(ticket, now = Date.now()) {
|
|
28
|
+
const entry = open.get(ticket);
|
|
29
|
+
open.delete(ticket);
|
|
30
|
+
return entry !== undefined && entry.expiresAt > now ? entry.value : undefined;
|
|
31
|
+
},
|
|
32
|
+
size(now = Date.now()) {
|
|
33
|
+
prune(now);
|
|
34
|
+
return open.size;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
bodyField,
|
|
9
9
|
readJsonBody,
|
|
10
10
|
sendJson,
|
|
11
|
-
type ApiSession,
|
|
12
11
|
} from '@metro-labs/http/api-http';
|
|
13
12
|
import { type AccountRef } from './account-attach.js';
|
|
14
13
|
import { type AccountRoute } from './account-routes.js';
|
|
@@ -51,13 +50,11 @@ export interface AccountApiDeps {
|
|
|
51
50
|
attachSessions: AttachSessionApi;
|
|
52
51
|
prepareAccount: (input: AttachInput) => Promise<PreparedAccount>;
|
|
53
52
|
attachAccount: (
|
|
54
|
-
subject: string,
|
|
55
53
|
agentId: string,
|
|
56
54
|
station: StationName,
|
|
57
55
|
config: Record<string, unknown>,
|
|
58
56
|
) => Promise<AccountRef>;
|
|
59
57
|
detachAccount: (
|
|
60
|
-
subject: string,
|
|
61
58
|
agentId: string,
|
|
62
59
|
station: StationName,
|
|
63
60
|
accountId: string,
|
|
@@ -65,7 +62,6 @@ export interface AccountApiDeps {
|
|
|
65
62
|
syncStations: (station: StationName) => Promise<void>;
|
|
66
63
|
reloadAgents: () => Promise<void>;
|
|
67
64
|
setAllowlist: (
|
|
68
|
-
subject: string,
|
|
69
65
|
agentId: string,
|
|
70
66
|
station: StationName,
|
|
71
67
|
accountId: string,
|
|
@@ -83,7 +79,6 @@ export interface AccountApiDeps {
|
|
|
83
79
|
args: Record<string, unknown>,
|
|
84
80
|
) => Promise<unknown>;
|
|
85
81
|
setAccountEnabled: (
|
|
86
|
-
subject: string,
|
|
87
82
|
agentId: string,
|
|
88
83
|
station: StationName,
|
|
89
84
|
accountId: string,
|
|
@@ -160,14 +155,12 @@ interface AttachPayload {
|
|
|
160
155
|
|
|
161
156
|
async function storeAccount(
|
|
162
157
|
deps: AccountApiDeps,
|
|
163
|
-
session: ApiSession,
|
|
164
158
|
agentId: string,
|
|
165
159
|
station: StationName,
|
|
166
160
|
prepared: PreparedAccount,
|
|
167
161
|
): Promise<AccountRef> {
|
|
168
162
|
try {
|
|
169
163
|
return await deps.attachAccount(
|
|
170
|
-
session.subject,
|
|
171
164
|
agentId,
|
|
172
165
|
station,
|
|
173
166
|
prepared.config,
|
|
@@ -178,9 +171,6 @@ async function storeAccount(
|
|
|
178
171
|
}
|
|
179
172
|
}
|
|
180
173
|
|
|
181
|
-
function ownerOf(session: ApiSession, agentId: string): AttachOwner {
|
|
182
|
-
return { subject: session.subject, agentId };
|
|
183
|
-
}
|
|
184
174
|
|
|
185
175
|
function asInput(body: unknown): Record<string, unknown> {
|
|
186
176
|
const out: Record<string, unknown> = {};
|
|
@@ -193,14 +183,13 @@ async function handleStart(
|
|
|
193
183
|
req: IncomingMessage,
|
|
194
184
|
res: ServerResponse,
|
|
195
185
|
deps: AccountApiDeps,
|
|
196
|
-
session: ApiSession,
|
|
197
186
|
agentId: string,
|
|
198
187
|
): Promise<void> {
|
|
199
188
|
const body = await readJsonBody(req);
|
|
200
189
|
const station = bodyField(body, 'station');
|
|
201
190
|
if (isInteractiveStation(station)) {
|
|
202
191
|
const view = await deps.attachSessions.start(
|
|
203
|
-
|
|
192
|
+
{ agentId },
|
|
204
193
|
station,
|
|
205
194
|
asInput(body),
|
|
206
195
|
);
|
|
@@ -210,7 +199,7 @@ async function handleStart(
|
|
|
210
199
|
if (!isAttachStation(station))
|
|
211
200
|
throw new ApiError(`station must be one of ${ATTACHABLE.join(', ')}`, 400);
|
|
212
201
|
const prepared = await deps.prepareAccount(attachInputOf(station, body));
|
|
213
|
-
const ref = await storeAccount(deps,
|
|
202
|
+
const ref = await storeAccount(deps, agentId, station, prepared);
|
|
214
203
|
log.info(
|
|
215
204
|
{ agentId: ref.agentId, station, account: ref.accountId },
|
|
216
205
|
'account-api: attached a station account',
|
|
@@ -237,12 +226,10 @@ async function handleDetach(
|
|
|
237
226
|
req: IncomingMessage,
|
|
238
227
|
res: ServerResponse,
|
|
239
228
|
deps: AccountApiDeps,
|
|
240
|
-
session: ApiSession,
|
|
241
229
|
agentId: string,
|
|
242
230
|
target: { station: StationName; accountId: string },
|
|
243
231
|
): Promise<void> {
|
|
244
232
|
const ref = await deps.detachAccount(
|
|
245
|
-
session.subject,
|
|
246
233
|
agentId,
|
|
247
234
|
target.station,
|
|
248
235
|
target.accountId,
|
|
@@ -274,12 +261,11 @@ async function handleAllowlist(
|
|
|
274
261
|
req: IncomingMessage,
|
|
275
262
|
res: ServerResponse,
|
|
276
263
|
deps: AccountApiDeps,
|
|
277
|
-
session: ApiSession,
|
|
278
264
|
agentId: string,
|
|
279
265
|
target: { station: StationName; accountId: string },
|
|
280
266
|
): Promise<void> {
|
|
281
267
|
const wanted = normalizeAllowlist(bodyField(await readJsonBody(req), 'allowlist'));
|
|
282
|
-
const allowlist = await deps.setAllowlist(
|
|
268
|
+
const allowlist = await deps.setAllowlist(agentId, target.station, target.accountId, wanted);
|
|
283
269
|
log.info({ agentId, station: target.station, account: target.accountId, senders: allowlist.length }, 'account-api: allowlist set');
|
|
284
270
|
sendJson(req, res, 200, {
|
|
285
271
|
agentId,
|
|
@@ -294,13 +280,12 @@ async function handleEnabled(
|
|
|
294
280
|
req: IncomingMessage,
|
|
295
281
|
res: ServerResponse,
|
|
296
282
|
deps: AccountApiDeps,
|
|
297
|
-
session: ApiSession,
|
|
298
283
|
agentId: string,
|
|
299
284
|
target: { station: StationName; accountId: string },
|
|
300
285
|
): Promise<void> {
|
|
301
286
|
const wanted = bodyField(await readJsonBody(req), 'enabled');
|
|
302
287
|
if (typeof wanted !== 'boolean') throw new ApiError('enabled must be true or false', 400);
|
|
303
|
-
const enabled = await deps.setAccountEnabled(
|
|
288
|
+
const enabled = await deps.setAccountEnabled(agentId, target.station, target.accountId, wanted);
|
|
304
289
|
log.info({ agentId, station: target.station, account: target.accountId, enabled }, 'account-api: account enabled flag set');
|
|
305
290
|
sendJson(req, res, 200, {
|
|
306
291
|
agentId,
|
|
@@ -345,43 +330,41 @@ async function dispatchRoute(
|
|
|
345
330
|
req: IncomingMessage,
|
|
346
331
|
res: ServerResponse,
|
|
347
332
|
deps: AccountApiDeps,
|
|
348
|
-
session: ApiSession,
|
|
349
333
|
agentId: string,
|
|
350
334
|
route: AccountRoute,
|
|
351
335
|
): Promise<void> {
|
|
352
336
|
if (route.kind === 'start')
|
|
353
|
-
return handleStart(req, res, deps,
|
|
337
|
+
return handleStart(req, res, deps, agentId);
|
|
354
338
|
if (route.kind === 'session')
|
|
355
339
|
return handleSession(
|
|
356
340
|
req,
|
|
357
341
|
res,
|
|
358
342
|
deps,
|
|
359
|
-
|
|
343
|
+
{ agentId },
|
|
360
344
|
route.attachId,
|
|
361
345
|
);
|
|
362
346
|
if (route.kind === 'step')
|
|
363
|
-
return handleStep(req, res, deps,
|
|
364
|
-
if (route.kind === 'allowlist') return handleAllowlist(req, res, deps,
|
|
365
|
-
if (route.kind === 'enabled') return handleEnabled(req, res, deps,
|
|
347
|
+
return handleStep(req, res, deps, { agentId }, route.attachId);
|
|
348
|
+
if (route.kind === 'allowlist') return handleAllowlist(req, res, deps, agentId, route);
|
|
349
|
+
if (route.kind === 'enabled') return handleEnabled(req, res, deps, agentId, route);
|
|
366
350
|
if (route.kind === 'resolve') return handleResolve(req, res, deps, route);
|
|
367
351
|
if (route.kind === 'name') return handleName(req, res, deps, route);
|
|
368
352
|
if (route.kind === 'senders') {
|
|
369
353
|
sendJson(req, res, 200, { senders: deps.recentSenders(route.station, route.accountId) });
|
|
370
354
|
return;
|
|
371
355
|
}
|
|
372
|
-
return handleDetach(req, res, deps,
|
|
356
|
+
return handleDetach(req, res, deps, agentId, route);
|
|
373
357
|
}
|
|
374
358
|
|
|
375
359
|
export async function handleAccountRoute(
|
|
376
360
|
req: IncomingMessage,
|
|
377
361
|
res: ServerResponse,
|
|
378
362
|
deps: AccountApiDeps,
|
|
379
|
-
session: ApiSession,
|
|
380
363
|
agentId: string,
|
|
381
364
|
route: AccountRoute,
|
|
382
365
|
): Promise<void> {
|
|
383
366
|
try {
|
|
384
|
-
await dispatchRoute(req, res, deps,
|
|
367
|
+
await dispatchRoute(req, res, deps, agentId, route);
|
|
385
368
|
} catch (err) {
|
|
386
369
|
apiFailure(req, res, err);
|
|
387
370
|
}
|
|
@@ -7,8 +7,6 @@ export class AgentAdminError extends ApiError {}
|
|
|
7
7
|
export interface AgentSummary {
|
|
8
8
|
id: string;
|
|
9
9
|
name: string | null;
|
|
10
|
-
owned: boolean;
|
|
11
|
-
key: string | null;
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
export interface CreatedAgent {
|
|
@@ -17,13 +15,6 @@ export interface CreatedAgent {
|
|
|
17
15
|
key: string;
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
export interface OwnedAgent {
|
|
21
|
-
id: string;
|
|
22
|
-
name: string | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type DeletedAgent = OwnedAgent;
|
|
26
|
-
|
|
27
18
|
export function normalizeAgentName(raw: unknown): string {
|
|
28
19
|
const name = typeof raw === 'string' ? raw.trim() : '';
|
|
29
20
|
if (!AGENT_NAME_RE.test(name))
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { parseId } from '@metro-labs/core/ids';
|
|
3
|
-
import { log } from '@metro-labs/core/log';
|
|
4
3
|
import {
|
|
5
4
|
apiFailure,
|
|
6
|
-
apiSession,
|
|
7
|
-
projectParam,
|
|
5
|
+
apiSession,
|
|
8
6
|
cors,
|
|
9
7
|
sendJson,
|
|
10
|
-
type ApiSession,
|
|
11
8
|
} from '@metro-labs/http/api-http';
|
|
12
9
|
import { ATTACHABLE, handleAccountRoute, type AccountApiDeps } from './accounts-api.js';
|
|
13
10
|
import {
|
|
@@ -15,38 +12,28 @@ import {
|
|
|
15
12
|
accountRouteAllows,
|
|
16
13
|
type AccountRoute,
|
|
17
14
|
} from './account-routes.js';
|
|
18
|
-
import {
|
|
19
|
-
type AgentSummary,
|
|
20
|
-
type DeletedAgent,
|
|
21
|
-
} from './admin.js';
|
|
15
|
+
import type { AgentSummary } from './admin.js';
|
|
22
16
|
|
|
23
17
|
const PREFIX = '/api/agents';
|
|
24
18
|
|
|
25
19
|
export interface AgentApiDeps extends AccountApiDeps {
|
|
26
|
-
listAgents: (
|
|
27
|
-
deleteAgent: (
|
|
28
|
-
subject: string,
|
|
29
|
-
id: string,
|
|
30
|
-
) => Promise<DeletedAgent>;
|
|
20
|
+
listAgents: () => Promise<AgentSummary[]>;
|
|
31
21
|
gatherAccounts: (allowed: Set<string>) => Promise<{
|
|
32
22
|
accounts: Record<string, unknown[]>;
|
|
33
23
|
unavailable: string[];
|
|
34
24
|
}>;
|
|
35
25
|
capabilities: () => Record<string, string[]>;
|
|
36
26
|
attachable?: string[];
|
|
37
|
-
liveness: () => Map<string, { connected: boolean; lastSeenAt: number }>;
|
|
38
27
|
connectorIds: (agentIds: string[]) => Promise<Map<string, string[]>>;
|
|
39
28
|
}
|
|
40
29
|
|
|
41
30
|
type Routable =
|
|
42
31
|
| { kind: 'collection' }
|
|
43
|
-
| { kind: 'agent'; id: string }
|
|
44
32
|
| { kind: 'accounts'; id: string; route: AccountRoute };
|
|
45
33
|
|
|
46
34
|
type Target = Routable | { kind: 'unknown' } | null;
|
|
47
35
|
|
|
48
36
|
function subTarget(id: string, rest: string[]): Target {
|
|
49
|
-
if (rest.length === 0) return { kind: 'agent', id };
|
|
50
37
|
if (rest[0] !== 'accounts') return { kind: 'unknown' };
|
|
51
38
|
const route = accountRoute(rest.slice(1));
|
|
52
39
|
return route === null ? { kind: 'unknown' } : { kind: 'accounts', id, route };
|
|
@@ -62,30 +49,8 @@ export function target(path: string): Target {
|
|
|
62
49
|
return id === null ? { kind: 'unknown' } : subTarget(id, segments.slice(1));
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
function
|
|
66
|
-
agent:
|
|
67
|
-
live: Map<string, { connected: boolean; lastSeenAt: number }>,
|
|
68
|
-
): Record<string, unknown> {
|
|
69
|
-
const found = agent.owned ? live.get(agent.id) : undefined;
|
|
70
|
-
if (found === undefined) return { connected: false, last_seen: null };
|
|
71
|
-
return {
|
|
72
|
-
connected: found.connected,
|
|
73
|
-
last_seen: new Date(found.lastSeenAt).toISOString(),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function agentPayload(
|
|
78
|
-
agent: AgentSummary,
|
|
79
|
-
live: Map<string, { connected: boolean; lastSeenAt: number }>,
|
|
80
|
-
connectors: Map<string, string[]>,
|
|
81
|
-
): Record<string, unknown> {
|
|
82
|
-
return {
|
|
83
|
-
id: agent.id,
|
|
84
|
-
name: agent.name,
|
|
85
|
-
owned: agent.owned,
|
|
86
|
-
connector_ids: connectors.get(agent.id) ?? [],
|
|
87
|
-
...livenessPayload(agent, live),
|
|
88
|
-
};
|
|
52
|
+
function agentPayload(agent: AgentSummary, connectors: Map<string, string[]>): Record<string, unknown> {
|
|
53
|
+
return { id: agent.id, name: agent.name, connector_ids: connectors.get(agent.id) ?? [] };
|
|
89
54
|
}
|
|
90
55
|
|
|
91
56
|
function wantsAccounts(req: IncomingMessage): boolean {
|
|
@@ -97,18 +62,11 @@ async function handleList(
|
|
|
97
62
|
req: IncomingMessage,
|
|
98
63
|
res: ServerResponse,
|
|
99
64
|
deps: AgentApiDeps,
|
|
100
|
-
session: ApiSession,
|
|
101
65
|
): Promise<void> {
|
|
102
|
-
const
|
|
103
|
-
if (project === null) {
|
|
104
|
-
sendJson(req, res, 400, { error: 'a project is required' });
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const list = await deps.listAgents(session.subject, project);
|
|
108
|
-
const live = deps.liveness();
|
|
66
|
+
const list = await deps.listAgents();
|
|
109
67
|
const connectors = await deps.connectorIds(list.map((a) => a.id));
|
|
110
68
|
const base = {
|
|
111
|
-
agents: list.map((a) => agentPayload(a,
|
|
69
|
+
agents: list.map((a) => agentPayload(a, connectors)),
|
|
112
70
|
capabilities: deps.capabilities(),
|
|
113
71
|
attachable: deps.attachable ?? ATTACHABLE,
|
|
114
72
|
};
|
|
@@ -122,48 +80,11 @@ async function handleList(
|
|
|
122
80
|
sendJson(req, res, 200, { ...base, accounts, unavailable });
|
|
123
81
|
}
|
|
124
82
|
|
|
125
|
-
|
|
126
|
-
req: IncomingMessage,
|
|
127
|
-
res: ServerResponse,
|
|
128
|
-
deps: AgentApiDeps,
|
|
129
|
-
session: ApiSession,
|
|
130
|
-
id: string,
|
|
131
|
-
): Promise<void> {
|
|
132
|
-
const gone = await deps.deleteAgent(session.subject, id);
|
|
133
|
-
log.info(
|
|
134
|
-
{ agent: gone.name, id: gone.id, owner: session.subject },
|
|
135
|
-
'agent-api: deleted agent',
|
|
136
|
-
);
|
|
137
|
-
sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
type AgentTarget = Exclude<Routable, { kind: 'accounts' }>;
|
|
141
|
-
|
|
142
|
-
async function routeAgent(
|
|
143
|
-
req: IncomingMessage,
|
|
144
|
-
res: ServerResponse,
|
|
145
|
-
deps: AgentApiDeps,
|
|
146
|
-
session: ApiSession,
|
|
147
|
-
tgt: AgentTarget,
|
|
148
|
-
): Promise<void> {
|
|
149
|
-
try {
|
|
150
|
-
if (tgt.kind === 'agent') {
|
|
151
|
-
requireAdmin(session);
|
|
152
|
-
await handleDelete(req, res, deps, session, tgt.id);
|
|
153
|
-
} else await handleList(req, res, deps, session);
|
|
154
|
-
} catch (err) {
|
|
155
|
-
apiFailure(req, res, err);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const ALLOWED: Record<AgentTarget['kind'], string[]> = {
|
|
160
|
-
collection: ['GET'],
|
|
161
|
-
agent: ['DELETE'],
|
|
162
|
-
};
|
|
83
|
+
const ALLOWED_LIST = ['GET'];
|
|
163
84
|
|
|
164
85
|
function methodAllowed(tgt: Routable, method: string | undefined): boolean {
|
|
165
86
|
if (tgt.kind === 'accounts') return accountRouteAllows(tgt.route, method);
|
|
166
|
-
return
|
|
87
|
+
return ALLOWED_LIST.includes(method ?? '');
|
|
167
88
|
}
|
|
168
89
|
|
|
169
90
|
async function dispatch(
|
|
@@ -177,9 +98,8 @@ async function dispatch(
|
|
|
177
98
|
sendJson(req, res, 401, { error: 'unauthorized' });
|
|
178
99
|
return;
|
|
179
100
|
}
|
|
180
|
-
if (tgt.kind === 'accounts')
|
|
181
|
-
|
|
182
|
-
else await routeAgent(req, res, deps, session, tgt);
|
|
101
|
+
if (tgt.kind === 'accounts') await handleAccountRoute(req, res, deps, tgt.id, tgt.route);
|
|
102
|
+
else await handleList(req, res, deps);
|
|
183
103
|
}
|
|
184
104
|
|
|
185
105
|
export function handleAgentApiRequest(
|