@cmmd-center/forge 0.9.1 → 0.9.3

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 (3) hide show
  1. package/dist/bin.cjs +124 -20
  2. package/dist/bin.mjs +124 -20
  3. package/package.json +19 -19
package/dist/bin.cjs CHANGED
@@ -32042,6 +32042,25 @@ function readHiddenLine(input) {
32042
32042
  });
32043
32043
  }
32044
32044
  //#endregion
32045
+ //#region src/connect/loginRedirect.ts
32046
+ /**
32047
+ * The OAuth redirect URI for `connect login --email`.
32048
+ *
32049
+ * This names where the *client* lives, not where CMMD lives. Sending CMMD's own
32050
+ * origin is rejected with a 400 — which is exactly how password login first
32051
+ * shipped broken.
32052
+ *
32053
+ * The exchange never actually fetches this URL: both legs run in-process and
32054
+ * the authorization code is read off the redirect's Location header. It only
32055
+ * has to match a URI CMMD accepts. CMMD follows the usual native-app rule of
32056
+ * matching loopback redirects on any port, so the configured port is sent
32057
+ * as-is, keeping the CLI consistent with what the browser flow would use.
32058
+ */
32059
+ const DEFAULT_PORT$1 = 3e3;
32060
+ function resolveLoginRedirectUri(input) {
32061
+ return `http://localhost:${typeof input.port === "number" && Number.isInteger(input.port) && input.port > 0 && input.port <= 65535 ? input.port : DEFAULT_PORT$1}/api/auth/callback`;
32062
+ }
32063
+ //#endregion
32045
32064
  //#region src/connect/passwordLogin.ts
