@stage-labs/metro 0.1.0-beta.31 → 0.1.0-beta.32

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 (50) hide show
  1. package/dist/cli.js +5 -0
  2. package/dist/runtime.js +4 -1
  3. package/dist/serve.js +57 -0
  4. package/package.json +1 -1
  5. package/runtime/node_modules/@metro-labs/mcp/package.json +1 -0
  6. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +5 -5
  7. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +16 -15
  8. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-connector-api.ts +9 -9
  9. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-pair.ts +1 -1
  10. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +8 -7
  11. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +5 -3
  12. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +2 -2
  13. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +109 -71
  14. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +16 -7
  15. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +14 -14
  16. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +8 -8
  17. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +3 -3
  18. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +12 -2
  19. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +11 -4
  20. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +158 -0
  21. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-secret.ts +21 -0
  22. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +31 -0
  23. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +1 -1
  24. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +10 -0
  25. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +17 -17
  26. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +2 -2
  27. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +1 -1
  28. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +4 -0
  29. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-config.ts +27 -0
  30. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +9 -9
  31. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-auth.ts +82 -0
  32. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-nonces.ts +43 -0
  33. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-routes.ts +89 -0
  34. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +2 -5
  35. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +15 -16
  36. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-connectors.ts +9 -9
  37. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +31 -31
  38. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +250 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +152 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +2 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +16 -7
  42. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +48 -39
  43. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +7 -7
  44. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +2 -1
  45. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +22 -12
  46. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +4 -4
  47. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +1 -1
  48. package/runtime/runtime.json +1 -1
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
@@ -4,7 +4,13 @@ import { type Server } from 'node:http';
4
4
  import { selfLine, userSelf } from './events.js';
5
5
  import { setTrainCallBackend } from './train-call.js';
6
6
  import { errMsg, log, logFatalSync } from './log.js';
7
- import { acquireLock, loadMetroEnv, STATE_DIR } from './paths.js';
7
+ import {
8
+ acquireLock,
9
+ isLocalMode,
10
+ loadMetroEnv,
11
+ STATE_DIR,
12
+ trainsDir,
13
+ } from './paths.js';
8
14
  import { installCrashGuard, markDaemonReady } from './crash-guard.js';
