replicas-engine 0.1.335 → 0.1.336

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 (2) hide show
  1. package/dist/src/index.js +163 -23
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -291,7 +291,7 @@ var WORKSPACE_SIZES = ["small", "large"];
291
291
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
292
292
 
293
293
  // ../shared/src/e2b.ts
294
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-22-v1";
294
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-22-v2";
295
295
 
296
296
  // ../shared/src/runtime-env.ts
297
297
  function parsePosixEnvFile(content) {
@@ -377,6 +377,51 @@ var InFlightMap = class {
377
377
  }
378
378
  };
379
379
 
380
+ // ../shared/src/github/url.ts
381
+ var GITHUB_HOST_RE = /(^|@|\/\/)github\.com[:/]/;
382
+ function isGitHubUrl(url) {
383
+ if (!url) return false;
384
+ return GITHUB_HOST_RE.test(url);
385
+ }
386
+ function stripCredentialsFromUrl(url) {
387
+ try {
388
+ const parsed = new URL(url);
389
+ if (parsed.username || parsed.password) {
390
+ parsed.username = "";
391
+ parsed.password = "";
392
+ return parsed.toString();
393
+ }
394
+ } catch {
395
+ }
396
+ return url;
397
+ }
398
+
399
+ // ../shared/src/urls.ts
400
+ function decodePathSegments(segments) {
401
+ return segments.map((segment) => {
402
+ try {
403
+ return decodeURIComponent(segment);
404
+ } catch {
405
+ return segment;
406
+ }
407
+ });
408
+ }
409
+ function normalizeRepositoryUrl(url, options = {}) {
410
+ if (!url) return null;
411
+ const stripped = stripCredentialsFromUrl(url).trim().replace(/\/+$/, "").replace(/\.git$/, "");
412
+ if (!stripped) return null;
413
+ try {
414
+ const parsed = new URL(stripped);
415
+ parsed.hash = "";
416
+ parsed.search = "";
417
+ parsed.pathname = parsed.pathname.replace(/\/+$/, "").replace(/\.git$/, "");
418
+ const normalized = `${parsed.origin}${parsed.pathname}`;
419
+ return options.lowercase ? normalized.toLowerCase() : normalized;
420
+ } catch {
421
+ return options.lowercase ? stripped.toLowerCase() : stripped;
422
+ }
423
+ }
424
+
380
425
  // ../shared/src/slash-commands.ts
381
426
  var MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4e3;