32046
32065
  async function runPasswordLogin(deps) {
32047
32066
  const email = deps.email.trim();
@@ -63823,7 +63842,7 @@ function normalizeNumberish(value) {
63823
63842
  }
63824
63843
  //#endregion
63825
63844
  //#region package.json
63826
- var version$1 = "0.9.1";
63845
+ var version$1 = "0.9.3";
63827
63846
  //#endregion
63828
63847
  //#region src/sentry.ts
63829
63848
  const SERVER_APP_NAME = "forge-server";
@@ -66398,11 +66417,14 @@ function resolveConnectDeviceIdentity(identityPath, options) {
66398
66417
  function resolveConnectRelayConfig(stateDir) {
66399
66418
  if (!readConnectState(node_path.join(stateDir, "connect.json")).linked) return {
66400
66419
  linked: false,
66401
- bridgeToken: void 0
66420
+ bridgeToken: void 0,
66421
+ runtimeId: void 0
66402
66422
  };
66423
+ const secrets = resolveConnectDeviceSecrets(node_path.join(stateDir, "connect-device.json"));
66403
66424
  return {
66404
66425
  linked: true,
66405
- bridgeToken: resolveConnectDeviceSecrets(node_path.join(stateDir, "connect-device.json")).bridgeToken
66426
+ bridgeToken: secrets.bridgeToken,
66427
+ runtimeId: secrets.runtimeId
66406
66428
  };
66407
66429
  }
66408
66430
  //#endregion
@@ -66568,6 +66590,7 @@ var ServerConfig = class ServerConfig extends require_Schema$1.Service()("forge/
66568
66590
  desktopBootstrapToken: void 0,
66569
66591
  desktopRelayBridgeToken: void 0,
66570
66592
  connectRelayBridgeToken: void 0,
66593
+ connectRelayRuntimeId: void 0,
66571
66594
  connectRelayLinked: false,
66572
66595
  connectDisplayName: "forge",
66573
66596
  connectPlatform: "linux",
@@ -68733,6 +68756,20 @@ function registerProxyRoute(route) {
68733
68756
  return add$1(route.method, route.forgePath, handler);
68734
68757
  }
68735
68758
  //#endregion
68759
+ //#region src/connect/credentialRefresh.ts
68760
+ async function refreshConnectCredential(deps) {
68761
+ if (deps.refreshToken.length === 0) return null;
68762
+ try {
68763
+ return toConnectCredential(await deps.postJson(`${deps.commandAppUrl}/api/mcp-server/oauth/token`, {
68764
+ grant_type: "refresh_token",
68765
+ refresh_token: deps.refreshToken,
68766
+ client_id: deps.clientId
68767
+ }));
68768
+ } catch {
68769
+ return null;
68770
+ }
68771
+ }
68772
+ //#endregion
68736
68773
  //#region src/relay/localBridge.ts
68737
68774
  const DESKTOP_RELAY_CONTEXT_HEADER = "x-forge-relay-context";
68738
68775
  const decodeRelayContext = require_Schema$1.decodeUnknownSync(RelayLocalBridgeContext);
@@ -69006,6 +69043,30 @@ function makeRelayDeliveryMiddlewareLayer(context, inbox, reconcileInterruptedCo
69006
69043
  });
69007
69044
  }
69008
69045
  //#endregion
69046
+ //#region src/relay/bridgeAccess.ts
69047
+ function relayBridgeAccessAllowed(input) {
69048
+ if (!input.isLoopback) return false;
69049
+ if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
69050
+ return input.mode === "desktop" || input.connectLinked;
69051
+ }
69052
+ function resolveAuthorizedBridgeRuntimeId(input) {
69053
+ const isDesktop = input.mode === "desktop";
69054
+ if (!relayBridgeAccessAllowed({
69055
+ mode: input.mode,
69056
+ connectLinked: input.connectLinked,
69057
+ isLoopback: input.isLoopback,
69058
+ suppliedToken: input.suppliedToken,
69059
+ expectedToken: isDesktop ? input.desktopToken : input.connectToken
69060
+ })) return null;
69061
+ return (isDesktop ? input.desktopRuntimeId : input.connectRuntimeId) ?? null;
69062
+ }
69063
+ //#endregion
69064
+ //#region src/relay/controlPlaneAuthority.ts
69065
+ function selectControlPlaneAuthority(input) {
69066
+ if (input.mode === "desktop") return "session";
69067
+ return input.connectLinked ? "credential" : null;
69068
+ }
69069
+ //#endregion
69009
69070
  //#region src/desktop/relayOwnerSession.ts
69010
69071
  var DesktopRelayOwnerSessionError = class extends require_Schema$1.TaggedError("DesktopRelayOwnerSessionError") {};
69011
69072
  const resolveDesktopRelayOwnerSession = (request) => require_Schema$1.gen(function* () {
@@ -69101,6 +69162,8 @@ function relayControlPlaneBodyMatchesRuntime(path, body, configuredRuntimeId) {
69101
69162
  if (!isRecord$17(body) || !isRecord$17(body.runtime)) return false;
69102
69163
  return body.runtime.runtimeId === configuredRuntimeId;
69103
69164
  }
69165
+ /** Matches the client id `connect login` used, so CMMD accepts the refresh. */
69166
+ const CONNECT_CONTROL_PLANE_CLIENT_ID = "cmmd-forge-web";
69104
69167
  function errorResponse(status, code, error) {
69105
69168
  return require_HttpServer.jsonUnsafe({
69106
69169
  error,
@@ -69119,9 +69182,16 @@ function relayProxyErrorResponse(error) {
69119
69182
  return buildProxyErrorResponse(error);
69120
69183
  }
69121
69184
  function authorizedBridgeRuntimeId(request, config) {
69122
- if (config.mode !== "desktop" || !isLoopbackRelayRequest(request)) return null;
69123
- if (!bridgeTokenMatches(request.headers["x-forge-relay-bridge-token"], config.desktopRelayBridgeToken)) return null;
69124
- return config.desktopRelayRuntimeId ?? null;
69185
+ return resolveAuthorizedBridgeRuntimeId({
69186
+ mode: config.mode === "desktop" ? "desktop" : "web",
69187
+ connectLinked: config.connectRelayLinked,
69188
+ isLoopback: isLoopbackRelayRequest(request),
69189
+ suppliedToken: request.headers[DESKTOP_RELAY_BRIDGE_TOKEN_HEADER],
69190
+ desktopToken: config.desktopRelayBridgeToken,
69191
+ connectToken: config.connectRelayBridgeToken,
69192
+ desktopRuntimeId: config.desktopRelayRuntimeId,
69193
+ connectRuntimeId: config.connectRelayRuntimeId
69194
+ });
69125
69195
  }
69126
69196
  function validateBridgeOperation(request, runtimeId) {
69127
69197
  if (request.runtimeId !== runtimeId) return {
@@ -69148,9 +69218,10 @@ function validateBridgeOperation(request, runtimeId) {
69148
69218
  }
69149
69219
  function dispatchRelayControlPlaneOperation(input) {
69150
69220
  return require_Schema$1.gen(function* () {
69151
- const resolveJwt = (forceRefresh = false) => resolveCmmdJwtForSession({
69152
- sessionId: input.owner.sessionId,
69153
- subject: input.owner.subject,
69221
+ const caller = input.caller;
69222
+ const resolveJwt = (forceRefresh = false) => caller.kind === "credential" ? forceRefresh ? require_Schema$1.promise(() => caller.renew()) : require_Schema$1.succeed(caller.accessToken) : resolveCmmdJwtForSession({
69223
+ sessionId: caller.sessionId,
69224
+ subject: caller.subject,
69154
69225
  logPrefix: "desktop-relay-control-plane",
69155
69226
  forceRefresh
69156
69227
  });
@@ -69172,19 +69243,57 @@ function dispatchRelayControlPlaneOperation(input) {
69172
69243
  }
69173
69244
  const desktopRelayControlPlaneRouteLayer = add$1("POST", DESKTOP_RELAY_CONTROL_PLANE_PATH, require_Schema$1.gen(function* () {
69174
69245
  const request = yield* require_HttpServer.HttpServerRequest;
69175
- const runtimeId = authorizedBridgeRuntimeId(request, yield* ServerConfig);
69246
+ const config = yield* ServerConfig;
69247
+ const runtimeId = authorizedBridgeRuntimeId(request, config);
69176
69248
  if (!runtimeId) return forbiddenResponse();
69177
69249
  const decoded = yield* require_HttpServer.schemaBodyJson(DesktopRelayControlPlaneRequest).pipe(require_Schema$1.result);
69178
69250
  if (require_Schema$1.isFailure$1(decoded)) return errorResponse(400, "RELAY_BRIDGE_REQUEST_INVALID", "Invalid relay bridge request.");
69179
69251
  const operation = validateBridgeOperation(decoded.success, runtimeId);
69180
69252
  if (!operation.ok) return operation.response;
69253
+ const authority = selectControlPlaneAuthority({
69254
+ mode: config.mode === "desktop" ? "desktop" : "web",
69255
+ connectLinked: config.connectRelayLinked
69256
+ });
69257
+ if (authority === null) return forbiddenResponse();
69258
+ if (authority === "credential") {
69259
+ const credential = readConnectCredential(node_path.join(config.stateDir, "connect-credential.json"), createFileSecretStorage({ keyPath: node_path.join(config.stateDir, "connect-secret.key") }));
69260
+ if (!credential) return errorResponse(401, "CMMD_AUTH_REQUIRED", "This machine is not signed in to CMMD. Run `cmmd-forge connect login`.");
69261
+ const credentialPath = node_path.join(config.stateDir, "connect-credential.json");
69262
+ const secrets = createFileSecretStorage({ keyPath: node_path.join(config.stateDir, "connect-secret.key") });
69263
+ return yield* dispatchRelayControlPlaneOperation({
69264
+ caller: {
69265
+ kind: "credential",
69266
+ accessToken: credential.accessToken,
69267
+ renew: async () => {
69268
+ const renewed = await refreshConnectCredential({
69269
+ commandAppUrl: resolveCommandAppUrl(),
69270
+ clientId: CONNECT_CONTROL_PLANE_CLIENT_ID,
69271
+ refreshToken: credential.refreshToken,
69272
+ postJson: async (url, body) => await (await fetch(url, {
69273
+ method: "POST",
69274
+ headers: { "content-type": "application/json" },
69275
+ body: JSON.stringify(body)
69276
+ })).json()
69277
+ });
69278
+ if (!renewed) return null;
69279
+ writeConnectCredential(credentialPath, secrets, renewed);
69280
+ return renewed.accessToken;
69281
+ }
69282
+ },
69283
+ path: operation.path,
69284
+ body: operation.body
69285
+ });
69286
+ }
69181
69287
  const ownerResult = yield* resolveDesktopRelayOwnerSession(request).pipe(require_Schema$1.result);
69182
69288
  if (require_Schema$1.isFailure$1(ownerResult)) {
69183
69289
  const error = ownerResult.failure;
69184
69290
  return errorResponse(error.status, error.code, error.message);
69185
69291
  }
69186
69292
  return yield* dispatchRelayControlPlaneOperation({
69187
- owner: ownerResult.success,
69293
+ caller: {
69294
+ kind: "session",
69295
+ ...ownerResult.success
69296
+ },
69188
69297
  path: operation.path,
69189
69298
  body: operation.body
69190
69299
  });
@@ -97901,13 +98010,6 @@ async function reconcileCmmdWorkRunPrMerged(deps, input) {
97901
98010
  };
97902
98011
  }
97903
98012
  //#endregion
97904
- //#region src/relay/bridgeAccess.ts
97905
- function relayBridgeAccessAllowed(input) {
97906
- if (!input.isLoopback) return false;
97907
- if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
97908
- return input.mode === "desktop" || input.connectLinked;
97909
- }
97910
- //#endregion
97911
98013
  //#region ../../packages/shared/src/sessionScopes.ts
97912
98014
  /**
97913
98015
  * Which RPC scopes a direct-WebSocket session holds, by its role.
@@ -307752,6 +307854,7 @@ const resolveServerConfig = (flags, cliLogLevel, options) => require_Schema$1.ge
307752
307854
  desktopBootstrapToken,
307753
307855
  desktopRelayBridgeToken,
307754
307856
  connectRelayBridgeToken: connectRelay.bridgeToken,
307857
+ connectRelayRuntimeId: connectRelay.runtimeId,
307755
307858
  connectRelayLinked: connectRelay.linked,
307756
307859
  connectDisplayName: node_os.hostname(),
307757
307860
  connectPlatform,
@@ -308171,7 +308274,8 @@ const connectLoginCommand = make$32("login", {
308171
308274
  noBrowser: noBrowserLoginFlag,
308172
308275
  email: loginEmailFlag
308173
308276
  }).pipe(withDescription$1("Sign this machine in to CMMD so it can be offered over the relay."), withHandler((flags) => require_Schema$1.gen(function* () {
308174
- const stateDir = (yield* resolveCliAuthConfig(flags, yield* LogLevel)).stateDir;
308277
+ const config = yield* resolveCliAuthConfig(flags, yield* LogLevel);
308278
+ const stateDir = config.stateDir;
308175
308279
  const startedAt = Date.now();
308176
308280
  const emailOption = flags.email;
308177
308281
  if (require_Schema$1.isSome(emailOption)) {
@@ -308186,7 +308290,7 @@ const connectLoginCommand = make$32("login", {
308186
308290
  exchange: (email, secret) => require_Schema$1.runPromise(exchangeCmmdPasswordForOAuthToken({
308187
308291
  email,
308188
308292
  password: secret,
308189
- redirectUri: `${resolveCommandAppUrl()}/api/auth/callback`
308293
+ redirectUri: resolveLoginRedirectUri({ port: config.port })
308190
308294
  })).catch((cause) => {
308191
308295
  if (isRejectedCredential(cause)) return {};
308192
308296
  throw cause;
package/dist/bin.mjs CHANGED
@@ -31816,6 +31816,25 @@ function readHiddenLine(input) {
31816
31816
  });
31817
31817
  }
31818
31818
  //#endregion
31819
+ //#region src/connect/loginRedirect.ts
31820
+ /**
31821
+ * The OAuth redirect URI for `connect login --email`.
31822
+ *
31823
+ * This names where the *client* lives, not where CMMD lives. Sending CMMD's own
31824
+ * origin is rejected with a 400 — which is exactly how password login first
31825
+ * shipped broken.
31826
+ *
31827
+ * The exchange never actually fetches this URL: both legs run in-process and
31828
+ * the authorization code is read off the redirect's Location header. It only
31829
+ * has to match a URI CMMD accepts. CMMD follows the usual native-app rule of
31830
+ * matching loopback redirects on any port, so the configured port is sent
31831
+ * as-is, keeping the CLI consistent with what the browser flow would use.
31832
+ */
31833
+ const DEFAULT_PORT$1 = 3e3;
31834
+ function resolveLoginRedirectUri(input) {
31835
+ return `http://localhost:${typeof input.port === "number" && Number.isInteger(input.port) && input.port > 0 && input.port <= 65535 ? input.port : DEFAULT_PORT$1}/api/auth/callback`;
31836
+ }
31837
+ //#endregion
31819
31838
  //#region src/connect/passwordLogin.ts
31820
31839
  async function runPasswordLogin(deps) {
31821
31840
  const email = deps.email.trim();
@@ -63597,7 +63616,7 @@ function normalizeNumberish(value) {
63597
63616
  }
63598
63617
  //#endregion
63599
63618
  //#region package.json
63600
- var version$1 = "0.9.1";
63619
+ var version$1 = "0.9.3";
63601
63620
  //#endregion
63602
63621
  //#region src/sentry.ts
63603
63622
  const SERVER_APP_NAME = "forge-server";
@@ -66164,11 +66183,14 @@ function resolveConnectDeviceIdentity(identityPath, options) {
66164
66183
  function resolveConnectRelayConfig(stateDir) {
66165
66184
  if (!readConnectState(Path$1.join(stateDir, "connect.json")).linked) return {
66166
66185
  linked: false,
66167
- bridgeToken: void 0
66186
+ bridgeToken: void 0,
66187
+ runtimeId: void 0
66168
66188
  };
66189
+ const secrets = resolveConnectDeviceSecrets(Path$1.join(stateDir, "connect-device.json"));
66169
66190
  return {
66170
66191
  linked: true,
66171
- bridgeToken: resolveConnectDeviceSecrets(Path$1.join(stateDir, "connect-device.json")).bridgeToken
66192
+ bridgeToken: secrets.bridgeToken,
66193
+ runtimeId: secrets.runtimeId
66172
66194
  };
66173
66195
  }
66174
66196
  //#endregion
@@ -66326,6 +66348,7 @@ var ServerConfig = class ServerConfig extends Service$1()("forge/config/ServerCo
66326
66348
  desktopBootstrapToken: void 0,
66327
66349
  desktopRelayBridgeToken: void 0,
66328
66350
  connectRelayBridgeToken: void 0,
66351
+ connectRelayRuntimeId: void 0,
66329
66352
  connectRelayLinked: false,
66330
66353
  connectDisplayName: "forge",
66331
66354
  connectPlatform: "linux",
@@ -68487,6 +68510,20 @@ function registerProxyRoute(route) {
68487
68510
  return add$1(route.method, route.forgePath, handler);
68488
68511
  }
68489
68512
  //#endregion
68513
+ //#region src/connect/credentialRefresh.ts
68514
+ async function refreshConnectCredential(deps) {
68515
+ if (deps.refreshToken.length === 0) return null;
68516
+ try {
68517
+ return toConnectCredential(await deps.postJson(`${deps.commandAppUrl}/api/mcp-server/oauth/token`, {
68518
+ grant_type: "refresh_token",
68519
+ refresh_token: deps.refreshToken,
68520
+ client_id: deps.clientId
68521
+ }));
68522
+ } catch {
68523
+ return null;
68524
+ }
68525
+ }
68526
+ //#endregion
68490
68527
  //#region src/relay/localBridge.ts
68491
68528
  const DESKTOP_RELAY_CONTEXT_HEADER = "x-forge-relay-context";
68492
68529
  const decodeRelayContext = decodeUnknownSync(RelayLocalBridgeContext);
@@ -68759,6 +68796,30 @@ function makeRelayDeliveryMiddlewareLayer(context, inbox, reconcileInterruptedCo
68759
68796
  });
68760
68797
  }
68761
68798
  //#endregion
68799
+ //#region src/relay/bridgeAccess.ts
68800
+ function relayBridgeAccessAllowed(input) {
68801
+ if (!input.isLoopback) return false;
68802
+ if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
68803
+ return input.mode === "desktop" || input.connectLinked;
68804
+ }
68805
+ function resolveAuthorizedBridgeRuntimeId(input) {
68806
+ const isDesktop = input.mode === "desktop";
68807
+ if (!relayBridgeAccessAllowed({
68808
+ mode: input.mode,
68809
+ connectLinked: input.connectLinked,
68810
+ isLoopback: input.isLoopback,
68811
+ suppliedToken: input.suppliedToken,
68812
+ expectedToken: isDesktop ? input.desktopToken : input.connectToken
68813
+ })) return null;
68814
+ return (isDesktop ? input.desktopRuntimeId : input.connectRuntimeId) ?? null;
68815
+ }
68816
+ //#endregion
68817
+ //#region src/relay/controlPlaneAuthority.ts
68818
+ function selectControlPlaneAuthority(input) {
68819
+ if (input.mode === "desktop") return "session";
68820
+ return input.connectLinked ? "credential" : null;
68821
+ }
68822
+ //#endregion
68762
68823
  //#region src/desktop/relayOwnerSession.ts
68763
68824
  var DesktopRelayOwnerSessionError = class extends TaggedError("DesktopRelayOwnerSessionError") {};
68764
68825
  const resolveDesktopRelayOwnerSession = (request) => gen(function* () {
@@ -68854,6 +68915,8 @@ function relayControlPlaneBodyMatchesRuntime(path, body, configuredRuntimeId) {
68854
68915
  if (!isRecord$17(body) || !isRecord$17(body.runtime)) return false;
68855
68916
  return body.runtime.runtimeId === configuredRuntimeId;
68856
68917
  }
68918
+ /** Matches the client id `connect login` used, so CMMD accepts the refresh. */
68919
+ const CONNECT_CONTROL_PLANE_CLIENT_ID = "cmmd-forge-web";
68857
68920
  function errorResponse(status, code, error) {
68858
68921
  return jsonUnsafe({
68859
68922
  error,
@@ -68872,9 +68935,16 @@ function relayProxyErrorResponse(error) {
68872
68935
  return buildProxyErrorResponse(error);
68873
68936
  }
68874
68937
  function authorizedBridgeRuntimeId(request, config) {
68875
- if (config.mode !== "desktop" || !isLoopbackRelayRequest(request)) return null;
68876
- if (!bridgeTokenMatches(request.headers["x-forge-relay-bridge-token"], config.desktopRelayBridgeToken)) return null;
68877
- return config.desktopRelayRuntimeId ?? null;
68938
+ return resolveAuthorizedBridgeRuntimeId({
68939
+ mode: config.mode === "desktop" ? "desktop" : "web",
68940
+ connectLinked: config.connectRelayLinked,
68941
+ isLoopback: isLoopbackRelayRequest(request),
68942
+ suppliedToken: request.headers[DESKTOP_RELAY_BRIDGE_TOKEN_HEADER],
68943
+ desktopToken: config.desktopRelayBridgeToken,
68944
+ connectToken: config.connectRelayBridgeToken,
68945
+ desktopRuntimeId: config.desktopRelayRuntimeId,
68946
+ connectRuntimeId: config.connectRelayRuntimeId
68947
+ });
68878
68948
  }
68879
68949
  function validateBridgeOperation(request, runtimeId) {
68880
68950
  if (request.runtimeId !== runtimeId) return {
@@ -68901,9 +68971,10 @@ function validateBridgeOperation(request, runtimeId) {
68901
68971
  }
68902
68972
  function dispatchRelayControlPlaneOperation(input) {
68903
68973
  return gen(function* () {
68904
- const resolveJwt = (forceRefresh = false) => resolveCmmdJwtForSession({
68905
- sessionId: input.owner.sessionId,
68906
- subject: input.owner.subject,
68974
+ const caller = input.caller;
68975
+ const resolveJwt = (forceRefresh = false) => caller.kind === "credential" ? forceRefresh ? promise(() => caller.renew()) : succeed(caller.accessToken) : resolveCmmdJwtForSession({
68976
+ sessionId: caller.sessionId,
68977
+ subject: caller.subject,
68907
68978
  logPrefix: "desktop-relay-control-plane",
68908
68979
  forceRefresh
68909
68980
  });
@@ -68925,19 +68996,57 @@ function dispatchRelayControlPlaneOperation(input) {
68925
68996
  }
68926
68997
  const desktopRelayControlPlaneRouteLayer = add$1("POST", DESKTOP_RELAY_CONTROL_PLANE_PATH, gen(function* () {
68927
68998
  const request = yield* HttpServerRequest;
68928
- const runtimeId = authorizedBridgeRuntimeId(request, yield* ServerConfig);
68999
+ const config = yield* ServerConfig;
69000
+ const runtimeId = authorizedBridgeRuntimeId(request, config);
68929
69001
  if (!runtimeId) return forbiddenResponse();
68930
69002
  const decoded = yield* schemaBodyJson(DesktopRelayControlPlaneRequest).pipe(result);
68931
69003
  if (isFailure$1(decoded)) return errorResponse(400, "RELAY_BRIDGE_REQUEST_INVALID", "Invalid relay bridge request.");
68932
69004
  const operation = validateBridgeOperation(decoded.success, runtimeId);
68933
69005
  if (!operation.ok) return operation.response;
69006
+ const authority = selectControlPlaneAuthority({
69007
+ mode: config.mode === "desktop" ? "desktop" : "web",
69008
+ connectLinked: config.connectRelayLinked
69009
+ });
69010
+ if (authority === null) return forbiddenResponse();
69011
+ if (authority === "credential") {
69012
+ const credential = readConnectCredential(Path$1.join(config.stateDir, "connect-credential.json"), createFileSecretStorage({ keyPath: Path$1.join(config.stateDir, "connect-secret.key") }));
69013
+ if (!credential) return errorResponse(401, "CMMD_AUTH_REQUIRED", "This machine is not signed in to CMMD. Run `cmmd-forge connect login`.");
69014
+ const credentialPath = Path$1.join(config.stateDir, "connect-credential.json");
69015
+ const secrets = createFileSecretStorage({ keyPath: Path$1.join(config.stateDir, "connect-secret.key") });
69016
+ return yield* dispatchRelayControlPlaneOperation({
69017
+ caller: {
69018
+ kind: "credential",
69019
+ accessToken: credential.accessToken,
69020
+ renew: async () => {
69021
+ const renewed = await refreshConnectCredential({
69022
+ commandAppUrl: resolveCommandAppUrl(),
69023
+ clientId: CONNECT_CONTROL_PLANE_CLIENT_ID,
69024
+ refreshToken: credential.refreshToken,
69025
+ postJson: async (url, body) => await (await fetch(url, {
69026
+ method: "POST",
69027
+ headers: { "content-type": "application/json" },
69028
+ body: JSON.stringify(body)
69029
+ })).json()
69030
+ });
69031
+ if (!renewed) return null;
69032
+ writeConnectCredential(credentialPath, secrets, renewed);
69033
+ return renewed.accessToken;
69034
+ }
69035
+ },
69036
+ path: operation.path,
69037
+ body: operation.body
69038
+ });
69039
+ }
68934
69040
  const ownerResult = yield* resolveDesktopRelayOwnerSession(request).pipe(result);
68935
69041
  if (isFailure$1(ownerResult)) {
68936
69042
  const error = ownerResult.failure;
68937
69043
  return errorResponse(error.status, error.code, error.message);
68938
69044
  }
68939
69045
  return yield* dispatchRelayControlPlaneOperation({
68940
- owner: ownerResult.success,
69046
+ caller: {
69047
+ kind: "session",
69048
+ ...ownerResult.success
69049
+ },
68941
69050
  path: operation.path,
68942
69051
  body: operation.body
68943
69052
  });
@@ -97589,13 +97698,6 @@ async function reconcileCmmdWorkRunPrMerged(deps, input) {
97589
97698
  };
97590
97699
  }
97591
97700
  //#endregion
97592
- //#region src/relay/bridgeAccess.ts
97593
- function relayBridgeAccessAllowed(input) {
97594
- if (!input.isLoopback) return false;
97595
- if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
97596
- return input.mode === "desktop" || input.connectLinked;
97597
- }
97598
- //#endregion
97599
97701
  //#region ../../packages/shared/src/sessionScopes.ts
97600
97702
  /**
97601
97703
  * Which RPC scopes a direct-WebSocket session holds, by its role.
@@ -307155,6 +307257,7 @@ const resolveServerConfig = (flags, cliLogLevel, options) => gen(function* () {
307155
307257
  desktopBootstrapToken,
307156
307258
  desktopRelayBridgeToken,
307157
307259
  connectRelayBridgeToken: connectRelay.bridgeToken,
307260
+ connectRelayRuntimeId: connectRelay.runtimeId,
307158
307261
  connectRelayLinked: connectRelay.linked,
307159
307262
  connectDisplayName: NodeOS.hostname(),
307160
307263
  connectPlatform,
@@ -307573,7 +307676,8 @@ const connectLoginCommand = make$32("login", {
307573
307676
  noBrowser: noBrowserLoginFlag,
307574
307677
  email: loginEmailFlag
307575
307678
  }).pipe(withDescription$1("Sign this machine in to CMMD so it can be offered over the relay."), withHandler((flags) => gen(function* () {
307576
- const stateDir = (yield* resolveCliAuthConfig(flags, yield* LogLevel)).stateDir;
307679
+ const config = yield* resolveCliAuthConfig(flags, yield* LogLevel);
307680
+ const stateDir = config.stateDir;
307577
307681
  const startedAt = Date.now();
307578
307682
  const emailOption = flags.email;
307579
307683
  if (isSome(emailOption)) {
@@ -307588,7 +307692,7 @@ const connectLoginCommand = make$32("login", {
307588
307692
  exchange: (email, secret) => runPromise(exchangeCmmdPasswordForOAuthToken({
307589
307693
  email,
307590
307694
  password: secret,
307591
- redirectUri: `${resolveCommandAppUrl()}/api/auth/callback`
307695
+ redirectUri: resolveLoginRedirectUri({ port: config.port })
307592
307696
  })).catch((cause) => {
307593
307697
  if (isRejectedCredential(cause)) return {};
307594
307698
  throw cause;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmmd-center/forge",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,15 +26,14 @@
26
26
  "dependencies": {
27
27
  "@anthropic-ai/claude-agent-sdk": "^0.3.232",
28
28
  "@cmmd-center/agent-browser": "0.6.2",
29
- "@cmmd-forge/relay-host": "workspace:*",
30
- "@effect/platform-bun": "catalog:",
31
- "@effect/platform-node": "catalog:",
32
- "@effect/sql-pg": "catalog:",
33
- "@effect/sql-sqlite-bun": "catalog:",
29
+ "@effect/platform-bun": "4.0.0-beta.45",
30
+ "@effect/platform-node": "4.0.0-beta.45",
31
+ "@effect/sql-pg": "4.0.0-beta.45",
32
+ "@effect/sql-sqlite-bun": "4.0.0-beta.45",
34
33
  "@opencode-ai/sdk": "^1.18.18",
35
34
  "@pierre/diffs": "^1.3.5",
36
35
  "@sentry/node": "^10.70.0",
37
- "effect": "catalog:",
36
+ "effect": "4.0.0-beta.45",
38
37
  "node-pty": "^1.1.0",
39
38
  "open": "^11.0.1",
40
39
  "pg": "^8.23.0",
@@ -44,20 +43,21 @@
44
43
  "zod": "^4.0.0"
45
44
  },
46
45
  "devDependencies": {
47
- "@cmmd-forge/client-runtime": "workspace:*",
48
- "@cmmd-forge/contracts": "workspace:*",
49
- "@cmmd-forge/shared": "workspace:*",
50
- "@cmmd-forge/web": "workspace:*",
51
- "@effect/language-service": "catalog:",
52
- "@effect/vitest": "catalog:",
53
- "@types/bun": "catalog:",
54
- "@types/node": "catalog:",
46
+ "@cmmd-forge/client-runtime": "0.0.0-alpha.1",
47
+ "@cmmd-forge/contracts": "0.9.0",
48
+ "@cmmd-forge/relay-host": "0.0.0-alpha.1",
49
+ "@cmmd-forge/shared": "0.0.0-alpha.1",
50
+ "@cmmd-forge/web": "0.9.0",
51
+ "@effect/language-service": "0.84.2",
52
+ "@effect/vitest": "4.0.0-beta.45",
53
+ "@types/bun": "^1.3.9",
54
+ "@types/node": "^24.10.13",
55
55
  "@types/pg": "^8.21.0",
56
56
  "@types/ws": "^8.5.13",
57
- "effect-acp": "workspace:*",
58
- "tsdown": "catalog:",
59
- "typescript": "catalog:",
60
- "vitest": "catalog:"
57
+ "effect-acp": "0.0.1",
58
+ "tsdown": "0.21.4",
59
+ "typescript": "^5.7.3",
60
+ "vitest": "^4.1.10"
61
61
  },
62
62
  "engines": {
63
63
  "node": "^22.16 || ^23.11 || >=24.10"