specrails-desktop 2.63.2 → 2.64.1

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 (76) hide show
  1. package/client/dist/assets/{ActivityFeedPage-B1bpTy7O.js → ActivityFeedPage-Dtp7EyHc.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-BussBbN3.js → AgentBrowserCapture-CLqlEFw8.js} +1 -1
  3. package/client/dist/assets/{AgentModeAnalyticsPane-BiT1n3BU.js → AgentModeAnalyticsPane-B8_hM3Tw.js} +2 -2
  4. package/client/dist/assets/{AgentModeCodePane-CUjNfeKa.js → AgentModeCodePane-CNF8ZNjE.js} +2 -2
  5. package/client/dist/assets/{AgentModeJobsPane-RVEwyS59.js → AgentModeJobsPane-CJpJbO9m.js} +1 -1
  6. package/client/dist/assets/AgentsPage-BC8XSR79.js +120 -0
  7. package/client/dist/assets/{AnalyticsPage-Bser2Ly3.js → AnalyticsPage-Cdv4jLtW.js} +1 -1
  8. package/client/dist/assets/{CodePage-BK0grm6e.js → CodePage-DI1kegAr.js} +1 -1
  9. package/client/dist/assets/{DesktopAnalyticsPage-DEBidZbs.js → DesktopAnalyticsPage-Bdp8E8U6.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-Dhyo57n9.js → DocsDialog-Bm4RS7FX.js} +1 -1
  11. package/client/dist/assets/{DocsPage-BcEkPXd6.js → DocsPage-DihYcU8r.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-mRmqdkjr.js → ExportDropdown-Bb1lHx3r.js} +1 -1
  13. package/client/dist/assets/{InteractiveJobComposer-BlCe3vrU.js → InteractiveJobComposer-BO6ASN-2.js} +1 -1
  14. package/client/dist/assets/{JobDetailModal-CkAejD1F.js → JobDetailModal-CuPYq7tP.js} +1 -1
  15. package/client/dist/assets/{JobDetailPage-P-FS_lKz.js → JobDetailPage-BusMjJi9.js} +1 -1
  16. package/client/dist/assets/{JobsPage-CoUXgYse.js → JobsPage-C0QbsF2d.js} +1 -1
  17. package/client/dist/assets/{LoopBuilderPage-iFylNk68.js → LoopBuilderPage-B7TlU4iS.js} +1 -1
  18. package/client/dist/assets/{LoopPreviewModal-4obS_Ptg.js → LoopPreviewModal-BRWtXOD2.js} +1 -1
  19. package/client/dist/assets/{LoopWindowSurface-CLr5OPqj.js → LoopWindowSurface-DE0v2K1p.js} +1 -1
  20. package/client/dist/assets/{LoopsPage-eKpWFtCp.js → LoopsPage-C4q1DmCE.js} +1 -1
  21. package/client/dist/assets/{MinimizedChatsContext-BHIUPVmC.js → MinimizedChatsContext-DHfigjHS.js} +1 -1
  22. package/client/dist/assets/{PluginsPage-BgTC2CQx.js → PluginsPage-Z1JHSMdR.js} +1 -1
  23. package/client/dist/assets/{ProjectSettingsDialog-B4chTfU8.js → ProjectSettingsDialog-BUNj0tvN.js} +1 -1
  24. package/client/dist/assets/{RepositoryDeliveries-DAIxIYsl.js → RepositoryDeliveries-B4ybHOyB.js} +1 -1
  25. package/client/dist/assets/{RepositoryScopeSelector-DeCoUsRN.js → RepositoryScopeSelector-2eSqR9WK.js} +1 -1
  26. package/client/dist/assets/{ReviewPacketPage-Cktv0-9b.js → ReviewPacketPage-hwfPNjyy.js} +1 -1
  27. package/client/dist/assets/{TemplatePreviewModal-BZTV1TM1.js → TemplatePreviewModal-V7FpMWuw.js} +1 -1
  28. package/client/dist/assets/{TicketDetailModalContext-DiPngt2g.js → TicketDetailModalContext-Dkwo2cDs.js} +1 -1
  29. package/client/dist/assets/{Trans-ZiXIfj50.js → Trans-BbFxRFtR.js} +1 -1
  30. package/client/dist/assets/{formatDistanceToNow-CdN5lTyD.js → formatDistanceToNow-NjPHBLfB.js} +1 -1
  31. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-62h40I4t.js → getTimezoneOffsetInMilliseconds-vJ7qUoqN.js} +1 -1
  32. package/client/dist/assets/{index-CECj3joE.js → index-Ce4yZtKf.js} +36 -36
  33. package/client/dist/assets/{jira-api-C_D_RDhl.js → jira-api-Cabj-H0J.js} +1 -1
  34. package/client/dist/assets/{project-repositories-Be0CWklo.js → project-repositories-Cnnuj9Ia.js} +1 -1
  35. package/client/dist/assets/{settings-Hw7av-B1.js → settings-B1NV8Ow9.js} +1 -1
  36. package/client/dist/assets/settings-BQ8XFZeE.js +1 -0
  37. package/client/dist/assets/settings-BRkxfBbO.js +1 -0
  38. package/client/dist/assets/settings-C_CcU1dP.js +1 -0
  39. package/client/dist/assets/settings-Cto9n0eL.js +1 -0
  40. package/client/dist/assets/{settings-nryN6wuS.js → settings-Pyw8uYtL.js} +1 -1
  41. package/client/dist/assets/settings-b3HVCNNd.js +1 -0
  42. package/client/dist/assets/settings-hdive4u6.js +1 -0
  43. package/client/dist/assets/{spending-eWiZGhC_.js → spending-CQnrWiR3.js} +1 -1
  44. package/client/dist/assets/{useDesktop-C0mRIlHT.js → useDesktop-BjLT0_EE.js} +1 -1
  45. package/client/dist/assets/{useRuntimeRuns-B1a6GChu.js → useRuntimeRuns-C97qEqMg.js} +1 -1
  46. package/client/dist/assets/{useSharedWebSocket-CcsE5986.js → useSharedWebSocket-Bj4LMp7b.js} +2 -2
  47. package/client/dist/index.html +15 -15
  48. package/docs/internals/api-reference.md +26 -0
  49. package/docs/internals/core-runtime-updates.md +3 -3
  50. package/docs/internals/source-map.md +9 -2
  51. package/local-runner/dist/specrails-local-runner.js +487 -148
  52. package/mcp-bridge/dist/specrails-mcp.js +330 -42
  53. package/package.json +6 -3
  54. package/server/dist/index.js +14 -1
  55. package/server/dist/mobile/mobile-admin-router.js +16 -0
  56. package/server/dist/mobile/mobile-gateway.js +18 -0
  57. package/server/dist/mobile/mobile-missions.js +6 -4
  58. package/server/dist/mobile/mobile-preview-channel.js +179 -0
  59. package/server/dist/mobile/mobile-preview.js +366 -0
  60. package/server/dist/mobile/mobile-router.js +9 -1
  61. package/server/dist/mobile/mobile-webrtc-peer.js +54 -4
  62. package/server/dist/mobile/mobile-ws.js +4 -2
  63. package/server/dist/modules/browser/domain/preview-origins.js +117 -0
  64. package/server/dist/modules/browser/runtime/browser-capture-manager.js +141 -18
  65. package/server/dist/modules/browser/runtime/browser-capture-types.js +10 -1
  66. package/server/dist/modules/browser/runtime/browser-playwright.js +19 -0
  67. package/server/dist/modules/browser/runtime/preview-targets.js +80 -0
  68. package/server/dist/modules/terminals/runtime/background-process-service.js +24 -1
  69. package/server/dist/project-registry.js +10 -0
  70. package/client/dist/assets/AgentsPage-DKE-eMC5.js +0 -118
  71. package/client/dist/assets/settings-BO2N-oPf.js +0 -1
  72. package/client/dist/assets/settings-BQChAs_w.js +0 -1
  73. package/client/dist/assets/settings-BzmYlcQB.js +0 -1
  74. package/client/dist/assets/settings-CGjXb6TX.js +0 -1
  75. package/client/dist/assets/settings-CMxEWY46.js +0 -1
  76. package/client/dist/assets/settings-C_dRSgI2.js +0 -1
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e) {
13
+ throw mod = 0, e;
14
+ }
15
+ };
9
16
  var __export = (target, all) => {
10
17
  for (var name in all)
11
18
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +34,110 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
34
  mod
28
35
  ));
