@stage-labs/metro 0.1.0-beta.301 → 0.1.0-beta.303

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +1 -1
  3. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +4 -1
  4. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +3 -11
  5. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +5 -8
  6. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +3 -1
  7. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +1 -11
  8. package/runtime/node_modules/@metro-labs/daemon/src/connectors/org-adopt.ts +57 -0
  9. package/runtime/node_modules/@metro-labs/daemon/src/connectors/org-runtime.ts +39 -95
  10. package/runtime/node_modules/@metro-labs/daemon/src/connectors/org-sync-control.ts +27 -45
  11. package/runtime/node_modules/@metro-labs/daemon/src/connectors/org-sync-watch.ts +2 -17
  12. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +4 -34
  13. package/runtime/node_modules/@metro-labs/daemon/src/connectors/publication.ts +8 -29
  14. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-session.ts +0 -1
  15. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +33 -138
  16. package/runtime/node_modules/@metro-labs/daemon/src/connectors/server-keys.ts +22 -9
  17. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +4 -58
  18. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +0 -12
  19. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +3 -3
  20. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +0 -7
  21. package/runtime/runtime.json +1 -1
  22. package/runtime/sdk-runner/runner.json +1 -1
  23. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +0 -316
  24. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +0 -105
  25. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +0 -18
  26. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-api.ts +0 -60
  27. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-control.ts +0 -366
  28. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-guard.ts +0 -95
  29. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-operations.ts +0 -226
  30. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-preview.ts +0 -235
  31. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-source.ts +0 -42
  32. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-state.ts +0 -142
  33. package/runtime/node_modules/@metro-labs/daemon/src/connectors/migration-url.ts +0 -9
  34. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +0 -178
  35. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +0 -171
  36. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-passive.ts +0 -96
  37. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +0 -393
  38. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-api.ts +0 -116
  39. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-crypto.ts +0 -44
  40. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-store.ts +0 -46
