codexui-android 0.1.108 → 0.1.110
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/assets/AutomationsPanel-DrI9_gvf.js +1 -0
- package/dist/assets/AutomationsPanel-Dy1km8EU.css +1 -0
- package/dist/assets/{DirectoryHub-BjsbwciN.css → DirectoryHub-BkBvhdUs.css} +1 -1
- package/dist/assets/DirectoryHub-e97BnXKM.js +2 -0
- package/dist/assets/ReviewPane-B5Ebl-29.css +1 -0
- package/dist/assets/ReviewPane-Q-Tx07Q7.js +1 -0
- package/dist/assets/{ThreadConversation-DKfW7vC7.js → ThreadConversation-C162xIoq.js} +18 -18
- package/dist/assets/{ThreadConversation-BwA4BCDu.css → ThreadConversation-wF1OnM7o.css} +1 -1
- package/dist/assets/{ThreadTerminalPanel-CrJKkAvB.js → ThreadTerminalPanel-C78jNJ5m.js} +1 -1
- package/dist/assets/index-BZZznPCP.css +1 -0
- package/dist/assets/index-xzqof2OU.js +63 -0
- package/dist/index.html +2 -2
- package/dist-cli/index.js +553 -93
- package/dist-cli/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/DirectoryHub-BeJqaP4D.js +0 -2
- package/dist/assets/ReviewPane-BAfo9D_N.css +0 -1
- package/dist/assets/ReviewPane-IP5BRZ-3.js +0 -1
- package/dist/assets/index-DfEYiJAK.js +0 -63
- package/dist/assets/index-KQ_3KeZN.css +0 -1
package/dist-cli/index.js
CHANGED
|
@@ -2047,6 +2047,9 @@ function deriveSkillPathInfo(skillPath, knownPaths = /* @__PURE__ */ new Set())
|
|
|
2047
2047
|
function getSkillsInstallDir() {
|
|
2048
2048
|
return join4(getCodexHomeDir2(), "skills");
|
|
2049
2049
|
}
|
|
2050
|
+
function getSharedSkillsInstallDir() {
|
|
2051
|
+
return join4(getSkillsInstallDir(), "shared_skills");
|
|
2052
|
+
}
|
|
2050
2053
|
var DEFAULT_COMMAND_TIMEOUT_MS = 12e4;
|
|
2051
2054
|
var SKILL_SEARCH_METADATA_LIMIT = 20;
|
|
2052
2055
|
var SKILL_SEARCH_METADATA_CONCURRENCY = 4;
|
|
@@ -2421,9 +2424,8 @@ var startupSyncStatus = {
|
|
|
2421
2424
|
lastSuccessAtIso: "",
|
|
2422
2425
|
lastError: ""
|
|
2423
2426
|
};
|
|
2424
|
-
async function
|
|
2427
|
+
async function scanInstalledSkillsFromDir(skillsDir) {
|
|
2425
2428
|
const map = /* @__PURE__ */ new Map();
|
|
2426
|
-
const skillsDir = getSkillsInstallDir();
|
|
2427
2429
|
try {
|
|
2428
2430
|
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
2429
2431
|
for (const entry of entries) {
|
|
@@ -2439,6 +2441,9 @@ async function scanInstalledSkillsFromDisk() {
|
|
|
2439
2441
|
}
|
|
2440
2442
|
return map;
|
|
2441
2443
|
}
|
|
2444
|
+
async function scanInstalledSkillsFromDisk() {
|
|
2445
|
+
return await scanInstalledSkillsFromDir(getSkillsInstallDir());
|
|
2446
|
+
}
|
|
2442
2447
|
async function collectInstalledSkillsMap(appServer) {
|
|
2443
2448
|
const installedMap = await scanInstalledSkillsFromDisk();
|
|
2444
2449
|
try {
|
|
@@ -2700,13 +2705,14 @@ async function writeRemoteSkillsManifest(token, repoOwner, repoName, skills) {
|
|
|
2700
2705
|
function toGitHubTokenRemote(repoOwner, repoName, token) {
|
|
2701
2706
|
return `https://x-access-token:${encodeURIComponent(token)}@github.com/${repoOwner}/${repoName}.git`;
|
|
2702
2707
|
}
|
|
2703
|
-
async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
|
|
2704
|
-
const localDir = getSkillsInstallDir();
|
|
2708
|
+
async function ensureSkillsWorkingTreeRepo(repoUrl, branch, options = {}) {
|
|
2709
|
+
const localDir = options.localDir ?? getSkillsInstallDir();
|
|
2705
2710
|
await mkdir3(localDir, { recursive: true });
|
|
2706
2711
|
const gitDir = join4(localDir, ".git");
|
|
2707
2712
|
let hasGitDir = false;
|
|
2708
2713
|
try {
|
|
2709
|
-
|
|
2714
|
+
const gitDirStat = await lstat(gitDir);
|
|
2715
|
+
hasGitDir = gitDirStat.isDirectory() || gitDirStat.isFile();
|
|
2710
2716
|
} catch {
|
|
2711
2717
|
hasGitDir = false;
|
|
2712
2718
|
}
|
|
@@ -2726,6 +2732,14 @@ async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
|
|
|
2726
2732
|
await runCommand2("git", ["remote", "set-url", "origin", repoUrl], { cwd: localDir });
|
|
2727
2733
|
}
|
|
2728
2734
|
await runGitFetchWithRefLockRetry(localDir);
|
|
2735
|
+
if (options.overwriteLocalFiles) {
|
|
2736
|
+
await runCommand2("git", ["reset", "--hard"], { cwd: localDir });
|
|
2737
|
+
await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
|
|
2738
|
+
await runCommand2("git", ["checkout", "-B", branch, `origin/${branch}`], { cwd: localDir });
|
|
2739
|
+
await runCommand2("git", ["reset", "--hard", `origin/${branch}`], { cwd: localDir });
|
|
2740
|
+
await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
|
|
2741
|
+
return localDir;
|
|
2742
|
+
}
|
|
2729
2743
|
try {
|
|
2730
2744
|
await runCommand2("git", ["merge", "--allow-unrelated-histories", "--no-edit", `origin/${branch}`], { cwd: localDir });
|
|
2731
2745
|
} catch {
|
|
@@ -2734,6 +2748,17 @@ async function ensureSkillsWorkingTreeRepo(repoUrl, branch) {
|
|
|
2734
2748
|
}
|
|
2735
2749
|
await runCommand2("git", ["remote", "set-url", "origin", repoUrl], { cwd: localDir });
|
|
2736
2750
|
await runGitFetchWithRefLockRetry(localDir);
|
|
2751
|
+
if (options.overwriteLocalFiles) {
|
|
2752
|
+
try {
|
|
2753
|
+
await runCommand2("git", ["reset", "--hard"], { cwd: localDir });
|
|
2754
|
+
} catch {
|
|
2755
|
+
}
|
|
2756
|
+
await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
|
|
2757
|
+
await runCommand2("git", ["checkout", "-B", branch, `origin/${branch}`], { cwd: localDir });
|
|
2758
|
+
await runCommand2("git", ["reset", "--hard", `origin/${branch}`], { cwd: localDir });
|
|
2759
|
+
await runCommand2("git", ["clean", "-fd"], { cwd: localDir });
|
|
2760
|
+
return localDir;
|
|
2761
|
+
}
|
|
2737
2762
|
const hasLocalChangesBeforeSync = await hasLocalUncommittedChanges(localDir);
|
|
2738
2763
|
const localMtimesBeforeSync = hasLocalChangesBeforeSync ? await snapshotFileMtimes(localDir) : /* @__PURE__ */ new Map();
|
|
2739
2764
|
await resolveMergeConflictsByNewerCommit(localDir, branch, localMtimesBeforeSync);
|
|
@@ -2994,13 +3019,19 @@ async function syncInstalledSkillsFolderToRepo(token, repoOwner, repoName, _inst
|
|
|
2994
3019
|
}
|
|
2995
3020
|
async function pullInstalledSkillsFolderFromRepo(token, repoOwner, repoName) {
|
|
2996
3021
|
const remoteUrl = toGitHubTokenRemote(repoOwner, repoName, token);
|
|
2997
|
-
const
|
|
2998
|
-
|
|
3022
|
+
const isUpstream = isUpstreamSkillsRepo(repoOwner, repoName);
|
|
3023
|
+
const branch = isUpstream ? PUBLIC_UPSTREAM_BRANCH_ANDROID : PRIVATE_SYNC_BRANCH;
|
|
3024
|
+
return await ensureSkillsWorkingTreeRepo(remoteUrl, branch, {
|
|
3025
|
+
...isUpstream ? { localDir: getSharedSkillsInstallDir() } : {},
|
|
3026
|
+
overwriteLocalFiles: isUpstream
|
|
3027
|
+
});
|
|
2999
3028
|
}
|
|
3000
3029
|
async function bootstrapSkillsFromUpstreamIntoLocal() {
|
|
3001
3030
|
const repoUrl = `https://github.com/${SYNC_UPSTREAM_SKILLS_OWNER}/${SYNC_UPSTREAM_SKILLS_REPO}.git`;
|
|
3002
|
-
|
|
3003
|
-
|
|
3031
|
+
return await ensureSkillsWorkingTreeRepo(repoUrl, PUBLIC_UPSTREAM_BRANCH_ANDROID, {
|
|
3032
|
+
localDir: getSharedSkillsInstallDir(),
|
|
3033
|
+
overwriteLocalFiles: true
|
|
3034
|
+
});
|
|
3004
3035
|
}
|
|
3005
3036
|
async function collectLocalSyncedSkills(appServer) {
|
|
3006
3037
|
const state = await readSkillsSyncState();
|
|
@@ -3305,12 +3336,31 @@ async function handleSkillsRoutes(req, res, url, context) {
|
|
|
3305
3336
|
try {
|
|
3306
3337
|
const state = await readSkillsSyncState();
|
|
3307
3338
|
if (!state.githubToken || !state.repoOwner || !state.repoName) {
|
|
3308
|
-
await bootstrapSkillsFromUpstreamIntoLocal();
|
|
3339
|
+
const repoDir = await bootstrapSkillsFromUpstreamIntoLocal();
|
|
3340
|
+
const localSkills2 = await scanInstalledSkillsFromDir(repoDir);
|
|
3341
|
+
try {
|
|
3342
|
+
await appServer.rpc("skills/list", { forceReload: true });
|
|
3343
|
+
} catch {
|
|
3344
|
+
}
|
|
3345
|
+
setJson3(res, 200, { ok: true, data: { synced: localSkills2.size, source: "upstream" } });
|
|
3346
|
+
return true;
|
|
3347
|
+
}
|
|
3348
|
+
if (isUpstreamSkillsRepo(state.repoOwner, state.repoName)) {
|
|
3349
|
+
const repoDir = await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName);
|
|
3350
|
+
const localSkills2 = await scanInstalledSkillsFromDir(repoDir);
|
|
3351
|
+
const pulledHead2 = await runCommandWithOutput("git", ["rev-parse", "HEAD"], { cwd: repoDir }).catch(() => "");
|
|
3352
|
+
await writeSkillsSyncState({
|
|
3353
|
+
...state,
|
|
3354
|
+
lastPullCommitSha: pulledHead2.trim(),
|
|
3355
|
+
lastSyncAttemptCount: 1,
|
|
3356
|
+
lastSyncError: "",
|
|
3357
|
+
lastSyncAtIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
3358
|
+
});
|
|
3309
3359
|
try {
|
|
3310
3360
|
await appServer.rpc("skills/list", { forceReload: true });
|
|
3311
3361
|
} catch {
|
|
3312
3362
|
}
|
|
3313
|
-
setJson3(res, 200, { ok: true, data: { synced:
|
|
3363
|
+
setJson3(res, 200, { ok: true, data: { synced: localSkills2.size, source: "upstream" } });
|
|
3314
3364
|
return true;
|
|
3315
3365
|
}
|
|
3316
3366
|
const remote = await readRemoteSkillsManifest(state.githubToken, state.repoOwner, state.repoName);
|
|
@@ -4203,13 +4253,6 @@ var CUSTOM_PROVIDER_ID = "custom-endpoint";
|
|
|
4203
4253
|
var OPENCODE_ZEN_PROVIDER_ID = "opencode-zen";
|
|
4204
4254
|
var OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
|
|
4205
4255
|
var OPENCODE_ZEN_DEFAULT_MODEL = "big-pickle";
|
|
4206
|
-
var OPENCODE_ZEN_FALLBACK_FREE_MODELS = [
|
|
4207
|
-
OPENCODE_ZEN_DEFAULT_MODEL,
|
|
4208
|
-
"minimax-m2.5-free",
|
|
4209
|
-
"hy3-preview-free",
|
|
4210
|
-
"nemotron-3-super-free",
|
|
4211
|
-
"trinity-large-preview-free"
|
|
4212
|
-
];
|
|
4213
4256
|
function createDefaultOpenCodeZenFreeModeState() {
|
|
4214
4257
|
return {
|
|
4215
4258
|
enabled: true,
|
|
@@ -4221,13 +4264,8 @@ function createDefaultOpenCodeZenFreeModeState() {
|
|
|
4221
4264
|
providerKeys: {}
|
|
4222
4265
|
};
|
|
4223
4266
|
}
|
|
4224
|
-
function
|
|
4225
|
-
|
|
4226
|
-
const freeIds = uniqueIds.filter((id) => id.endsWith("-free") || id === OPENCODE_ZEN_DEFAULT_MODEL);
|
|
4227
|
-
return freeIds.length > 0 ? freeIds : OPENCODE_ZEN_FALLBACK_FREE_MODELS;
|
|
4228
|
-
}
|
|
4229
|
-
function shouldCreateDefaultFreeModeStateForMissingAuth(current, hasUsableCodexAuth) {
|
|
4230
|
-
return current == null && !hasUsableCodexAuth;
|
|
4267
|
+
function shouldCreateDefaultFreeModeStateForMissingAuth(current, hasUsableCodexAuth2) {
|
|
4268
|
+
return current == null && !hasUsableCodexAuth2;
|
|
4231
4269
|
}
|
|
4232
4270
|
function getFreeModeEnvVars(state) {
|
|
4233
4271
|
if (!state.enabled) return {};
|
|
@@ -4358,14 +4396,6 @@ function extractTextParts(value) {
|
|
|
4358
4396
|
if (!Array.isArray(value)) return "";
|
|
4359
4397
|
return value.map((part) => part && typeof part === "object" && typeof part.text === "string" ? part.text : "").filter((part) => part.length > 0).join("\n");
|
|
4360
4398
|
}
|
|
4361
|
-
function buildReasoningOutputItem(reasoningContent) {
|
|
4362
|
-
return {
|
|
4363
|
-
type: "reasoning",
|
|
4364
|
-
id: `rs_${Date.now()}`,
|
|
4365
|
-
summary: [{ type: "summary_text", text: reasoningContent }],
|
|
4366
|
-
content: []
|
|
4367
|
-
};
|
|
4368
|
-
}
|
|
4369
4399
|
function responsesInputToMessages(input, instructions) {
|
|
4370
4400
|
const messages = [];
|
|
4371
4401
|
let pendingReasoningContent = "";
|
|
@@ -4487,7 +4517,12 @@ function chatCompletionToResponsesFormat(chatResponse, model) {
|
|
|
4487
4517
|
});
|
|
4488
4518
|
}
|
|
4489
4519
|
if (message.reasoning_content) {
|
|
4490
|
-
output.push(
|
|
4520
|
+
output.push({
|
|
4521
|
+
type: "reasoning",
|
|
4522
|
+
id: `rs_${Date.now()}`,
|
|
4523
|
+
summary: [],
|
|
4524
|
+
content: [{ type: "reasoning_text", text: message.reasoning_content }]
|
|
4525
|
+
});
|
|
4491
4526
|
}
|
|
4492
4527
|
}
|
|
4493
4528
|
const usage = chatResponse.usage;
|
|
@@ -4553,7 +4588,12 @@ function forwardStreamingTextResponse(upstreamRes, res, model) {
|
|
|
4553
4588
|
const messageItem = { type: "message", role: "assistant", content: [{ type: "output_text", text: fullText }], status: "completed" };
|
|
4554
4589
|
const output = [messageItem];
|
|
4555
4590
|
if (fullReasoningText) {
|
|
4556
|
-
output.push(
|
|
4591
|
+
output.push({
|
|
4592
|
+
type: "reasoning",
|
|
4593
|
+
id: `rs_${Date.now()}`,
|
|
4594
|
+
summary: [],
|
|
4595
|
+
content: [{ type: "reasoning_text", text: fullReasoningText }]
|
|
4596
|
+
});
|
|
4557
4597
|
}
|
|
4558
4598
|
res.write(`data: {"type":"response.output_text.done","output_index":0,"content_index":0,"text":"${escapedFull}"}
|
|
4559
4599
|
|
|
@@ -5192,10 +5232,34 @@ function shellQuote(value) {
|
|
|
5192
5232
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
5193
5233
|
}
|
|
5194
5234
|
|
|
5235
|
+
// src/pathUtils.ts
|
|
5236
|
+
function stripWindowsDevicePathPrefix(value) {
|
|
5237
|
+
const trimmed = value.trim();
|
|
5238
|
+
if (!trimmed) return "";
|
|
5239
|
+
if (trimmed.startsWith("\\\\?\\UNC\\")) {
|
|
5240
|
+
return `\\\\${trimmed.slice("\\\\?\\UNC\\".length)}`;
|
|
5241
|
+
}
|
|
5242
|
+
if (trimmed.startsWith("\\\\?\\")) {
|
|
5243
|
+
return trimmed.slice("\\\\?\\".length);
|
|
5244
|
+
}
|
|
5245
|
+
return trimmed;
|
|
5246
|
+
}
|
|
5247
|
+
function normalizePathForUi(value) {
|
|
5248
|
+
return stripWindowsDevicePathPrefix(value);
|
|
5249
|
+
}
|
|
5250
|
+
function isWindowsLikePath(value) {
|
|
5251
|
+
return /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\");
|
|
5252
|
+
}
|
|
5253
|
+
function isAbsoluteLikePath(value) {
|
|
5254
|
+
const normalized = normalizePathForUi(value);
|
|
5255
|
+
return normalized.startsWith("/") || isWindowsLikePath(normalized);
|
|
5256
|
+
}
|
|
5257
|
+
|
|
5195
5258
|
// src/server/codexAppServerBridge.ts
|
|
5196
5259
|
var COMPOSIO_CONNECTORS_PAGE_LIMIT_MAX = 1e3;
|
|
5197
5260
|
var PROVIDER_MODELS_FETCH_TIMEOUT_MS = 5e3;
|
|
5198
5261
|
var THREAD_RESPONSE_TURN_LIMIT = 10;
|
|
5262
|
+
var THREAD_TURN_PAGE_READ_CACHE_TTL_MS = 3e4;
|
|
5199
5263
|
var THREAD_METHODS_WITH_TURNS = /* @__PURE__ */ new Set(["thread/read", "thread/resume", "thread/fork", "thread/rollback"]);
|
|
5200
5264
|
var THREAD_SEARCH_FULL_TEXT_THREAD_LIMIT = 100;
|
|
5201
5265
|
var PROJECTLESS_THREAD_DIRECTORY_MAX_ATTEMPTS = 100;
|
|
@@ -5698,11 +5762,13 @@ function trimThreadTurnsInRpcResult(method, result) {
|
|
|
5698
5762
|
const thread = asRecord5(record?.thread);
|
|
5699
5763
|
const turns = Array.isArray(thread?.turns) ? thread.turns : null;
|
|
5700
5764
|
if (!record || !thread || !turns || turns.length <= THREAD_RESPONSE_TURN_LIMIT) return result;
|
|
5765
|
+
const startTurnIndex = Math.max(0, turns.length - THREAD_RESPONSE_TURN_LIMIT);
|
|
5701
5766
|
return {
|
|
5702
5767
|
...record,
|
|
5768
|
+
threadTurnStartIndex: startTurnIndex,
|
|
5703
5769
|
thread: {
|
|
5704
5770
|
...thread,
|
|
5705
|
-
turns: turns.slice(
|
|
5771
|
+
turns: turns.slice(startTurnIndex)
|
|
5706
5772
|
}
|
|
5707
5773
|
};
|
|
5708
5774
|
}
|
|
@@ -5720,6 +5786,37 @@ function getErrorMessage5(payload, fallback) {
|
|
|
5720
5786
|
}
|
|
5721
5787
|
return fallback;
|
|
5722
5788
|
}
|
|
5789
|
+
function isUnauthenticatedRateLimitError(error) {
|
|
5790
|
+
const message = getErrorMessage5(error, "").toLowerCase();
|
|
5791
|
+
return message.includes("authentication required") && message.includes("rate limits");
|
|
5792
|
+
}
|
|
5793
|
+
var warnedCodexAuthReadFailures = /* @__PURE__ */ new Set();
|
|
5794
|
+
function getErrorCode(error) {
|
|
5795
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code ?? "") : null;
|
|
5796
|
+
}
|
|
5797
|
+
function getCodexAuthReadErrorMessage(error) {
|
|
5798
|
+
return error instanceof Error && error.message.trim().length > 0 ? error.message : String(error);
|
|
5799
|
+
}
|
|
5800
|
+
function warnCodexAuthReadFailure(authPath, error) {
|
|
5801
|
+
const message = getCodexAuthReadErrorMessage(error);
|
|
5802
|
+
const warningKey = `${authPath}:${message}`;
|
|
5803
|
+
if (warnedCodexAuthReadFailures.has(warningKey)) return;
|
|
5804
|
+
warnedCodexAuthReadFailures.add(warningKey);
|
|
5805
|
+
console.warn("[codex-auth] Unable to read Codex auth state", { path: authPath, error: message });
|
|
5806
|
+
}
|
|
5807
|
+
async function hasUsableCodexAuth() {
|
|
5808
|
+
const authPath = getCodexAuthPath();
|
|
5809
|
+
try {
|
|
5810
|
+
const raw = await readFile3(authPath, "utf8");
|
|
5811
|
+
const auth = JSON.parse(raw);
|
|
5812
|
+
return Boolean(auth.tokens?.access_token?.trim() || auth.tokens?.refresh_token?.trim());
|
|
5813
|
+
} catch (error) {
|
|
5814
|
+
if (getErrorCode(error) !== "ENOENT") {
|
|
5815
|
+
warnCodexAuthReadFailure(authPath, error);
|
|
5816
|
+
}
|
|
5817
|
+
return false;
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5723
5820
|
function setJson4(res, statusCode, payload) {
|
|
5724
5821
|
res.statusCode = statusCode;
|
|
5725
5822
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
@@ -5770,6 +5867,56 @@ async function createProjectlessThreadDirectory(prompt) {
|
|
|
5770
5867
|
}
|
|
5771
5868
|
throw new Error("Unable to create a unique new chat folder");
|
|
5772
5869
|
}
|
|
5870
|
+
function normalizeGithubCloneUrl(rawUrl) {
|
|
5871
|
+
const trimmedUrl = rawUrl.trim();
|
|
5872
|
+
if (!trimmedUrl) throw new Error("Missing GitHub repository URL");
|
|
5873
|
+
const sshMatch = trimmedUrl.match(/^git@github\.com:([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/u);
|
|
5874
|
+
if (sshMatch) {
|
|
5875
|
+
const repoName2 = sshMatch[2];
|
|
5876
|
+
return { url: `git@github.com:${sshMatch[1]}/${repoName2}.git`, repoName: repoName2 };
|
|
5877
|
+
}
|
|
5878
|
+
let parsed;
|
|
5879
|
+
try {
|
|
5880
|
+
parsed = new URL(trimmedUrl);
|
|
5881
|
+
} catch {
|
|
5882
|
+
throw new Error("Enter a valid GitHub repository URL");
|
|
5883
|
+
}
|
|
5884
|
+
if (parsed.hostname.toLowerCase() !== "github.com") {
|
|
5885
|
+
throw new Error("Only github.com repository URLs are supported");
|
|
5886
|
+
}
|
|
5887
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
5888
|
+
if (segments.length < 2) {
|
|
5889
|
+
throw new Error("Enter a GitHub repository URL with owner and repository name");
|
|
5890
|
+
}
|
|
5891
|
+
const owner = segments[0];
|
|
5892
|
+
const repoName = segments[1].replace(/\.git$/iu, "");
|
|
5893
|
+
if (!/^[A-Za-z0-9_.-]+$/u.test(owner) || !/^[A-Za-z0-9_.-]+$/u.test(repoName)) {
|
|
5894
|
+
throw new Error("GitHub repository owner or name contains unsupported characters");
|
|
5895
|
+
}
|
|
5896
|
+
return { url: `https://github.com/${owner}/${repoName}.git`, repoName };
|
|
5897
|
+
}
|
|
5898
|
+
async function cloneGithubRepositoryIntoBase(rawUrl, rawBasePath) {
|
|
5899
|
+
const basePath = rawBasePath.trim();
|
|
5900
|
+
if (!basePath) throw new Error("Missing clone destination folder");
|
|
5901
|
+
const normalizedBasePath = isAbsolute2(basePath) ? basePath : resolve2(basePath);
|
|
5902
|
+
await ensureRealDirectory(normalizedBasePath, "Clone destination folder");
|
|
5903
|
+
const { url, repoName } = normalizeGithubCloneUrl(rawUrl);
|
|
5904
|
+
const targetPath = join6(normalizedBasePath, repoName);
|
|
5905
|
+
try {
|
|
5906
|
+
await stat4(targetPath);
|
|
5907
|
+
throw new Error(`Destination already exists: ${targetPath}`);
|
|
5908
|
+
} catch (error) {
|
|
5909
|
+
if (error?.code !== "ENOENT") throw error;
|
|
5910
|
+
}
|
|
5911
|
+
try {
|
|
5912
|
+
await runCommand3("git", ["clone", url, targetPath], { cwd: normalizedBasePath, timeoutMs: 5 * 6e4 });
|
|
5913
|
+
} catch (error) {
|
|
5914
|
+
await rm4(targetPath, { recursive: true, force: true }).catch(() => void 0);
|
|
5915
|
+
throw error;
|
|
5916
|
+
}
|
|
5917
|
+
await persistWorkspaceRoot(targetPath, "");
|
|
5918
|
+
return targetPath;
|
|
5919
|
+
}
|
|
5773
5920
|
function normalizeHeaderValue(value) {
|
|
5774
5921
|
if (typeof value === "string") {
|
|
5775
5922
|
const trimmed = value.trim();
|
|
@@ -7179,14 +7326,33 @@ async function runCommand3(command, args, options = {}) {
|
|
|
7179
7326
|
});
|
|
7180
7327
|
let stdout = "";
|
|
7181
7328
|
let stderr = "";
|
|
7329
|
+
let timedOut = false;
|
|
7330
|
+
let closed = false;
|
|
7331
|
+
const timeout = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? setTimeout(() => {
|
|
7332
|
+
timedOut = true;
|
|
7333
|
+
proc.kill("SIGTERM");
|
|
7334
|
+
setTimeout(() => {
|
|
7335
|
+
if (!closed) proc.kill("SIGKILL");
|
|
7336
|
+
}, 5e3).unref();
|
|
7337
|
+
}, options.timeoutMs) : null;
|
|
7338
|
+
timeout?.unref();
|
|
7182
7339
|
proc.stdout.on("data", (chunk) => {
|
|
7183
7340
|
stdout += chunk.toString();
|
|
7184
7341
|
});
|
|
7185
7342
|
proc.stderr.on("data", (chunk) => {
|
|
7186
7343
|
stderr += chunk.toString();
|
|
7187
7344
|
});
|
|
7188
|
-
proc.on("error",
|
|
7345
|
+
proc.on("error", (error) => {
|
|
7346
|
+
if (timeout) clearTimeout(timeout);
|
|
7347
|
+
reject(error);
|
|
7348
|
+
});
|
|
7189
7349
|
proc.on("close", (code) => {
|
|
7350
|
+
closed = true;
|
|
7351
|
+
if (timeout) clearTimeout(timeout);
|
|
7352
|
+
if (timedOut) {
|
|
7353
|
+
reject(new Error(`Command timed out after ${options.timeoutMs}ms (${command} ${args.join(" ")})`));
|
|
7354
|
+
return;
|
|
7355
|
+
}
|
|
7190
7356
|
if (code === 0) {
|
|
7191
7357
|
resolve4();
|
|
7192
7358
|
return;
|
|
@@ -7562,23 +7728,70 @@ function readTomlString(value) {
|
|
|
7562
7728
|
function serializeTomlString(value) {
|
|
7563
7729
|
return JSON.stringify(value);
|
|
7564
7730
|
}
|
|
7731
|
+
function parseTomlStringArray(value) {
|
|
7732
|
+
const trimmed = value.trim();
|
|
7733
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return [];
|
|
7734
|
+
try {
|
|
7735
|
+
const parsed = JSON.parse(trimmed);
|
|
7736
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
7737
|
+
} catch {
|
|
7738
|
+
return [];
|
|
7739
|
+
}
|
|
7740
|
+
}
|
|
7741
|
+
function serializeTomlStringArray(values) {
|
|
7742
|
+
return `[${values.map((value) => serializeTomlString(value)).join(", ")}]`;
|
|
7743
|
+
}
|
|
7565
7744
|
function parseAutomationToml(raw) {
|
|
7566
7745
|
const values = {};
|
|
7746
|
+
const extraTomlLines = [];
|
|
7747
|
+
const knownKeys = /* @__PURE__ */ new Set([
|
|
7748
|
+
"version",
|
|
7749
|
+
"id",
|
|
7750
|
+
"kind",
|
|
7751
|
+
"name",
|
|
7752
|
+
"prompt",
|
|
7753
|
+
"status",
|
|
7754
|
+
"rrule",
|
|
7755
|
+
"target_thread_id",
|
|
7756
|
+
"cwds",
|
|
7757
|
+
"created_at",
|
|
7758
|
+
"updated_at"
|
|
7759
|
+
]);
|
|
7760
|
+
let isInsideExtraTable = false;
|
|
7567
7761
|
for (const line of raw.split(/\r?\n/u)) {
|
|
7568
7762
|
const trimmed = line.trim();
|
|
7569
|
-
if (!trimmed || trimmed.startsWith("#")
|
|
7763
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
7764
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
7765
|
+
isInsideExtraTable = true;
|
|
7766
|
+
extraTomlLines.push(trimmed);
|
|
7767
|
+
continue;
|
|
7768
|
+
}
|
|
7769
|
+
if (isInsideExtraTable) {
|
|
7770
|
+
extraTomlLines.push(trimmed);
|
|
7771
|
+
continue;
|
|
7772
|
+
}
|
|
7773
|
+
if (!trimmed.includes("=")) {
|
|
7774
|
+
extraTomlLines.push(trimmed);
|
|
7775
|
+
continue;
|
|
7776
|
+
}
|
|
7570
7777
|
const separatorIndex = trimmed.indexOf("=");
|
|
7571
7778
|
const key = trimmed.slice(0, separatorIndex).trim();
|
|
7572
7779
|
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
7573
|
-
if (key)
|
|
7780
|
+
if (!key) continue;
|
|
7781
|
+
if (knownKeys.has(key)) {
|
|
7782
|
+
values[key] = value;
|
|
7783
|
+
} else {
|
|
7784
|
+
extraTomlLines.push(trimmed);
|
|
7785
|
+
}
|
|
7574
7786
|
}
|
|
7575
7787
|
const id = readTomlString(values.id ?? "");
|
|
7576
|
-
const kindValue = readTomlString(values.kind ?? "heartbeat");
|
|
7788
|
+
const kindValue = readTomlString(values.kind ?? (values.cwds ? "cron" : "heartbeat"));
|
|
7577
7789
|
const name = readTomlString(values.name ?? "");
|
|
7578
7790
|
const prompt = readTomlString(values.prompt ?? "");
|
|
7579
7791
|
const rrule = readTomlString(values.rrule ?? "");
|
|
7580
7792
|
const statusValue = readTomlString(values.status ?? "ACTIVE");
|
|
7581
7793
|
const targetThreadId = readTomlString(values.target_thread_id ?? "") || null;
|
|
7794
|
+
const cwds = parseTomlStringArray(values.cwds ?? "");
|
|
7582
7795
|
const createdAtMs = Number.parseInt(values.created_at ?? "", 10);
|
|
7583
7796
|
const updatedAtMs = Number.parseInt(values.updated_at ?? "", 10);
|
|
7584
7797
|
if (!id || !name || !prompt || !rrule) return null;
|
|
@@ -7592,6 +7805,8 @@ function parseAutomationToml(raw) {
|
|
|
7592
7805
|
rrule,
|
|
7593
7806
|
status: statusValue,
|
|
7594
7807
|
targetThreadId,
|
|
7808
|
+
cwds,
|
|
7809
|
+
extraTomlLines,
|
|
7595
7810
|
createdAtMs: Number.isFinite(createdAtMs) ? createdAtMs : null,
|
|
7596
7811
|
updatedAtMs: Number.isFinite(updatedAtMs) ? updatedAtMs : null,
|
|
7597
7812
|
nextRunAtMs: null
|
|
@@ -7605,11 +7820,19 @@ function serializeAutomationToml(record) {
|
|
|
7605
7820
|
`name = ${serializeTomlString(record.name)}`,
|
|
7606
7821
|
`prompt = ${serializeTomlString(record.prompt)}`,
|
|
7607
7822
|
`status = ${serializeTomlString(record.status)}`,
|
|
7608
|
-
`rrule = ${serializeTomlString(record.rrule)}
|
|
7609
|
-
|
|
7823
|
+
`rrule = ${serializeTomlString(record.rrule)}`
|
|
7824
|
+
];
|
|
7825
|
+
if (record.targetThreadId) {
|
|
7826
|
+
lines.push(`target_thread_id = ${serializeTomlString(record.targetThreadId)}`);
|
|
7827
|
+
}
|
|
7828
|
+
if (record.cwds.length > 0) {
|
|
7829
|
+
lines.push(`cwds = ${serializeTomlStringArray(record.cwds)}`);
|
|
7830
|
+
}
|
|
7831
|
+
lines.push(
|
|
7610
7832
|
`created_at = ${String(record.createdAtMs ?? Date.now())}`,
|
|
7611
7833
|
`updated_at = ${String(record.updatedAtMs ?? Date.now())}`
|
|
7612
|
-
|
|
7834
|
+
);
|
|
7835
|
+
lines.push(...record.extraTomlLines);
|
|
7613
7836
|
return `${lines.join("\n")}
|
|
7614
7837
|
`;
|
|
7615
7838
|
}
|
|
@@ -7693,6 +7916,8 @@ async function writeThreadHeartbeatAutomation(input) {
|
|
|
7693
7916
|
rrule,
|
|
7694
7917
|
status: input.status,
|
|
7695
7918
|
targetThreadId: threadId,
|
|
7919
|
+
cwds: [],
|
|
7920
|
+
extraTomlLines: existing?.extraTomlLines ?? [],
|
|
7696
7921
|
createdAtMs: existing?.createdAtMs ?? now,
|
|
7697
7922
|
updatedAtMs: now,
|
|
7698
7923
|
nextRunAtMs: null
|
|
@@ -7721,6 +7946,114 @@ async function deleteThreadHeartbeatAutomation(threadId, automationId = "") {
|
|
|
7721
7946
|
await Promise.all(automations.map((automation) => rm4(join6(getCodexAutomationsDir(), automation.id), { recursive: true, force: true })));
|
|
7722
7947
|
return true;
|
|
7723
7948
|
}
|
|
7949
|
+
async function listProjectCronAutomations() {
|
|
7950
|
+
const automationRoot = getCodexAutomationsDir();
|
|
7951
|
+
const next = {};
|
|
7952
|
+
let entries;
|
|
7953
|
+
try {
|
|
7954
|
+
entries = await readdir2(automationRoot, { withFileTypes: true });
|
|
7955
|
+
} catch {
|
|
7956
|
+
return next;
|
|
7957
|
+
}
|
|
7958
|
+
for (const entry of entries) {
|
|
7959
|
+
if (!entry.isDirectory()) continue;
|
|
7960
|
+
const automation = await readAutomationRecordFromFile(join6(automationRoot, entry.name, "automation.toml"));
|
|
7961
|
+
if (!automation || automation.kind !== "cron" || automation.cwds.length === 0) continue;
|
|
7962
|
+
for (const cwd of automation.cwds) {
|
|
7963
|
+
next[cwd] = [...next[cwd] ?? [], automation];
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
for (const automations of Object.values(next)) {
|
|
7967
|
+
automations.sort((first, second) => {
|
|
7968
|
+
const firstCreatedAt = first.createdAtMs ?? 0;
|
|
7969
|
+
const secondCreatedAt = second.createdAtMs ?? 0;
|
|
7970
|
+
if (firstCreatedAt !== secondCreatedAt) return firstCreatedAt - secondCreatedAt;
|
|
7971
|
+
return first.id.localeCompare(second.id);
|
|
7972
|
+
});
|
|
7973
|
+
}
|
|
7974
|
+
return next;
|
|
7975
|
+
}
|
|
7976
|
+
async function readProjectCronAutomations(projectName) {
|
|
7977
|
+
const all = await listProjectCronAutomations();
|
|
7978
|
+
return all[projectName] ?? [];
|
|
7979
|
+
}
|
|
7980
|
+
async function readProjectCronAutomation(projectName, automationId = "") {
|
|
7981
|
+
const automations = await readProjectCronAutomations(projectName);
|
|
7982
|
+
if (automationId) return automations.find((automation) => automation.id === automationId) ?? null;
|
|
7983
|
+
return automations[0] ?? null;
|
|
7984
|
+
}
|
|
7985
|
+
async function writeProjectCronAutomation(input) {
|
|
7986
|
+
const projectName = input.projectName.trim();
|
|
7987
|
+
const name = input.name.trim();
|
|
7988
|
+
const prompt = input.prompt.trim();
|
|
7989
|
+
const rrule = input.rrule.trim();
|
|
7990
|
+
if (!projectName || !name || !prompt || !rrule) {
|
|
7991
|
+
throw new Error("projectName, name, prompt, and rrule are required");
|
|
7992
|
+
}
|
|
7993
|
+
if (!isAbsoluteLikePath(projectName)) {
|
|
7994
|
+
throw new Error("Project automation cwd must be an absolute path");
|
|
7995
|
+
}
|
|
7996
|
+
const automationRoot = getCodexAutomationsDir();
|
|
7997
|
+
await mkdir4(automationRoot, { recursive: true });
|
|
7998
|
+
const existing = input.id ? await readProjectCronAutomation(projectName, input.id.trim()) : null;
|
|
7999
|
+
const entries = await readdir2(automationRoot, { withFileTypes: true }).catch(() => []);
|
|
8000
|
+
const existingIds = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
|
|
8001
|
+
const id = existing?.id ?? resolveUniqueAutomationId(existingIds, projectName, name);
|
|
8002
|
+
const automationDir = join6(automationRoot, id);
|
|
8003
|
+
const now = Date.now();
|
|
8004
|
+
const record = {
|
|
8005
|
+
id,
|
|
8006
|
+
kind: "cron",
|
|
8007
|
+
name,
|
|
8008
|
+
prompt,
|
|
8009
|
+
rrule,
|
|
8010
|
+
status: input.status,
|
|
8011
|
+
targetThreadId: null,
|
|
8012
|
+
cwds: Array.from(/* @__PURE__ */ new Set([...existing?.cwds ?? [], projectName])),
|
|
8013
|
+
extraTomlLines: existing?.extraTomlLines ?? [],
|
|
8014
|
+
createdAtMs: existing?.createdAtMs ?? now,
|
|
8015
|
+
updatedAtMs: now,
|
|
8016
|
+
nextRunAtMs: null
|
|
8017
|
+
};
|
|
8018
|
+
await mkdir4(automationDir, { recursive: true });
|
|
8019
|
+
await writeFile4(join6(automationDir, "automation.toml"), serializeAutomationToml(record), "utf8");
|
|
8020
|
+
const memoryPath = join6(automationDir, "memory.md");
|
|
8021
|
+
try {
|
|
8022
|
+
await stat4(memoryPath);
|
|
8023
|
+
} catch {
|
|
8024
|
+
await writeFile4(memoryPath, "", "utf8");
|
|
8025
|
+
}
|
|
8026
|
+
return record;
|
|
8027
|
+
}
|
|
8028
|
+
async function deleteProjectCronAutomation(projectName, automationId = "") {
|
|
8029
|
+
const normalizedProjectName = projectName.trim();
|
|
8030
|
+
const normalizedAutomationId = automationId.trim();
|
|
8031
|
+
if (!normalizedProjectName || !isAbsoluteLikePath(normalizedProjectName)) return false;
|
|
8032
|
+
if (normalizedAutomationId) {
|
|
8033
|
+
const automation = await readProjectCronAutomation(normalizedProjectName, normalizedAutomationId);
|
|
8034
|
+
if (!automation) return false;
|
|
8035
|
+
const remainingCwds = automation.cwds.filter((cwd) => cwd !== normalizedProjectName);
|
|
8036
|
+
if (remainingCwds.length > 0) {
|
|
8037
|
+
const record = { ...automation, cwds: remainingCwds, updatedAtMs: Date.now() };
|
|
8038
|
+
await writeFile4(join6(getCodexAutomationsDir(), automation.id, "automation.toml"), serializeAutomationToml(record), "utf8");
|
|
8039
|
+
} else {
|
|
8040
|
+
await rm4(join6(getCodexAutomationsDir(), automation.id), { recursive: true, force: true });
|
|
8041
|
+
}
|
|
8042
|
+
return true;
|
|
8043
|
+
}
|
|
8044
|
+
const automations = await readProjectCronAutomations(normalizedProjectName);
|
|
8045
|
+
if (automations.length === 0) return false;
|
|
8046
|
+
await Promise.all(automations.map(async (automation) => {
|
|
8047
|
+
const remainingCwds = automation.cwds.filter((cwd) => cwd !== normalizedProjectName);
|
|
8048
|
+
if (remainingCwds.length > 0) {
|
|
8049
|
+
const record = { ...automation, cwds: remainingCwds, updatedAtMs: Date.now() };
|
|
8050
|
+
await writeFile4(join6(getCodexAutomationsDir(), automation.id, "automation.toml"), serializeAutomationToml(record), "utf8");
|
|
8051
|
+
return;
|
|
8052
|
+
}
|
|
8053
|
+
await rm4(join6(getCodexAutomationsDir(), automation.id), { recursive: true, force: true });
|
|
8054
|
+
}));
|
|
8055
|
+
return true;
|
|
8056
|
+
}
|
|
7724
8057
|
var MAX_THREAD_TITLES = 500;
|
|
7725
8058
|
var EMPTY_THREAD_TITLE_CACHE = { titles: {}, order: [] };
|
|
7726
8059
|
var PINNED_THREAD_IDS_KEY = "pinned-thread-ids";
|
|
@@ -8463,6 +8796,8 @@ var AppServerProcess = class {
|
|
|
8463
8796
|
this.appServerArgs = buildAppServerArgs();
|
|
8464
8797
|
this.streamEventsByThreadId = /* @__PURE__ */ new Map();
|
|
8465
8798
|
this.lastThreadReadSnapshotByThreadId = /* @__PURE__ */ new Map();
|
|
8799
|
+
this.threadTurnPageReadCacheByThreadId = /* @__PURE__ */ new Map();
|
|
8800
|
+
this.threadTurnPageReadPromiseByThreadId = /* @__PURE__ */ new Map();
|
|
8466
8801
|
this.capturedItemsByThreadId = /* @__PURE__ */ new Map();
|
|
8467
8802
|
this.liveStateCache = /* @__PURE__ */ new Map();
|
|
8468
8803
|
this.chatgptAuthRefreshPromise = null;
|
|
@@ -8575,7 +8910,10 @@ var AppServerProcess = class {
|
|
|
8575
8910
|
this.recordStreamEvent(notification);
|
|
8576
8911
|
this.captureItemFromNotification(notification);
|
|
8577
8912
|
const nThreadId = this.extractThreadIdFromParams(notification.params);
|
|
8578
|
-
if (nThreadId)
|
|
8913
|
+
if (nThreadId) {
|
|
8914
|
+
this.invalidateLiveStateCache(nThreadId);
|
|
8915
|
+
this.threadTurnPageReadCacheByThreadId.delete(nThreadId);
|
|
8916
|
+
}
|
|
8579
8917
|
for (const listener of this.notificationListeners) {
|
|
8580
8918
|
listener(notification);
|
|
8581
8919
|
}
|
|
@@ -8619,10 +8957,33 @@ var AppServerProcess = class {
|
|
|
8619
8957
|
}
|
|
8620
8958
|
storeThreadReadSnapshot(threadId, snapshot) {
|
|
8621
8959
|
this.lastThreadReadSnapshotByThreadId.set(threadId, snapshot);
|
|
8960
|
+
this.threadTurnPageReadCacheByThreadId.delete(threadId);
|
|
8622
8961
|
}
|
|
8623
8962
|
getLastThreadReadSnapshot(threadId) {
|
|
8624
8963
|
return this.lastThreadReadSnapshotByThreadId.get(threadId) ?? null;
|
|
8625
8964
|
}
|
|
8965
|
+
async readThreadForTurnPage(threadId) {
|
|
8966
|
+
const now = Date.now();
|
|
8967
|
+
const cached = this.threadTurnPageReadCacheByThreadId.get(threadId);
|
|
8968
|
+
if (cached && cached.expiresAt > now) return cached.result;
|
|
8969
|
+
if (cached) this.threadTurnPageReadCacheByThreadId.delete(threadId);
|
|
8970
|
+
const pending = this.threadTurnPageReadPromiseByThreadId.get(threadId);
|
|
8971
|
+
if (pending) return pending;
|
|
8972
|
+
const promise = this.rpc("thread/read", {
|
|
8973
|
+
threadId,
|
|
8974
|
+
includeTurns: true
|
|
8975
|
+
}).then((result) => {
|
|
8976
|
+
this.threadTurnPageReadCacheByThreadId.set(threadId, {
|
|
8977
|
+
result,
|
|
8978
|
+
expiresAt: Date.now() + THREAD_TURN_PAGE_READ_CACHE_TTL_MS
|
|
8979
|
+
});
|
|
8980
|
+
return result;
|
|
8981
|
+
}).finally(() => {
|
|
8982
|
+
this.threadTurnPageReadPromiseByThreadId.delete(threadId);
|
|
8983
|
+
});
|
|
8984
|
+
this.threadTurnPageReadPromiseByThreadId.set(threadId, promise);
|
|
8985
|
+
return promise;
|
|
8986
|
+
}
|
|
8626
8987
|
cacheLiveState(threadId, data, turnCount, sessionSize) {
|
|
8627
8988
|
this.liveStateCache.set(threadId, { data, turnCount, sessionSize });
|
|
8628
8989
|
}
|
|
@@ -9291,11 +9652,10 @@ async function buildThreadSearchIndex(appServer) {
|
|
|
9291
9652
|
const docsById = new Map(docs.map((doc) => [doc.id, doc]));
|
|
9292
9653
|
return { docsById };
|
|
9293
9654
|
}
|
|
9294
|
-
function createCodexBridgeMiddleware(
|
|
9655
|
+
function createCodexBridgeMiddleware() {
|
|
9295
9656
|
const { appServer, terminalManager, methodCatalog, telegramBridge, backendQueueProcessor } = getSharedBridgeState();
|
|
9296
9657
|
let threadSearchIndex = null;
|
|
9297
9658
|
let threadSearchIndexPromise = null;
|
|
9298
|
-
let backgroundServicesStarted = false;
|
|
9299
9659
|
async function getThreadSearchIndex() {
|
|
9300
9660
|
if (threadSearchIndex) return threadSearchIndex;
|
|
9301
9661
|
if (!threadSearchIndexPromise) {
|
|
@@ -9308,21 +9668,14 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
9308
9668
|
}
|
|
9309
9669
|
return threadSearchIndexPromise;
|
|
9310
9670
|
}
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
telegramBridge.start();
|
|
9320
|
-
}).catch(() => {
|
|
9321
|
-
});
|
|
9322
|
-
}
|
|
9323
|
-
if (options.startBackgroundServices !== false) {
|
|
9324
|
-
startBackgroundServices();
|
|
9325
|
-
}
|
|
9671
|
+
void initializeSkillsSyncOnStartup(appServer);
|
|
9672
|
+
void readTelegramBridgeConfig().then((config) => {
|
|
9673
|
+
if (!config.botToken) return;
|
|
9674
|
+
telegramBridge.configureToken(config.botToken);
|
|
9675
|
+
telegramBridge.configureAllowedUserIds(config.allowedUserIds);
|
|
9676
|
+
telegramBridge.start();
|
|
9677
|
+
}).catch(() => {
|
|
9678
|
+
});
|
|
9326
9679
|
const middleware = async (req, res, next) => {
|
|
9327
9680
|
const requestStartNs = process.hrtime.bigint();
|
|
9328
9681
|
const rawUrl = req.url ?? "";
|
|
@@ -9413,11 +9766,7 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
9413
9766
|
}
|
|
9414
9767
|
if (url.pathname.startsWith("/codex-api/free-mode")) {
|
|
9415
9768
|
let readFreeModeState2 = function() {
|
|
9416
|
-
|
|
9417
|
-
if (state.provider === "opencode-zen" && !state.model?.trim()) {
|
|
9418
|
-
return { ...state, model: OPENCODE_ZEN_DEFAULT_MODEL };
|
|
9419
|
-
}
|
|
9420
|
-
return state;
|
|
9769
|
+
return ensureDefaultFreeModeStateForMissingAuthSync(statePath) ?? { enabled: false, apiKey: null, model: FREE_MODE_DEFAULT_MODEL };
|
|
9421
9770
|
};
|
|
9422
9771
|
var readFreeModeState = readFreeModeState2;
|
|
9423
9772
|
const statePath = join6(getCodexHomeDir3(), FREE_MODE_STATE_FILE);
|
|
@@ -9569,7 +9918,7 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
9569
9918
|
if (resolvedKey) {
|
|
9570
9919
|
prevKeys[providerType] = resolvedKey;
|
|
9571
9920
|
}
|
|
9572
|
-
const resolvedModel = providerType === "openrouter" ? current.model || FREE_MODE_DEFAULT_MODEL : providerType === "custom" ? await fetchCustomEndpointDefaultModel(baseUrl, resolvedKey) :
|
|
9921
|
+
const resolvedModel = providerType === "openrouter" ? current.model || FREE_MODE_DEFAULT_MODEL : providerType === "custom" ? await fetchCustomEndpointDefaultModel(baseUrl, resolvedKey) : "";
|
|
9573
9922
|
const state = {
|
|
9574
9923
|
enabled: true,
|
|
9575
9924
|
apiKey: resolvedKey,
|
|
@@ -9714,7 +10063,20 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
9714
10063
|
setJson4(res, 400, { error: "Invalid body: expected { method, params? }" });
|
|
9715
10064
|
return;
|
|
9716
10065
|
}
|
|
9717
|
-
|
|
10066
|
+
if (body.method === "account/rateLimits/read" && !await hasUsableCodexAuth()) {
|
|
10067
|
+
setJson4(res, 200, { result: null });
|
|
10068
|
+
return;
|
|
10069
|
+
}
|
|
10070
|
+
let rpcResult;
|
|
10071
|
+
try {
|
|
10072
|
+
rpcResult = await callRpcWithArchiveRecovery(appServer, body.method, body.params ?? null);
|
|
10073
|
+
} catch (error) {
|
|
10074
|
+
if (body.method === "account/rateLimits/read" && isUnauthenticatedRateLimitError(error)) {
|
|
10075
|
+
setJson4(res, 200, { result: null });
|
|
10076
|
+
return;
|
|
10077
|
+
}
|
|
10078
|
+
throw error;
|
|
10079
|
+
}
|
|
9718
10080
|
const trimmedResult = trimThreadTurnsInRpcResult(body.method, rpcResult);
|
|
9719
10081
|
const sanitizedResult = await sanitizeThreadTurnsInlinePayloads(body.method, trimmedResult);
|
|
9720
10082
|
const result = THREAD_METHODS_WITH_TURNS.has(body.method) ? await mergeSessionSkillInputsIntoThreadResult(sanitizedResult) : sanitizedResult;
|
|
@@ -9729,6 +10091,61 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
9729
10091
|
setJson4(res, 200, { result });
|
|
9730
10092
|
return;
|
|
9731
10093
|
}
|
|
10094
|
+
if (req.method === "GET" && url.pathname === "/codex-api/thread-turn-page") {
|
|
10095
|
+
try {
|
|
10096
|
+
const threadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
10097
|
+
const beforeTurnId = url.searchParams.get("beforeTurnId")?.trim() ?? "";
|
|
10098
|
+
const limitRaw = url.searchParams.get("limit")?.trim() ?? String(THREAD_RESPONSE_TURN_LIMIT);
|
|
10099
|
+
const limit = Math.max(1, Math.min(50, Number.parseInt(limitRaw, 10) || THREAD_RESPONSE_TURN_LIMIT));
|
|
10100
|
+
if (!threadId) {
|
|
10101
|
+
setJson4(res, 400, { error: "Missing threadId" });
|
|
10102
|
+
return;
|
|
10103
|
+
}
|
|
10104
|
+
const threadReadResult = await appServer.readThreadForTurnPage(threadId);
|
|
10105
|
+
const record = asRecord5(threadReadResult);
|
|
10106
|
+
const thread = asRecord5(record?.thread);
|
|
10107
|
+
if (!record || !thread) {
|
|
10108
|
+
setJson4(res, 502, { error: "thread/read returned an invalid thread response" });
|
|
10109
|
+
return;
|
|
10110
|
+
}
|
|
10111
|
+
const turns = Array.isArray(thread.turns) ? thread.turns : [];
|
|
10112
|
+
const beforeIndex = beforeTurnId ? turns.findIndex((turn) => asRecord5(turn)?.id === beforeTurnId) : turns.length;
|
|
10113
|
+
if (beforeTurnId && beforeIndex < 0) {
|
|
10114
|
+
setJson4(res, 200, {
|
|
10115
|
+
result: {
|
|
10116
|
+
...record,
|
|
10117
|
+
thread: {
|
|
10118
|
+
...thread,
|
|
10119
|
+
turns: []
|
|
10120
|
+
}
|
|
10121
|
+
},
|
|
10122
|
+
startTurnIndex: 0,
|
|
10123
|
+
hasMoreOlder: false
|
|
10124
|
+
});
|
|
10125
|
+
return;
|
|
10126
|
+
}
|
|
10127
|
+
const endIndex = beforeIndex;
|
|
10128
|
+
const startIndex = Math.max(0, endIndex - limit);
|
|
10129
|
+
const pageTurns = turns.slice(startIndex, endIndex);
|
|
10130
|
+
const pagedResult = {
|
|
10131
|
+
...record,
|
|
10132
|
+
thread: {
|
|
10133
|
+
...thread,
|
|
10134
|
+
turns: pageTurns
|
|
10135
|
+
}
|
|
10136
|
+
};
|
|
10137
|
+
const sanitized = await sanitizeThreadTurnsInlinePayloads("thread/read", pagedResult);
|
|
10138
|
+
const result = await mergeSessionSkillInputsIntoThreadResult(sanitized);
|
|
10139
|
+
setJson4(res, 200, {
|
|
10140
|
+
result,
|
|
10141
|
+
startTurnIndex: startIndex,
|
|
10142
|
+
hasMoreOlder: startIndex > 0
|
|
10143
|
+
});
|
|
10144
|
+
} catch (error) {
|
|
10145
|
+
setJson4(res, 500, { error: getErrorMessage5(error, "Failed to load earlier thread messages") });
|
|
10146
|
+
}
|
|
10147
|
+
return;
|
|
10148
|
+
}
|
|
9732
10149
|
if (req.method === "GET" && url.pathname === "/codex-api/thread-file-change-fallback") {
|
|
9733
10150
|
const threadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
9734
10151
|
if (!threadId) {
|
|
@@ -10018,28 +10435,21 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10018
10435
|
try {
|
|
10019
10436
|
const modelsUrl = "https://opencode.ai/zen/v1/models";
|
|
10020
10437
|
const headers = {};
|
|
10021
|
-
|
|
10022
|
-
if (hasZenApiKey) {
|
|
10438
|
+
if (fmState.apiKey && fmState.apiKey !== "dummy") {
|
|
10023
10439
|
headers["Authorization"] = `Bearer ${fmState.apiKey}`;
|
|
10024
10440
|
}
|
|
10025
10441
|
const resp = await fetch(modelsUrl, { headers, signal: AbortSignal.timeout(8e3) });
|
|
10026
10442
|
if (resp.ok) {
|
|
10027
10443
|
const json = await resp.json();
|
|
10028
10444
|
const allIds = (json.data ?? []).map((m) => m.id).filter(Boolean);
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
source: "opencode-zen"
|
|
10033
|
-
});
|
|
10445
|
+
const freeIds = allIds.filter((id) => id.endsWith("-free") || id === "big-pickle");
|
|
10446
|
+
const paidIds = allIds.filter((id) => !id.endsWith("-free") && id !== "big-pickle");
|
|
10447
|
+
setJson4(res, 200, { data: [...freeIds, ...paidIds], exclusive: true, source: "opencode-zen" });
|
|
10034
10448
|
return;
|
|
10035
10449
|
}
|
|
10036
10450
|
} catch {
|
|
10037
10451
|
}
|
|
10038
|
-
setJson4(res, 200, {
|
|
10039
|
-
data: getOpenCodeZenFreeModelIds([]),
|
|
10040
|
-
exclusive: true,
|
|
10041
|
-
source: "opencode-zen"
|
|
10042
|
-
});
|
|
10452
|
+
setJson4(res, 200, { data: ["big-pickle", "minimax-m2.5-free", "nemotron-3-super-free", "trinity-large-preview-free"], exclusive: true, source: "opencode-zen" });
|
|
10043
10453
|
return;
|
|
10044
10454
|
}
|
|
10045
10455
|
if (fmState.provider === "custom" && fmState.customBaseUrl) {
|
|
@@ -10342,7 +10752,7 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10342
10752
|
if (currentBranch && !branchActivityByName.has(currentBranch)) {
|
|
10343
10753
|
branchActivityByName.set(currentBranch, { timestamp: Number.MAX_SAFE_INTEGER, isRemote: false });
|
|
10344
10754
|
}
|
|
10345
|
-
const
|
|
10755
|
+
const options = Array.from(branchActivityByName.entries()).map(([value, metadata]) => ({
|
|
10346
10756
|
value,
|
|
10347
10757
|
label: value,
|
|
10348
10758
|
isCurrent: value === currentBranch,
|
|
@@ -10356,7 +10766,7 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10356
10766
|
setJson4(res, 200, {
|
|
10357
10767
|
data: {
|
|
10358
10768
|
...state,
|
|
10359
|
-
options
|
|
10769
|
+
options
|
|
10360
10770
|
}
|
|
10361
10771
|
});
|
|
10362
10772
|
} catch (error) {
|
|
@@ -10602,6 +11012,18 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10602
11012
|
setJson4(res, 200, { data: { path: normalizedPath } });
|
|
10603
11013
|
return;
|
|
10604
11014
|
}
|
|
11015
|
+
if (req.method === "POST" && url.pathname === "/codex-api/github-clone") {
|
|
11016
|
+
const payload = asRecord5(await readJsonBody2(req));
|
|
11017
|
+
const repoUrl = typeof payload?.url === "string" ? payload.url.trim() : "";
|
|
11018
|
+
const basePath = typeof payload?.basePath === "string" ? payload.basePath.trim() : "";
|
|
11019
|
+
try {
|
|
11020
|
+
const clonedPath = await cloneGithubRepositoryIntoBase(repoUrl, basePath);
|
|
11021
|
+
setJson4(res, 200, { data: { path: clonedPath } });
|
|
11022
|
+
} catch (error) {
|
|
11023
|
+
setJson4(res, 400, { error: error instanceof Error ? error.message : "Failed to clone GitHub repository" });
|
|
11024
|
+
}
|
|
11025
|
+
return;
|
|
11026
|
+
}
|
|
10605
11027
|
if (req.method === "POST" && url.pathname === "/codex-api/projectless-thread-cwd") {
|
|
10606
11028
|
const payload = asRecord5(await readJsonBody2(req));
|
|
10607
11029
|
const prompt = typeof payload?.prompt === "string" ? payload.prompt : null;
|
|
@@ -10730,6 +11152,11 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10730
11152
|
setJson4(res, 200, { data: automationsByThreadId });
|
|
10731
11153
|
return;
|
|
10732
11154
|
}
|
|
11155
|
+
if (req.method === "GET" && url.pathname === "/codex-api/project-automations") {
|
|
11156
|
+
const automationsByProjectName = await listProjectCronAutomations();
|
|
11157
|
+
setJson4(res, 200, { data: automationsByProjectName });
|
|
11158
|
+
return;
|
|
11159
|
+
}
|
|
10733
11160
|
if (req.method === "GET" && url.pathname === "/codex-api/thread-automation") {
|
|
10734
11161
|
const threadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
10735
11162
|
const automationId = url.searchParams.get("automationId")?.trim() ?? "";
|
|
@@ -10741,6 +11168,17 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10741
11168
|
setJson4(res, 200, { data: automation });
|
|
10742
11169
|
return;
|
|
10743
11170
|
}
|
|
11171
|
+
if (req.method === "GET" && url.pathname === "/codex-api/project-automation") {
|
|
11172
|
+
const projectName = url.searchParams.get("projectName")?.trim() ?? "";
|
|
11173
|
+
const automationId = url.searchParams.get("automationId")?.trim() ?? "";
|
|
11174
|
+
if (!projectName) {
|
|
11175
|
+
setJson4(res, 400, { error: "Missing projectName" });
|
|
11176
|
+
return;
|
|
11177
|
+
}
|
|
11178
|
+
const automation = automationId ? await readProjectCronAutomation(projectName, automationId) : await readProjectCronAutomations(projectName);
|
|
11179
|
+
setJson4(res, 200, { data: automation });
|
|
11180
|
+
return;
|
|
11181
|
+
}
|
|
10744
11182
|
if (req.method === "POST" && url.pathname === "/codex-api/thread-search") {
|
|
10745
11183
|
const payload = asRecord5(await readJsonBody2(req));
|
|
10746
11184
|
const query = typeof payload?.query === "string" ? payload.query.trim() : "";
|
|
@@ -10799,6 +11237,26 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10799
11237
|
setJson4(res, 200, { data: automation });
|
|
10800
11238
|
return;
|
|
10801
11239
|
}
|
|
11240
|
+
if (req.method === "PUT" && url.pathname === "/codex-api/project-automation") {
|
|
11241
|
+
const payload = asRecord5(await readJsonBody2(req));
|
|
11242
|
+
const projectName = typeof payload?.projectName === "string" ? payload.projectName.trim() : "";
|
|
11243
|
+
const id = typeof payload?.id === "string" ? payload.id.trim() : "";
|
|
11244
|
+
const name = typeof payload?.name === "string" ? payload.name.trim() : "";
|
|
11245
|
+
const prompt = typeof payload?.prompt === "string" ? payload.prompt.trim() : "";
|
|
11246
|
+
const rrule = typeof payload?.rrule === "string" ? payload.rrule.trim() : "";
|
|
11247
|
+
const status = payload?.status === "PAUSED" ? "PAUSED" : "ACTIVE";
|
|
11248
|
+
if (!projectName || !name || !prompt || !rrule) {
|
|
11249
|
+
setJson4(res, 400, { error: "projectName, name, prompt, and rrule are required" });
|
|
11250
|
+
return;
|
|
11251
|
+
}
|
|
11252
|
+
if (!isAbsoluteLikePath(projectName)) {
|
|
11253
|
+
setJson4(res, 400, { error: "Project automation cwd must be an absolute path" });
|
|
11254
|
+
return;
|
|
11255
|
+
}
|
|
11256
|
+
const automation = await writeProjectCronAutomation({ projectName, id, name, prompt, rrule, status });
|
|
11257
|
+
setJson4(res, 200, { data: automation });
|
|
11258
|
+
return;
|
|
11259
|
+
}
|
|
10802
11260
|
if (req.method === "POST" && url.pathname === "/codex-api/thread-automation/run") {
|
|
10803
11261
|
const payload = asRecord5(await readJsonBody2(req));
|
|
10804
11262
|
const threadId = typeof payload?.threadId === "string" ? payload.threadId.trim() : "";
|
|
@@ -10828,6 +11286,17 @@ function createCodexBridgeMiddleware(options = {}) {
|
|
|
10828
11286
|
setJson4(res, 200, { data: { removed } });
|
|
10829
11287
|
return;
|
|
10830
11288
|
}
|
|
11289
|
+
if (req.method === "DELETE" && url.pathname === "/codex-api/project-automation") {
|
|
11290
|
+
const projectName = url.searchParams.get("projectName")?.trim() ?? "";
|
|
11291
|
+
const automationId = url.searchParams.get("automationId")?.trim() ?? "";
|
|
11292
|
+
if (!projectName) {
|
|
11293
|
+
setJson4(res, 400, { error: "Missing projectName" });
|
|
11294
|
+
return;
|
|
11295
|
+
}
|
|
11296
|
+
const removed = await deleteProjectCronAutomation(projectName, automationId);
|
|
11297
|
+
setJson4(res, 200, { data: { removed } });
|
|
11298
|
+
return;
|
|
11299
|
+
}
|
|
10831
11300
|
if (req.method === "POST" && url.pathname === "/codex-api/telegram/configure-bot") {
|
|
10832
11301
|
const payload = asRecord5(await readJsonBody2(req));
|
|
10833
11302
|
const botToken = typeof payload?.botToken === "string" ? payload.botToken.trim() : "";
|
|
@@ -10913,7 +11382,6 @@ data: ${JSON.stringify({ ok: true })}
|
|
|
10913
11382
|
backendQueueProcessor.dispose();
|
|
10914
11383
|
appServer.dispose();
|
|
10915
11384
|
};
|
|
10916
|
-
middleware.startBackgroundServices = startBackgroundServices;
|
|
10917
11385
|
middleware.subscribeNotifications = (listener) => {
|
|
10918
11386
|
const unsubscribeAppServer = appServer.onNotification((notification) => {
|
|
10919
11387
|
listener({
|
|
@@ -11615,9 +12083,7 @@ function readWildcardPathParam(value) {
|
|
|
11615
12083
|
}
|
|
11616
12084
|
function createServer(options = {}) {
|
|
11617
12085
|
const app = express();
|
|
11618
|
-
const bridge = createCodexBridgeMiddleware(
|
|
11619
|
-
startBackgroundServices: options.deferBridgeBackgroundServices !== true
|
|
11620
|
-
});
|
|
12086
|
+
const bridge = createCodexBridgeMiddleware();
|
|
11621
12087
|
const authSession = options.password ? createAuthSession(options.password) : null;
|
|
11622
12088
|
if (authSession) {
|
|
11623
12089
|
app.use(authSession.middleware);
|
|
@@ -11763,7 +12229,6 @@ function createServer(options = {}) {
|
|
|
11763
12229
|
return {
|
|
11764
12230
|
app,
|
|
11765
12231
|
dispose: () => bridge.dispose(),
|
|
11766
|
-
startBridgeBackgroundServices: () => bridge.startBackgroundServices(),
|
|
11767
12232
|
attachWebSocket: (server) => {
|
|
11768
12233
|
const wss = new WebSocketServer({ noServer: true });
|
|
11769
12234
|
server.on("upgrade", (req, socket, head) => {
|
|
@@ -12243,15 +12708,10 @@ async function startServer(options) {
|
|
|
12243
12708
|
const passwordResolution = resolvePassword(options.password);
|
|
12244
12709
|
const password = passwordResolution.password;
|
|
12245
12710
|
const generatedPasswordPath = password && passwordResolution.generated ? await persistGeneratedPassword(password) : null;
|
|
12246
|
-
const { app, dispose, attachWebSocket
|
|
12247
|
-
password,
|
|
12248
|
-
deferBridgeBackgroundServices: true
|
|
12249
|
-
});
|
|
12711
|
+
const { app, dispose, attachWebSocket } = createServer({ password });
|
|
12250
12712
|
const server = createServer2(app);
|
|
12251
12713
|
attachWebSocket(server);
|
|
12252
12714
|
const port = await listenWithFallback(server, requestedPort);
|
|
12253
|
-
process.env.CODEXUI_SERVER_PORT = String(port);
|
|
12254
|
-
startBridgeBackgroundServices();
|
|
12255
12715
|
let tunnelChild = null;
|
|
12256
12716
|
let tunnelUrl = null;
|
|
12257
12717
|
if (options.tunnel) {
|