29
36
 
37
+ // node_modules/content-type/index.js
38
+ var require_content_type = __commonJS({
39
+ "node_modules/content-type/index.js"(exports2) {
40
+ "use strict";
41
+ var PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g;
42
+ var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/;
43
+ var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
44
+ var QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g;
45
+ var QUOTE_REGEXP = /([\\"])/g;
46
+ var TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
47
+ exports2.format = format;
48
+ exports2.parse = parse2;
49
+ function format(obj) {
50
+ if (!obj || typeof obj !== "object") {
51
+ throw new TypeError("argument obj is required");
52
+ }
53
+ var parameters = obj.parameters;
54
+ var type = obj.type;
55
+ if (!type || !TYPE_REGEXP.test(type)) {
56
+ throw new TypeError("invalid type");
57
+ }
58
+ var string4 = type;
59
+ if (parameters && typeof parameters === "object") {
60
+ var param;
61
+ var params = Object.keys(parameters).sort();
62
+ for (var i = 0; i < params.length; i++) {
63
+ param = params[i];
64
+ if (!TOKEN_REGEXP.test(param)) {
65
+ throw new TypeError("invalid parameter name");
66
+ }
67
+ string4 += "; " + param + "=" + qstring(parameters[param]);
68
+ }
69
+ }
70
+ return string4;
71
+ }
72
+ function parse2(string4) {
73
+ if (!string4) {
74
+ throw new TypeError("argument string is required");
75
+ }
76
+ var header = typeof string4 === "object" ? getcontenttype(string4) : string4;
77
+ if (typeof header !== "string") {
78
+ throw new TypeError("argument string is required to be a string");
79
+ }
80
+ var index = header.indexOf(";");
81
+ var type = index !== -1 ? header.slice(0, index).trim() : header.trim();
82
+ if (!TYPE_REGEXP.test(type)) {
83
+ throw new TypeError("invalid media type");
84
+ }
85
+ var obj = new ContentType(type.toLowerCase());
86
+ if (index !== -1) {
87
+ var key;
88
+ var match;
89
+ var value;
90
+ PARAM_REGEXP.lastIndex = index;
91
+ while (match = PARAM_REGEXP.exec(header)) {
92
+ if (match.index !== index) {
93
+ throw new TypeError("invalid parameter format");
94
+ }
95
+ index += match[0].length;
96
+ key = match[1].toLowerCase();
97
+ value = match[2];
98
+ if (value.charCodeAt(0) === 34) {
99
+ value = value.slice(1, -1);
100
+ if (value.indexOf("\\") !== -1) {
101
+ value = value.replace(QESC_REGEXP, "$1");
102
+ }
103
+ }
104
+ obj.parameters[key] = value;
105
+ }
106
+ if (index !== header.length) {
107
+ throw new TypeError("invalid parameter format");
108
+ }
109
+ }
110
+ return obj;
111
+ }
112
+ function getcontenttype(obj) {
113
+ var header;
114
+ if (typeof obj.getHeader === "function") {
115
+ header = obj.getHeader("content-type");
116
+ } else if (typeof obj.headers === "object") {
117
+ header = obj.headers && obj.headers["content-type"];
118
+ }
119
+ if (typeof header !== "string") {
120
+ throw new TypeError("content-type header is missing from object");
121
+ }
122
+ return header;
123
+ }
124
+ function qstring(val) {
125
+ var str = String(val);
126
+ if (TOKEN_REGEXP.test(str)) {
127
+ return str;
128
+ }
129
+ if (str.length > 0 && !TEXT_REGEXP.test(str)) {
130
+ throw new TypeError("invalid parameter value");
131
+ }
132
+ return '"' + str.replace(QUOTE_REGEXP, "\\$1") + '"';
133
+ }
134
+ function ContentType(type) {
135
+ this.parameters = /* @__PURE__ */ Object.create(null);
136
+ this.type = type;
137
+ }
138
+ }
139
+ });
140
+
30
141
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
31
142
  var import_node_process = __toESM(require("node:process"), 1);
32
143
 
@@ -5416,8 +5527,17 @@ var ServerResultSchema = union([
5416
5527
  ]);
5417
5528
 
5418
5529
  // node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
5530
+ var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
5419
5531
  var ReadBuffer = class {
5532
+ constructor(options) {
5533
+ this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
5534
+ }
5420
5535
  append(chunk) {
5536
+ const newSize = (this._buffer?.length ?? 0) + chunk.length;
5537
+ if (newSize > this._maxBufferSize) {
5538
+ this.clear();
5539
+ throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
5540
+ }
5421
5541
  this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
5422
5542
  }
5423
5543
  readMessage() {
@@ -5445,18 +5565,24 @@ function serializeMessage(message) {
5445
5565
 
5446
5566
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
5447
5567
  var StdioServerTransport = class {
5448
- constructor(_stdin = import_node_process.default.stdin, _stdout = import_node_process.default.stdout) {
5568
+ constructor(_stdin = import_node_process.default.stdin, _stdout = import_node_process.default.stdout, options) {
5449
5569
  this._stdin = _stdin;
5450
5570
  this._stdout = _stdout;
5451
- this._readBuffer = new ReadBuffer();
5452
5571
  this._started = false;
5453
5572
  this._ondata = (chunk) => {
5454
- this._readBuffer.append(chunk);
5455
- this.processReadBuffer();
5573
+ try {
5574
+ this._readBuffer.append(chunk);
5575
+ this.processReadBuffer();
5576
+ } catch (error2) {
5577
+ this.onerror?.(error2);
5578
+ this.close().catch(() => {
5579
+ });
5580
+ }
5456
5581
  };
5457
5582
  this._onerror = (error2) => {
5458
5583
  this.onerror?.(error2);
5459
5584
  };
5585
+ this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
5460
5586
  }
5461
5587
  /**
5462
5588
  * Starts listening for messages on stdin.
@@ -5504,6 +5630,23 @@ var StdioServerTransport = class {
5504
5630
  }
5505
5631
  };
5506
5632
 
5633
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/shared/mediaType.js
5634
+ var import_content_type = __toESM(require_content_type(), 1);
5635
+ function mediaTypeEssence(header) {
5636
+ if (!header) {
5637
+ return void 0;
5638
+ }
5639
+ try {
5640
+ return import_content_type.default.parse(header).type;
5641
+ } catch {
5642
+ const essence = (header.split(";", 1)[0] ?? "").trim().toLowerCase();
5643
+ if (essence === "" || header.slice(essence.length).includes(",")) {
5644
+ return void 0;
5645
+ }
5646
+ return essence;
5647
+ }
5648
+ }
5649
+
5507
5650
  // node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js
5508
5651
  function normalizeHeaders(headers) {
5509
5652
  if (!headers)
@@ -5530,6 +5673,68 @@ function createFetchWithInit(baseFetch = fetch, baseInit) {
5530
5673
  return baseFetch(url2, mergedInit);
5531
5674
  };
5532
5675
  }
5676
+ var MAX_REDIRECTS = 5;
5677
+ var REDIRECT_STATUSES = [301, 302, 303, 307, 308];
5678
+ function isWithinOrigin(from, to) {
5679
+ if (from.protocol === to.protocol && from.hostname === to.hostname && from.port === to.port) {
5680
+ return true;
5681
+ }
5682
+ return from.hostname === to.hostname && from.protocol === "http:" && from.port === "" && to.protocol === "https:" && to.port === "";
5683
+ }
5684
+ function redirectTarget(response, requestUrl) {
5685
+ const location = REDIRECT_STATUSES.includes(response.status) ? response.headers.get("location") : null;
5686
+ try {
5687
+ return location ? new URL(location, requestUrl) : void 0;
5688
+ } catch {
5689
+ return void 0;
5690
+ }
5691
+ }
5692
+ function followWithinOrigin(baseFetch, url2, init, response, followed = 0) {
5693
+ const target = redirectTarget(response, url2);
5694
+ const keepsMethod = response.status === 307 || response.status === 308 || (init?.method ?? "GET").toUpperCase() === "GET";
5695
+ if (!target || followed === MAX_REDIRECTS || !keepsMethod) {
5696
+ return void 0;
5697
+ }
5698
+ const from = new URL(url2);
5699
+ const addsUserinfo = (target.username || target.password) && (target.username !== from.username || target.password !== from.password);
5700
+ if (addsUserinfo || !isWithinOrigin(from, target)) {
5701
+ return void 0;
5702
+ }
5703
+ return Promise.resolve(response.body?.cancel()).then(() => baseFetch(target, { ...init, redirect: "manual" })).then((next) => followWithinOrigin(baseFetch, target, init, next, followed + 1) ?? next);
5704
+ }
5705
+ var followingFetches = /* @__PURE__ */ new WeakSet();
5706
+ function fetchLeavingRedirects(baseFetch) {
5707
+ const following = (url2, init) => (baseFetch ?? fetch)(url2, init);
5708
+ followingFetches.add(following);
5709
+ return following;
5710
+ }
5711
+ function fetchWithinOrigin(baseFetch = fetch) {
5712
+ if (followingFetches.has(baseFetch)) {
5713
+ return baseFetch;
5714
+ }
5715
+ return (url2, init) => {
5716
+ if (init?.redirect === "error" || init?.redirect === "manual") {
5717
+ return baseFetch(url2, init);
5718
+ }
5719
+ return Promise.resolve(baseFetch(url2, { ...init, redirect: "manual" })).then((response) => followWithinOrigin(baseFetch, url2, init, response) ?? response);
5720
+ };
5721
+ }
5722
+ function unfollowedRedirect(response, requestUrl) {
5723
+ if (response.type === "opaqueredirect") {
5724
+ return "Redirect not followed: this runtime does not expose the redirect target";
5725
+ }
5726
+ const from = response.url || requestUrl;
5727
+ const target = redirectTarget(response, from);
5728
+ if (!target) {
5729
+ return void 0;
5730
+ }
5731
+ target.username = target.password = target.search = target.hash = "";
5732
+ if (target.protocol === "http:" && new URL(from).protocol === "https:") {
5733
+ target.protocol = "https:";
5734
+ return `Redirect to plain http not followed; try ${target.href} instead`;
5735
+ }
5736
+ return `Redirect to ${target.href} not followed`;
5737
+ }
5533
5738
 
5534
5739
  // node_modules/pkce-challenge/dist/index.node.js
5535
5740
  var crypto;
@@ -5676,7 +5881,12 @@ var OAuthTokensSchema = object({
5676
5881
  token_type: string2(),
5677
5882
  expires_in: coerce_exports.number().optional(),
5678
5883
  scope: string2().optional(),
5679
- refresh_token: string2().optional()
5884
+ refresh_token: string2().optional(),
5885
+ /**
5886
+ * Not part of the wire format: the authorization server this value was obtained from, added by
5887
+ * the client's `auth()` before it is stored and compared when it is read back.
5888
+ */
5889
+ issuer: string2().optional().catch(void 0)
5680
5890
  }).strip();
5681
5891
  var OAuthErrorResponseSchema = object({
5682
5892
  error: string2(),
@@ -5706,7 +5916,12 @@ var OAuthClientInformationSchema = object({
5706
5916
  client_id: string2(),
5707
5917
  client_secret: string2().optional(),
5708
5918
  client_id_issued_at: number2().optional(),
5709
- client_secret_expires_at: number2().optional()
5919
+ client_secret_expires_at: number2().optional(),
5920
+ /**
5921
+ * Not part of the wire format: the authorization server this value was obtained from, added by
5922
+ * the client's `auth()` before it is stored and compared when it is read back.
5923
+ */
5924
+ issuer: string2().optional().catch(void 0)
5710
5925
  }).strip();
5711
5926
  var OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
5712
5927
  var OAuthClientRegistrationErrorSchema = object({
@@ -5904,10 +6119,31 @@ async function parseErrorResponse(input) {
5904
6119
  const errorClass = OAUTH_ERRORS[error2] || ServerError;
5905
6120
  return new errorClass(error_description || "", error_uri);
5906
6121
  } catch (error2) {
5907
- const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error2}. Raw body: ${body}`;
6122
+ const redirect = input instanceof Response ? unfollowedRedirect(input, input.url) : void 0;
6123
+ const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}${redirect ?? `Invalid OAuth error response: ${error2}. Raw body: ${body}`}`;
5908
6124
  return new ServerError(errorMessage);
5909
6125
  }
5910
6126
  }
6127
+ function issuersMatch(a, b) {
6128
+ let [x, y] = [a, b];
6129
+ try {
6130
+ [x, y] = [new URL(a).href, new URL(b).href];
6131
+ } catch {
6132
+ }
6133
+ return x === y || x.endsWith("/") && x.slice(0, -1) === y || y.endsWith("/") && y.slice(0, -1) === x;
6134
+ }
6135
+ function discardIfIssuerMismatch(stored, issuer) {
6136
+ if (!stored)
6137
+ return void 0;
6138
+ if (typeof stored.issuer !== "string")
6139
+ return stored.issuer == null ? stored : { ...stored, issuer: void 0 };
6140
+ return issuersMatch(stored.issuer, issuer) ? stored : void 0;
6141
+ }
6142
+ var TokenResponseSchema = OAuthTokensSchema.omit({ issuer: true });
6143
+ var RegistrationResponseSchema = OAuthClientInformationFullSchema.omit({ issuer: true });
6144
+ function boundElsewhereError(stored, issuer) {
6145
+ return new Error(`OAuth client information is bound to authorization server ${stored.issuer} and is not presented to ${issuer}. Clear the stored client information, or correct \`expectedIssuer\`, if the authorization server has moved.`);
6146
+ }
5911
6147
  async function auth(provider, options) {
5912
6148
  try {
5913
6149
  return await authInternal(provider, options);
@@ -5961,9 +6197,24 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
5961
6197
  authorizationServerMetadata: metadata
5962
6198
  });
5963
6199
  }
5964
- const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
6200
+ const issuer = String(authorizationServerUrl);
6201
+ const selectedResource = await selectResourceURL(serverUrl, provider, resourceMetadata);
6202
+ const resource = selectedResource && resourceMetadata && !provider.validateResourceURL ? resourceMetadata.resource : selectedResource;
5965
6203
  const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
5966
- let clientInformation = await Promise.resolve(provider.clientInformation());
6204
+ const storedClientInformation = await Promise.resolve(provider.clientInformation());
6205
+ let clientInformation = discardIfIssuerMismatch(storedClientInformation, issuer);
6206
+ const canRegisterAgain = provider.saveClientInformation !== void 0 && !!provider.redirectUrl;
6207
+ if (storedClientInformation && !clientInformation && !canRegisterAgain) {
6208
+ throw boundElsewhereError(storedClientInformation, issuer);
6209
+ }
6210
+ const unstampedClientInformation = clientInformation?.issuer == null ? clientInformation : void 0;
6211
+ const bindClientInformation = async () => {
6212
+ try {
6213
+ if (unstampedClientInformation)
6214
+ await provider.saveClientInformation?.({ ...unstampedClientInformation, issuer });
6215
+ } catch {
6216
+ }
6217
+ };
5967
6218
  if (!clientInformation) {
5968
6219
  if (authorizationCode !== void 0) {
5969
6220
  throw new Error("Existing OAuth client information is required when exchanging an authorization code");
@@ -5975,9 +6226,7 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
5975
6226
  }
5976
6227
  const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
5977
6228
  if (shouldUseUrlBasedClientId) {
5978
- clientInformation = {
5979
- client_id: clientMetadataUrl
5980
- };
6229
+ clientInformation = { client_id: clientMetadataUrl, issuer };
5981
6230
  await provider.saveClientInformation?.(clientInformation);
5982
6231
  } else {
5983
6232
  if (!provider.saveClientInformation) {
@@ -5989,8 +6238,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
5989
6238
  scope: resolvedScope,
5990
6239
  fetchFn
5991
6240
  });
5992
- await provider.saveClientInformation(fullInformation);
5993
- clientInformation = fullInformation;
6241
+ clientInformation = { ...fullInformation, issuer };
6242
+ await provider.saveClientInformation(clientInformation);
5994
6243
  }
5995
6244
  }
5996
6245
  const nonInteractiveFlow = !provider.redirectUrl;
@@ -6001,10 +6250,14 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
6001
6250
  authorizationCode,
6002
6251
  fetchFn
6003
6252
  });
6004
- await provider.saveTokens(tokens2);
6253
+ await bindClientInformation();
6254
+ await provider.saveTokens({ ...tokens2, issuer });
6005
6255
  return "AUTHORIZED";
6006
6256
  }
6007
- const tokens = await provider.tokens();
6257
+ const tokens = discardIfIssuerMismatch(await provider.tokens(), issuer);
6258
+ if (tokens?.refresh_token && tokens.issuer == null) {
6259
+ console.warn("[mcp-sdk] stored OAuth tokens have no 'issuer' property (saved by an earlier version, or by a provider that does not keep it) and are used as-is; make sure your OAuthClientProvider stores what saveTokens() and saveClientInformation() receive unchanged.");
6260
+ }
6008
6261
  if (tokens?.refresh_token) {
6009
6262
  try {
6010
6263
  const newTokens = await refreshAuthorization(authorizationServerUrl, {
@@ -6015,7 +6268,8 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
6015
6268
  addClientAuthentication: provider.addClientAuthentication,
6016
6269
  fetchFn
6017
6270
  });
6018
- await provider.saveTokens(newTokens);
6271
+ await bindClientInformation();
6272
+ await provider.saveTokens({ ...newTokens, issuer });
6019
6273
  return "AUTHORIZED";
6020
6274
  } catch (error2) {
6021
6275
  if (!(error2 instanceof OAuthError) || error2 instanceof ServerError) {
@@ -6114,7 +6368,7 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
6114
6368
  }
6115
6369
  async function fetchWithCorsRetry(url2, headers, fetchFn = fetch) {
6116
6370
  try {
6117
- return await fetchFn(url2, { headers });
6371
+ return await fetchWithinOrigin(fetchFn)(url2, { headers });
6118
6372
  } catch (error2) {
6119
6373
  if (error2 instanceof TypeError) {
6120
6374
  if (headers) {
@@ -6139,7 +6393,7 @@ async function tryMetadataDiscovery(url2, protocolVersion, fetchFn = fetch) {
6139
6393
  return await fetchWithCorsRetry(url2, headers, fetchFn);
6140
6394
  }
6141
6395
  function shouldAttemptFallback(response, pathname) {
6142
- return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
6396
+ return !response || !response.ok && response.status < 500 && pathname !== "/";
6143
6397
  }
6144
6398
  async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
6145
6399
  const issuer = new URL(serverUrl);
@@ -6205,7 +6459,7 @@ async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fet
6205
6459
  }
6206
6460
  if (!response.ok) {
6207
6461
  await response.body?.cancel();
6208
- if (response.status >= 400 && response.status < 500) {
6462
+ if (response.status < 500) {
6209
6463
  continue;
6210
6464
  }
6211
6465
  throw new Error(`HTTP ${response.status} trying to load ${type === "oauth" ? "OAuth" : "OpenID provider"} metadata from ${endpointUrl}`);
@@ -6238,6 +6492,9 @@ async function discoverOAuthServerInfo(serverUrl, opts) {
6238
6492
  resourceMetadata
6239
6493
  };
6240
6494
  }
6495
+ function resourceIndicatorToString(resource) {
6496
+ return typeof resource === "string" ? resource : resource.href;
6497
+ }
6241
6498
  async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource }) {
6242
6499
  let authorizationUrl;
6243
6500
  if (metadata) {
@@ -6269,7 +6526,7 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
6269
6526
  authorizationUrl.searchParams.append("prompt", "consent");
6270
6527
  }
6271
6528
  if (resource) {
6272
- authorizationUrl.searchParams.set("resource", resource.href);
6529
+ authorizationUrl.searchParams.set("resource", resourceIndicatorToString(resource));
6273
6530
  }
6274
6531
  return { authorizationUrl, codeVerifier };
6275
6532
  }
@@ -6288,7 +6545,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
6288
6545
  Accept: "application/json"
6289
6546
  });
6290
6547
  if (resource) {
6291
- tokenRequestParams.set("resource", resource.href);
6548
+ tokenRequestParams.set("resource", resourceIndicatorToString(resource));
6292
6549
  }
6293
6550
  if (addClientAuthentication) {
6294
6551
  await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
@@ -6297,7 +6554,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
6297
6554
  const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
6298
6555
  applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
6299
6556
  }
6300
- const response = await (fetchFn ?? fetch)(tokenUrl, {
6557
+ const response = await fetchWithinOrigin(fetchFn ?? fetch)(tokenUrl, {
6301
6558
  method: "POST",
6302
6559
  headers,
6303
6560
  body: tokenRequestParams
@@ -6305,7 +6562,7 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
6305
6562
  if (!response.ok) {
6306
6563
  throw await parseErrorResponse(response);
6307
6564
  }
6308
- return OAuthTokensSchema.parse(await response.json());
6565
+ return TokenResponseSchema.parse(await response.json());
6309
6566
  }
6310
6567
  async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
6311
6568
  const tokenRequestParams = new URLSearchParams({
@@ -6323,6 +6580,15 @@ async function refreshAuthorization(authorizationServerUrl, { metadata, clientIn
6323
6580
  return { refresh_token: refreshToken, ...tokens };
6324
6581
  }
6325
6582
  async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
6583
+ const readClientInformation = async () => {
6584
+ const storedClientInformation = await provider.clientInformation();
6585
+ const checked = discardIfIssuerMismatch(storedClientInformation, String(authorizationServerUrl));
6586
+ if (storedClientInformation && !checked) {
6587
+ throw boundElsewhereError(storedClientInformation, String(authorizationServerUrl));
6588
+ }
6589
+ return checked;
6590
+ };
6591
+ let clientInformation = await readClientInformation();
6326
6592
  const scope = provider.clientMetadata.scope;
6327
6593
  let tokenRequestParams;
6328
6594
  if (provider.prepareTokenRequest) {
@@ -6338,11 +6604,11 @@ async function fetchToken(provider, authorizationServerUrl, { metadata, resource
6338
6604
  const codeVerifier = await provider.codeVerifier();
6339
6605
  tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
6340
6606
  }
6341
- const clientInformation = await provider.clientInformation();
6607
+ clientInformation ?? (clientInformation = await readClientInformation());
6342
6608
  return executeTokenRequest(authorizationServerUrl, {
6343
6609
  metadata,
6344
6610
  tokenRequestParams,
6345
- clientInformation: clientInformation ?? void 0,
6611
+ clientInformation,
6346
6612
  addClientAuthentication: provider.addClientAuthentication,
6347
6613
  resource,
6348
6614
  fetchFn
@@ -6358,7 +6624,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
6358
6624
  } else {
6359
6625
  registrationUrl = new URL("/register", authorizationServerUrl);
6360
6626
  }
6361
- const response = await (fetchFn ?? fetch)(registrationUrl, {
6627
+ const response = await fetchWithinOrigin(fetchFn ?? fetch)(registrationUrl, {
6362
6628
  method: "POST",
6363
6629
  headers: {
6364
6630
  "Content-Type": "application/json"
@@ -6371,7 +6637,7 @@ async function registerClient(authorizationServerUrl, { metadata, clientMetadata
6371
6637
  if (!response.ok) {
6372
6638
  throw await parseErrorResponse(response);
6373
6639
  }
6374
- return OAuthClientInformationFullSchema.parse(await response.json());
6640
+ return RegistrationResponseSchema.parse(await response.json());
6375
6641
  }
6376
6642
 
6377
6643
  // node_modules/eventsource-parser/dist/index.js
@@ -6598,9 +6864,19 @@ var StreamableHTTPClientTransport = class {
6598
6864
  this._authProvider = opts?.authProvider;
6599
6865
  this._fetch = opts?.fetch;
6600
6866
  this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
6867
+ this._followRedirects = opts?.redirectPolicy === "follow";
6868
+ if (this._followRedirects) {
6869
+ this._fetch = fetchLeavingRedirects(this._fetch);
6870
+ this._fetchWithInit = fetchLeavingRedirects(this._fetchWithInit);
6871
+ }
6601
6872
  this._sessionId = opts?.sessionId;
6602
6873
  this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
6603
6874
  }
6875
+ /** Error text for a redirect `response` that was not followed, or `undefined` for any other response. */
6876
+ _unfollowedRedirect(response, url2) {
6877
+ const text = unfollowedRedirect(response, url2);
6878
+ return text && !this._followRedirects ? `${text} (redirectPolicy: 'same-origin')` : text;
6879
+ }
6604
6880
  async _authThenStart() {
6605
6881
  if (!this._authProvider) {
6606
6882
  throw new UnauthorizedError("No auth provider");
@@ -6650,11 +6926,13 @@ var StreamableHTTPClientTransport = class {
6650
6926
  if (resumptionToken) {
6651
6927
  headers.set("last-event-id", resumptionToken);
6652
6928
  }
6653
- const response = await (this._fetch ?? fetch)(this._url, {
6654
- method: "GET",
6655
- headers,
6656
- signal: this._abortController?.signal
6657
- });
6929
+ const fetchFn = this._fetch ?? fetch;
6930
+ const init = { method: "GET", headers, signal: this._abortController?.signal };
6931
+ let response = await fetchFn(this._url, this._followRedirects ? init : { ...init, redirect: "manual" });
6932
+ const followed = this._followRedirects ? void 0 : followWithinOrigin(fetchFn, this._url, init, response);
6933
+ if (followed) {
6934
+ response = await followed;
6935
+ }
6658
6936
  if (!response.ok) {
6659
6937
  await response.body?.cancel();
6660
6938
  if (response.status === 401 && this._authProvider) {
@@ -6663,7 +6941,7 @@ var StreamableHTTPClientTransport = class {
6663
6941
  if (response.status === 405) {
6664
6942
  return;
6665
6943
  }
6666
- throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
6944
+ throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
6667
6945
  }
6668
6946
  this._handleSseStream(response.body, options, true);
6669
6947
  } catch (error2) {
@@ -6826,7 +7104,7 @@ var StreamableHTTPClientTransport = class {
6826
7104
  body: JSON.stringify(message),
6827
7105
  signal: this._abortController?.signal
6828
7106
  };
6829
- const response = await (this._fetch ?? fetch)(this._url, init);
7107
+ const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
6830
7108
  const sessionId = response.headers.get("mcp-session-id");
6831
7109
  if (sessionId) {
6832
7110
  this._sessionId = sessionId;
@@ -6878,7 +7156,7 @@ var StreamableHTTPClientTransport = class {
6878
7156
  return this.send(message);
6879
7157
  }
6880
7158
  }
6881
- throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
7159
+ throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${this._unfollowedRedirect(response, this._url) ?? text}`);
6882
7160
  }
6883
7161
  this._hasCompletedAuthFlow = false;
6884
7162
  this._lastUpscopingHeader = void 0;
@@ -6891,11 +7169,12 @@ var StreamableHTTPClientTransport = class {
6891
7169
  }
6892
7170
  const messages = Array.isArray(message) ? message : [message];
6893
7171
  const hasRequests = messages.filter((msg) => "method" in msg && "id" in msg && msg.id !== void 0).length > 0;
6894
- const contentType = response.headers.get("content-type");
7172
+ const contentType2 = response.headers.get("content-type");
7173
+ const responseMediaType = mediaTypeEssence(contentType2);
6895
7174
  if (hasRequests) {
6896
- if (contentType?.includes("text/event-stream")) {
7175
+ if (responseMediaType === "text/event-stream") {
6897
7176
  this._handleSseStream(response.body, { onresumptiontoken }, false);
6898
- } else if (contentType?.includes("application/json")) {
7177
+ } else if (responseMediaType === "application/json") {
6899
7178
  const data = await response.json();
6900
7179
  const responseMessages = Array.isArray(data) ? data.map((msg) => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(data)];
6901
7180
  for (const msg of responseMessages) {
@@ -6903,7 +7182,7 @@ var StreamableHTTPClientTransport = class {
6903
7182
  }
6904
7183
  } else {
6905
7184
  await response.body?.cancel();
6906
- throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
7185
+ throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType2}`);
6907
7186
  }
6908
7187
  } else {
6909
7188
  await response.body?.cancel();
@@ -6939,10 +7218,10 @@ var StreamableHTTPClientTransport = class {
6939
7218
  headers,
6940
7219
  signal: this._abortController?.signal
6941
7220
  };
6942
- const response = await (this._fetch ?? fetch)(this._url, init);
7221
+ const response = await fetchWithinOrigin(this._fetch ?? fetch)(this._url, init);
6943
7222
  await response.body?.cancel();
6944
7223
  if (!response.ok && response.status !== 405) {
6945
- throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
7224
+ throw new StreamableHTTPError(response.status, `Failed to terminate session: ${this._unfollowedRedirect(response, this._url) ?? response.statusText}`);
6946
7225
  }
6947
7226
  this._sessionId = void 0;
6948
7227
  } catch (error2) {
@@ -7251,3 +7530,12 @@ main().catch((err) => {
7251
7530
  `);
7252
7531
  process.exit(1);
7253
7532
  });
7533
+ /*! Bundled license information:
7534
+
7535
+ content-type/index.js:
7536
+ (*!
7537
+ * content-type
7538
+ * Copyright(c) 2015 Douglas Christopher Wilson
7539
+ * MIT Licensed
7540
+ *)
7541
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.63.2",
3
+ "version": "2.64.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,7 +62,7 @@
62
62
  "cross-spawn": "^7.0.6",
63
63
  "diff": "^9.0.0",
64
64
  "express": "^5.2.1",
65
- "js-yaml": "4.1.1",
65
+ "js-yaml": "4.3.2",
66
66
  "multer": "^2.2.0",
67
67
  "node-pty": "^1.0.0",
68
68
  "pdf-parse": "^2.4.5",
@@ -96,6 +96,9 @@
96
96
  },
97
97
  "overrides": {
98
98
  "ip": "npm:@eggjs/ip@2.1.0",
99
- "shell-quote": "1.9.0"
99
+ "shell-quote": "1.12.0",
100
+ "@tailwindcss/typography": {
101
+ "postcss-selector-parser": "7.1.6"
102
+ }
100
103
  }
101
104
  }
@@ -623,7 +623,20 @@ function applyPtyWsRateLimiting(ws) {
623
623
  // Second HTTPS+WSS listener in THIS process; the main server stays loopback.
624
624
  // The control plane is desktop-only: loopback + (via /api below) requireAuth.
625
625
  // Mounted before the /api desktop router so its sub-path is unambiguous.
626
- const mobileGateway = new mobile_1.MobileGateway({ desktopDb: registry.desktopDb, desktopPort: port, broadcast });
626
+ const mobileGateway = new mobile_1.MobileGateway({
627
+ desktopDb: registry.desktopDb,
628
+ desktopPort: port,
629
+ broadcast,
630
+ // Companion app preview: each project's browser + the process-log targets.
631
+ preview: {
632
+ enabled: () => BROWSER_CAPTURE_ENABLED,
633
+ browserFor: (projectId) => registry.getContext(projectId)?.browserCaptureManager ?? null,
634
+ allBrowsers: () => registry.listProjects()
635
+ .map((project) => registry.getContext(project.id)?.browserCaptureManager)
636
+ .filter((manager) => manager != null),
637
+ targets: registry.previewTargets,
638
+ },
639
+ });
627
640
  _mobileGateway = mobileGateway;
628
641
  app.use('/api/mobile', auth_1.requireLoopback, (0, mobile_1.createMobileAdminRouter)({
629
642
  gateway: mobileGateway,