382
427
  function parseGoalCommand(message) {
@@ -2734,13 +2779,6 @@ var MEDIA_KIND = {
2734
2779
  };
2735
2780
  var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
2736
2781
 
2737
- // ../shared/src/github/url.ts
2738
- var GITHUB_HOST_RE = /(^|@|\/\/)github\.com[:/]/;
2739
- function isGitHubUrl(url) {
2740
- if (!url) return false;
2741
- return GITHUB_HOST_RE.test(url);
2742
- }
2743
-
2744
2782
  // ../shared/src/skill-registry.ts
2745
2783
  var SKILL_REGISTRY_MANIFEST_VERSION = 1;
2746
2784
 
@@ -3562,8 +3600,8 @@ var GitService = class {
3562
3600
  defaultBranchCache = /* @__PURE__ */ new Map();
3563
3601
  cachedPrByRepo = /* @__PURE__ */ new Map();
3564
3602
  // No invalidation on purpose — `git remote set-url` mid-session is rare and
3565
- // the worst case is `gh pr view` stays skipped until the next engine restart.
3566
- originIsGitHubCache = /* @__PURE__ */ new Map();
3603
+ // the worst case is PR/MR lookup stays skipped until the next engine restart.
3604
+ originInfoCache = /* @__PURE__ */ new Map();
3567
3605
  // Broadcaster + UI /repos requests fan in here every ~2s; one shared run.
3568
3606
  listReposInFlight = new InFlightMap();
3569
3607
  diffStatsInFlight = new InFlightMap();
@@ -3926,7 +3964,8 @@ var GitService = class {
3926
3964
  }
3927
3965
  }
3928
3966
  async lookupPrOnRemote(repoName, repoPath, branch) {
3929
- if (!await this.originIsGitHub(repoPath)) {
3967
+ const origin = await this.getOriginInfo(repoPath);
3968
+ if (origin.provider === "unknown") {
3930
3969
  return { status: "not_found" };
3931
3970
  }
3932
3971
  try {
@@ -3941,6 +3980,9 @@ var GitService = class {
3941
3980
  } catch {
3942
3981
  return { status: "error" };
3943
3982
  }
3983
+ if (origin.provider === "gitlab") {
3984
+ return this.lookupGitLabMrOnRemote(repoName, origin.gitLab, branch);
3985
+ }
3944
3986
  try {
3945
3987
  const { stdout } = await execFileAsync("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
3946
3988
  cwd: repoPath,
@@ -3955,24 +3997,120 @@ var GitService = class {
3955
3997
  return { status: "error" };
3956
3998
  }
3957
3999
  }
3958
- async originIsGitHub(repoPath) {
3959
- const cached = this.originIsGitHubCache.get(repoPath);
4000
+ async lookupGitLabMrOnRemote(repoName, remote, branch) {
4001
+ const token = await this.getGitLabAccessToken(remote.host);
4002
+ if (!token) {
4003
+ return { status: "not_found" };
4004
+ }
4005
+ const url = `${remote.instanceUrl}/api/v4/projects/${encodeURIComponent(remote.projectPath)}/merge_requests?state=opened&source_branch=${encodeURIComponent(branch)}`;
4006
+ try {
4007
+ const response = await fetch(url, {
4008
+ headers: { Authorization: `Bearer ${token}` },
4009
+ signal: AbortSignal.timeout(1e4)
4010
+ });
4011
+ if (!response.ok) {
4012
+ console.warn(`[GitService] GitLab MR lookup failed for ${repoName}: ${response.status}`);
4013
+ return response.status === 404 ? { status: "not_found" } : { status: "error" };
4014
+ }
4015
+ const data = await response.json();
4016
+ if (!Array.isArray(data)) return { status: "not_found" };
4017
+ const mergeRequest = data.find((item) => isRecord4(item) && item.source_branch === branch) ?? data[0];
4018
+ if (!isRecord4(mergeRequest)) return { status: "not_found" };
4019
+ const webUrl = mergeRequest.web_url;
4020
+ if (typeof webUrl === "string" && webUrl.length > 0) return { status: "found", url: webUrl };
4021
+ const iid = mergeRequest.iid;
4022
+ return typeof iid === "number" ? { status: "found", url: `${remote.instanceUrl}/${remote.projectPath}/-/merge_requests/${iid}` } : { status: "not_found" };
4023
+ } catch (error) {
4024
+ const message = error instanceof Error ? error.message : String(error);
4025
+ console.warn(`[GitService] GitLab MR lookup failed for ${repoName}: ${message}`);
4026
+ return { status: "error" };
4027
+ }
4028
+ }
4029
+ async getOriginInfo(repoPath) {
4030
+ const cached = this.originInfoCache.get(repoPath);
3960
4031
  if (cached !== void 0) {
3961
4032
  return cached;
3962
4033
  }
3963
- let isGitHub = false;
4034
+ let info = { provider: "unknown" };
3964
4035
  try {
3965
4036
  const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
3966
4037
  cwd: repoPath,
3967
4038
  encoding: "utf-8",
3968
4039
  maxBuffer: SUBPROCESS_MAX_BUFFER
3969
4040
  });
3970
- isGitHub = isGitHubUrl(stdout.trim());
4041
+ const originUrl = stdout.trim();
4042
+ if (isGitHubUrl(originUrl)) {
4043
+ info = { provider: "github" };
4044
+ } else {
4045
+ const gitLabRemote = this.parseGitLabRemote(originUrl);
4046
+ if (gitLabRemote) info = { provider: "gitlab", gitLab: gitLabRemote };
4047
+ }
3971
4048
  } catch {
3972
- isGitHub = false;
4049
+ info = { provider: "unknown" };
3973
4050
  }
3974
- this.originIsGitHubCache.set(repoPath, isGitHub);
3975
- return isGitHub;
4051
+ this.originInfoCache.set(repoPath, info);
4052
+ return info;
4053
+ }
4054
+ parseGitLabRemote(remoteUrl) {
4055
+ const sshUrl = remoteUrl.trim().replace(/\/+$/, "").replace(/\.git$/, "");
4056
+ try {
4057
+ const parsed = new URL(sshUrl);
4058
+ if (parsed.protocol === "ssh:") {
4059
+ const projectPath = decodePathSegments(parsed.pathname.split("/").filter(Boolean)).join("/");
4060
+ if (!parsed.hostname || !projectPath) return null;
4061
+ const host = parsed.hostname.toLowerCase();
4062
+ return {
4063
+ host,
4064
+ instanceUrl: `https://${host}`,
4065
+ projectPath
4066
+ };
4067
+ }
4068
+ } catch {
4069
+ }
4070
+ const scpLike = sshUrl.match(/^git@([^:/]+):(?:(?:\d+)\/)?(.+)$/);
4071
+ if (scpLike) {
4072
+ const [, host, projectPath] = scpLike;
4073
+ if (!host || !projectPath) return null;
4074
+ const decodedProjectPath = decodePathSegments(projectPath.split("/").filter(Boolean)).join("/");
4075
+ return {
4076
+ host: host.toLowerCase(),
4077
+ instanceUrl: `https://${host}`,
4078
+ projectPath: decodedProjectPath
4079
+ };
4080
+ }
4081
+ const normalized = normalizeRepositoryUrl(remoteUrl);
4082
+ if (!normalized) return null;
4083
+ try {
4084
+ const parsed = new URL(normalized);
4085
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
4086
+ const projectPath = decodePathSegments(parsed.pathname.split("/").filter(Boolean)).join("/");
4087
+ if (!projectPath) return null;
4088
+ return {
4089
+ host: parsed.host.toLowerCase(),
4090
+ instanceUrl: parsed.origin,
4091
+ projectPath
4092
+ };
4093
+ } catch {
4094
+ }
4095
+ return null;
4096
+ }
4097
+ async getGitLabAccessToken(host) {
4098
+ try {
4099
+ const credentials = await readFile3(join5(ENGINE_ENV.HOME_DIR, ".git-credentials"), "utf-8");
4100
+ for (const line of credentials.split("\n")) {
4101
+ const trimmed = line.trim();
4102
+ if (!trimmed) continue;
4103
+ try {
4104
+ const parsed = new URL(trimmed);
4105
+ if (parsed.protocol === "https:" && parsed.host.toLowerCase() === host && parsed.password) {
4106
+ return decodeURIComponent(parsed.password);
4107
+ }
4108
+ } catch {
4109
+ }
4110
+ }
4111
+ } catch {
4112
+ }
4113
+ return null;
3976
4114
  }
3977
4115
  async resolveDefaultBranch(repoPath) {
3978
4116
  const cached = this.defaultBranchCache.get(repoPath);
@@ -7359,7 +7497,7 @@ var AspClient = class {
7359
7497
  // src/managers/codex-asp/app-server-process.ts
7360
7498
  var DEFAULT_CODEX_BINARY = "codex";
7361
7499
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
7362
- var ENGINE_PACKAGE_VERSION = "0.1.335";
7500
+ var ENGINE_PACKAGE_VERSION = "0.1.336";
7363
7501
  var INITIALIZE_METHOD = "initialize";
7364
7502
  var INITIALIZED_NOTIFICATION = "initialized";
7365
7503
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -10798,10 +10936,12 @@ var ChatService = class {
10798
10936
  }
10799
10937
  }
10800
10938
  async publishAgentTurnCompleteWebhook(chat) {
10801
- try {
10802
- await githubTokenManager.refreshOnce();
10803
- } catch {
10804
- }
10939
+ await Promise.all([
10940
+ githubTokenManager.refreshOnce().catch(() => {
10941
+ }),
10942
+ gitlabTokenManager.refreshOnce().catch(() => {
10943
+ })
10944
+ ]);
10805
10945
  const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
10806
10946
  const observedBranches = chat.observedBranchesByRepo;
10807
10947
  chat.observedBranchesByRepo = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.335",
3
+ "version": "0.1.336",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",