9
15
  import {
10
16
  loadTunnelConfig,
@@ -12,7 +18,7 @@ import {
12
18
  warnOnLegacyWebhooks,
13
19
  webhookPort,
14
20
  } from './tunnel.js';
15
- import { TrainSupervisor, TRAINS_DIR } from './supervisor.js';
21
+ import { TrainSupervisor } from './supervisor.js';
16
22
  import {
17
23
  makeEmit,
18
24
  startWebhookServer,
@@ -27,10 +33,15 @@ import {
27
33
  } from '../db/runtimes.js';
28
34
  import {
29
35
  blockedStationsFor,
30
- mintAgentCodeForEmail,
31
- releaseRuntimeForEmail,
36
+ mintAgentCodeForUser,
37
+ releaseRuntimeForUser,
32
38
  } from '../db/runtime-admin.js';
33
39
  import { loadAgentForRuntime, localAgentKey } from '../db/materialize.js';
40
+ import { fileSource } from '../db/file-source.js';
41
+ import { ensureLocalSessionSecret } from './local-secret.js';
42
+ import { localConnectHint, localSessionApis } from './local-mode.js';
43
+ import type { ModeInfo } from './mode-api.js';
44
+ import type { SessionApis } from './session-apis.js';
34
45
  import {
35
46
  agentLiveness,
36
47
  closeAgentSession,
@@ -48,45 +59,46 @@ import {
48
59
  materializeFromDb,
49
60
  reloadAccountsFromDb,
50
61
  reloadFrom,
62
+ type ReloadedStations,
51
63
  } from '../db/materialize.js';
52
64
  import {
53
65
  httpSource,
54
66
  runtimeConfigFromEnv,
55
67
  startRuntimePoller,
56
68
  } from './runtime-source.js';
57
- import { createAgentForEmail, deleteAgentForEmail, listAgentsForEmail, ownedAgentOrThrow, resetAgentKeyForEmail, type ResetAgentKey,} from '../db/agent-admin.js';
69
+ import { createAgentForUser, deleteAgentForUser, listAgentsForUser, ownedAgentOrThrow, resetAgentKeyForUser, type ResetAgentKey,} from '../db/agent-admin.js';
58
70
  import {
59
71
  attachAccountToAgent,
60
72
  detachAccountFromAgent,
61
73
  } from '../db/account-attach.js';
62
74
  import {
63
- createConnectorForEmail,
64
- createPendingConnectorForEmail,
65
- deleteConnectorForEmail,
66
- disconnectConnectorForEmail,
75
+ createConnectorForUser,
76
+ createPendingConnectorForUser,
77
+ deleteConnectorForUser,
78
+ disconnectConnectorForUser,
67
79
  connectorNamesByIds,
68
- getConnectorForEmail,
69
- listConnectorsForEmail,
70
- renameConnectorForEmail,
71
- reconnectConnectorForEmail,
72
- verifyConnectorForEmail,
80
+ getConnectorForUser,
81
+ listConnectorsForUser,
82
+ renameConnectorForUser,
83
+ reconnectConnectorForUser,
84
+ verifyConnectorForUser,
73
85
  } from '../db/connectors.js';
74
86
  import { relayTarget } from '../db/connector-relay.js';
75
87
  import {
76
- addMemberForEmail,
77
- createProjectForEmail,
78
- deleteProjectForEmail,
79
- listMembersForEmail,
80
- listProjectsForEmail,
81
- removeMemberForEmail,
82
- renameProjectForEmail,
83
- setMemberRoleForEmail,
88
+ addMemberForUser,
89
+ createProjectForUser,
90
+ deleteProjectForUser,
91
+ listMembersForUser,
92
+ listProjectsForUser,
93
+ removeMemberForUser,
94
+ renameProjectForUser,
95
+ setMemberRoleForUser,
84
96
  } from '../db/projects.js';
85
97
  import {
86
- addConnectorToAgentForEmail,
87
- agentConnectorsForEmail,
98
+ addConnectorToAgentForUser,
99
+ agentConnectorsForUser,
88
100
  connectorIdsByAgent,
89
- removeConnectorFromAgentForEmail,
101
+ removeConnectorFromAgentForUser,
90
102
  } from '../db/agent-connectors.js';
91
103
  import type { AgentConnectorApiDeps } from './agent-connector-api.js';
92
104
  import type { StationName } from '../db/schema.js';
@@ -124,12 +136,15 @@ setTrainCallBackend((train, action, args) =>
124
136
  );
125
137
 
126
138
  const runtime = runtimeConfigFromEnv();
127
- const localSource = runtime === null ? null : httpSource(runtime);
139
+ const linkedSource = runtime === null ? null : httpSource(runtime);
140
+ const localSource = linkedSource ?? (isLocalMode() ? fileSource : null);
128
141
 
129
142
  function announceLocalEndpoint(): void {
143
+ if (isLocalMode()) process.stderr.write(`\n${localConnectHint(webhookPort())}`);
130
144
  const key = localAgentKey();
131
145
  if (key === null) {
132
- log.warn('this agent has no key — reset it in the web UI to connect an agent');
146
+ if (isLocalMode()) log.info('no agent on this machine yet');
147
+ else log.warn('this agent has no key — reset it in the web UI to connect an agent');
133
148
  return;
134
149
  }
135
150
  const url = `http://127.0.0.1:${String(webhookPort())}/mcp?token=${key}`;
@@ -153,8 +168,11 @@ async function stopLocalStations(): Promise<void> {
153
168
  for (const station of supervisor.running()) await supervisor.stopTrain(station);
154
169
  }
155
170
 
171
+ const reloadStations = (): Promise<ReloadedStations> =>
172
+ localSource === null ? reloadAccountsFromDb() : reloadFrom(localSource);
173
+
156
174
  async function syncStations(station: StationName): Promise<void> {
157
- const { removed } = await reloadAccountsFromDb();
175
+ const { removed } = await reloadStations();
158
176
  if (stationByName(station)?.hasTrain === false) return;
159
177
  if (removed.includes(station)) await supervisor.stopTrain(station);
160
178
  else supervisor.requestReload(station);
@@ -162,11 +180,11 @@ async function syncStations(station: StationName): Promise<void> {
162
180
 
163
181
  const attachSessions = new AttachSessions({
164
182
  authorize: async (owner) => {
165
- await ownedAgentOrThrow(owner.email, owner.agentId);
183
+ await ownedAgentOrThrow(owner.subject, owner.agentId);
166
184
  },
167
185
  complete: async (owner, station, config) => {
168
186
  const ref = await attachAccountToAgent(
169
- owner.email,
187
+ owner.subject,
170
188
  owner.agentId,
171
189
  station,
172
190
  config,
@@ -186,10 +204,10 @@ const attachSessions = new AttachSessions({
186
204
  });
187
205
 
188
206
  async function resetAgentKey(
189
- email: string,
207
+ subject: string,
190
208
  id: string,
191
209
  ): Promise<ResetAgentKey> {
192
- const reset = await resetAgentKeyForEmail(email, id);
210
+ const reset = await resetAgentKeyForUser(subject, id);
193
211
  const closed = await closeAgentSession(id);
194
212
  log.info(
195
213
  { agent: reset.name, id: reset.id, sessionClosed: closed },
@@ -200,14 +218,14 @@ async function resetAgentKey(
200
218
 
201
219
  const agentApi: AgentApiDeps = {
202
220
  attachSessions,
203
- listAgents: listAgentsForEmail,
204
- createAgent: createAgentForEmail,
205
- deleteAgent: deleteAgentForEmail,
221
+ listAgents: listAgentsForUser,
222
+ createAgent: createAgentForUser,
223
+ deleteAgent: deleteAgentForUser,
206
224
  resetKey: resetAgentKey,
207
225
  gatherAccounts: gatherAccountsForAgents,
208
226
  capabilities: accountStationCapabilities,
209
227
  liveness: agentLiveness,
210
- releaseRuntime: releaseRuntimeForEmail,
228
+ releaseRuntime: releaseRuntimeForUser,
211
229
  connectorIds: connectorIdsByAgent,
212
230
  runtimes: runtimeLabels,
213
231
  prepareAccount,
@@ -225,66 +243,86 @@ const runApi: RunApiDeps = {
225
243
  };
226
244
 
227
245
  const agentConnectorApi: AgentConnectorApiDeps = {
228
- agentConnectors: agentConnectorsForEmail,
229
- addConnector: addConnectorToAgentForEmail,
230
- removeConnector: removeConnectorFromAgentForEmail,
231
- mintCode: mintAgentCodeForEmail,
246
+ agentConnectors: agentConnectorsForUser,
247
+ addConnector: addConnectorToAgentForUser,
248
+ removeConnector: removeConnectorFromAgentForUser,
249
+ mintCode: mintAgentCodeForUser,
232
250
  };
233
251
 
234
252
  const projectApi: ProjectApiDeps = {
235
- listProjects: listProjectsForEmail,
236
- createProject: createProjectForEmail,
237
- renameProject: renameProjectForEmail,
238
- deleteProject: deleteProjectForEmail,
239
- listMembers: listMembersForEmail,
240
- addMember: addMemberForEmail,
241
- setMemberRole: setMemberRoleForEmail,
242
- removeMember: removeMemberForEmail,
253
+ listProjects: listProjectsForUser,
254
+ createProject: createProjectForUser,
255
+ renameProject: renameProjectForUser,
256
+ deleteProject: deleteProjectForUser,
257
+ listMembers: listMembersForUser,
258
+ addMember: addMemberForUser,
259
+ setMemberRole: setMemberRoleForUser,
260
+ removeMember: removeMemberForUser,
243
261
  };
244
262
 
245
263
  const connectorApi: ConnectorApiDeps = {
246
- listConnectors: listConnectorsForEmail,
264
+ listConnectors: listConnectorsForUser,
247
265
  connectorNamesByIds,
248
- agentConnectors: agentConnectorsForEmail,
266
+ agentConnectors: agentConnectorsForUser,
249
267
  fenceRuntime,
250
- renameConnector: renameConnectorForEmail,
251
- createConnector: createConnectorForEmail,
252
- createPendingConnector: createPendingConnectorForEmail,
253
- reconnectConnector: reconnectConnectorForEmail,
254
- getConnector: getConnectorForEmail,
255
- verifyConnector: verifyConnectorForEmail,
256
- disconnectConnector: disconnectConnectorForEmail,
257
- deleteConnector: deleteConnectorForEmail,
268
+ renameConnector: renameConnectorForUser,
269
+ createConnector: createConnectorForUser,
270
+ createPendingConnector: createPendingConnectorForUser,
271
+ reconnectConnector: reconnectConnectorForUser,
272
+ getConnector: getConnectorForUser,
273
+ verifyConnector: verifyConnectorForUser,
274
+ disconnectConnector: disconnectConnectorForUser,
275
+ deleteConnector: deleteConnectorForUser,
258
276
  };
259
277
 
278
+ const hostedMode = (): ModeInfo => ({
279
+ mode: linkedSource === null ? 'hosted' : 'linked',
280
+ owner: null,
281
+ project: null,
282
+ });
283
+
284
+ function sessionApis(): SessionApis {
285
+ if (isLocalMode())
286
+ return localSessionApis({
287
+ syncStations,
288
+ closeAgentSession,
289
+ gatherAccounts: gatherAccountsForAgents,
290
+ capabilities: accountStationCapabilities,
291
+ liveness: agentLiveness,
292
+ prepareAccount,
293
+ });
294
+ return {
295
+ agentApi,
296
+ agentConnectorApi,
297
+ connectorApi,
298
+ projectApi,
299
+ runApi,
300
+ relayApi: { target: relayTarget, fence: fenceRuntime },
301
+ mode: hostedMode,
302
+ };
303
+ }
304
+
260
305
  async function main(): Promise<void> {
306
+ if (isLocalMode()) ensureLocalSessionSecret();
261
307
  if (localSource === null) await materializeFromDb();
262
- else await materializeFrom(localSource);
308
+ else await materializeFrom(localSource, { allowEmpty: linkedSource === null });
263
309
  warnOnLegacyWebhooks();
264
310
  supervisor.start();
265
311
  const metroMcp = await createMetroMcp();
266
312
  webhookServer = await startWebhookServer(
267
313
  emit,
268
- {
269
- agentApi,
270
- agentConnectorApi,
271
- connectorApi,
272
- projectApi,
273
- runApi,
274
- relayApi: { target: relayTarget, fence: fenceRuntime },
275
- },
314
+ sessionApis(),
276
315
  metroMcp.httpHandler,
277
316
  metroCall,
278
317
  );
279
318
  metroMcp.startInbound();
280
319
  startUploadReaper();
281
- if (localSource !== null) {
320
+ if (linkedSource !== null)
282
321
  startRuntimePoller({ sync: syncLocal, stopAll: stopLocalStations });
283
- announceLocalEndpoint();
284
- }
322
+ if (localSource !== null) announceLocalEndpoint();
285
323
  tunnel?.start();
286
324
  log.info(
287
- { tunnel: !!tunnel, trainsDir: TRAINS_DIR, mcp: '/' },
325
+ { tunnel: !!tunnel, trainsDir: trainsDir(), mcp: '/' },
288
326
  'dispatcher ready',
289
327
  );
290
328
  markDaemonReady();
@@ -13,7 +13,7 @@ import { extractToken } from '../mcp/request-identity.js';
13
13
  import { publicBaseOrDefault } from './attach-serve.js';
14
14
  import { AGENT_CODE_RE, takeAgentCode } from './agent-pair.js';
15
15
  import { relayServersJson } from './connector-json.js';
16
- import { sessionTtlFromEnv } from './google-oauth.js';
16
+ import { sessionTtlFromEnv } from './session-config.js';
17
17
  import { signAgentToken } from './session.js';
18
18
  import type { ConnectorApiDeps } from './connector-api.js';
19
19
 
@@ -58,14 +58,19 @@ async function handleClaim(
58
58
  });
59
59
  return;
60
60
  }
61
- const agent = await deps.agentConnectors(taken.email, taken.agentId);
61
+ const agent = await deps.agentConnectors(taken.subject, taken.agentId);
62
62
  const token = signAgentToken(
63
- { email: taken.email, agentId: agent.id },
63
+ { subject: taken.subject, agentId: agent.id },
64
64
  secret,
65
65
  { ttlSec: sessionTtlFromEnv() },
66
66
  );
67
- log.info({ email: taken.email, agent: agent.id }, 'cli: code claimed');
68
- sendJson(req, res, 200, { token, email: taken.email, agent: agent.name });
67
+ log.info({ subject: taken.subject, agent: agent.id }, 'cli: code claimed');
68
+ sendJson(req, res, 200, {
69
+ token,
70
+ email: taken.subject,
71
+ subject: taken.subject,
72
+ agent: agent.name,
73
+ });
69
74
  }
70
75
 
71
76
  async function handleRead(
@@ -80,9 +85,13 @@ async function handleRead(
80
85
  return;
81
86
  }
82
87
  await assertLease(who, deps.fenceRuntime);
83
- const agent = await deps.agentConnectors(who.email, who.agentId);
88
+ const agent = await deps.agentConnectors(who.subject, who.agentId);
84
89
  if (path === SESSION_PATH) {
85
- sendJson(req, res, 200, { email: who.email, agent: agent.name });
90
+ sendJson(req, res, 200, {
91
+ email: who.subject,
92
+ subject: who.subject,
93
+ agent: agent.name,
94
+ });
86
95
  return;
87
96
  }
88
97
  const entries = await deps.connectorNamesByIds(agent.connectorIds);
@@ -35,23 +35,23 @@ const asText = (value: unknown): string =>
35
35
  typeof value === 'string' ? value : '';
36
36
 
37
37
  export interface ConnectorApiDeps extends OAuthRouteDeps {
38
- listConnectors: (email: string, project: string) => Promise<Connector[]>;
38
+ listConnectors: (subject: string, project: string) => Promise<Connector[]>;
39
39
  connectorNamesByIds: (ids: string[]) => Promise<RelayServerEntry[]>;
40
- agentConnectors: (email: string, agentId: string) => Promise<AgentConnectors>;
40
+ agentConnectors: (subject: string, agentId: string) => Promise<AgentConnectors>;
41
41
  fenceRuntime: Fence;
42
42
  createConnector: (
43
- email: string,
43
+ subject: string,
44
44
  project: string,
45
45
  input: ConnectorInput,
46
46
  ) => Promise<Connector>;
47
- verifyConnector: (email: string, id: string) => Promise<ConnectorCheck>;
48
- disconnectConnector: (email: string, id: string) => Promise<Connector>;
47
+ verifyConnector: (subject: string, id: string) => Promise<ConnectorCheck>;
48
+ disconnectConnector: (subject: string, id: string) => Promise<Connector>;
49
49
  renameConnector: (
50
- email: string,
50
+ subject: string,
51
51
  id: string,
52
52
  name: string,
53
53
  ) => Promise<Connector>;
54
- deleteConnector: (email: string, id: string) => Promise<DeletedConnector>;
54
+ deleteConnector: (subject: string, id: string) => Promise<DeletedConnector>;
55
55
  }
56
56
 
57
57
  type Routable =
@@ -115,7 +115,7 @@ async function handleList(
115
115
  sendJson(req, res, 400, { error: 'a project is required' });
116
116
  return;
117
117
  }
118
- const rows = await deps.listConnectors(session.email, project);
118
+ const rows = await deps.listConnectors(session.subject, project);
119
119
  sendJson(req, res, 200, {
120
120
  connectors: rows.map((row) => connectorPayload(row)),
121
121
  });
@@ -135,7 +135,7 @@ async function handleCreate(
135
135
  }
136
136
  const offered = asText(bodyField(body, 'value')).trim() !== '';
137
137
  try {
138
- const created = await deps.createConnector(session.email, project, {
138
+ const created = await deps.createConnector(session.subject, project, {
139
139
  name: bodyField(body, 'name'),
140
140
  url: bodyField(body, 'url'),
141
141
  header: bodyField(body, 'header'),
@@ -161,7 +161,7 @@ async function handleVerify(
161
161
  session: ApiSession,
162
162
  id: string,
163
163
  ): Promise<void> {
164
- const check = await deps.verifyConnector(session.email, id);
164
+ const check = await deps.verifyConnector(session.subject, id);
165
165
  log.info(
166
166
  { id: check.id, name: check.name, ok: check.ok },
167
167
  'connector-api: re-verified connector',
@@ -176,7 +176,7 @@ async function handleDisconnect(
176
176
  session: ApiSession,
177
177
  id: string,
178
178
  ): Promise<void> {
179
- const row = await deps.disconnectConnector(session.email, id);
179
+ const row = await deps.disconnectConnector(session.subject, id);
180
180
  log.info(
181
181
  { id: row.id, name: row.name },
182
182
  'connector-api: signed the connector out',
@@ -191,7 +191,7 @@ async function handleDelete(
191
191
  session: ApiSession,
192
192
  id: string,
193
193
  ): Promise<void> {
194
- const gone = await deps.deleteConnector(session.email, id);
194
+ const gone = await deps.deleteConnector(session.subject, id);
195
195
  log.info(
196
196
  { id: gone.id, name: gone.name },
197
197
  'connector-api: deleted connector',
@@ -210,7 +210,7 @@ async function handleConnector(
210
210
  await handleDelete(req, res, deps, session, id);
211
211
  return;
212
212
  }
213
- const row = await deps.getConnector(session.email, id);
213
+ const row = await deps.getConnector(session.subject, id);
214
214
  sendJson(req, res, 200, connectorPayload(row, true));
215
215
  }
216
216
 
@@ -226,7 +226,7 @@ async function handleRename(
226
226
  sendJson(req, res, 400, { error: 'name is required' });
227
227
  return;
228
228
  }
229
- const row = await deps.renameConnector(session.email, id, name);
229
+ const row = await deps.renameConnector(session.subject, id, name);
230
230
  log.info({ id: row.id, name: row.name }, 'connector-api: renamed connector');
231
231
  sendJson(req, res, 200, connectorPayload(row));
232
232
  }
@@ -24,16 +24,16 @@ export function hostOf(url: string): string {
24
24
 
25
25
  export interface OAuthRouteDeps {
26
26
  createPendingConnector: (
27
- email: string,
27
+ subject: string,
28
28
  project: string,
29
29
  input: { name: unknown; url: unknown },
30
30
  ) => Promise<Connector>;
31
31
  reconnectConnector: (
32
- email: string,
32
+ subject: string,
33
33
  id: string,
34
34
  auth: OAuthAuth,
35
35
  ) => Promise<Connector>;
36
- getConnector: (email: string, id: string) => Promise<Connector>;
36
+ getConnector: (subject: string, id: string) => Promise<Connector>;
37
37
  }
38
38
 
39
39
  export async function startOAuth(
@@ -46,12 +46,12 @@ export async function startOAuth(
46
46
  payload: (row: Connector) => Record<string, unknown>,
47
47
  ): Promise<void> {
48
48
  const url = parseConnectorUrl(bodyField(body, 'url'));
49
- const row = await deps.createPendingConnector(session.email, project, {
49
+ const row = await deps.createPendingConnector(session.subject, project, {
50
50
  name: bodyField(body, 'name'),
51
51
  url: bodyField(body, 'url'),
52
52
  });
53
53
  const authorize = await beginOAuth({
54
- email: session.email,
54
+ subject: session.subject,
55
55
  name: row.name,
56
56
  url,
57
57
  returnTo: asText(bodyField(body, 'returnTo')),
@@ -71,11 +71,11 @@ export async function handleConnect(
71
71
  session: ApiSession,
72
72
  id: string,
73
73
  ): Promise<void> {
74
- const row = await deps.getConnector(session.email, id);
74
+ const row = await deps.getConnector(session.subject, id);
75
75
  const url = parseConnectorUrl(row.url);
76
76
  const body = await readJsonBody(req);
77
77
  const authorize = await beginOAuth({
78
- email: session.email,
78
+ subject: session.subject,
79
79
  name: row.name,
80
80
  url,
81
81
  returnTo: asText(bodyField(body, 'returnTo')),
@@ -110,7 +110,7 @@ async function saveOAuth(
110
110
  entry: PendingAuth,
111
111
  auth: OAuthAuth,
112
112
  ): Promise<Connector> {
113
- return deps.reconnectConnector(entry.email, entry.connectorId, auth);
113
+ return deps.reconnectConnector(entry.subject, entry.connectorId, auth);
114
114
  }
115
115
 
116
116
  async function settleCallback(
@@ -4,7 +4,7 @@ import {
4
4
  type OAuthAuth,
5
5
  type OAuthTokens,
6
6
  } from './connector-verify.js';
7
- import { validateReturnTo } from './google-oauth.js';
7
+ import { validateReturnTo } from './session-config.js';
8
8
  import {
9
9
  authorizeUrl,
10
10
  exchangeCode,
@@ -32,7 +32,7 @@ const resourceOf = (url: URL): string => url.toString();
32
32
 
33
33
  export interface BeginInput {
34
34
  connectorId: string;
35
- email: string;
35
+ subject: string;
36
36
  name: string;
37
37
  url: URL;
38
38
  returnTo: string;
@@ -47,7 +47,7 @@ export async function beginOAuth(input: BeginInput): Promise<string> {
47
47
  const verifier = newVerifier();
48
48
  const resource = resourceOf(input.url);
49
49
  const state = startPending({
50
- email: input.email,
50
+ subject: input.subject,
51
51
  name: input.name,
52
52
  url: input.url.toString(),
53
53
  resource,
@@ -27,7 +27,8 @@ import {
27
27
  } from './tunnel.js';
28
28
  import { attachmentEventUrl, handleAttachRequest } from './attach-serve.js';
29
29
  import { webhookEntry } from '@metro-labs/webhook';
30
- import { handleGoogleAuthRequest } from './google-oauth.js';
30
+ import { handleSiweAuthRequest } from './siwe-routes.js';
31
+ import { handleModeRequest } from './mode-api.js';
31
32
  import { handleUploadRequest } from './upload-api.js';
32
33
  import {
33
34
  handleMonitorRequest,
@@ -323,6 +324,15 @@ async function handleWebhookRoute(
323
324
  return true;
324
325
  }
325
326
 
327
+ function handleSignInRoutes(
328
+ req: IncomingMessage,
329
+ res: ServerResponse,
330
+ apis: SessionApis,
331
+ ): boolean {
332
+ if (handleSiweAuthRequest(req, res, apis.siwe)) return true;
333
+ return apis.mode !== undefined && handleModeRequest(req, res, apis.mode);
334
+ }
335
+
326
336
  async function handlePreMcpRoutes(
327
337
  req: IncomingMessage,
328
338
  res: ServerResponse,
@@ -331,7 +341,7 @@ async function handlePreMcpRoutes(
331
341
  monitorCall?: MonitorCall,
332
342
  ): Promise<boolean> {
333
343
  if (handleHealth(req, res)) return true;
334
- if (await handleGoogleAuthRequest(req, res)) return true;
344
+ if (handleSignInRoutes(req, res, apis)) return true;
335
345
  if (handleSessionApis(req, res, apis)) return true;
336
346
  if (handleUploadRequest(req, res)) return true;
337
347
  if (handleAttachRequest(req, res)) return true;
@@ -71,9 +71,16 @@ export function daemonSelf(): Line {
71
71
  }
72
72
 
73
73
  export function selfLine(): Line | null {
74
- if (process.env.CLAUDECODE) {
75
- const s = claudeSessionId();
76
- return s ? Line.claude(claudeUserId(), s) : null;
74
+ if (!process.env.CLAUDECODE) return null;
75
+ const s = claudeSessionId();
76
+ if (!s) return null;
77
+ try {
78
+ return Line.claude(claudeUserId(), s);
79
+ } catch (e) {
80
+ log.warn(
81
+ { reason: errMsg(e) },
82
+ 'could not resolve a Claude account id; relaying with no self line',
83
+ );
84
+ return null;
77
85
  }
78
- return null;
79
86
  }