gitxelectron 1.2.2 → 1.2.4
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/out/main/index.js +316 -16
- package/out/preload/index.js +14 -0
- package/out/renderer/assets/index-Bl7RyoCQ.css +1337 -0
- package/out/renderer/assets/{index-DKnAX29A.js → index-Bzibm93j.js} +384 -85
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/out/renderer/assets/index-Y6NTls08.css +0 -193
package/out/main/index.js
CHANGED
|
@@ -576,7 +576,14 @@ function initGit(sessionId, baseDir) {
|
|
|
576
576
|
baseDir,
|
|
577
577
|
binary: "git",
|
|
578
578
|
maxConcurrentProcesses: 6,
|
|
579
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
579
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true },
|
|
580
|
+
progress: ({ method, stage, progress }) => {
|
|
581
|
+
try {
|
|
582
|
+
const wc = electron.webContents.fromId(sessionId);
|
|
583
|
+
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
584
|
+
} catch (e) {
|
|
585
|
+
}
|
|
586
|
+
}
|
|
580
587
|
};
|
|
581
588
|
const git = simpleGit.simpleGit(options).env({
|
|
582
589
|
...process.env,
|
|
@@ -586,11 +593,19 @@ function initGit(sessionId, baseDir) {
|
|
|
586
593
|
});
|
|
587
594
|
gitSessions.set(sessionId, { git, repoPath: baseDir });
|
|
588
595
|
}
|
|
589
|
-
function createSafeGit(baseDir) {
|
|
596
|
+
function createSafeGit(baseDir, sessionId) {
|
|
590
597
|
const options = {
|
|
591
598
|
binary: "git",
|
|
592
599
|
maxConcurrentProcesses: 6,
|
|
593
|
-
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
600
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true },
|
|
601
|
+
progress: ({ method, stage, progress }) => {
|
|
602
|
+
if (!sessionId) return;
|
|
603
|
+
try {
|
|
604
|
+
const wc = electron.webContents.fromId(sessionId);
|
|
605
|
+
if (wc) wc.send("git-progress", { method, stage, progress });
|
|
606
|
+
} catch (e) {
|
|
607
|
+
}
|
|
608
|
+
}
|
|
594
609
|
};
|
|
595
610
|
if (baseDir) {
|
|
596
611
|
options.baseDir = baseDir;
|
|
@@ -712,7 +727,7 @@ async function stageFiles(sessionId, files) {
|
|
|
712
727
|
const chunkSize = 50;
|
|
713
728
|
for (let i = 0; i < fileArray.length; i += chunkSize) {
|
|
714
729
|
const chunk = fileArray.slice(i, i + chunkSize);
|
|
715
|
-
await git.add(chunk);
|
|
730
|
+
await git.add(["--force", ...chunk]);
|
|
716
731
|
}
|
|
717
732
|
return true;
|
|
718
733
|
}
|
|
@@ -1169,7 +1184,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1169
1184
|
}
|
|
1170
1185
|
try {
|
|
1171
1186
|
if (destExists && destHasGit) {
|
|
1172
|
-
const existingGit = createSafeGit(destPath);
|
|
1187
|
+
const existingGit = createSafeGit(destPath, sessionId);
|
|
1173
1188
|
try {
|
|
1174
1189
|
await existingGit.raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1175
1190
|
const remotes = await existingGit.getRemotes();
|
|
@@ -1191,10 +1206,10 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1191
1206
|
}
|
|
1192
1207
|
}
|
|
1193
1208
|
if (destExists && destIsEmpty) {
|
|
1194
|
-
const emptyDirGit = createSafeGit(destPath);
|
|
1209
|
+
const emptyDirGit = createSafeGit(destPath, sessionId);
|
|
1195
1210
|
await emptyDirGit.raw(["-c", "credential.helper=", "clone", authUrl, "."]);
|
|
1196
1211
|
try {
|
|
1197
|
-
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1212
|
+
await createSafeGit(void 0, sessionId).raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1198
1213
|
} catch (e) {
|
|
1199
1214
|
}
|
|
1200
1215
|
return destPath;
|
|
@@ -1206,10 +1221,10 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
|
1206
1221
|
}
|
|
1207
1222
|
const baseDir = path__namespace.dirname(destPath);
|
|
1208
1223
|
const targetDirName = path__namespace.basename(destPath);
|
|
1209
|
-
const parentGit = createSafeGit(baseDir);
|
|
1224
|
+
const parentGit = createSafeGit(baseDir, sessionId);
|
|
1210
1225
|
await parentGit.raw(["-c", "credential.helper=", "clone", authUrl, targetDirName]);
|
|
1211
1226
|
try {
|
|
1212
|
-
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1227
|
+
await createSafeGit(void 0, sessionId).raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1213
1228
|
} catch (e) {
|
|
1214
1229
|
}
|
|
1215
1230
|
return destPath;
|
|
@@ -1333,6 +1348,142 @@ ${errorMsg}
|
|
|
1333
1348
|
`AI Analysis failed after trying all provided keys. Last error: ${lastError?.message}`
|
|
1334
1349
|
);
|
|
1335
1350
|
}
|
|
1351
|
+
const AUTO_SYNC_SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitQuickPush" app.
|
|
1352
|
+
The user's Auto-Push process failed (likely due to remote changes or a merge conflict).
|
|
1353
|
+
You will receive the Git Status, the Raw Error Message, and optionally the contents of any Conflicted Files.
|
|
1354
|
+
|
|
1355
|
+
Your job is to analyze the issue and return a JSON object with this exact shape:
|
|
1356
|
+
{
|
|
1357
|
+
"reason": "A clear, user-friendly explanation of why the push failed.",
|
|
1358
|
+
"proposedFix": "A clear explanation of what specific changes you will make to fix it (e.g. 'I will run git pull --rebase and resolve the merge conflict in app.js by keeping both changes.')",
|
|
1359
|
+
"resolvedCode": {
|
|
1360
|
+
"path/to/file.ts": "THE_FULL_RESOLVED_FILE_CONTENT_HERE"
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
Guidelines:
|
|
1365
|
+
- If there are conflicted files provided, you MUST include the "resolvedCode" object. The keys must be the exact file paths, and the values must be the ENTIRE file content with all \`<<<<<<< HEAD\` conflict markers cleanly resolved.
|
|
1366
|
+
- If there are no conflicted files (e.g. just a standard rejection that a pull will fix), omit "resolvedCode".
|
|
1367
|
+
- Return ONLY valid JSON. No markdown code block wrappers.`;
|
|
1368
|
+
async function analyzePushErrorForAutoSync(provider, apiKeyInput, errorMsg, gitStatus, _repoPath, conflictedFilesContext, githubToken) {
|
|
1369
|
+
if (!apiKeyInput && provider === "gemini") {
|
|
1370
|
+
apiKeyInput = "AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw";
|
|
1371
|
+
}
|
|
1372
|
+
const keys = apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k);
|
|
1373
|
+
let prompt = `
|
|
1374
|
+
GIT STATUS:
|
|
1375
|
+
${JSON.stringify(gitStatus, null, 2)}
|
|
1376
|
+
|
|
1377
|
+
RAW ERROR MESSAGE:
|
|
1378
|
+
${errorMsg}
|
|
1379
|
+
`;
|
|
1380
|
+
if (conflictedFilesContext && Object.keys(conflictedFilesContext).length > 0) {
|
|
1381
|
+
prompt += `
|
|
1382
|
+
CONFLICTED FILES CONTENT (Resolve these markers):
|
|
1383
|
+
`;
|
|
1384
|
+
for (const [filePath, content] of Object.entries(conflictedFilesContext)) {
|
|
1385
|
+
prompt += `
|
|
1386
|
+
--- FILE: ${filePath} ---
|
|
1387
|
+
${content}
|
|
1388
|
+
--------------------------
|
|
1389
|
+
`;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
let lastError = null;
|
|
1393
|
+
if (githubToken) {
|
|
1394
|
+
try {
|
|
1395
|
+
const openai = new OpenAI({
|
|
1396
|
+
apiKey: githubToken,
|
|
1397
|
+
baseURL: "https://models.inference.ai.azure.com"
|
|
1398
|
+
});
|
|
1399
|
+
const response = await openai.chat.completions.create({
|
|
1400
|
+
model: "gpt-4o-mini",
|
|
1401
|
+
messages: [
|
|
1402
|
+
{ role: "system", content: AUTO_SYNC_SYSTEM_PROMPT },
|
|
1403
|
+
{ role: "user", content: prompt }
|
|
1404
|
+
],
|
|
1405
|
+
response_format: { type: "json_object" }
|
|
1406
|
+
});
|
|
1407
|
+
let responseText = response.choices[0].message.content || "{}";
|
|
1408
|
+
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
1409
|
+
const parsed = JSON.parse(responseText);
|
|
1410
|
+
if (parsed.reason && parsed.proposedFix) {
|
|
1411
|
+
return parsed;
|
|
1412
|
+
}
|
|
1413
|
+
} catch (e) {
|
|
1414
|
+
lastError = e;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
for (const apiKey of keys) {
|
|
1418
|
+
let responseText = "";
|
|
1419
|
+
try {
|
|
1420
|
+
if (provider === "gemini") {
|
|
1421
|
+
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
1422
|
+
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
1423
|
+
const result = await model.generateContent([{ text: AUTO_SYNC_SYSTEM_PROMPT }, { text: prompt }]);
|
|
1424
|
+
responseText = result.response.text();
|
|
1425
|
+
}
|
|
1426
|
+
responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
|
|
1427
|
+
const parsed = JSON.parse(responseText);
|
|
1428
|
+
if (!parsed.reason || !parsed.proposedFix) {
|
|
1429
|
+
throw new Error("AI returned an invalid response format.");
|
|
1430
|
+
}
|
|
1431
|
+
return parsed;
|
|
1432
|
+
} catch (e) {
|
|
1433
|
+
lastError = e;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
throw new Error(`AI Analysis failed. Last error: ${lastError?.message}`);
|
|
1437
|
+
}
|
|
1438
|
+
async function generateCommitMessage(provider, apiKeyInput, diffStr, githubToken) {
|
|
1439
|
+
const systemPrompt = `You are an expert developer. Generate a concise, clear Git commit message based on the provided git diff.
|
|
1440
|
+
Format your response exactly like a standard Git commit message:
|
|
1441
|
+
<type>: <short summary>
|
|
1442
|
+
|
|
1443
|
+
<detailed description>
|
|
1444
|
+
|
|
1445
|
+
Do not wrap it in JSON or code blocks. Return just the plain text of the commit message.
|
|
1446
|
+
If the diff is empty or invalid, return "Auto-sync: update files".`;
|
|
1447
|
+
const truncatedDiff = diffStr.length > 1e4 ? diffStr.substring(0, 1e4) + "\n...[diff truncated]" : diffStr;
|
|
1448
|
+
const prompt = `GIT DIFF:
|
|
1449
|
+
${truncatedDiff}`;
|
|
1450
|
+
let keys = "".split(",").map((k) => k.trim()).filter((k) => k);
|
|
1451
|
+
if (!keys.length && provider === "gemini") {
|
|
1452
|
+
keys = ["AQ.Ab8RN6LFu9vxQBWCSzE72V4CG2ILxnreqAP4AZQzSmXZ_1vphw"];
|
|
1453
|
+
}
|
|
1454
|
+
if (githubToken) {
|
|
1455
|
+
try {
|
|
1456
|
+
const openai = new OpenAI({
|
|
1457
|
+
apiKey: githubToken,
|
|
1458
|
+
baseURL: "https://models.inference.ai.azure.com"
|
|
1459
|
+
});
|
|
1460
|
+
const response = await openai.chat.completions.create({
|
|
1461
|
+
model: "gpt-4o-mini",
|
|
1462
|
+
messages: [
|
|
1463
|
+
{ role: "system", content: systemPrompt },
|
|
1464
|
+
{ role: "user", content: prompt }
|
|
1465
|
+
]
|
|
1466
|
+
});
|
|
1467
|
+
const content = response.choices[0].message.content?.trim();
|
|
1468
|
+
if (content) return content;
|
|
1469
|
+
} catch (e) {
|
|
1470
|
+
console.warn(`[AI Service] GitHub Copilot fallback for commit gen failed: ${e.message}`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
for (const apiKey of keys) {
|
|
1474
|
+
try {
|
|
1475
|
+
if (provider === "gemini") {
|
|
1476
|
+
const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
|
|
1477
|
+
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
1478
|
+
const result = await model.generateContent([{ text: systemPrompt }, { text: prompt }]);
|
|
1479
|
+
return result.response.text().trim();
|
|
1480
|
+
}
|
|
1481
|
+
} catch (e) {
|
|
1482
|
+
console.warn(`[AI Service] Provider ${provider} commit gen failed: ${e.message}`);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return "Auto-sync: update files";
|
|
1486
|
+
}
|
|
1336
1487
|
const getConfigPath = () => path__namespace.join(electron.app.getPath("userData"), "gitx-config.json");
|
|
1337
1488
|
const saveLastWorkspace = async (workspacePath) => {
|
|
1338
1489
|
try {
|
|
@@ -1362,10 +1513,17 @@ const getLastWorkspace = async () => {
|
|
|
1362
1513
|
let currentWatcher = null;
|
|
1363
1514
|
let syncTimeout = null;
|
|
1364
1515
|
let isAutoSyncEnabled = false;
|
|
1516
|
+
let isResolving = false;
|
|
1517
|
+
let globalOnLog = null;
|
|
1518
|
+
let globalOnErrorApproval = null;
|
|
1519
|
+
let globalOnResolveComplete = null;
|
|
1365
1520
|
const DEBOUNCE_MS = 3e3;
|
|
1366
1521
|
const changedFiles = /* @__PURE__ */ new Set();
|
|
1367
|
-
function toggleAutoSync(sessionId, repoPath, enabled, onLog) {
|
|
1522
|
+
function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, onResolveComplete) {
|
|
1368
1523
|
isAutoSyncEnabled = enabled;
|
|
1524
|
+
globalOnLog = onLog;
|
|
1525
|
+
if (onErrorApproval) globalOnErrorApproval = onErrorApproval;
|
|
1526
|
+
if (onResolveComplete) globalOnResolveComplete = onResolveComplete;
|
|
1369
1527
|
if (currentWatcher) {
|
|
1370
1528
|
currentWatcher.close();
|
|
1371
1529
|
currentWatcher = null;
|
|
@@ -1393,7 +1551,7 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog) {
|
|
|
1393
1551
|
ignoreInitial: true
|
|
1394
1552
|
});
|
|
1395
1553
|
currentWatcher.on("all", (_event, filePath) => {
|
|
1396
|
-
if (!isAutoSyncEnabled) return;
|
|
1554
|
+
if (!isAutoSyncEnabled || isResolving) return;
|
|
1397
1555
|
const relativePath = path__namespace.relative(repoPath, filePath).replace(/\\/g, "/");
|
|
1398
1556
|
changedFiles.add(relativePath);
|
|
1399
1557
|
if (syncTimeout) {
|
|
@@ -1419,16 +1577,141 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
1419
1577
|
await stageFiles(sessionId, existingFiles);
|
|
1420
1578
|
const status = await getStatus(sessionId);
|
|
1421
1579
|
const branch = status.current || "main";
|
|
1422
|
-
|
|
1423
|
-
|
|
1580
|
+
onLog("Generating AI commit message...");
|
|
1581
|
+
let commitMsg = `Auto-sync: update ${filesToSync.length > 1 ? filesToSync.length + " files" : filesToSync[0]}`;
|
|
1582
|
+
try {
|
|
1583
|
+
const diffStr = await getFileDiff(sessionId, "", true);
|
|
1584
|
+
if (diffStr && diffStr.trim() !== "") {
|
|
1585
|
+
const githubToken = getToken(sessionId) || void 0;
|
|
1586
|
+
const aiMsg = await generateCommitMessage("gemini", "", diffStr, githubToken);
|
|
1587
|
+
if (aiMsg) {
|
|
1588
|
+
commitMsg = aiMsg;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
} catch (aiErr) {
|
|
1592
|
+
console.warn("AI commit generation failed, falling back to default", aiErr);
|
|
1593
|
+
}
|
|
1594
|
+
onLog(`Committing: "${commitMsg.split("\n")[0]}"...`);
|
|
1424
1595
|
await commitChanges(sessionId, commitMsg);
|
|
1425
1596
|
onLog(`Pushing to ${branch}...`);
|
|
1426
1597
|
await pushToRemote(sessionId, "origin", branch);
|
|
1427
1598
|
onLog(`✅ Successfully pushed auto-sync commit!`);
|
|
1428
1599
|
} catch (error) {
|
|
1429
1600
|
onLog(`❌ Auto-sync failed: ${error.message}`);
|
|
1601
|
+
if (globalOnErrorApproval && !isResolving) {
|
|
1602
|
+
isResolving = true;
|
|
1603
|
+
onLog("Attempting to resolve push failure...");
|
|
1604
|
+
handleAutoSyncError(sessionId, error.message, onLog).catch(
|
|
1605
|
+
(err) => onLog(`Resolution failed: ${err.message}`)
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1430
1608
|
}
|
|
1431
1609
|
}
|
|
1610
|
+
async function handleAutoSyncError(sessionId, errorMsg, onLog) {
|
|
1611
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1612
|
+
if (!repoPath) {
|
|
1613
|
+
isResolving = false;
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
try {
|
|
1617
|
+
onLog("Pulling remote changes with rebase...");
|
|
1618
|
+
await git.pull("origin", void 0, { "--rebase": "true" });
|
|
1619
|
+
onLog("Pull successful, attempting push again...");
|
|
1620
|
+
const status2 = await getStatus(sessionId);
|
|
1621
|
+
await pushToRemote(sessionId, "origin", status2.current || "main");
|
|
1622
|
+
onLog("✅ Error resolved and push resumed!");
|
|
1623
|
+
isResolving = false;
|
|
1624
|
+
if (globalOnResolveComplete) globalOnResolveComplete();
|
|
1625
|
+
return;
|
|
1626
|
+
} catch (pullErr) {
|
|
1627
|
+
onLog(`Pull failed (possible conflict): ${pullErr.message}`);
|
|
1628
|
+
}
|
|
1629
|
+
const status = await getStatus(sessionId);
|
|
1630
|
+
if (status.conflicted.length === 0) {
|
|
1631
|
+
onLog("No conflicts found but resolution failed. Giving up.");
|
|
1632
|
+
isResolving = false;
|
|
1633
|
+
if (globalOnErrorApproval) {
|
|
1634
|
+
globalOnErrorApproval({
|
|
1635
|
+
reason: "Failed to resolve error automatically. No file conflicts found.",
|
|
1636
|
+
proposedFix: "Manual intervention required.",
|
|
1637
|
+
files: []
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
onLog("Analyzing conflicts with AI...");
|
|
1643
|
+
const conflictedFilesContext = {};
|
|
1644
|
+
for (const f of status.conflicted) {
|
|
1645
|
+
try {
|
|
1646
|
+
const content = await fs__namespace.readFile(path__namespace.join(repoPath, f), "utf-8");
|
|
1647
|
+
conflictedFilesContext[f] = content;
|
|
1648
|
+
} catch (e) {
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
try {
|
|
1652
|
+
const githubToken = getToken(sessionId) || void 0;
|
|
1653
|
+
const aiResponse = await analyzePushErrorForAutoSync(
|
|
1654
|
+
"gemini",
|
|
1655
|
+
"",
|
|
1656
|
+
errorMsg,
|
|
1657
|
+
status,
|
|
1658
|
+
repoPath,
|
|
1659
|
+
conflictedFilesContext,
|
|
1660
|
+
githubToken
|
|
1661
|
+
);
|
|
1662
|
+
if (globalOnErrorApproval) {
|
|
1663
|
+
globalOnErrorApproval({
|
|
1664
|
+
reason: aiResponse.reason,
|
|
1665
|
+
proposedFix: aiResponse.proposedFix,
|
|
1666
|
+
files: status.conflicted,
|
|
1667
|
+
resolvedCode: aiResponse.resolvedCode,
|
|
1668
|
+
sessionId
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
onLog(`AI analysis failed: ${err.message}`);
|
|
1673
|
+
isResolving = false;
|
|
1674
|
+
if (globalOnErrorApproval) {
|
|
1675
|
+
globalOnErrorApproval({
|
|
1676
|
+
reason: "AI analysis failed.",
|
|
1677
|
+
proposedFix: "Manual intervention required.",
|
|
1678
|
+
files: status.conflicted
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
async function applyAiFixAndResume(sessionId, resolvedCode) {
|
|
1684
|
+
const { git, repoPath } = getGitSession(sessionId);
|
|
1685
|
+
if (!repoPath) return false;
|
|
1686
|
+
if (globalOnLog) globalOnLog("Applying AI resolution to conflicted files...");
|
|
1687
|
+
try {
|
|
1688
|
+
if (resolvedCode) {
|
|
1689
|
+
for (const [file, content] of Object.entries(resolvedCode)) {
|
|
1690
|
+
await fs__namespace.writeFile(path__namespace.join(repoPath, file), content, "utf-8");
|
|
1691
|
+
await git.add(["--force", file]);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
try {
|
|
1695
|
+
await git.rebase({ "--continue": null });
|
|
1696
|
+
} catch (e) {
|
|
1697
|
+
await git.commit("Auto-resolved merge conflict by AI");
|
|
1698
|
+
}
|
|
1699
|
+
const status = await getStatus(sessionId);
|
|
1700
|
+
if (globalOnLog) globalOnLog("Pushing AI-resolved changes...");
|
|
1701
|
+
await pushToRemote(sessionId, "origin", status.current || "main");
|
|
1702
|
+
if (globalOnLog) globalOnLog("✅ Push successful!");
|
|
1703
|
+
isResolving = false;
|
|
1704
|
+
if (globalOnResolveComplete) globalOnResolveComplete();
|
|
1705
|
+
return true;
|
|
1706
|
+
} catch (err) {
|
|
1707
|
+
if (globalOnLog) globalOnLog(`AI resolution failed: ${err.message}`);
|
|
1708
|
+
isResolving = false;
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
function cancelAiFix() {
|
|
1713
|
+
isResolving = false;
|
|
1714
|
+
}
|
|
1432
1715
|
const icon = path.join(__dirname, "../../resources/icon.png");
|
|
1433
1716
|
function createWindow(initialPath) {
|
|
1434
1717
|
const mainWindow = new electron.BrowserWindow({
|
|
@@ -1786,9 +2069,26 @@ electron.app.whenReady().then(async () => {
|
|
|
1786
2069
|
return { success: true, count: copiedFiles.length, files: copiedFiles };
|
|
1787
2070
|
});
|
|
1788
2071
|
electron.ipcMain.on("toggle-auto-sync", (event, repoPath, enabled) => {
|
|
1789
|
-
toggleAutoSync(
|
|
1790
|
-
event.sender.
|
|
1791
|
-
|
|
2072
|
+
toggleAutoSync(
|
|
2073
|
+
event.sender.id,
|
|
2074
|
+
repoPath,
|
|
2075
|
+
enabled,
|
|
2076
|
+
(msg) => {
|
|
2077
|
+
event.sender.send("auto-sync-log", msg);
|
|
2078
|
+
},
|
|
2079
|
+
(payload) => {
|
|
2080
|
+
event.sender.send("auto-sync-error-approval", payload);
|
|
2081
|
+
},
|
|
2082
|
+
() => {
|
|
2083
|
+
event.sender.send("auto-sync-resolved");
|
|
2084
|
+
}
|
|
2085
|
+
);
|
|
2086
|
+
});
|
|
2087
|
+
electron.ipcMain.handle("approve-ai-fix", async (event, resolvedCode) => {
|
|
2088
|
+
return await applyAiFixAndResume(event.sender.id, resolvedCode);
|
|
2089
|
+
});
|
|
2090
|
+
electron.ipcMain.on("reject-ai-fix", () => {
|
|
2091
|
+
cancelAiFix();
|
|
1792
2092
|
});
|
|
1793
2093
|
electron.ipcMain.handle("get-app-version", () => {
|
|
1794
2094
|
return electron.app.getVersion();
|
package/out/preload/index.js
CHANGED
|
@@ -6,6 +6,10 @@ const api = {
|
|
|
6
6
|
onSetCliRepo: (callback) => {
|
|
7
7
|
electron.ipcRenderer.on("set-cli-repo", (_event, repoPath) => callback(repoPath));
|
|
8
8
|
},
|
|
9
|
+
onGitProgress: (callback) => {
|
|
10
|
+
electron.ipcRenderer.removeAllListeners("git-progress");
|
|
11
|
+
electron.ipcRenderer.on("git-progress", (_event, data) => callback(data));
|
|
12
|
+
},
|
|
9
13
|
selectDirectory: () => electron.ipcRenderer.invoke("select-directory"),
|
|
10
14
|
setRepoPath: (path) => electron.ipcRenderer.invoke("set-repo-path", path),
|
|
11
15
|
initRepo: (path) => electron.ipcRenderer.invoke("init-repo", path),
|
|
@@ -33,6 +37,8 @@ const api = {
|
|
|
33
37
|
setGitConfig: (key, value, global = false) => electron.ipcRenderer.invoke("set-git-config", key, value, global),
|
|
34
38
|
readFile: (file) => electron.ipcRenderer.invoke("read-file", file),
|
|
35
39
|
writeFile: (file, content) => electron.ipcRenderer.invoke("write-file", file, content),
|
|
40
|
+
approveAiFix: (resolvedCode) => electron.ipcRenderer.invoke("approve-ai-fix", resolvedCode),
|
|
41
|
+
rejectAiFix: () => electron.ipcRenderer.send("reject-ai-fix"),
|
|
36
42
|
executeRawGit: (args) => electron.ipcRenderer.invoke("execute-raw-git", args),
|
|
37
43
|
// NEW: Multi-Account & Window
|
|
38
44
|
newWindow: (repoPath) => electron.ipcRenderer.invoke("new-window", repoPath),
|
|
@@ -79,6 +85,14 @@ const api = {
|
|
|
79
85
|
electron.ipcRenderer.removeAllListeners("auto-sync-log");
|
|
80
86
|
electron.ipcRenderer.on("auto-sync-log", (_event, msg) => callback(msg));
|
|
81
87
|
},
|
|
88
|
+
onAutoSyncErrorApproval: (callback) => {
|
|
89
|
+
electron.ipcRenderer.removeAllListeners("auto-sync-error-approval");
|
|
90
|
+
electron.ipcRenderer.on("auto-sync-error-approval", (_event, payload) => callback(payload));
|
|
91
|
+
},
|
|
92
|
+
onAutoSyncResolved: (callback) => {
|
|
93
|
+
electron.ipcRenderer.removeAllListeners("auto-sync-resolved");
|
|
94
|
+
electron.ipcRenderer.on("auto-sync-resolved", () => callback());
|
|
95
|
+
},
|
|
82
96
|
getAppVersion: () => electron.ipcRenderer.invoke("get-app-version"),
|
|
83
97
|
// AI Fixer API
|
|
84
98
|
askAi: (provider, apiKey, errorMsg, gitStatus, repoPath) => electron.ipcRenderer.invoke("ask-ai", provider, apiKey, errorMsg, gitStatus, repoPath),
|