replicas-engine 0.1.477 → 0.1.478
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.
- package/dist/src/index.js +730 -751
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { serve } from "@hono/node-server";
|
|
5
5
|
import { Hono as Hono2 } from "hono";
|
|
6
|
-
import { existsSync as
|
|
6
|
+
import { existsSync as existsSync10 } from "fs";
|
|
7
7
|
|
|
8
8
|
// ../shared/src/type-guards.ts
|
|
9
9
|
function isRecord(value) {
|
|
@@ -582,7 +582,7 @@ var WORKSPACE_SIZES = ["small", "large"];
|
|
|
582
582
|
var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
|
|
583
583
|
|
|
584
584
|
// ../shared/src/e2b.ts
|
|
585
|
-
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-22-
|
|
585
|
+
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-22-v3";
|
|
586
586
|
|
|
587
587
|
// ../shared/src/runtime-env.ts
|
|
588
588
|
function shellQuotePosix(value) {
|
|
@@ -644,10 +644,30 @@ function parsePosixEnvFile(content) {
|
|
|
644
644
|
}
|
|
645
645
|
|
|
646
646
|
// ../shared/src/git.ts
|
|
647
|
-
function
|
|
647
|
+
function parseGitRemote(remoteUrl) {
|
|
648
|
+
const normalized = remoteUrl.trim().replace(/\/+$/, "").replace(/\.git$/, "");
|
|
649
|
+
try {
|
|
650
|
+
const parsed = new URL(normalized);
|
|
651
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && parsed.protocol !== "ssh:") return null;
|
|
652
|
+
const path7 = parsed.pathname.replace(/^\/+/, "");
|
|
653
|
+
const host2 = parsed.protocol === "ssh:" ? parsed.hostname : parsed.host;
|
|
654
|
+
if (!host2 || !path7) return null;
|
|
655
|
+
return {
|
|
656
|
+
host: host2.toLowerCase(),
|
|
657
|
+
path: path7,
|
|
658
|
+
protocol: parsed.protocol === "http:" ? "http" : parsed.protocol === "https:" ? "https" : "ssh"
|
|
659
|
+
};
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
const match = normalized.match(/^(?:[^@/\s]+@)?([^:/\s]+):(?:(?:\d+)\/)?(.+)$/);
|
|
663
|
+
if (!match) return null;
|
|
664
|
+
const [, host, path6] = match;
|
|
665
|
+
return host && path6 ? { host: host.toLowerCase(), path: path6, protocol: "ssh" } : null;
|
|
666
|
+
}
|
|
667
|
+
function gitIdentityConfigCommands(identity, scope = "global") {
|
|
648
668
|
return [
|
|
649
|
-
["config",
|
|
650
|
-
["config",
|
|
669
|
+
["config", `--${scope}`, "user.name", identity.name],
|
|
670
|
+
["config", `--${scope}`, "user.email", identity.email]
|
|
651
671
|
];
|
|
652
672
|
}
|
|
653
673
|
|
|
@@ -676,18 +696,6 @@ function isGitHubUrl(url) {
|
|
|
676
696
|
if (!url) return false;
|
|
677
697
|
return GITHUB_HOST_RE.test(url);
|
|
678
698
|
}
|
|
679
|
-
function stripCredentialsFromUrl(url) {
|
|
680
|
-
try {
|
|
681
|
-
const parsed = new URL(url);
|
|
682
|
-
if (parsed.username || parsed.password) {
|
|
683
|
-
parsed.username = "";
|
|
684
|
-
parsed.password = "";
|
|
685
|
-
return parsed.toString();
|
|
686
|
-
}
|
|
687
|
-
} catch {
|
|
688
|
-
}
|
|
689
|
-
return url;
|
|
690
|
-
}
|
|
691
699
|
|
|
692
700
|
// ../shared/src/urls.ts
|
|
693
701
|
function decodePathSegments(segments) {
|
|
@@ -699,21 +707,6 @@ function decodePathSegments(segments) {
|
|
|
699
707
|
}
|
|
700
708
|
});
|
|
701
709
|
}
|
|
702
|
-
function normalizeRepositoryUrl(url, options = {}) {
|
|
703
|
-
if (!url) return null;
|
|
704
|
-
const stripped = stripCredentialsFromUrl(url).trim().replace(/\/+$/, "").replace(/\.git$/, "");
|
|
705
|
-
if (!stripped) return null;
|
|
706
|
-
try {
|
|
707
|
-
const parsed = new URL(stripped);
|
|
708
|
-
parsed.hash = "";
|
|
709
|
-
parsed.search = "";
|
|
710
|
-
parsed.pathname = parsed.pathname.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
711
|
-
const normalized = `${parsed.origin}${parsed.pathname}`;
|
|
712
|
-
return options.lowercase ? normalized.toLowerCase() : normalized;
|
|
713
|
-
} catch {
|
|
714
|
-
return options.lowercase ? stripped.toLowerCase() : stripped;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
710
|
|
|
718
711
|
// ../shared/src/slash-commands.ts
|
|
719
712
|
function normalizeSlashCommandName(name) {
|
|
@@ -4992,502 +4985,98 @@ function removeCredentialFileLines(filePath, shouldRemove) {
|
|
|
4992
4985
|
});
|
|
4993
4986
|
}
|
|
4994
4987
|
|
|
4995
|
-
// src/
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
4988
|
+
// src/git/service.ts
|
|
4989
|
+
import { readdir, readFile as readFile3, stat } from "fs/promises";
|
|
4990
|
+
import { spawn } from "child_process";
|
|
4991
|
+
import { join as join5 } from "path";
|
|
4992
|
+
|
|
4993
|
+
// src/utils/state.ts
|
|
4994
|
+
import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
|
|
4995
|
+
import { existsSync } from "fs";
|
|
4996
|
+
import { join as join3 } from "path";
|
|
4997
|
+
import { homedir as homedir3 } from "os";
|
|
4998
|
+
|
|
4999
|
+
// src/utils/type-guards.ts
|
|
5000
|
+
function isRecord4(value) {
|
|
5001
|
+
return typeof value === "object" && value !== null;
|
|
5005
5002
|
}
|
|
5006
5003
|
|
|
5007
|
-
// src/
|
|
5008
|
-
var
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
}
|
|
5012
|
-
async doRefresh(_config) {
|
|
5013
|
-
console.log("[GitHubTokenManager] Refreshing GitHub token...");
|
|
5014
|
-
const response = await monolithRequest("/v1/engine/github/refresh-token");
|
|
5015
|
-
if (!response.ok) {
|
|
5016
|
-
if (response.status === 403) {
|
|
5017
|
-
await this.clearGitHubCredentials();
|
|
5018
|
-
}
|
|
5019
|
-
const errorText = await response.text();
|
|
5020
|
-
throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
|
|
5021
|
-
}
|
|
5022
|
-
const data = await response.json();
|
|
5023
|
-
const ghToken = data.userToken?.token ?? data.token;
|
|
5024
|
-
const ghUsername = data.userToken?.username ?? "x-access-token";
|
|
5025
|
-
await this.updateGitCredentials(ghToken);
|
|
5026
|
-
await this.updateGhHostsFile(ghToken, ghUsername);
|
|
5027
|
-
if (data.gitIdentity) {
|
|
5028
|
-
await updateGitIdentity(data.gitIdentity, "GitHubTokenManager");
|
|
5029
|
-
}
|
|
5030
|
-
if (data.userToken) {
|
|
5031
|
-
console.log(`[GitHubTokenManager] Token refreshed with user token for PR attribution, user token expires at ${data.userToken.expiresAt}`);
|
|
5032
|
-
} else {
|
|
5033
|
-
console.log(`[GitHubTokenManager] Token refreshed successfully, expires at ${data.expiresAt}`);
|
|
5034
|
-
}
|
|
5035
|
-
}
|
|
5036
|
-
async updateGitCredentials(token) {
|
|
5037
|
-
const credentialsPath = path.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
5038
|
-
try {
|
|
5039
|
-
await upsertCredentialFileLines(credentialsPath, ["github.com"], [
|
|
5040
|
-
`https://x-access-token:${token}@github.com`
|
|
5041
|
-
]);
|
|
5042
|
-
console.log(`[GitHubTokenManager] Updated ${credentialsPath}`);
|
|
5043
|
-
} catch (error) {
|
|
5044
|
-
console.error("[GitHubTokenManager] Failed to update git credentials:", error);
|
|
5045
|
-
}
|
|
5046
|
-
}
|
|
5047
|
-
async updateGhHostsFile(token, username) {
|
|
5048
|
-
const hostsPath = path.join(ENGINE_ENV.HOME_DIR, ".config", "gh", "hosts.yml");
|
|
5049
|
-
const content = `github.com:
|
|
5050
|
-
oauth_token: ${JSON.stringify(token)}
|
|
5051
|
-
user: ${JSON.stringify(username)}
|
|
5052
|
-
git_protocol: https
|
|
5053
|
-
`;
|
|
5054
|
-
try {
|
|
5055
|
-
await writeSecureCredentialFile(hostsPath, content, { ensureParentDir: true });
|
|
5056
|
-
console.log(`[GitHubTokenManager] Updated ${hostsPath}`);
|
|
5057
|
-
} catch (error) {
|
|
5058
|
-
console.error("[GitHubTokenManager] Failed to update gh hosts file:", error);
|
|
5059
|
-
}
|
|
5060
|
-
}
|
|
5061
|
-
async clearGitHubCredentials() {
|
|
5062
|
-
const credentialsPath = path.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
5063
|
-
await removeCredentialFileLines(
|
|
5064
|
-
credentialsPath,
|
|
5065
|
-
(line) => line.endsWith("@github.com")
|
|
5066
|
-
);
|
|
5067
|
-
}
|
|
5004
|
+
// src/utils/state.ts
|
|
5005
|
+
var STATE_DIR = join3(homedir3(), ".replicas");
|
|
5006
|
+
var STATE_FILE = join3(STATE_DIR, "engine-state.json");
|
|
5007
|
+
var DEFAULT_STATE = {
|
|
5008
|
+
repos: {}
|
|
5068
5009
|
};
|
|
5069
|
-
var
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5010
|
+
var stateWriteLock = new AsyncLock();
|
|
5011
|
+
async function updateEngineState(updater) {
|
|
5012
|
+
await stateWriteLock.run(async () => {
|
|
5013
|
+
await mkdir2(STATE_DIR, { recursive: true });
|
|
5014
|
+
const currentState = await loadEngineState();
|
|
5015
|
+
const nextState = updater(currentState);
|
|
5016
|
+
await atomicWriteFile(STATE_FILE, JSON.stringify(nextState, null, 2));
|
|
5017
|
+
});
|
|
5018
|
+
}
|
|
5019
|
+
function isEngineRepoDiff(value) {
|
|
5020
|
+
return isRecord4(value) && typeof value.added === "number" && typeof value.removed === "number";
|
|
5021
|
+
}
|
|
5022
|
+
function coerceRepoState(value) {
|
|
5023
|
+
if (!isRecord4(value)) {
|
|
5024
|
+
return null;
|
|
5076
5025
|
}
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
if (!data.token) {
|
|
5087
|
-
await this.clearGitLabCredentials(data.hosts);
|
|
5088
|
-
console.log(`[GitLabTokenManager] No GitLab token to install: ${data.reason}`);
|
|
5089
|
-
return;
|
|
5090
|
-
}
|
|
5091
|
-
const hosts = data.hosts.length > 0 ? data.hosts : ["gitlab.com"];
|
|
5092
|
-
const credentialsPath = path2.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
5093
|
-
await upsertCredentialFileLines(
|
|
5094
|
-
credentialsPath,
|
|
5095
|
-
hosts,
|
|
5096
|
-
hosts.map((host) => `https://oauth2:${data.token}@${host}`)
|
|
5097
|
-
);
|
|
5098
|
-
console.log(`[GitLabTokenManager] Updated ${credentialsPath} for ${hosts.join(", ")}`);
|
|
5099
|
-
if (data.gitIdentity) {
|
|
5100
|
-
await updateGitIdentity(data.gitIdentity, "GitLabTokenManager");
|
|
5101
|
-
}
|
|
5026
|
+
if (typeof value.name !== "string") return null;
|
|
5027
|
+
if (typeof value.path !== "string") return null;
|
|
5028
|
+
if (typeof value.defaultBranch !== "string") return null;
|
|
5029
|
+
if (typeof value.currentBranch !== "string") return null;
|
|
5030
|
+
if (!Array.isArray(value.prUrls)) return null;
|
|
5031
|
+
const prUrls = [];
|
|
5032
|
+
for (const entry of value.prUrls) {
|
|
5033
|
+
if (typeof entry !== "string") return null;
|
|
5034
|
+
if (!prUrls.includes(entry)) prUrls.push(entry);
|
|
5102
5035
|
}
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5036
|
+
if (!(value.gitDiff === null || isEngineRepoDiff(value.gitDiff))) return null;
|
|
5037
|
+
if (typeof value.startHooksCompleted !== "boolean") return null;
|
|
5038
|
+
return {
|
|
5039
|
+
name: value.name,
|
|
5040
|
+
path: value.path,
|
|
5041
|
+
defaultBranch: value.defaultBranch,
|
|
5042
|
+
currentBranch: value.currentBranch,
|
|
5043
|
+
prUrls,
|
|
5044
|
+
gitDiff: value.gitDiff,
|
|
5045
|
+
startHooksCompleted: value.startHooksCompleted
|
|
5046
|
+
};
|
|
5047
|
+
}
|
|
5048
|
+
function coerceEngineState(value) {
|
|
5049
|
+
if (!isRecord4(value)) {
|
|
5050
|
+
return {};
|
|
5109
5051
|
}
|
|
5110
|
-
};
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
// src/managers/auth-env-transition.ts
|
|
5118
|
-
function applyAuthEnvTransition(params) {
|
|
5119
|
-
const newOwned = new Set(params.authKeysByMethod[params.newMethod]);
|
|
5120
|
-
const prevOwned = new Set(params.authKeysByMethod[params.prevMethod]);
|
|
5121
|
-
for (const key of params.authKeys) {
|
|
5122
|
-
const value = params.newEnvVars[key];
|
|
5123
|
-
if (value !== void 0) {
|
|
5124
|
-
for (const env of params.envs) {
|
|
5125
|
-
env[key] = value;
|
|
5126
|
-
}
|
|
5127
|
-
} else if (prevOwned.has(key) && !newOwned.has(key)) {
|
|
5128
|
-
for (const env of params.envs) {
|
|
5129
|
-
delete env[key];
|
|
5052
|
+
const partial = {};
|
|
5053
|
+
if (isRecord4(value.repos)) {
|
|
5054
|
+
const repos = {};
|
|
5055
|
+
for (const [repoName, repoState] of Object.entries(value.repos)) {
|
|
5056
|
+
const coerced = coerceRepoState(repoState);
|
|
5057
|
+
if (coerced) {
|
|
5058
|
+
repos[repoName] = coerced;
|
|
5130
5059
|
}
|
|
5131
5060
|
}
|
|
5061
|
+
partial.repos = repos;
|
|
5132
5062
|
}
|
|
5063
|
+
return partial;
|
|
5133
5064
|
}
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
super("ClaudeTokenManager");
|
|
5139
|
-
}
|
|
5140
|
-
getSkipReason() {
|
|
5141
|
-
const method = ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD;
|
|
5142
|
-
if (method === "api_key" || method === "bedrock" || method === "foundry") {
|
|
5143
|
-
return `auth method is ${method}`;
|
|
5144
|
-
}
|
|
5145
|
-
return null;
|
|
5146
|
-
}
|
|
5147
|
-
async doRefresh(_config) {
|
|
5148
|
-
await this.refreshWithRequest();
|
|
5149
|
-
}
|
|
5150
|
-
async refreshWithRequest(request) {
|
|
5151
|
-
console.log("[ClaudeTokenManager] Refreshing Claude credentials...");
|
|
5152
|
-
const response = await monolithRequest("/v1/engine/claude/refresh-credentials", {
|
|
5153
|
-
body: request
|
|
5154
|
-
});
|
|
5155
|
-
if (!response.ok) {
|
|
5156
|
-
const errorText = await response.text();
|
|
5157
|
-
throw new Error(`Credentials refresh failed: ${response.status} ${errorText}`);
|
|
5065
|
+
async function loadEngineState() {
|
|
5066
|
+
try {
|
|
5067
|
+
if (!existsSync(STATE_FILE)) {
|
|
5068
|
+
return { ...DEFAULT_STATE };
|
|
5158
5069
|
}
|
|
5159
|
-
const
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
await this.refreshWithRequest(failedMethod && failedMethod !== "none" ? {
|
|
5170
|
-
failedMethod,
|
|
5171
|
-
failureReason
|
|
5172
|
-
} : void 0);
|
|
5173
|
-
if (ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD === "oauth") {
|
|
5174
|
-
this.start().catch((error) => {
|
|
5175
|
-
console.error("[ClaudeTokenManager] Failed to restart OAuth refresh service after fallback:", error);
|
|
5176
|
-
});
|
|
5177
|
-
}
|
|
5178
|
-
return true;
|
|
5179
|
-
} catch (error) {
|
|
5180
|
-
console.error("[ClaudeTokenManager] Failed to fetch fresh credentials:", error);
|
|
5181
|
-
return false;
|
|
5182
|
-
}
|
|
5183
|
-
}
|
|
5184
|
-
async applyCredentialsResponse(response) {
|
|
5185
|
-
if (response.type === "oauth") {
|
|
5186
|
-
await this.writeOauthCredentialsFile({
|
|
5187
|
-
accessToken: response.accessToken,
|
|
5188
|
-
refreshToken: response.refreshToken,
|
|
5189
|
-
expiresAt: response.expiresAt,
|
|
5190
|
-
scopes: response.scopes,
|
|
5191
|
-
subscriptionType: response.subscriptionType
|
|
5192
|
-
});
|
|
5193
|
-
} else {
|
|
5194
|
-
await this.removeOauthCredentialsFile();
|
|
5195
|
-
}
|
|
5196
|
-
const envVars = claudeAuthEnvFromResponse(response);
|
|
5197
|
-
applyAuthEnvTransition({
|
|
5198
|
-
prevMethod: ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD ?? "none",
|
|
5199
|
-
newMethod: envVars.REPLICAS_CLAUDE_AUTH_METHOD ?? "none",
|
|
5200
|
-
authKeys: CLAUDE_AUTH_ENV_KEYS,
|
|
5201
|
-
authKeysByMethod: CLAUDE_AUTH_ENV_KEYS_BY_METHOD,
|
|
5202
|
-
newEnvVars: envVars,
|
|
5203
|
-
envs: [ENGINE_ENV, process.env]
|
|
5204
|
-
});
|
|
5205
|
-
}
|
|
5206
|
-
async writeOauthCredentialsFile(credentials) {
|
|
5207
|
-
const credentialsPath = path3.join(ENGINE_ENV.HOME_DIR, ".claude", ".credentials.json");
|
|
5208
|
-
const claudeCliConfig = {
|
|
5209
|
-
claudeAiOauth: {
|
|
5210
|
-
accessToken: credentials.accessToken,
|
|
5211
|
-
refreshToken: credentials.refreshToken,
|
|
5212
|
-
expiresAt: new Date(credentials.expiresAt).getTime(),
|
|
5213
|
-
scopes: credentials.scopes,
|
|
5214
|
-
subscriptionType: credentials.subscriptionType
|
|
5215
|
-
}
|
|
5216
|
-
};
|
|
5217
|
-
try {
|
|
5218
|
-
await writeSecureCredentialFile(
|
|
5219
|
-
credentialsPath,
|
|
5220
|
-
JSON.stringify(claudeCliConfig, null, 2),
|
|
5221
|
-
{ ensureParentDir: true }
|
|
5222
|
-
);
|
|
5223
|
-
console.log(`[ClaudeTokenManager] Updated ${credentialsPath}`);
|
|
5224
|
-
} catch (error) {
|
|
5225
|
-
console.error("[ClaudeTokenManager] Failed to update credentials file:", error);
|
|
5226
|
-
}
|
|
5227
|
-
}
|
|
5228
|
-
async removeOauthCredentialsFile() {
|
|
5229
|
-
const credentialsPath = path3.join(ENGINE_ENV.HOME_DIR, ".claude", ".credentials.json");
|
|
5230
|
-
try {
|
|
5231
|
-
await fs.unlink(credentialsPath);
|
|
5232
|
-
} catch {
|
|
5233
|
-
}
|
|
5234
|
-
}
|
|
5235
|
-
};
|
|
5236
|
-
var claudeTokenManager = new ClaudeTokenManager();
|
|
5237
|
-
|
|
5238
|
-
// src/managers/codex-token-manager.ts
|
|
5239
|
-
import { promises as fs2 } from "fs";
|
|
5240
|
-
import path4 from "path";
|
|
5241
|
-
var CodexTokenManager = class extends BaseRefreshManager {
|
|
5242
|
-
constructor() {
|
|
5243
|
-
super("CodexTokenManager");
|
|
5244
|
-
}
|
|
5245
|
-
getSkipReason() {
|
|
5246
|
-
if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "api_key" || ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "foundry") {
|
|
5247
|
-
return `auth method is ${ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD}`;
|
|
5248
|
-
}
|
|
5249
|
-
if (!ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD && ENGINE_ENV.OPENAI_API_KEY) {
|
|
5250
|
-
return "OPENAI_API_KEY is set";
|
|
5251
|
-
}
|
|
5252
|
-
return null;
|
|
5253
|
-
}
|
|
5254
|
-
async doRefresh(_config) {
|
|
5255
|
-
await this.refreshWithRequest();
|
|
5256
|
-
}
|
|
5257
|
-
async refreshWithRequest(request) {
|
|
5258
|
-
console.log("[CodexTokenManager] Refreshing Codex credentials...");
|
|
5259
|
-
const response = await monolithRequest("/v1/engine/codex/refresh-credentials", {
|
|
5260
|
-
body: request
|
|
5261
|
-
});
|
|
5262
|
-
if (!response.ok) {
|
|
5263
|
-
const errorText = await response.text();
|
|
5264
|
-
throw new Error(`Credentials refresh failed: ${response.status} ${errorText}`);
|
|
5265
|
-
}
|
|
5266
|
-
const data = await response.json();
|
|
5267
|
-
await this.applyCredentialsResponse(data);
|
|
5268
|
-
console.log(`[CodexTokenManager] Credentials refreshed (method=${data.type})`);
|
|
5269
|
-
}
|
|
5270
|
-
async fetchFreshCredentials(failureReason) {
|
|
5271
|
-
const config = this.getRuntimeConfig();
|
|
5272
|
-
if (!config) return false;
|
|
5273
|
-
try {
|
|
5274
|
-
console.log("[CodexTokenManager] Fetching fresh credentials from monolith after auth failure...");
|
|
5275
|
-
const failedMethod = ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD;
|
|
5276
|
-
await this.refreshWithRequest(failedMethod === "oauth" || failedMethod === "api_key" || failedMethod === "foundry" ? {
|
|
5277
|
-
failedMethod,
|
|
5278
|
-
failureReason
|
|
5279
|
-
} : void 0);
|
|
5280
|
-
if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "oauth") {
|
|
5281
|
-
this.start().catch((error) => {
|
|
5282
|
-
console.error("[CodexTokenManager] Failed to restart OAuth refresh service after fallback:", error);
|
|
5283
|
-
});
|
|
5284
|
-
}
|
|
5285
|
-
return true;
|
|
5286
|
-
} catch (error) {
|
|
5287
|
-
console.error("[CodexTokenManager] Failed to fetch fresh credentials:", error);
|
|
5288
|
-
return false;
|
|
5289
|
-
}
|
|
5290
|
-
}
|
|
5291
|
-
async applyCredentialsResponse(response) {
|
|
5292
|
-
if (response.type === "oauth") {
|
|
5293
|
-
await this.writeOauthCredentialsFile(response);
|
|
5294
|
-
} else {
|
|
5295
|
-
await this.removeOauthCredentialsFile();
|
|
5296
|
-
}
|
|
5297
|
-
const envVars = codexAuthEnvFromResponse(response);
|
|
5298
|
-
applyAuthEnvTransition({
|
|
5299
|
-
prevMethod: ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD ?? "none",
|
|
5300
|
-
newMethod: envVars.REPLICAS_CODEX_AUTH_METHOD ?? "none",
|
|
5301
|
-
authKeys: CODEX_AUTH_ENV_KEYS,
|
|
5302
|
-
authKeysByMethod: CODEX_AUTH_ENV_KEYS_BY_METHOD,
|
|
5303
|
-
newEnvVars: envVars,
|
|
5304
|
-
envs: [ENGINE_ENV, process.env]
|
|
5305
|
-
});
|
|
5306
|
-
}
|
|
5307
|
-
async writeOauthCredentialsFile(credentials) {
|
|
5308
|
-
const authPath = path4.join(ENGINE_ENV.HOME_DIR, ".codex", "auth.json");
|
|
5309
|
-
const codexAuthConfig = {
|
|
5310
|
-
OPENAI_API_KEY: null,
|
|
5311
|
-
tokens: {
|
|
5312
|
-
id_token: credentials.idToken,
|
|
5313
|
-
access_token: credentials.accessToken,
|
|
5314
|
-
refresh_token: credentials.refreshToken,
|
|
5315
|
-
account_id: credentials.accountId
|
|
5316
|
-
},
|
|
5317
|
-
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
5318
|
-
};
|
|
5319
|
-
try {
|
|
5320
|
-
await writeSecureCredentialFile(
|
|
5321
|
-
authPath,
|
|
5322
|
-
JSON.stringify(codexAuthConfig, null, 2),
|
|
5323
|
-
{ ensureParentDir: true }
|
|
5324
|
-
);
|
|
5325
|
-
console.log(`[CodexTokenManager] Updated ${authPath}`);
|
|
5326
|
-
} catch (error) {
|
|
5327
|
-
console.error("[CodexTokenManager] Failed to update credentials file:", error);
|
|
5328
|
-
}
|
|
5329
|
-
}
|
|
5330
|
-
async removeOauthCredentialsFile() {
|
|
5331
|
-
const authPath = path4.join(ENGINE_ENV.HOME_DIR, ".codex", "auth.json");
|
|
5332
|
-
try {
|
|
5333
|
-
await fs2.unlink(authPath);
|
|
5334
|
-
} catch {
|
|
5335
|
-
}
|
|
5336
|
-
}
|
|
5337
|
-
};
|
|
5338
|
-
var codexTokenManager = new CodexTokenManager();
|
|
5339
|
-
|
|
5340
|
-
// src/managers/infisical-token-manager.ts
|
|
5341
|
-
import { rm } from "fs/promises";
|
|
5342
|
-
import path5 from "path";
|
|
5343
|
-
var InfisicalTokenManager = class extends BaseRefreshManager {
|
|
5344
|
-
constructor(request = monolithRequest, paths = {
|
|
5345
|
-
homeDir: ENGINE_ENV.HOME_DIR,
|
|
5346
|
-
workspaceRoot: ENGINE_ENV.WORKSPACE_ROOT
|
|
5347
|
-
}, applyCredentials = applyInfisicalCredentials) {
|
|
5348
|
-
super("InfisicalTokenManager", 5 * 60 * 1e3);
|
|
5349
|
-
this.request = request;
|
|
5350
|
-
this.paths = paths;
|
|
5351
|
-
this.applyCredentials = applyCredentials;
|
|
5352
|
-
}
|
|
5353
|
-
request;
|
|
5354
|
-
paths;
|
|
5355
|
-
applyCredentials;
|
|
5356
|
-
nextRefreshDelayMs = 5 * 60 * 1e3;
|
|
5357
|
-
async doRefresh(_config) {
|
|
5358
|
-
const response = await this.request("/v1/engine/infisical/refresh-token");
|
|
5359
|
-
if (!response.ok) throw new Error(`Token refresh failed: ${response.status} ${await response.text()}`);
|
|
5360
|
-
const data = await response.json();
|
|
5361
|
-
await this.applyCredentials(data, this.paths);
|
|
5362
|
-
const ttlMs = data.configured ? Date.parse(data.expiresAt) - Date.now() : Number.NaN;
|
|
5363
|
-
this.nextRefreshDelayMs = Number.isFinite(ttlMs) ? Math.max(1e3, Math.min(5 * 60 * 1e3, Math.floor(ttlMs * 0.8))) : 5 * 60 * 1e3;
|
|
5364
|
-
}
|
|
5365
|
-
getNextRefreshDelayMs() {
|
|
5366
|
-
return this.nextRefreshDelayMs;
|
|
5367
|
-
}
|
|
5368
|
-
};
|
|
5369
|
-
async function applyInfisicalCredentials(data, paths) {
|
|
5370
|
-
const credentialPath = path5.join(paths.homeDir, ".replicas", "infisical-env.sh");
|
|
5371
|
-
const configPath = path5.join(paths.workspaceRoot, ".infisical.json");
|
|
5372
|
-
if (!data.configured) {
|
|
5373
|
-
delete process.env.INFISICAL_TOKEN;
|
|
5374
|
-
delete process.env.INFISICAL_DOMAIN;
|
|
5375
|
-
await Promise.all([rm(credentialPath, { force: true }), rm(configPath, { force: true })]);
|
|
5376
|
-
return;
|
|
5377
|
-
}
|
|
5378
|
-
process.env.INFISICAL_TOKEN = data.token;
|
|
5379
|
-
process.env.INFISICAL_DOMAIN = data.siteUrl;
|
|
5380
|
-
await Promise.all([
|
|
5381
|
-
writeSecureCredentialFile(credentialPath, [
|
|
5382
|
-
`export INFISICAL_TOKEN=${shellQuotePosix(data.token)}`,
|
|
5383
|
-
`export INFISICAL_DOMAIN=${shellQuotePosix(data.siteUrl)}`,
|
|
5384
|
-
"export INFISICAL_DISABLE_UPDATE_CHECK=true",
|
|
5385
|
-
""
|
|
5386
|
-
].join("\n"), { ensureParentDir: true }),
|
|
5387
|
-
writeSecureCredentialFile(configPath, `${JSON.stringify({
|
|
5388
|
-
workspaceId: data.projectId,
|
|
5389
|
-
defaultEnvironment: data.environment,
|
|
5390
|
-
gitBranchToEnvironmentMapping: null,
|
|
5391
|
-
domain: data.siteUrl
|
|
5392
|
-
}, null, 2)}
|
|
5393
|
-
`, { ensureParentDir: true })
|
|
5394
|
-
]);
|
|
5395
|
-
}
|
|
5396
|
-
var infisicalTokenManager = new InfisicalTokenManager();
|
|
5397
|
-
|
|
5398
|
-
// src/git/service.ts
|
|
5399
|
-
import { readdir, readFile as readFile3, stat } from "fs/promises";
|
|
5400
|
-
import { existsSync as existsSync2 } from "fs";
|
|
5401
|
-
import { spawn } from "child_process";
|
|
5402
|
-
import { join as join5 } from "path";
|
|
5403
|
-
|
|
5404
|
-
// src/utils/state.ts
|
|
5405
|
-
import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
|
|
5406
|
-
import { existsSync } from "fs";
|
|
5407
|
-
import { join as join3 } from "path";
|
|
5408
|
-
import { homedir as homedir3 } from "os";
|
|
5409
|
-
|
|
5410
|
-
// src/utils/type-guards.ts
|
|
5411
|
-
function isRecord4(value) {
|
|
5412
|
-
return typeof value === "object" && value !== null;
|
|
5413
|
-
}
|
|
5414
|
-
|
|
5415
|
-
// src/utils/state.ts
|
|
5416
|
-
var STATE_DIR = join3(homedir3(), ".replicas");
|
|
5417
|
-
var STATE_FILE = join3(STATE_DIR, "engine-state.json");
|
|
5418
|
-
var DEFAULT_STATE = {
|
|
5419
|
-
repos: {}
|
|
5420
|
-
};
|
|
5421
|
-
var stateWriteLock = new AsyncLock();
|
|
5422
|
-
async function updateEngineState(updater) {
|
|
5423
|
-
await stateWriteLock.run(async () => {
|
|
5424
|
-
await mkdir2(STATE_DIR, { recursive: true });
|
|
5425
|
-
const currentState = await loadEngineState();
|
|
5426
|
-
const nextState = updater(currentState);
|
|
5427
|
-
await atomicWriteFile(STATE_FILE, JSON.stringify(nextState, null, 2));
|
|
5428
|
-
});
|
|
5429
|
-
}
|
|
5430
|
-
function isEngineRepoDiff(value) {
|
|
5431
|
-
return isRecord4(value) && typeof value.added === "number" && typeof value.removed === "number";
|
|
5432
|
-
}
|
|
5433
|
-
function coerceRepoState(value) {
|
|
5434
|
-
if (!isRecord4(value)) {
|
|
5435
|
-
return null;
|
|
5436
|
-
}
|
|
5437
|
-
if (typeof value.name !== "string") return null;
|
|
5438
|
-
if (typeof value.path !== "string") return null;
|
|
5439
|
-
if (typeof value.defaultBranch !== "string") return null;
|
|
5440
|
-
if (typeof value.currentBranch !== "string") return null;
|
|
5441
|
-
if (!Array.isArray(value.prUrls)) return null;
|
|
5442
|
-
const prUrls = [];
|
|
5443
|
-
for (const entry of value.prUrls) {
|
|
5444
|
-
if (typeof entry !== "string") return null;
|
|
5445
|
-
if (!prUrls.includes(entry)) prUrls.push(entry);
|
|
5446
|
-
}
|
|
5447
|
-
if (!(value.gitDiff === null || isEngineRepoDiff(value.gitDiff))) return null;
|
|
5448
|
-
if (typeof value.startHooksCompleted !== "boolean") return null;
|
|
5449
|
-
return {
|
|
5450
|
-
name: value.name,
|
|
5451
|
-
path: value.path,
|
|
5452
|
-
defaultBranch: value.defaultBranch,
|
|
5453
|
-
currentBranch: value.currentBranch,
|
|
5454
|
-
prUrls,
|
|
5455
|
-
gitDiff: value.gitDiff,
|
|
5456
|
-
startHooksCompleted: value.startHooksCompleted
|
|
5457
|
-
};
|
|
5458
|
-
}
|
|
5459
|
-
function coerceEngineState(value) {
|
|
5460
|
-
if (!isRecord4(value)) {
|
|
5461
|
-
return {};
|
|
5462
|
-
}
|
|
5463
|
-
const partial = {};
|
|
5464
|
-
if (isRecord4(value.repos)) {
|
|
5465
|
-
const repos = {};
|
|
5466
|
-
for (const [repoName, repoState] of Object.entries(value.repos)) {
|
|
5467
|
-
const coerced = coerceRepoState(repoState);
|
|
5468
|
-
if (coerced) {
|
|
5469
|
-
repos[repoName] = coerced;
|
|
5470
|
-
}
|
|
5471
|
-
}
|
|
5472
|
-
partial.repos = repos;
|
|
5473
|
-
}
|
|
5474
|
-
return partial;
|
|
5475
|
-
}
|
|
5476
|
-
async function loadEngineState() {
|
|
5477
|
-
try {
|
|
5478
|
-
if (!existsSync(STATE_FILE)) {
|
|
5479
|
-
return { ...DEFAULT_STATE };
|
|
5480
|
-
}
|
|
5481
|
-
const content = await readFile2(STATE_FILE, "utf-8");
|
|
5482
|
-
const state = coerceEngineState(JSON.parse(content));
|
|
5483
|
-
return {
|
|
5484
|
-
...DEFAULT_STATE,
|
|
5485
|
-
...state,
|
|
5486
|
-
repos: state.repos ?? {}
|
|
5487
|
-
};
|
|
5488
|
-
} catch (error) {
|
|
5489
|
-
console.error("[EngineState] Failed to load state, using defaults:", error);
|
|
5490
|
-
return { ...DEFAULT_STATE };
|
|
5070
|
+
const content = await readFile2(STATE_FILE, "utf-8");
|
|
5071
|
+
const state = coerceEngineState(JSON.parse(content));
|
|
5072
|
+
return {
|
|
5073
|
+
...DEFAULT_STATE,
|
|
5074
|
+
...state,
|
|
5075
|
+
repos: state.repos ?? {}
|
|
5076
|
+
};
|
|
5077
|
+
} catch (error) {
|
|
5078
|
+
console.error("[EngineState] Failed to load state, using defaults:", error);
|
|
5079
|
+
return { ...DEFAULT_STATE };
|
|
5491
5080
|
}
|
|
5492
5081
|
}
|
|
5493
5082
|
async function loadRepoState(repoName) {
|
|
@@ -5563,8 +5152,7 @@ function appendUniqueUrl(urls, url) {
|
|
|
5563
5152
|
var GitService = class {
|
|
5564
5153
|
defaultBranchCache = /* @__PURE__ */ new Map();
|
|
5565
5154
|
cachedPrByRepo = /* @__PURE__ */ new Map();
|
|
5566
|
-
//
|
|
5567
|
-
// the worst case is PR/MR lookup stays skipped until the next engine restart.
|
|
5155
|
+
// An 'unknown' result usually means the origin isn't wired up yet during provisioning, so caching it would permanently mask a remote added moments later.
|
|
5568
5156
|
originInfoCache = /* @__PURE__ */ new Map();
|
|
5569
5157
|
// Broadcaster + UI /repos requests fan in here every ~2s; one shared run.
|
|
5570
5158
|
listReposInFlight = new InFlightMap();
|
|
@@ -5817,260 +5405,651 @@ var GitService = class {
|
|
|
5817
5405
|
}
|
|
5818
5406
|
});
|
|
5819
5407
|
}
|
|
5820
|
-
async getUntrackedAsDiff(repoPath) {
|
|
5408
|
+
async getUntrackedAsDiff(repoPath) {
|
|
5409
|
+
try {
|
|
5410
|
+
return await this.getUntrackedDiff(repoPath);
|
|
5411
|
+
} catch (error) {
|
|
5412
|
+
console.error("Error building untracked diff:", error);
|
|
5413
|
+
return "";
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
async getPullRequestUrl(repoName, repoPath, currentBranchArg, persistedRepoStateArg) {
|
|
5417
|
+
try {
|
|
5418
|
+
const currentBranch = currentBranchArg ?? await getCurrentBranch(repoPath);
|
|
5419
|
+
if (!currentBranch) {
|
|
5420
|
+
return { status: "not_found" };
|
|
5421
|
+
}
|
|
5422
|
+
const cachedPr = this.cachedPrByRepo.get(repoName);
|
|
5423
|
+
if (cachedPr && cachedPr.currentBranch === currentBranch) {
|
|
5424
|
+
return { status: "found", url: cachedPr.prUrl };
|
|
5425
|
+
}
|
|
5426
|
+
const persistedRepoState = persistedRepoStateArg ?? await loadRepoState(repoName);
|
|
5427
|
+
this.cachedPrByRepo.delete(repoName);
|
|
5428
|
+
const result = await this.lookupPrOnRemote(repoName, repoPath, currentBranch);
|
|
5429
|
+
if (result.status === "found") {
|
|
5430
|
+
this.cachedPrByRepo.set(repoName, { prUrl: result.url, currentBranch });
|
|
5431
|
+
if (persistedRepoState && !persistedRepoState.prUrls.includes(result.url)) {
|
|
5432
|
+
await saveRepoState(
|
|
5433
|
+
repoName,
|
|
5434
|
+
{ prUrls: appendUniqueUrl(persistedRepoState.prUrls, result.url) },
|
|
5435
|
+
persistedRepoState
|
|
5436
|
+
);
|
|
5437
|
+
}
|
|
5438
|
+
}
|
|
5439
|
+
return result;
|
|
5440
|
+
} catch (error) {
|
|
5441
|
+
console.error("Error checking for pull request:", error);
|
|
5442
|
+
return { status: "error" };
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
async lookupPrOnRemote(repoName, repoPath, branch) {
|
|
5446
|
+
const origin = await this.getOriginInfo(repoPath);
|
|
5447
|
+
if (origin.provider === "unknown") {
|
|
5448
|
+
return { status: "not_found" };
|
|
5449
|
+
}
|
|
5450
|
+
try {
|
|
5451
|
+
const { stdout } = await execFileAsync("git", ["ls-remote", "--heads", "origin", branch], {
|
|
5452
|
+
cwd: repoPath,
|
|
5453
|
+
encoding: "utf-8",
|
|
5454
|
+
maxBuffer: SUBPROCESS_MAX_BUFFER
|
|
5455
|
+
});
|
|
5456
|
+
if (!stdout.trim()) {
|
|
5457
|
+
return { status: "not_found" };
|
|
5458
|
+
}
|
|
5459
|
+
} catch {
|
|
5460
|
+
return { status: "error" };
|
|
5461
|
+
}
|
|
5462
|
+
if (origin.provider === "gitlab") {
|
|
5463
|
+
return this.lookupGitLabMrOnRemote(repoName, origin.gitLab, branch);
|
|
5464
|
+
}
|
|
5465
|
+
try {
|
|
5466
|
+
const { stdout } = await execFileAsync("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
|
|
5467
|
+
cwd: repoPath,
|
|
5468
|
+
encoding: "utf-8",
|
|
5469
|
+
maxBuffer: SUBPROCESS_MAX_BUFFER
|
|
5470
|
+
});
|
|
5471
|
+
const prInfo = stdout.trim();
|
|
5472
|
+
return prInfo ? { status: "found", url: prInfo } : { status: "not_found" };
|
|
5473
|
+
} catch (error) {
|
|
5474
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5475
|
+
console.warn(`[GitService] gh pr view ${branch} failed for ${repoName}: ${message}`);
|
|
5476
|
+
return { status: "error" };
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
async lookupGitLabMrOnRemote(repoName, remote, branch) {
|
|
5480
|
+
const token = await this.getGitLabAccessToken(remote.host);
|
|
5481
|
+
if (!token) {
|
|
5482
|
+
return { status: "not_found" };
|
|
5483
|
+
}
|
|
5484
|
+
const url = `${remote.instanceUrl}/api/v4/projects/${encodeURIComponent(remote.projectPath)}/merge_requests?source_branch=${encodeURIComponent(branch)}&order_by=updated_at`;
|
|
5485
|
+
try {
|
|
5486
|
+
const response = await fetch(url, {
|
|
5487
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
5488
|
+
signal: AbortSignal.timeout(1e4)
|
|
5489
|
+
});
|
|
5490
|
+
if (!response.ok) {
|
|
5491
|
+
console.warn(`[GitService] GitLab MR lookup failed for ${repoName}: ${response.status}`);
|
|
5492
|
+
return response.status === 404 ? { status: "not_found" } : { status: "error" };
|
|
5493
|
+
}
|
|
5494
|
+
const data = await response.json();
|
|
5495
|
+
if (!Array.isArray(data)) return { status: "not_found" };
|
|
5496
|
+
const branchMatches = data.filter((item) => isRecord4(item) && item.source_branch === branch);
|
|
5497
|
+
const mergeRequest = branchMatches.find((item) => item.state === "opened") ?? branchMatches[0] ?? data[0];
|
|
5498
|
+
if (!isRecord4(mergeRequest)) return { status: "not_found" };
|
|
5499
|
+
const webUrl = mergeRequest.web_url;
|
|
5500
|
+
if (typeof webUrl === "string" && webUrl.length > 0) return { status: "found", url: webUrl };
|
|
5501
|
+
const iid = mergeRequest.iid;
|
|
5502
|
+
return typeof iid === "number" ? { status: "found", url: `${remote.instanceUrl}/${remote.projectPath}/-/merge_requests/${iid}` } : { status: "not_found" };
|
|
5503
|
+
} catch (error) {
|
|
5504
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5505
|
+
console.warn(`[GitService] GitLab MR lookup failed for ${repoName}: ${message}`);
|
|
5506
|
+
return { status: "error" };
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
async resolveCodeHostProvider(repoPath) {
|
|
5510
|
+
const origin = await this.getOriginInfo(repoPath);
|
|
5511
|
+
return origin.provider === "unknown" ? void 0 : origin.provider;
|
|
5512
|
+
}
|
|
5513
|
+
async getRepositoryOriginHost(repoPath, refresh = false) {
|
|
5514
|
+
if (refresh) this.originInfoCache.delete(repoPath);
|
|
5515
|
+
const origin = await this.getOriginInfo(repoPath);
|
|
5516
|
+
if (origin.provider === "github") return "github.com";
|
|
5517
|
+
if (origin.provider === "gitlab") return origin.gitLab.host;
|
|
5518
|
+
return null;
|
|
5519
|
+
}
|
|
5520
|
+
async getOriginInfo(repoPath) {
|
|
5521
|
+
const cached = this.originInfoCache.get(repoPath);
|
|
5522
|
+
if (cached !== void 0) {
|
|
5523
|
+
return cached;
|
|
5524
|
+
}
|
|
5525
|
+
let info = { provider: "unknown" };
|
|
5526
|
+
try {
|
|
5527
|
+
const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
|
|
5528
|
+
cwd: repoPath,
|
|
5529
|
+
encoding: "utf-8",
|
|
5530
|
+
maxBuffer: SUBPROCESS_MAX_BUFFER
|
|
5531
|
+
});
|
|
5532
|
+
const originUrl = stdout.trim();
|
|
5533
|
+
if (isGitHubUrl(originUrl)) {
|
|
5534
|
+
info = { provider: "github" };
|
|
5535
|
+
} else {
|
|
5536
|
+
const gitLabRemote = this.parseGitLabRemote(originUrl);
|
|
5537
|
+
if (gitLabRemote) info = { provider: "gitlab", gitLab: gitLabRemote };
|
|
5538
|
+
}
|
|
5539
|
+
} catch {
|
|
5540
|
+
info = { provider: "unknown" };
|
|
5541
|
+
}
|
|
5542
|
+
if (info.provider !== "unknown") this.originInfoCache.set(repoPath, info);
|
|
5543
|
+
return info;
|
|
5544
|
+
}
|
|
5545
|
+
parseGitLabRemote(remoteUrl) {
|
|
5546
|
+
const parsed = parseGitRemote(remoteUrl);
|
|
5547
|
+
if (!parsed) return null;
|
|
5548
|
+
const projectPath = decodePathSegments(parsed.path.split("/").filter(Boolean)).join("/");
|
|
5549
|
+
return {
|
|
5550
|
+
host: parsed.host,
|
|
5551
|
+
instanceUrl: `${parsed.protocol === "http" ? "http" : "https"}://${parsed.host}`,
|
|
5552
|
+
projectPath
|
|
5553
|
+
};
|
|
5554
|
+
}
|
|
5555
|
+
async getGitLabAccessToken(host) {
|
|
5556
|
+
try {
|
|
5557
|
+
const credentials = await readFile3(join5(ENGINE_ENV.HOME_DIR, ".git-credentials"), "utf-8");
|
|
5558
|
+
for (const line of credentials.split("\n")) {
|
|
5559
|
+
const trimmed = line.trim();
|
|
5560
|
+
if (!trimmed) continue;
|
|
5561
|
+
try {
|
|
5562
|
+
const parsed = new URL(trimmed);
|
|
5563
|
+
if (parsed.protocol === "https:" && parsed.host.toLowerCase() === host && parsed.password) {
|
|
5564
|
+
return decodeURIComponent(parsed.password);
|
|
5565
|
+
}
|
|
5566
|
+
} catch {
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
} catch {
|
|
5570
|
+
}
|
|
5571
|
+
return null;
|
|
5572
|
+
}
|
|
5573
|
+
async resolveDefaultBranch(repoPath) {
|
|
5574
|
+
const cached = this.defaultBranchCache.get(repoPath);
|
|
5575
|
+
if (cached) {
|
|
5576
|
+
return cached;
|
|
5577
|
+
}
|
|
5578
|
+
const fromSymbolicRef = await this.resolveDefaultBranchFromSymbolicRef(repoPath);
|
|
5579
|
+
if (fromSymbolicRef) {
|
|
5580
|
+
this.defaultBranchCache.set(repoPath, fromSymbolicRef);
|
|
5581
|
+
return fromSymbolicRef;
|
|
5582
|
+
}
|
|
5583
|
+
return "main";
|
|
5584
|
+
}
|
|
5585
|
+
async resolveDefaultBranchFromSymbolicRef(repoPath) {
|
|
5586
|
+
try {
|
|
5587
|
+
const output = await runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
|
|
5588
|
+
const match = output.match(/^origin\/(.+)$/);
|
|
5589
|
+
return match ? match[1] : null;
|
|
5590
|
+
} catch {
|
|
5591
|
+
return null;
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
async refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState, observedBranches, includeDiffs = false) {
|
|
5595
|
+
const prResult = await this.getPullRequestUrl(repo.name, repo.path, currentBranch, persistedState);
|
|
5596
|
+
let prUrls = persistedState?.prUrls ?? [];
|
|
5597
|
+
if (prResult.status === "found") {
|
|
5598
|
+
prUrls = appendUniqueUrl(prUrls, prResult.url);
|
|
5599
|
+
}
|
|
5600
|
+
if (observedBranches) {
|
|
5601
|
+
for (const branch of observedBranches) {
|
|
5602
|
+
if (branch === currentBranch) continue;
|
|
5603
|
+
const branchResult = await this.lookupPrOnRemote(repo.name, repo.path, branch);
|
|
5604
|
+
if (branchResult.status === "found") {
|
|
5605
|
+
prUrls = appendUniqueUrl(prUrls, branchResult.url);
|
|
5606
|
+
}
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
const gitDiff = await this.getGitDiffStats(repo.path, repo.defaultBranch);
|
|
5610
|
+
const fullDiff = includeDiffs && gitDiff ? await this.getFullGitDiff(repo.path, repo.defaultBranch) : null;
|
|
5611
|
+
const state = {
|
|
5612
|
+
name: repo.name,
|
|
5613
|
+
path: repo.path,
|
|
5614
|
+
defaultBranch: repo.defaultBranch,
|
|
5615
|
+
currentBranch,
|
|
5616
|
+
prUrls,
|
|
5617
|
+
gitDiff: includeDiffs && gitDiff ? { ...gitDiff, ...fullDiff === null ? {} : { fullDiff } } : gitDiff,
|
|
5618
|
+
startHooksCompleted,
|
|
5619
|
+
provider: await this.resolveCodeHostProvider(repo.path)
|
|
5620
|
+
};
|
|
5621
|
+
await saveRepoState(repo.name, state, state);
|
|
5622
|
+
return state;
|
|
5623
|
+
}
|
|
5624
|
+
async safeStat(path6) {
|
|
5625
|
+
try {
|
|
5626
|
+
return await stat(path6);
|
|
5627
|
+
} catch {
|
|
5628
|
+
return null;
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
};
|
|
5632
|
+
var gitService = new GitService();
|
|
5633
|
+
|
|
5634
|
+
// src/utils/git-identity.ts
|
|
5635
|
+
async function updateGitIdentity(identity, tag, hosts) {
|
|
5636
|
+
if (!hosts) {
|
|
5637
|
+
try {
|
|
5638
|
+
const [nameArgs, emailArgs] = gitIdentityConfigCommands(identity);
|
|
5639
|
+
await execFileAsync("git", nameArgs);
|
|
5640
|
+
await execFileAsync("git", emailArgs);
|
|
5641
|
+
console.log(`[${tag}] Updated global git identity to ${identity.name} <${identity.email}>`);
|
|
5642
|
+
} catch (error) {
|
|
5643
|
+
console.error(`[${tag}] Failed to update global git identity:`, error);
|
|
5644
|
+
}
|
|
5645
|
+
return;
|
|
5646
|
+
}
|
|
5647
|
+
const normalizedHosts = hosts.map((host) => host.toLowerCase());
|
|
5648
|
+
const repositories = await gitService.listRepositories();
|
|
5649
|
+
await Promise.all(repositories.map(async (repository) => {
|
|
5650
|
+
try {
|
|
5651
|
+
const host = await gitService.getRepositoryOriginHost(repository.path, true);
|
|
5652
|
+
if (!host || !normalizedHosts.includes(host)) return;
|
|
5653
|
+
const [nameArgs, emailArgs] = gitIdentityConfigCommands(identity, "local");
|
|
5654
|
+
await execFileAsync("git", nameArgs, { cwd: repository.path });
|
|
5655
|
+
await execFileAsync("git", emailArgs, { cwd: repository.path });
|
|
5656
|
+
console.log(`[${tag}] Updated git identity for ${repository.path} to ${identity.name} <${identity.email}>`);
|
|
5657
|
+
} catch (error) {
|
|
5658
|
+
console.error(`[${tag}] Failed to update git identity for ${repository.path}:`, error);
|
|
5659
|
+
}
|
|
5660
|
+
}));
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
// src/managers/github-token-manager.ts
|
|
5664
|
+
var GitHubTokenManager = class extends BaseRefreshManager {
|
|
5665
|
+
constructor() {
|
|
5666
|
+
super("GitHubTokenManager");
|
|
5667
|
+
}
|
|
5668
|
+
async doRefresh(_config) {
|
|
5669
|
+
console.log("[GitHubTokenManager] Refreshing GitHub token...");
|
|
5670
|
+
const response = await monolithRequest("/v1/engine/github/refresh-token");
|
|
5671
|
+
if (!response.ok) {
|
|
5672
|
+
if (response.status === 403) {
|
|
5673
|
+
await this.clearGitHubCredentials();
|
|
5674
|
+
}
|
|
5675
|
+
const errorText = await response.text();
|
|
5676
|
+
throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
|
|
5677
|
+
}
|
|
5678
|
+
const data = await response.json();
|
|
5679
|
+
const ghToken = data.userToken?.token ?? data.token;
|
|
5680
|
+
const ghUsername = data.userToken?.username ?? "x-access-token";
|
|
5681
|
+
await this.updateGitCredentials(ghToken);
|
|
5682
|
+
await this.updateGhHostsFile(ghToken, ghUsername);
|
|
5683
|
+
if (data.gitIdentity) {
|
|
5684
|
+
await updateGitIdentity(data.gitIdentity, "GitHubTokenManager", ["github.com"]);
|
|
5685
|
+
}
|
|
5686
|
+
if (data.userToken) {
|
|
5687
|
+
console.log(`[GitHubTokenManager] Token refreshed with user token for PR attribution, user token expires at ${data.userToken.expiresAt}`);
|
|
5688
|
+
} else {
|
|
5689
|
+
console.log(`[GitHubTokenManager] Token refreshed successfully, expires at ${data.expiresAt}`);
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
async updateGitCredentials(token) {
|
|
5693
|
+
const credentialsPath = path.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
5821
5694
|
try {
|
|
5822
|
-
|
|
5695
|
+
await upsertCredentialFileLines(credentialsPath, ["github.com"], [
|
|
5696
|
+
`https://x-access-token:${token}@github.com`
|
|
5697
|
+
]);
|
|
5698
|
+
console.log(`[GitHubTokenManager] Updated ${credentialsPath}`);
|
|
5823
5699
|
} catch (error) {
|
|
5824
|
-
console.error("
|
|
5825
|
-
return "";
|
|
5700
|
+
console.error("[GitHubTokenManager] Failed to update git credentials:", error);
|
|
5826
5701
|
}
|
|
5827
5702
|
}
|
|
5828
|
-
async
|
|
5703
|
+
async updateGhHostsFile(token, username) {
|
|
5704
|
+
const hostsPath = path.join(ENGINE_ENV.HOME_DIR, ".config", "gh", "hosts.yml");
|
|
5705
|
+
const content = `github.com:
|
|
5706
|
+
oauth_token: ${JSON.stringify(token)}
|
|
5707
|
+
user: ${JSON.stringify(username)}
|
|
5708
|
+
git_protocol: https
|
|
5709
|
+
`;
|
|
5829
5710
|
try {
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
return { status: "not_found" };
|
|
5833
|
-
}
|
|
5834
|
-
const cachedPr = this.cachedPrByRepo.get(repoName);
|
|
5835
|
-
if (cachedPr && cachedPr.currentBranch === currentBranch) {
|
|
5836
|
-
return { status: "found", url: cachedPr.prUrl };
|
|
5837
|
-
}
|
|
5838
|
-
const persistedRepoState = persistedRepoStateArg ?? await loadRepoState(repoName);
|
|
5839
|
-
this.cachedPrByRepo.delete(repoName);
|
|
5840
|
-
const result = await this.lookupPrOnRemote(repoName, repoPath, currentBranch);
|
|
5841
|
-
if (result.status === "found") {
|
|
5842
|
-
this.cachedPrByRepo.set(repoName, { prUrl: result.url, currentBranch });
|
|
5843
|
-
if (persistedRepoState && !persistedRepoState.prUrls.includes(result.url)) {
|
|
5844
|
-
await saveRepoState(
|
|
5845
|
-
repoName,
|
|
5846
|
-
{ prUrls: appendUniqueUrl(persistedRepoState.prUrls, result.url) },
|
|
5847
|
-
persistedRepoState
|
|
5848
|
-
);
|
|
5849
|
-
}
|
|
5850
|
-
}
|
|
5851
|
-
return result;
|
|
5711
|
+
await writeSecureCredentialFile(hostsPath, content, { ensureParentDir: true });
|
|
5712
|
+
console.log(`[GitHubTokenManager] Updated ${hostsPath}`);
|
|
5852
5713
|
} catch (error) {
|
|
5853
|
-
console.error("
|
|
5854
|
-
return { status: "error" };
|
|
5714
|
+
console.error("[GitHubTokenManager] Failed to update gh hosts file:", error);
|
|
5855
5715
|
}
|
|
5856
5716
|
}
|
|
5857
|
-
async
|
|
5858
|
-
const
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5717
|
+
async clearGitHubCredentials() {
|
|
5718
|
+
const credentialsPath = path.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
5719
|
+
await removeCredentialFileLines(
|
|
5720
|
+
credentialsPath,
|
|
5721
|
+
(line) => line.endsWith("@github.com")
|
|
5722
|
+
);
|
|
5723
|
+
}
|
|
5724
|
+
};
|
|
5725
|
+
var githubTokenManager = new GitHubTokenManager();
|
|
5726
|
+
|
|
5727
|
+
// src/managers/gitlab-token-manager.ts
|
|
5728
|
+
import path2 from "path";
|
|
5729
|
+
var GitLabTokenManager = class extends BaseRefreshManager {
|
|
5730
|
+
constructor() {
|
|
5731
|
+
super("GitLabTokenManager");
|
|
5732
|
+
}
|
|
5733
|
+
async doRefresh(_config) {
|
|
5734
|
+
const response = await monolithRequest("/v1/engine/gitlab/refresh-token");
|
|
5735
|
+
if (!response.ok) {
|
|
5736
|
+
if (response.status === 403) {
|
|
5737
|
+
await this.clearGitLabCredentials();
|
|
5870
5738
|
}
|
|
5871
|
-
|
|
5872
|
-
return { status: "error" };
|
|
5739
|
+
throw new Error(`Token refresh failed: ${response.status} ${await response.text()}`);
|
|
5873
5740
|
}
|
|
5874
|
-
|
|
5875
|
-
|
|
5741
|
+
const data = await response.json();
|
|
5742
|
+
if (!data.token) {
|
|
5743
|
+
await this.clearGitLabCredentials(data.hosts);
|
|
5744
|
+
console.log(`[GitLabTokenManager] No GitLab token to install: ${data.reason}`);
|
|
5745
|
+
return;
|
|
5876
5746
|
}
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
})
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
console.warn(`[GitService] gh pr view ${branch} failed for ${repoName}: ${message}`);
|
|
5888
|
-
return { status: "error" };
|
|
5747
|
+
const hosts = data.hosts.length > 0 ? data.hosts : ["gitlab.com"];
|
|
5748
|
+
const credentialsPath = path2.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
5749
|
+
await upsertCredentialFileLines(
|
|
5750
|
+
credentialsPath,
|
|
5751
|
+
hosts,
|
|
5752
|
+
hosts.map((host) => `https://oauth2:${data.token}@${host}`)
|
|
5753
|
+
);
|
|
5754
|
+
console.log(`[GitLabTokenManager] Updated ${credentialsPath} for ${hosts.join(", ")}`);
|
|
5755
|
+
if (data.gitIdentity) {
|
|
5756
|
+
await updateGitIdentity(data.gitIdentity, "GitLabTokenManager", hosts);
|
|
5889
5757
|
}
|
|
5890
5758
|
}
|
|
5891
|
-
async
|
|
5892
|
-
const
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5759
|
+
async clearGitLabCredentials(hosts = []) {
|
|
5760
|
+
const credentialsPath = path2.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
5761
|
+
await removeCredentialFileLines(
|
|
5762
|
+
credentialsPath,
|
|
5763
|
+
(line) => line.startsWith("https://oauth2:") && (hosts.length === 0 || hosts.some((host) => line.endsWith(`@${host}`)))
|
|
5764
|
+
);
|
|
5765
|
+
}
|
|
5766
|
+
};
|
|
5767
|
+
var gitlabTokenManager = new GitLabTokenManager();
|
|
5768
|
+
|
|
5769
|
+
// src/managers/claude-token-manager.ts
|
|
5770
|
+
import { promises as fs } from "fs";
|
|
5771
|
+
import path3 from "path";
|
|
5772
|
+
|
|
5773
|
+
// src/managers/auth-env-transition.ts
|
|
5774
|
+
function applyAuthEnvTransition(params) {
|
|
5775
|
+
const newOwned = new Set(params.authKeysByMethod[params.newMethod]);
|
|
5776
|
+
const prevOwned = new Set(params.authKeysByMethod[params.prevMethod]);
|
|
5777
|
+
for (const key of params.authKeys) {
|
|
5778
|
+
const value = params.newEnvVars[key];
|
|
5779
|
+
if (value !== void 0) {
|
|
5780
|
+
for (const env of params.envs) {
|
|
5781
|
+
env[key] = value;
|
|
5782
|
+
}
|
|
5783
|
+
} else if (prevOwned.has(key) && !newOwned.has(key)) {
|
|
5784
|
+
for (const env of params.envs) {
|
|
5785
|
+
delete env[key];
|
|
5905
5786
|
}
|
|
5906
|
-
const data = await response.json();
|
|
5907
|
-
if (!Array.isArray(data)) return { status: "not_found" };
|
|
5908
|
-
const branchMatches = data.filter((item) => isRecord4(item) && item.source_branch === branch);
|
|
5909
|
-
const mergeRequest = branchMatches.find((item) => item.state === "opened") ?? branchMatches[0] ?? data[0];
|
|
5910
|
-
if (!isRecord4(mergeRequest)) return { status: "not_found" };
|
|
5911
|
-
const webUrl = mergeRequest.web_url;
|
|
5912
|
-
if (typeof webUrl === "string" && webUrl.length > 0) return { status: "found", url: webUrl };
|
|
5913
|
-
const iid = mergeRequest.iid;
|
|
5914
|
-
return typeof iid === "number" ? { status: "found", url: `${remote.instanceUrl}/${remote.projectPath}/-/merge_requests/${iid}` } : { status: "not_found" };
|
|
5915
|
-
} catch (error) {
|
|
5916
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
5917
|
-
console.warn(`[GitService] GitLab MR lookup failed for ${repoName}: ${message}`);
|
|
5918
|
-
return { status: "error" };
|
|
5919
5787
|
}
|
|
5920
5788
|
}
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5791
|
+
// src/managers/claude-token-manager.ts
|
|
5792
|
+
var ClaudeTokenManager = class extends BaseRefreshManager {
|
|
5793
|
+
constructor() {
|
|
5794
|
+
super("ClaudeTokenManager");
|
|
5924
5795
|
}
|
|
5925
|
-
|
|
5926
|
-
const
|
|
5927
|
-
if (
|
|
5928
|
-
return
|
|
5796
|
+
getSkipReason() {
|
|
5797
|
+
const method = ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD;
|
|
5798
|
+
if (method === "api_key" || method === "bedrock" || method === "foundry") {
|
|
5799
|
+
return `auth method is ${method}`;
|
|
5929
5800
|
}
|
|
5930
|
-
|
|
5801
|
+
return null;
|
|
5802
|
+
}
|
|
5803
|
+
async doRefresh(_config) {
|
|
5804
|
+
await this.refreshWithRequest();
|
|
5805
|
+
}
|
|
5806
|
+
async refreshWithRequest(request) {
|
|
5807
|
+
console.log("[ClaudeTokenManager] Refreshing Claude credentials...");
|
|
5808
|
+
const response = await monolithRequest("/v1/engine/claude/refresh-credentials", {
|
|
5809
|
+
body: request
|
|
5810
|
+
});
|
|
5811
|
+
if (!response.ok) {
|
|
5812
|
+
const errorText = await response.text();
|
|
5813
|
+
throw new Error(`Credentials refresh failed: ${response.status} ${errorText}`);
|
|
5814
|
+
}
|
|
5815
|
+
const data = await response.json();
|
|
5816
|
+
await this.applyCredentialsResponse(data);
|
|
5817
|
+
console.log(`[ClaudeTokenManager] Credentials refreshed (method=${data.type})`);
|
|
5818
|
+
}
|
|
5819
|
+
async fetchFreshCredentials(failureReason) {
|
|
5820
|
+
const config = this.getRuntimeConfig();
|
|
5821
|
+
if (!config) return false;
|
|
5931
5822
|
try {
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5823
|
+
console.log("[ClaudeTokenManager] Fetching fresh credentials from monolith after auth failure...");
|
|
5824
|
+
const failedMethod = ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD;
|
|
5825
|
+
await this.refreshWithRequest(failedMethod && failedMethod !== "none" ? {
|
|
5826
|
+
failedMethod,
|
|
5827
|
+
failureReason
|
|
5828
|
+
} : void 0);
|
|
5829
|
+
if (ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD === "oauth") {
|
|
5830
|
+
this.start().catch((error) => {
|
|
5831
|
+
console.error("[ClaudeTokenManager] Failed to restart OAuth refresh service after fallback:", error);
|
|
5832
|
+
});
|
|
5833
|
+
}
|
|
5834
|
+
return true;
|
|
5835
|
+
} catch (error) {
|
|
5836
|
+
console.error("[ClaudeTokenManager] Failed to fetch fresh credentials:", error);
|
|
5837
|
+
return false;
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
async applyCredentialsResponse(response) {
|
|
5841
|
+
if (response.type === "oauth") {
|
|
5842
|
+
await this.writeOauthCredentialsFile({
|
|
5843
|
+
accessToken: response.accessToken,
|
|
5844
|
+
refreshToken: response.refreshToken,
|
|
5845
|
+
expiresAt: response.expiresAt,
|
|
5846
|
+
scopes: response.scopes,
|
|
5847
|
+
subscriptionType: response.subscriptionType
|
|
5936
5848
|
});
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5849
|
+
} else {
|
|
5850
|
+
await this.removeOauthCredentialsFile();
|
|
5851
|
+
}
|
|
5852
|
+
const envVars = claudeAuthEnvFromResponse(response);
|
|
5853
|
+
applyAuthEnvTransition({
|
|
5854
|
+
prevMethod: ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD ?? "none",
|
|
5855
|
+
newMethod: envVars.REPLICAS_CLAUDE_AUTH_METHOD ?? "none",
|
|
5856
|
+
authKeys: CLAUDE_AUTH_ENV_KEYS,
|
|
5857
|
+
authKeysByMethod: CLAUDE_AUTH_ENV_KEYS_BY_METHOD,
|
|
5858
|
+
newEnvVars: envVars,
|
|
5859
|
+
envs: [ENGINE_ENV, process.env]
|
|
5860
|
+
});
|
|
5861
|
+
}
|
|
5862
|
+
async writeOauthCredentialsFile(credentials) {
|
|
5863
|
+
const credentialsPath = path3.join(ENGINE_ENV.HOME_DIR, ".claude", ".credentials.json");
|
|
5864
|
+
const claudeCliConfig = {
|
|
5865
|
+
claudeAiOauth: {
|
|
5866
|
+
accessToken: credentials.accessToken,
|
|
5867
|
+
refreshToken: credentials.refreshToken,
|
|
5868
|
+
expiresAt: new Date(credentials.expiresAt).getTime(),
|
|
5869
|
+
scopes: credentials.scopes,
|
|
5870
|
+
subscriptionType: credentials.subscriptionType
|
|
5943
5871
|
}
|
|
5944
|
-
}
|
|
5945
|
-
|
|
5872
|
+
};
|
|
5873
|
+
try {
|
|
5874
|
+
await writeSecureCredentialFile(
|
|
5875
|
+
credentialsPath,
|
|
5876
|
+
JSON.stringify(claudeCliConfig, null, 2),
|
|
5877
|
+
{ ensureParentDir: true }
|
|
5878
|
+
);
|
|
5879
|
+
console.log(`[ClaudeTokenManager] Updated ${credentialsPath}`);
|
|
5880
|
+
} catch (error) {
|
|
5881
|
+
console.error("[ClaudeTokenManager] Failed to update credentials file:", error);
|
|
5946
5882
|
}
|
|
5947
|
-
this.originInfoCache.set(repoPath, info);
|
|
5948
|
-
return info;
|
|
5949
5883
|
}
|
|
5950
|
-
|
|
5951
|
-
const
|
|
5884
|
+
async removeOauthCredentialsFile() {
|
|
5885
|
+
const credentialsPath = path3.join(ENGINE_ENV.HOME_DIR, ".claude", ".credentials.json");
|
|
5952
5886
|
try {
|
|
5953
|
-
|
|
5954
|
-
if (parsed.protocol === "ssh:") {
|
|
5955
|
-
const projectPath = decodePathSegments(parsed.pathname.split("/").filter(Boolean)).join("/");
|
|
5956
|
-
if (!parsed.hostname || !projectPath) return null;
|
|
5957
|
-
const host = parsed.hostname.toLowerCase();
|
|
5958
|
-
return {
|
|
5959
|
-
host,
|
|
5960
|
-
instanceUrl: `https://${host}`,
|
|
5961
|
-
projectPath
|
|
5962
|
-
};
|
|
5963
|
-
}
|
|
5887
|
+
await fs.unlink(credentialsPath);
|
|
5964
5888
|
} catch {
|
|
5965
5889
|
}
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5890
|
+
}
|
|
5891
|
+
};
|
|
5892
|
+
var claudeTokenManager = new ClaudeTokenManager();
|
|
5893
|
+
|
|
5894
|
+
// src/managers/codex-token-manager.ts
|
|
5895
|
+
import { promises as fs2 } from "fs";
|
|
5896
|
+
import path4 from "path";
|
|
5897
|
+
var CodexTokenManager = class extends BaseRefreshManager {
|
|
5898
|
+
constructor() {
|
|
5899
|
+
super("CodexTokenManager");
|
|
5900
|
+
}
|
|
5901
|
+
getSkipReason() {
|
|
5902
|
+
if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "api_key" || ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "foundry") {
|
|
5903
|
+
return `auth method is ${ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD}`;
|
|
5976
5904
|
}
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
try {
|
|
5980
|
-
const parsed = new URL(normalized);
|
|
5981
|
-
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
5982
|
-
const projectPath = decodePathSegments(parsed.pathname.split("/").filter(Boolean)).join("/");
|
|
5983
|
-
if (!projectPath) return null;
|
|
5984
|
-
return {
|
|
5985
|
-
host: parsed.host.toLowerCase(),
|
|
5986
|
-
instanceUrl: parsed.origin,
|
|
5987
|
-
projectPath
|
|
5988
|
-
};
|
|
5989
|
-
} catch {
|
|
5905
|
+
if (!ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD && ENGINE_ENV.OPENAI_API_KEY) {
|
|
5906
|
+
return "OPENAI_API_KEY is set";
|
|
5990
5907
|
}
|
|
5991
5908
|
return null;
|
|
5992
5909
|
}
|
|
5993
|
-
async
|
|
5910
|
+
async doRefresh(_config) {
|
|
5911
|
+
await this.refreshWithRequest();
|
|
5912
|
+
}
|
|
5913
|
+
async refreshWithRequest(request) {
|
|
5914
|
+
console.log("[CodexTokenManager] Refreshing Codex credentials...");
|
|
5915
|
+
const response = await monolithRequest("/v1/engine/codex/refresh-credentials", {
|
|
5916
|
+
body: request
|
|
5917
|
+
});
|
|
5918
|
+
if (!response.ok) {
|
|
5919
|
+
const errorText = await response.text();
|
|
5920
|
+
throw new Error(`Credentials refresh failed: ${response.status} ${errorText}`);
|
|
5921
|
+
}
|
|
5922
|
+
const data = await response.json();
|
|
5923
|
+
await this.applyCredentialsResponse(data);
|
|
5924
|
+
console.log(`[CodexTokenManager] Credentials refreshed (method=${data.type})`);
|
|
5925
|
+
}
|
|
5926
|
+
async fetchFreshCredentials(failureReason) {
|
|
5927
|
+
const config = this.getRuntimeConfig();
|
|
5928
|
+
if (!config) return false;
|
|
5994
5929
|
try {
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
}
|
|
6005
|
-
}
|
|
5930
|
+
console.log("[CodexTokenManager] Fetching fresh credentials from monolith after auth failure...");
|
|
5931
|
+
const failedMethod = ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD;
|
|
5932
|
+
await this.refreshWithRequest(failedMethod === "oauth" || failedMethod === "api_key" || failedMethod === "foundry" ? {
|
|
5933
|
+
failedMethod,
|
|
5934
|
+
failureReason
|
|
5935
|
+
} : void 0);
|
|
5936
|
+
if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "oauth") {
|
|
5937
|
+
this.start().catch((error) => {
|
|
5938
|
+
console.error("[CodexTokenManager] Failed to restart OAuth refresh service after fallback:", error);
|
|
5939
|
+
});
|
|
6006
5940
|
}
|
|
6007
|
-
|
|
5941
|
+
return true;
|
|
5942
|
+
} catch (error) {
|
|
5943
|
+
console.error("[CodexTokenManager] Failed to fetch fresh credentials:", error);
|
|
5944
|
+
return false;
|
|
6008
5945
|
}
|
|
6009
|
-
return null;
|
|
6010
5946
|
}
|
|
6011
|
-
async
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
5947
|
+
async applyCredentialsResponse(response) {
|
|
5948
|
+
if (response.type === "oauth") {
|
|
5949
|
+
await this.writeOauthCredentialsFile(response);
|
|
5950
|
+
} else {
|
|
5951
|
+
await this.removeOauthCredentialsFile();
|
|
6015
5952
|
}
|
|
6016
|
-
const
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
5953
|
+
const envVars = codexAuthEnvFromResponse(response);
|
|
5954
|
+
applyAuthEnvTransition({
|
|
5955
|
+
prevMethod: ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD ?? "none",
|
|
5956
|
+
newMethod: envVars.REPLICAS_CODEX_AUTH_METHOD ?? "none",
|
|
5957
|
+
authKeys: CODEX_AUTH_ENV_KEYS,
|
|
5958
|
+
authKeysByMethod: CODEX_AUTH_ENV_KEYS_BY_METHOD,
|
|
5959
|
+
newEnvVars: envVars,
|
|
5960
|
+
envs: [ENGINE_ENV, process.env]
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
async writeOauthCredentialsFile(credentials) {
|
|
5964
|
+
const authPath = path4.join(ENGINE_ENV.HOME_DIR, ".codex", "auth.json");
|
|
5965
|
+
const codexAuthConfig = {
|
|
5966
|
+
OPENAI_API_KEY: null,
|
|
5967
|
+
tokens: {
|
|
5968
|
+
id_token: credentials.idToken,
|
|
5969
|
+
access_token: credentials.accessToken,
|
|
5970
|
+
refresh_token: credentials.refreshToken,
|
|
5971
|
+
account_id: credentials.accountId
|
|
5972
|
+
},
|
|
5973
|
+
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
5974
|
+
};
|
|
5975
|
+
try {
|
|
5976
|
+
await writeSecureCredentialFile(
|
|
5977
|
+
authPath,
|
|
5978
|
+
JSON.stringify(codexAuthConfig, null, 2),
|
|
5979
|
+
{ ensureParentDir: true }
|
|
5980
|
+
);
|
|
5981
|
+
console.log(`[CodexTokenManager] Updated ${authPath}`);
|
|
5982
|
+
} catch (error) {
|
|
5983
|
+
console.error("[CodexTokenManager] Failed to update credentials file:", error);
|
|
6020
5984
|
}
|
|
6021
|
-
return "main";
|
|
6022
5985
|
}
|
|
6023
|
-
async
|
|
5986
|
+
async removeOauthCredentialsFile() {
|
|
5987
|
+
const authPath = path4.join(ENGINE_ENV.HOME_DIR, ".codex", "auth.json");
|
|
6024
5988
|
try {
|
|
6025
|
-
|
|
6026
|
-
const match = output.match(/^origin\/(.+)$/);
|
|
6027
|
-
return match ? match[1] : null;
|
|
5989
|
+
await fs2.unlink(authPath);
|
|
6028
5990
|
} catch {
|
|
6029
|
-
return null;
|
|
6030
5991
|
}
|
|
6031
5992
|
}
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
const gitDiff = await this.getGitDiffStats(repo.path, repo.defaultBranch);
|
|
6048
|
-
const fullDiff = includeDiffs && gitDiff ? await this.getFullGitDiff(repo.path, repo.defaultBranch) : null;
|
|
6049
|
-
const state = {
|
|
6050
|
-
name: repo.name,
|
|
6051
|
-
path: repo.path,
|
|
6052
|
-
defaultBranch: repo.defaultBranch,
|
|
6053
|
-
currentBranch,
|
|
6054
|
-
prUrls,
|
|
6055
|
-
gitDiff: includeDiffs && gitDiff ? { ...gitDiff, ...fullDiff === null ? {} : { fullDiff } } : gitDiff,
|
|
6056
|
-
startHooksCompleted,
|
|
6057
|
-
provider: await this.resolveCodeHostProvider(repo.path)
|
|
6058
|
-
};
|
|
6059
|
-
await saveRepoState(repo.name, state, state);
|
|
6060
|
-
return state;
|
|
5993
|
+
};
|
|
5994
|
+
var codexTokenManager = new CodexTokenManager();
|
|
5995
|
+
|
|
5996
|
+
// src/managers/infisical-token-manager.ts
|
|
5997
|
+
import { rm } from "fs/promises";
|
|
5998
|
+
import path5 from "path";
|
|
5999
|
+
var InfisicalTokenManager = class extends BaseRefreshManager {
|
|
6000
|
+
constructor(request = monolithRequest, paths = {
|
|
6001
|
+
homeDir: ENGINE_ENV.HOME_DIR,
|
|
6002
|
+
workspaceRoot: ENGINE_ENV.WORKSPACE_ROOT
|
|
6003
|
+
}, applyCredentials = applyInfisicalCredentials) {
|
|
6004
|
+
super("InfisicalTokenManager", 5 * 60 * 1e3);
|
|
6005
|
+
this.request = request;
|
|
6006
|
+
this.paths = paths;
|
|
6007
|
+
this.applyCredentials = applyCredentials;
|
|
6061
6008
|
}
|
|
6062
|
-
|
|
6063
|
-
|
|
6009
|
+
request;
|
|
6010
|
+
paths;
|
|
6011
|
+
applyCredentials;
|
|
6012
|
+
nextRefreshDelayMs = 5 * 60 * 1e3;
|
|
6013
|
+
async doRefresh(_config) {
|
|
6014
|
+
const response = await this.request("/v1/engine/infisical/refresh-token");
|
|
6015
|
+
if (!response.ok) throw new Error(`Token refresh failed: ${response.status} ${await response.text()}`);
|
|
6016
|
+
const data = await response.json();
|
|
6017
|
+
await this.applyCredentials(data, this.paths);
|
|
6018
|
+
const ttlMs = data.configured ? Date.parse(data.expiresAt) - Date.now() : Number.NaN;
|
|
6019
|
+
this.nextRefreshDelayMs = Number.isFinite(ttlMs) ? Math.max(1e3, Math.min(5 * 60 * 1e3, Math.floor(ttlMs * 0.8))) : 5 * 60 * 1e3;
|
|
6064
6020
|
}
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
return await stat(path6);
|
|
6068
|
-
} catch {
|
|
6069
|
-
return null;
|
|
6070
|
-
}
|
|
6021
|
+
getNextRefreshDelayMs() {
|
|
6022
|
+
return this.nextRefreshDelayMs;
|
|
6071
6023
|
}
|
|
6072
6024
|
};
|
|
6073
|
-
|
|
6025
|
+
async function applyInfisicalCredentials(data, paths) {
|
|
6026
|
+
const credentialPath = path5.join(paths.homeDir, ".replicas", "infisical-env.sh");
|
|
6027
|
+
const configPath = path5.join(paths.workspaceRoot, ".infisical.json");
|
|
6028
|
+
if (!data.configured) {
|
|
6029
|
+
delete process.env.INFISICAL_TOKEN;
|
|
6030
|
+
delete process.env.INFISICAL_DOMAIN;
|
|
6031
|
+
await Promise.all([rm(credentialPath, { force: true }), rm(configPath, { force: true })]);
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
process.env.INFISICAL_TOKEN = data.token;
|
|
6035
|
+
process.env.INFISICAL_DOMAIN = data.siteUrl;
|
|
6036
|
+
await Promise.all([
|
|
6037
|
+
writeSecureCredentialFile(credentialPath, [
|
|
6038
|
+
`export INFISICAL_TOKEN=${shellQuotePosix(data.token)}`,
|
|
6039
|
+
`export INFISICAL_DOMAIN=${shellQuotePosix(data.siteUrl)}`,
|
|
6040
|
+
"export INFISICAL_DISABLE_UPDATE_CHECK=true",
|
|
6041
|
+
""
|
|
6042
|
+
].join("\n"), { ensureParentDir: true }),
|
|
6043
|
+
writeSecureCredentialFile(configPath, `${JSON.stringify({
|
|
6044
|
+
workspaceId: data.projectId,
|
|
6045
|
+
defaultEnvironment: data.environment,
|
|
6046
|
+
gitBranchToEnvironmentMapping: null,
|
|
6047
|
+
domain: data.siteUrl
|
|
6048
|
+
}, null, 2)}
|
|
6049
|
+
`, { ensureParentDir: true })
|
|
6050
|
+
]);
|
|
6051
|
+
}
|
|
6052
|
+
var infisicalTokenManager = new InfisicalTokenManager();
|
|
6074
6053
|
|
|
6075
6054
|
// src/utils/logger.ts
|
|
6076
6055
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -6195,14 +6174,14 @@ var engineLogger = new EngineLogger();
|
|
|
6195
6174
|
|
|
6196
6175
|
// src/services/replicas-config-service.ts
|
|
6197
6176
|
import { readFile as readFile6, appendFile, writeFile as writeFile4, mkdir as mkdir6 } from "fs/promises";
|
|
6198
|
-
import { existsSync as
|
|
6177
|
+
import { existsSync as existsSync3 } from "fs";
|
|
6199
6178
|
import { join as join9 } from "path";
|
|
6200
6179
|
import { homedir as homedir7 } from "os";
|
|
6201
6180
|
import { spawn as spawn2 } from "child_process";
|
|
6202
6181
|
|
|
6203
6182
|
// src/services/environment-details-service.ts
|
|
6204
6183
|
import { mkdir as mkdir4, readFile as readFile4 } from "fs/promises";
|
|
6205
|
-
import { existsSync as
|
|
6184
|
+
import { existsSync as existsSync2 } from "fs";
|
|
6206
6185
|
import { homedir as homedir5 } from "os";
|
|
6207
6186
|
import { join as join7 } from "path";
|
|
6208
6187
|
var REPLICAS_DIR = join7(homedir5(), ".replicas");
|
|
@@ -6213,7 +6192,7 @@ var OPENCODE_AUTH_PATH = join7(homedir5(), ".local", "share", "opencode", "auth.
|
|
|
6213
6192
|
var GH_HOSTS_PATH = join7(homedir5(), ".config", "gh", "hosts.yml");
|
|
6214
6193
|
var GIT_CREDENTIALS_PATH = join7(homedir5(), ".git-credentials");
|
|
6215
6194
|
function detectClaudeAuthMethod() {
|
|
6216
|
-
if (
|
|
6195
|
+
if (existsSync2(CLAUDE_CREDENTIALS_PATH)) {
|
|
6217
6196
|
return "oauth";
|
|
6218
6197
|
}
|
|
6219
6198
|
if (ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD === "foundry") {
|
|
@@ -6228,7 +6207,7 @@ function detectClaudeAuthMethod() {
|
|
|
6228
6207
|
return "none";
|
|
6229
6208
|
}
|
|
6230
6209
|
function detectCodexAuthMethod() {
|
|
6231
|
-
if (
|
|
6210
|
+
if (existsSync2(CODEX_AUTH_PATH)) {
|
|
6232
6211
|
return "oauth";
|
|
6233
6212
|
}
|
|
6234
6213
|
if (ENGINE_ENV.OPENAI_API_KEY) {
|
|
@@ -6241,7 +6220,7 @@ function detectCursorAuthMethod() {
|
|
|
6241
6220
|
return ENGINE_ENV.CURSOR_API_KEY ? "api_key" : "none";
|
|
6242
6221
|
}
|
|
6243
6222
|
function detectOpencodeAuthMethod() {
|
|
6244
|
-
return
|
|
6223
|
+
return existsSync2(OPENCODE_AUTH_PATH) || ENGINE_ENV.OPENROUTER_API_KEY ? "api_key" : "none";
|
|
6245
6224
|
}
|
|
6246
6225
|
function detectPiAuthMethod() {
|
|
6247
6226
|
return ENGINE_ENV.OPENROUTER_API_KEY ? "api_key" : "none";
|
|
@@ -6309,7 +6288,7 @@ function createDefaultDetails() {
|
|
|
6309
6288
|
}
|
|
6310
6289
|
async function readDetails() {
|
|
6311
6290
|
try {
|
|
6312
|
-
if (!
|
|
6291
|
+
if (!existsSync2(DETAILS_FILE)) {
|
|
6313
6292
|
return createDefaultDetails();
|
|
6314
6293
|
}
|
|
6315
6294
|
const raw = await readFile4(DETAILS_FILE, "utf-8");
|
|
@@ -6341,7 +6320,7 @@ var EnvironmentDetailsService = class {
|
|
|
6341
6320
|
details.opencodeAuthMethod = detectOpencodeAuthMethod();
|
|
6342
6321
|
details.piAuthMethod = detectPiAuthMethod();
|
|
6343
6322
|
details.gitIdentityConfigured = gitIdentityConfigured;
|
|
6344
|
-
const ghConfigured =
|
|
6323
|
+
const ghConfigured = existsSync2(GH_HOSTS_PATH);
|
|
6345
6324
|
details.githubAccessConfigured = ghConfigured;
|
|
6346
6325
|
details.githubCredentialsConfigured = ghConfigured;
|
|
6347
6326
|
details.gitlabAccessConfigured = gitlabAccessConfigured;
|
|
@@ -6540,7 +6519,7 @@ If your task depends on setup being complete, check the log file before proceedi
|
|
|
6540
6519
|
async function readReplicasConfigFromDir(dirPath) {
|
|
6541
6520
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
6542
6521
|
const configPath = join9(dirPath, filename);
|
|
6543
|
-
if (!
|
|
6522
|
+
if (!existsSync3(configPath)) {
|
|
6544
6523
|
continue;
|
|
6545
6524
|
}
|
|
6546
6525
|
const data = await readFile6(configPath, "utf-8");
|
|
@@ -6975,14 +6954,14 @@ var eventService = new EventService();
|
|
|
6975
6954
|
|
|
6976
6955
|
// src/services/preview-service.ts
|
|
6977
6956
|
import { mkdir as mkdir8, readFile as readFile7 } from "fs/promises";
|
|
6978
|
-
import { existsSync as
|
|
6957
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6979
6958
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6980
6959
|
import { homedir as homedir9 } from "os";
|
|
6981
6960
|
import { dirname as dirname2, join as join11 } from "path";
|
|
6982
6961
|
var PREVIEW_PORTS_FILE = join11(homedir9(), ".replicas", "preview-ports.json");
|
|
6983
6962
|
async function readPreviewsFile() {
|
|
6984
6963
|
try {
|
|
6985
|
-
if (!
|
|
6964
|
+
if (!existsSync4(PREVIEW_PORTS_FILE)) {
|
|
6986
6965
|
return { previews: [] };
|
|
6987
6966
|
}
|
|
6988
6967
|
const raw = await readFile7(PREVIEW_PORTS_FILE, "utf-8");
|
|
@@ -7089,7 +7068,7 @@ async function registerDesktopPreview() {
|
|
|
7089
7068
|
}
|
|
7090
7069
|
|
|
7091
7070
|
// src/services/chat/chat-service.ts
|
|
7092
|
-
import { existsSync as
|
|
7071
|
+
import { existsSync as existsSync7 } from "fs";
|
|
7093
7072
|
import { appendFile as appendFile3, copyFile, mkdir as mkdir14, readFile as readFile14, rename as rename2, rm as rm2 } from "fs/promises";
|
|
7094
7073
|
import { homedir as homedir15 } from "os";
|
|
7095
7074
|
import { join as join22 } from "path";
|
|
@@ -9811,7 +9790,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
|
9811
9790
|
var MIN_CODEX_CLI_VERSION = "0.144.6";
|
|
9812
9791
|
var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
|
|
9813
9792
|
var codexCliVersionEnsured = null;
|
|
9814
|
-
var ENGINE_PACKAGE_VERSION = "0.1.
|
|
9793
|
+
var ENGINE_PACKAGE_VERSION = "0.1.478";
|
|
9815
9794
|
var INITIALIZE_METHOD = "initialize";
|
|
9816
9795
|
var INITIALIZED_NOTIFICATION = "initialized";
|
|
9817
9796
|
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
@@ -10096,7 +10075,7 @@ var CodexQuotaStatusTracker = class {
|
|
|
10096
10075
|
};
|
|
10097
10076
|
|
|
10098
10077
|
// src/managers/codex-asp/mappers.ts
|
|
10099
|
-
import { existsSync as
|
|
10078
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
10100
10079
|
var localImageCache = /* @__PURE__ */ new Map();
|
|
10101
10080
|
var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
|
|
10102
10081
|
var THREAD_START_METHOD = "thread/start";
|
|
@@ -10193,7 +10172,7 @@ function transcriptItemsForTurn(turn) {
|
|
|
10193
10172
|
function userImageForLocalPath(path6) {
|
|
10194
10173
|
const cached = localImageCache.get(path6);
|
|
10195
10174
|
if (cached) return cached;
|
|
10196
|
-
if (!
|
|
10175
|
+
if (!existsSync5(path6)) return null;
|
|
10197
10176
|
const image = {
|
|
10198
10177
|
type: "image",
|
|
10199
10178
|
mediaType: inferMediaType(path6),
|
|
@@ -12344,7 +12323,7 @@ ${request.message}` : request.message;
|
|
|
12344
12323
|
};
|
|
12345
12324
|
|
|
12346
12325
|
// src/managers/opencode-manager.ts
|
|
12347
|
-
import { existsSync as
|
|
12326
|
+
import { existsSync as existsSync6 } from "fs";
|
|
12348
12327
|
import { mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
|
|
12349
12328
|
import { delimiter, dirname as dirname6, join as join18 } from "path";
|
|
12350
12329
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
@@ -12392,7 +12371,7 @@ var opencodeAuthSchema = z4.record(z4.string(), z4.object({
|
|
|
12392
12371
|
key: z4.string().optional()
|
|
12393
12372
|
}));
|
|
12394
12373
|
async function hasOpenCodeGoCredentials() {
|
|
12395
|
-
if (!
|
|
12374
|
+
if (!existsSync6(OPENCODE_AUTH_PATH2)) return false;
|
|
12396
12375
|
try {
|
|
12397
12376
|
const auth = opencodeAuthSchema.safeParse(JSON.parse(await readFile11(OPENCODE_AUTH_PATH2, "utf8")));
|
|
12398
12377
|
return auth.success && auth.data[OPENCODE_GO_PROVIDER]?.type === "api" && Boolean(auth.data[OPENCODE_GO_PROVIDER]?.key);
|
|
@@ -14183,10 +14162,10 @@ function isChatMessageSender(value) {
|
|
|
14183
14162
|
return typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
|
|
14184
14163
|
}
|
|
14185
14164
|
function isCodexAvailable() {
|
|
14186
|
-
return
|
|
14165
|
+
return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
|
|
14187
14166
|
}
|
|
14188
14167
|
function isOpencodeAvailable() {
|
|
14189
|
-
return
|
|
14168
|
+
return existsSync7(OPENCODE_AUTH_PATH3) || Boolean(ENGINE_ENV.OPENROUTER_API_KEY);
|
|
14190
14169
|
}
|
|
14191
14170
|
function isPiAvailable() {
|
|
14192
14171
|
return Boolean(ENGINE_ENV.OPENROUTER_API_KEY);
|
|
@@ -15332,7 +15311,7 @@ import { join as join26, resolve as resolve3 } from "path";
|
|
|
15332
15311
|
// src/services/warm-hooks-service.ts
|
|
15333
15312
|
import { spawn as spawn4 } from "child_process";
|
|
15334
15313
|
import { readFile as readFile17 } from "fs/promises";
|
|
15335
|
-
import { existsSync as
|
|
15314
|
+
import { existsSync as existsSync8 } from "fs";
|
|
15336
15315
|
import { join as join25 } from "path";
|
|
15337
15316
|
|
|
15338
15317
|
// src/services/warm-hook-logs-service.ts
|
|
@@ -15454,7 +15433,7 @@ var warmHookLogsService = new WarmHookLogsService();
|
|
|
15454
15433
|
async function readRepoWarmHook(repoPath) {
|
|
15455
15434
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
15456
15435
|
const configPath = join25(repoPath, filename);
|
|
15457
|
-
if (!
|
|
15436
|
+
if (!existsSync8(configPath)) {
|
|
15458
15437
|
continue;
|
|
15459
15438
|
}
|
|
15460
15439
|
try {
|
|
@@ -15715,7 +15694,7 @@ ${combinedScript}` : combinedScript;
|
|
|
15715
15694
|
|
|
15716
15695
|
// src/services/terminal-service.ts
|
|
15717
15696
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
15718
|
-
import { existsSync as
|
|
15697
|
+
import { existsSync as existsSync9 } from "fs";
|
|
15719
15698
|
import { spawn as spawn5 } from "node-pty";
|
|
15720
15699
|
var MAX_REPLAY_CHARS = 1024 * 1024;
|
|
15721
15700
|
var MAX_TERMINAL_SESSIONS = 8;
|
|
@@ -15734,7 +15713,7 @@ var TerminalService = class {
|
|
|
15734
15713
|
});
|
|
15735
15714
|
}
|
|
15736
15715
|
const id = randomUUID6();
|
|
15737
|
-
const shell = process.env.SHELL &&
|
|
15716
|
+
const shell = process.env.SHELL && existsSync9(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
|
|
15738
15717
|
const pty = spawn5(shell, ["-l"], {
|
|
15739
15718
|
name: "xterm-256color",
|
|
15740
15719
|
cols,
|
|
@@ -16809,7 +16788,7 @@ async function timeStartupStep(name, fn) {
|
|
|
16809
16788
|
async function waitForInitializationGate() {
|
|
16810
16789
|
if (!ENGINE_ENV.REPLICAS_ENGINE_DEFER_INITIALIZATION) return;
|
|
16811
16790
|
const deadline = Date.now() + ENGINE_INIT_GATE_TIMEOUT_MS;
|
|
16812
|
-
while (!
|
|
16791
|
+
while (!existsSync10(SANDBOX_PATHS.ENGINE_INIT_GATE)) {
|
|
16813
16792
|
if (Date.now() >= deadline) {
|
|
16814
16793
|
throw new Error("Timed out waiting for deferred engine initialization gate");
|
|
16815
16794
|
}
|