@@ -1,316 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import { log } from '@metro-labs/core/log';
3
- import { healthOf } from './health.js';
4
- import type { RemoteTool } from './tools.js';
5
- import type { ConnectorMigrationPreview } from './migration-preview.js';
6
- import { normalizePolicy, type ToolPolicy } from '../policy/policy.js';
7
- import {
8
- apiFailure,
9
- apiSession,
10
- bodyField,
11
- cors,
12
- readJsonBody,
13
- requireAdmin,
14
- sendJson,
15
- stringOf,
16
- } from '@metro-labs/http/api-http';
17
- import { parseId } from '@metro-labs/core/ids';
18
- import {
19
- handleCallback,
20
- handleConnect,
21
- hostOf,
22
- startOAuth,
23
- type OAuthRouteDeps,
24
- } from './oauth-routes.js';
25
- import { ConnectorUnauthorized } from '@metro-labs/http/connector-url';
26
- import type {
27
- Connector,
28
- ConnectorCheck,
29
- ConnectorInput,
30
- DeletedConnector,
31
- } from './store.js';
32
-
33
- const PREFIX = '/api/connectors';
34
-
35
- export interface ConnectorApiDeps extends OAuthRouteDeps {
36
- migrationPreview?: () => ConnectorMigrationPreview;
37
- listConnectors: () => Promise<Connector[]>;
38
- createConnector: (input: ConnectorInput) => Promise<Connector>;
39
- verifyConnector: (id: string) => Promise<ConnectorCheck>;
40
- connectorTools: (id: string) => Promise<RemoteTool[]>;
41
- disconnectConnector: (id: string) => Promise<Connector>;
42
- renameConnector: (id: string, name: string) => Promise<Connector>;
43
- deleteConnector: (id: string) => Promise<DeletedConnector>;
44
- setConnectorPolicy: (id: string, policy: ToolPolicy) => Promise<Connector>;
45
- }
46
-
47
- type Routable =
48
- | { kind: 'collection' }
49
- | { kind: 'migration-preview' }
50
- | { kind: 'callback' }
51
- | { kind: 'connector'; id: string }
52
- | { kind: 'verify'; id: string }
53
- | { kind: 'tools'; id: string }
54
- | { kind: 'connect'; id: string }
55
- | { kind: 'disconnect'; id: string }
56
- | { kind: 'rename'; id: string }
57
- | { kind: 'policy'; id: string };
58
-
59
- type Target = Routable | { kind: 'unknown' } | null;
60
-
61
- function subTarget(id: string, rest: string[]): Target {
62
- if (rest.length === 0) return { kind: 'connector', id };
63
- if (rest.length > 1) return { kind: 'unknown' };
64
- const head = rest[0];
65
- if (head === 'verify') return { kind: 'verify', id };
66
- if (head === 'tools') return { kind: 'tools', id };
67
- if (head === 'connect') return { kind: 'connect', id };
68
- if (head === 'disconnect') return { kind: 'disconnect', id };
69
- if (head === 'rename') return { kind: 'rename', id };
70
- if (head === 'policy') return { kind: 'policy', id };
71
- return { kind: 'unknown' };
72
- }
73
-
74
- function target(path: string): Target {
75
- if (path === PREFIX || path === `${PREFIX}/`) return { kind: 'collection' };
76
- if (!path.startsWith(`${PREFIX}/`)) return null;
77
- const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
78
- const head = segments[0];
79
- if (head === undefined) return { kind: 'collection' };
80
- if (head === 'callback' && segments.length === 1) return { kind: 'callback' };
81
- if (head === 'migration-preview' && segments.length === 1) return { kind: 'migration-preview' };
82
- const id = parseId(head);
83
- return id === null ? { kind: 'unknown' } : subTarget(id, segments.slice(1));
84
- }
85
-
86
- function connectorPayload(row: Connector): Record<string, unknown> {
87
- return {
88
- id: row.id,
89
- name: row.name,
90
- url: row.url,
91
- auth: row.auth,
92
- header: row.header,
93
- clientId: row.client?.clientId ?? null,
94
- signIn: row.signIn,
95
- verified: row.verified,
96
- policy: row.policy,
97
- health: healthOf(row.id),
98
- };
99
- }
100
-
101
- async function handleList(
102
- req: IncomingMessage,
103
- res: ServerResponse,
104
- deps: ConnectorApiDeps,
105
- ): Promise<void> {
106
- const rows = await deps.listConnectors();
107
- sendJson(req, res, 200, {
108
- connectors: rows.map((row) => connectorPayload(row)),
109
- });
110
- }
111
-
112
- async function handleCreate(
113
- req: IncomingMessage,
114
- res: ServerResponse,
115
- deps: ConnectorApiDeps,
116
- ): Promise<void> {
117
- const body = await readJsonBody(req);
118
- const offered = stringOf(bodyField(body, 'value')).trim() !== '';
119
- try {
120
- const created = await deps.createConnector({
121
- name: bodyField(body, 'name'),
122
- url: bodyField(body, 'url'),
123
- header: bodyField(body, 'header'),
124
- value: bodyField(body, 'value'),
125
- clientId: bodyField(body, 'clientId'),
126
- clientSecret: bodyField(body, 'clientSecret'),
127
- });
128
- log.info(
129
- { id: created.id, name: created.name, host: hostOf(created.url) },
130
- 'connector-api: created connector',
131
- );
132
- sendJson(req, res, 201, connectorPayload(created));
133
- } catch (err) {
134
- if (offered || !(err instanceof ConnectorUnauthorized)) throw err;
135
- await startOAuth(req, res, deps, body, (row) =>
136
- connectorPayload(row),
137
- );
138
- }
139
- }
140
-
141
- async function handleVerify(
142
- req: IncomingMessage,
143
- res: ServerResponse,
144
- deps: ConnectorApiDeps,
145
- id: string,
146
- ): Promise<void> {
147
- const check = await deps.verifyConnector(id);
148
- log.info(
149
- { id: check.id, name: check.name, ok: check.ok },
150
- 'connector-api: re-verified connector',
151
- );
152
- sendJson(req, res, 200, check);
153
- }
154
-
155
- async function handleDisconnect(
156
- req: IncomingMessage,
157
- res: ServerResponse,
158
- deps: ConnectorApiDeps,
159
- id: string,
160
- ): Promise<void> {
161
- const row = await deps.disconnectConnector(id);
162
- log.info(
163
- { id: row.id, name: row.name },
164
- 'connector-api: signed the connector out',
165
- );
166
- sendJson(req, res, 200, connectorPayload(row));
167
- }
168
-
169
- async function handleDelete(
170
- req: IncomingMessage,
171
- res: ServerResponse,
172
- deps: ConnectorApiDeps,
173
- id: string,
174
- ): Promise<void> {
175
- const gone = await deps.deleteConnector(id);
176
- log.info(
177
- { id: gone.id, name: gone.name },
178
- 'connector-api: deleted connector',
179
- );
180
- sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
181
- }
182
-
183
- async function handleConnector(
184
- req: IncomingMessage,
185
- res: ServerResponse,
186
- deps: ConnectorApiDeps,
187
- id: string,
188
- ): Promise<void> {
189
- if (req.method !== 'GET') {
190
- await handleDelete(req, res, deps, id);
191
- return;
192
- }
193
- const row = await deps.getConnector(id);
194
- sendJson(req, res, 200, connectorPayload(row));
195
- }
196
-
197
- async function handleRename(
198
- req: IncomingMessage,
199
- res: ServerResponse,
200
- deps: ConnectorApiDeps,
201
- id: string,
202
- ): Promise<void> {
203
- const name = bodyField(await readJsonBody(req), 'name');
204
- if (typeof name !== 'string') {
205
- sendJson(req, res, 400, { error: 'name is required' });
206
- return;
207
- }
208
- const row = await deps.renameConnector(id, name);
209
- log.info({ id: row.id, name: row.name }, 'connector-api: renamed connector');
210
- sendJson(req, res, 200, connectorPayload(row));
211
- }
212
-
213
- async function handlePolicy(
214
- req: IncomingMessage,
215
- res: ServerResponse,
216
- deps: ConnectorApiDeps,
217
- id: string,
218
- ): Promise<void> {
219
- const policy = normalizePolicy(bodyField(await readJsonBody(req), 'policy'));
220
- const row = await deps.setConnectorPolicy(id, policy);
221
- log.info({ id: row.id, name: row.name, policy: row.policy }, 'connector-api: tool policy set');
222
- sendJson(req, res, 200, connectorPayload(row));
223
- }
224
-
225
- type ById = Exclude<Routable['kind'], 'collection' | 'callback' | 'migration-preview'>;
226
-
227
- const BY_ID: Record<ById, (req: IncomingMessage, res: ServerResponse, deps: ConnectorApiDeps, id: string) => Promise<void>> = {
228
- verify: handleVerify,
229
- tools: async (req, res, deps, id) => {
230
- sendJson(req, res, 200, { tools: await deps.connectorTools(id) });
231
- },
232
- connect: handleConnect,
233
- disconnect: handleDisconnect,
234
- rename: handleRename,
235
- policy: handlePolicy,
236
- connector: handleConnector,
237
- };
238
-
239
- async function route(
240
- req: IncomingMessage,
241
- res: ServerResponse,
242
- deps: ConnectorApiDeps,
243
- tgt: Routable,
244
- ): Promise<void> {
245
- try {
246
- if (tgt.kind === 'callback') return;
247
- if (tgt.kind === 'migration-preview') {
248
- if (deps.migrationPreview === undefined) sendJson(req, res, 503, { error: 'Migration preview is unavailable.' });
249
- else sendJson(req, res, 200, deps.migrationPreview());
250
- return;
251
- }
252
- if (tgt.kind !== 'collection') await BY_ID[tgt.kind](req, res, deps, tgt.id);
253
- else if (req.method === 'GET') await handleList(req, res, deps);
254
- else await handleCreate(req, res, deps);
255
- } catch (err) {
256
- apiFailure(req, res, err, 'connector-api');
257
- }
258
- }
259
-
260
- const ALLOWED: Record<Routable['kind'], string[]> = {
261
- collection: ['GET', 'POST'],
262
- 'migration-preview': ['GET'],
263
- callback: ['GET'],
264
- connector: ['GET', 'DELETE'],
265
- verify: ['POST'],
266
- tools: ['GET'],
267
- connect: ['POST'],
268
- disconnect: ['POST'],
269
- rename: ['POST'],
270
- policy: ['PUT'],
271
- };
272
-
273
- async function dispatch(
274
- req: IncomingMessage,
275
- res: ServerResponse,
276
- deps: ConnectorApiDeps,
277
- tgt: Routable,
278
- ): Promise<void> {
279
- if (tgt.kind === 'callback') {
280
- handleCallback(req, res, deps);
281
- return;
282
- }
283
- const session = await apiSession(req);
284
- if (!session) {
285
- sendJson(req, res, 401, { error: 'unauthorized' });
286
- return;
287
- }
288
- if (tgt.kind === 'migration-preview') requireAdmin(session);
289
- await route(req, res, deps, tgt);
290
- }
291
-
292
- export function handleConnectorApiRequest(
293
- req: IncomingMessage,
294
- res: ServerResponse,
295
- deps: ConnectorApiDeps,
296
- ): boolean {
297
- const tgt = target((req.url ?? '').split('?')[0] ?? '');
298
- if (tgt === null) return false;
299
- if (req.method === 'OPTIONS') {
300
- res.writeHead(204, cors(req)).end();
301
- return true;
302
- }
303
- if (tgt.kind === 'unknown') {
304
- sendJson(req, res, 404, { error: 'no such connector' });
305
- return true;
306
- }
307
- if (!ALLOWED[tgt.kind].includes(req.method ?? '')) {
308
- sendJson(req, res, 405, { error: 'method not allowed' });
309
- return true;
310
- }
311
- dispatch(req, res, deps, tgt).catch((err: unknown) => {
312
- if (res.headersSent) return;
313
- apiFailure(req, res, err, 'connector-api');
314
- });
315
- return true;
316
- }
@@ -1,105 +0,0 @@
1
- import { stringOf } from '@metro-labs/http/api-http';
2
- import type {
3
- ConnectorAuth,
4
- VerifiedRecord,
5
- VerifiedServer,
6
- } from '@metro-labs/http/connector-mcp';
7
- import type { OAuthClient } from '@metro-labs/http/connector-oauth';
8
- import { isRecord } from '@metro-labs/core/is-record';
9
- import type { ToolGroup } from '@metro-labs/core/stations/types';
10
- import { parsePolicy, type ToolPolicy } from '../policy/policy.js';
11
-
12
- export type ConnectorSignIn = 'connected' | 'disconnected' | null;
13
-
14
- export interface ConnectorConfig {
15
- auth: ConnectorAuth;
16
- createdAt: string;
17
- verified: VerifiedRecord;
18
- oauth: boolean;
19
- client: OAuthClient | null;
20
- policy?: ToolPolicy;
21
- toolGroups?: Record<string, ToolGroup>;
22
- }
23
-
24
- function readClient(raw: unknown): OAuthClient | null {
25
- if (!isRecord(raw)) return null;
26
- const clientId = stringOf(raw.clientId);
27
- if (clientId === '') return null;
28
- const clientSecret = stringOf(raw.clientSecret);
29
- return { clientId, ...(clientSecret === '' ? {} : { clientSecret }) };
30
- }
31
-
32
- function readOAuth(raw: Record<string, unknown>): ConnectorAuth {
33
- const accessToken = stringOf(raw.accessToken);
34
- const clientId = stringOf(raw.clientId);
35
- const tokenEndpoint = stringOf(raw.tokenEndpoint);
36
- if (accessToken === '' || clientId === '' || tokenEndpoint === '')
37
- return { kind: 'none' };
38
- const refreshToken = stringOf(raw.refreshToken);
39
- const clientSecret = stringOf(raw.clientSecret);
40
- const scope = stringOf(raw.scope);
41
- return {
42
- kind: 'oauth',
43
- accessToken,
44
- clientId,
45
- tokenEndpoint,
46
- issuer: stringOf(raw.issuer),
47
- ...(typeof raw.resource === 'string' ? { resource: raw.resource } : {}),
48
- ...(typeof raw.tokenEndpointAuthMethod === 'string' ? { tokenEndpointAuthMethod: raw.tokenEndpointAuthMethod } : {}),
49
- ...(refreshToken === '' ? {} : { refreshToken }),
50
- ...(clientSecret === '' ? {} : { clientSecret }),
51
- ...(scope === '' ? {} : { scope }),
52
- ...(typeof raw.expiresAt === 'number' ? { expiresAt: raw.expiresAt } : {}),
53
- };
54
- }
55
-
56
- function readAuth(raw: unknown): ConnectorAuth {
57
- if (!isRecord(raw)) return { kind: 'none' };
58
- if (raw.kind === 'oauth') return readOAuth(raw);
59
- if (raw.kind !== 'header') return { kind: 'none' };
60
- const name = stringOf(raw.name);
61
- const value = stringOf(raw.value);
62
- if (name === '' || value === '') return { kind: 'none' };
63
- return { kind: 'header', name, value };
64
- }
65
-
66
- function readVerified(raw: unknown): VerifiedRecord {
67
- const record = isRecord(raw) ? raw : {};
68
- return { at: stringOf(record.at), server: stringOf(record.server) };
69
- }
70
-
71
- const TOOL_NAME_MAX = 128;
72
- const TOOL_GROUPS_MAX = 1000;
73
-
74
- function readToolGroups(raw: unknown): Record<string, ToolGroup> | undefined {
75
- if (!isRecord(raw)) return undefined;
76
- const kept = Object.entries(raw)
77
- .filter((entry): entry is [string, ToolGroup] => entry[0].length <= TOOL_NAME_MAX && (entry[1] === 'read' || entry[1] === 'write'))
78
- .slice(0, TOOL_GROUPS_MAX);
79
- return kept.length > 0 ? Object.fromEntries(kept) : undefined;
80
- }
81
-
82
- export function readConfig(raw: unknown): ConnectorConfig {
83
- const record = isRecord(raw) ? raw : {};
84
- const auth = readAuth(record.auth);
85
- const policy = parsePolicy(record.policy, 'connectors.json');
86
- const toolGroups = readToolGroups(record.toolGroups);
87
- return {
88
- auth,
89
- createdAt: stringOf(record.createdAt),
90
- verified: readVerified(record.verified),
91
- oauth: record.oauth === true || auth.kind === 'oauth',
92
- client: readClient(record.client),
93
- ...(policy === undefined || Object.keys(policy).length === 0 ? {} : { policy }),
94
- ...(toolGroups === undefined ? {} : { toolGroups }),
95
- };
96
- }
97
-
98
- export function signInState(config: ConnectorConfig): ConnectorSignIn {
99
- if (config.auth.kind === 'oauth') return 'connected';
100
- return config.oauth ? 'disconnected' : null;
101
- }
102
-
103
- export function stamp(server: VerifiedServer): VerifiedRecord {
104
- return { at: new Date().toISOString(), server: server.server };
105
- }
@@ -1,18 +0,0 @@
1
- export interface ConnectorHealth {
2
- ok: boolean;
3
- reason: string | null;
4
- at: string;
5
- }
6
-
7
- const seen = new Map<string, ConnectorHealth>();
8
-
9
- export function noteHealth(id: string, ok: boolean, reason: string | null = null): void {
10
- seen.set(id, { ok, reason: ok ? null : reason, at: new Date().toISOString() });
11
- }
12
-
13
- export const healthOf = (id: string): ConnectorHealth | null => seen.get(id) ?? null;
14
-
15
- export function forgetHealth(id?: string): void {
16
- if (id === undefined) seen.clear();
17
- else seen.delete(id);
18
- }
@@ -1,60 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import { isRecord } from '@metro-labs/core/is-record';
3
- import { parseId } from '@metro-labs/core/ids';
4
- import { ApiError } from '@metro-labs/http/api-error';
5
- import { readJsonBody, sessionRoute } from '@metro-labs/http/api-http';
6
- import { publicOAuthResource } from '@metro-labs/http/connector-public-url';
7
- import type { ConnectorMigrations, MigrationAction } from './migration-control.js';
8
-
9
- const PATH = '/api/connectors/migrations';
10
- const actions: readonly MigrationAction[] = ['move', 'prepare', 'reconcile', 'cutover', 'rollback', 'resolve'];
11
- export type MigrationApiDeps = Pick<ConnectorMigrations, 'status' | 'run'>;
12
-
13
- interface MigrationInput {
14
- sourceId: string;
15
- action: MigrationAction;
16
- scope: string;
17
- operationId: string | null;
18
- uncertainty: string | null;
19
- oauthResource?: string;
20
- }
21
-
22
- function operationOf(value: unknown): string | null {
23
- if (value === null) return null;
24
- const id = parseId(value);
25
- if (id === null) throw new ApiError('A migration needs its current operation ID, or null for an unprepared source.', 400);
26
- return id;
27
- }
28
-
29
- function receiptOf(body: Record<string, unknown>, action: MigrationAction): string | null {
30
- if (action !== 'resolve') {
31
- if (Object.hasOwn(body, 'uncertainty')) throw new ApiError('Only request settlement accepts an uncertainty receipt.', 400);
32
- return null;
33
- }
34
- if (typeof body.uncertainty !== 'string' || !/^[a-f0-9]{64}$/.test(body.uncertainty)) throw new ApiError('Confirming settled vendor requests needs the current uncertainty receipt.', 400);
35
- return body.uncertainty;
36
- }
37
-
38
- function inputOf(body: unknown): MigrationInput {
39
- if (!isRecord(body) || Object.keys(body).some((key) => !['scope', 'sourceId', 'action', 'operationId', 'uncertainty', 'oauthResource'].includes(key)) || typeof body.scope !== 'string' || !/^[a-f0-9]{64}$/.test(body.scope)) {
40
- throw new ApiError('A migration needs its connector, action and current enrollment scope.', 400);
41
- }
42
- const sourceId = parseId(body.sourceId);
43
- const action = actions.find((value) => value === body.action);
44
- if (sourceId === null || action === undefined) throw new ApiError('A migration needs a valid connector and action.', 400);
45
- return {
46
- sourceId, action, scope: body.scope, operationId: operationOf(body.operationId), uncertainty: receiptOf(body, action),
47
- ...(body.oauthResource === undefined ? {} : { oauthResource: publicOAuthResource(body.oauthResource) }),
48
- };
49
- }
50
-
51
- export function handleMigrationRequest(req: IncomingMessage, res: ServerResponse, deps: MigrationApiDeps): boolean {
52
- return sessionRoute(req, res, { methods: { [PATH]: ['GET', 'POST'] }, admin: true, label: 'connector-migration-api' }, async (session) => {
53
- const body = req.method === 'POST' ? await readJsonBody(req) : null;
54
- const current = deps.status();
55
- if (current.owner === null || current.owner !== session.subject) throw new ApiError('This agent belongs to another organization.', 403);
56
- if (req.method !== 'POST') return current;
57
- const { sourceId, action, scope, operationId, uncertainty, oauthResource } = inputOf(body);
58
- return deps.run(sourceId, action, scope, operationId, uncertainty, oauthResource);
59
- });
60
- }