@aiderdesk/aiderdesk 0.62.0 → 0.64.0
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/renderer/assets/{_baseUniq-B7TxjYgy.js → _baseUniq-C6Q8LpuQ.js} +1 -1
- package/out/renderer/assets/{arc-TuVjX2tH.js → arc-DoIK-bD2.js} +1 -1
- package/out/renderer/assets/{architectureDiagram-Q4EWVU46-CnAcyRvO.js → architectureDiagram-Q4EWVU46-B8_dgBXp.js} +5 -5
- package/out/renderer/assets/{blockDiagram-DXYQGD6D-DBoJhGAM.js → blockDiagram-DXYQGD6D-BDOvGPDN.js} +6 -6
- package/out/renderer/assets/{c4Diagram-AHTNJAMY-BzW0gKwX.js → c4Diagram-AHTNJAMY-1ABZnJ2v.js} +2 -2
- package/out/renderer/assets/{channel-CqR4FAVX.js → channel-Cr_H2zdE.js} +1 -1
- package/out/renderer/assets/{chunk-4BX2VUAB-DQjg_Naa.js → chunk-4BX2VUAB-d88VZY9C.js} +1 -1
- package/out/renderer/assets/{chunk-4TB4RGXK-DkcI1yaW.js → chunk-4TB4RGXK-DLcMuHVI.js} +5 -5
- package/out/renderer/assets/{chunk-55IACEB6-pFPEMTmI.js → chunk-55IACEB6-BO1oJBQV.js} +1 -1
- package/out/renderer/assets/{chunk-EDXVE4YY-DRk838hn.js → chunk-EDXVE4YY-Dt80V_EG.js} +1 -1
- package/out/renderer/assets/{chunk-FMBD7UC4-BagQdd5u.js → chunk-FMBD7UC4-D5MNbIWZ.js} +1 -1
- package/out/renderer/assets/{chunk-OYMX7WX6-DOGJ-XHp.js → chunk-OYMX7WX6-DBFhtMcs.js} +3 -3
- package/out/renderer/assets/{chunk-QZHKN3VN-CdzjbbBk.js → chunk-QZHKN3VN-Bxwt_pyh.js} +1 -1
- package/out/renderer/assets/{chunk-YZCP3GAM-BKZRTlUj.js → chunk-YZCP3GAM-gAcMGuhT.js} +1 -1
- package/out/renderer/assets/{classDiagram-6PBFFD2Q-CjVhmlxb.js → classDiagram-6PBFFD2Q-B7lgamMP.js} +6 -6
- package/out/renderer/assets/{classDiagram-v2-HSJHXN6E-CjVhmlxb.js → classDiagram-v2-HSJHXN6E-B7lgamMP.js} +6 -6
- package/out/renderer/assets/{clone-B-kqpcr4.js → clone-DKkqtIT8.js} +1 -1
- package/out/renderer/assets/{cose-bilkent-S5V4N54A-CZLRecVB.js → cose-bilkent-S5V4N54A-BZNBIG2x.js} +1 -1
- package/out/renderer/assets/{dagre-KV5264BT-BUAzC7Um.js → dagre-KV5264BT-C3hXUNb-.js} +6 -6
- package/out/renderer/assets/{diagram-5BDNPKRD-DNQEDszc.js → diagram-5BDNPKRD-DNh45EqP.js} +6 -6
- package/out/renderer/assets/{diagram-G4DWMVQ6-D9YY6W2O.js → diagram-G4DWMVQ6-8lhqJfPk.js} +6 -6
- package/out/renderer/assets/{diagram-MMDJMWI5-B5H49OVD.js → diagram-MMDJMWI5-BcI1Ek4N.js} +5 -5
- package/out/renderer/assets/{diagram-TYMM5635-Bw7r467p.js → diagram-TYMM5635-DuHcW-s7.js} +5 -5
- package/out/renderer/assets/{erDiagram-SMLLAGMA-Bz4ALNLp.js → erDiagram-SMLLAGMA-I6Q9HYdF.js} +4 -4
- package/out/renderer/assets/{flowDiagram-DWJPFMVM-Uqg1ZeN2.js → flowDiagram-DWJPFMVM-BzRjtX5C.js} +6 -6
- package/out/renderer/assets/{ganttDiagram-T4ZO3ILL-Dm0tRLCH.js → ganttDiagram-T4ZO3ILL-DVkem_IA.js} +1 -1
- package/out/renderer/assets/{gitGraphDiagram-UUTBAWPF-R8imfIrj.js → gitGraphDiagram-UUTBAWPF-BYpvdMpK.js} +6 -6
- package/out/renderer/assets/{graph-B5YWEnt0.js → graph-CAtr5PoG.js} +2 -2
- package/out/renderer/assets/{index-D7Xi0GX5.js → index-CNL53LoL.js} +4230 -2874
- package/out/renderer/assets/{index-B6Zj03wk.css → index-Duw36zwk.css} +127 -0
- package/out/renderer/assets/{infoDiagram-42DDH7IO-CWPYNP1k.js → infoDiagram-42DDH7IO-BcmBthOY.js} +4 -4
- package/out/renderer/assets/{ishikawaDiagram-UXIWVN3A-CFTrc-S6.js → ishikawaDiagram-UXIWVN3A-moTWny-V.js} +1 -1
- package/out/renderer/assets/{journeyDiagram-VCZTEJTY-Dfd-piIf.js → journeyDiagram-VCZTEJTY-DOW8zaZt.js} +4 -4
- package/out/renderer/assets/{kanban-definition-6JOO6SKY-BocrfAtb.js → kanban-definition-6JOO6SKY-DpJjTob4.js} +2 -2
- package/out/renderer/assets/{layout-DHzrXVin.js → layout-BvH51Ui9.js} +4 -4
- package/out/renderer/assets/{min-D6g96v7R.js → min-CowxrbD6.js} +2 -2
- package/out/renderer/assets/{mindmap-definition-QFDTVHPH-D9cyK1Gb.js → mindmap-definition-QFDTVHPH-DggFFNHq.js} +3 -3
- package/out/renderer/assets/{pieDiagram-DEJITSTG-LUWwVJA5.js → pieDiagram-DEJITSTG-BED4dnMF.js} +6 -6
- package/out/renderer/assets/{quadrantDiagram-34T5L4WZ-DdWTi9yW.js → quadrantDiagram-34T5L4WZ-RpQ3qNU5.js} +1 -1
- package/out/renderer/assets/{requirementDiagram-MS252O5E-BoLT3tay.js → requirementDiagram-MS252O5E-VQt4zBMB.js} +3 -3
- package/out/renderer/assets/{sankeyDiagram-XADWPNL6-B4gY_QI-.js → sankeyDiagram-XADWPNL6-DywR7qAk.js} +1 -1
- package/out/renderer/assets/{sequenceDiagram-FGHM5R23-B-JJvxQW.js → sequenceDiagram-FGHM5R23-CVPfZD4e.js} +3 -3
- package/out/renderer/assets/{stateDiagram-FHFEXIEX-BntwOBUs.js → stateDiagram-FHFEXIEX-BrH8Q8ZG.js} +8 -8
- package/out/renderer/assets/{stateDiagram-v2-QKLJ7IA2-C__eWVIe.js → stateDiagram-v2-QKLJ7IA2-BTWk2K0H.js} +4 -4
- package/out/renderer/assets/{timeline-definition-GMOUNBTQ-C7ch2INk.js → timeline-definition-GMOUNBTQ-DwDUCrTb.js} +2 -2
- package/out/renderer/assets/{vennDiagram-DHZGUBPP-CQ__NSue.js → vennDiagram-DHZGUBPP-Bjvr7yGM.js} +1 -1
- package/out/renderer/assets/{wardley-RL74JXVD-Kh6mNiRV.js → wardley-RL74JXVD-Bo-sW7uQ.js} +3 -3
- package/out/renderer/assets/{wardleyDiagram-NUSXRM2D-CfRYzJMU.js → wardleyDiagram-NUSXRM2D-DRW_1PCJ.js} +5 -5
- package/out/renderer/assets/{xychartDiagram-5P7HB3ND-BFq2zIne.js → xychartDiagram-5P7HB3ND-Ds-qS4nC.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/out/resources/connector/connector.py +9 -6
- package/out/resources/skills/extension-creator/references/config-components.md +6 -6
- package/out/runner.js +1142 -246
- package/package.json +2 -2
- package/patches/@ai-sdk+deepseek+1.0.37.patch +150 -0
- package/out/resources/linux/probe +0 -0
- package/out/resources/linux-x64/probe +0 -0
- /package/patches/{ai+5.0.172.patch → ai+5.0.179.patch} +0 -0
package/out/runner.js
CHANGED
|
@@ -47,7 +47,6 @@ const filenamifyImport = require("filenamify");
|
|
|
47
47
|
const slugify = require("slugify");
|
|
48
48
|
const Turndown = require("turndown");
|
|
49
49
|
const cheerio = require("cheerio");
|
|
50
|
-
const yamlFrontMatter = require("yaml-front-matter");
|
|
51
50
|
const uuid = require("uuid");
|
|
52
51
|
const index_js = require("@modelcontextprotocol/sdk/client/index.js");
|
|
53
52
|
const stdio_js = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
@@ -59,6 +58,7 @@ const express = require("express");
|
|
|
59
58
|
const cors = require("cors");
|
|
60
59
|
const cloudflared = require("cloudflared");
|
|
61
60
|
const socket_io = require("socket.io");
|
|
61
|
+
const yamlFrontMatter = require("yaml-front-matter");
|
|
62
62
|
const debounce = require("lodash/debounce.js");
|
|
63
63
|
const crypto = require("crypto");
|
|
64
64
|
const undici = require("undici");
|
|
@@ -243,6 +243,7 @@ const WorktreeSchema = zod.z.object({
|
|
|
243
243
|
path: zod.z.string(),
|
|
244
244
|
baseBranch: zod.z.string().optional(),
|
|
245
245
|
baseCommit: zod.z.string().optional(),
|
|
246
|
+
branch: zod.z.string().optional(),
|
|
246
247
|
prunable: zod.z.boolean().optional()
|
|
247
248
|
});
|
|
248
249
|
const MergeStateSchema = zod.z.object({
|
|
@@ -299,7 +300,10 @@ const ProjectSettingsSchema = zod.z.object({
|
|
|
299
300
|
contextCompactingThreshold: zod.z.number().optional(),
|
|
300
301
|
weakModelLocked: zod.z.boolean().optional(),
|
|
301
302
|
autoApproveLocked: zod.z.boolean().optional(),
|
|
302
|
-
updatedFilesGroupMode: zod.z.enum(["grouped", "flat"]).default("flat")
|
|
303
|
+
updatedFilesGroupMode: zod.z.enum(["grouped", "flat"]).default("flat"),
|
|
304
|
+
disabledRuleFiles: zod.z.array(zod.z.string()).default([]),
|
|
305
|
+
contextSidebarSectionsOrder: zod.z.array(zod.z.string()).default([]),
|
|
306
|
+
contextSidebarSectionsHidden: zod.z.array(zod.z.string()).default([])
|
|
303
307
|
});
|
|
304
308
|
var ToolApprovalState = /* @__PURE__ */ ((ToolApprovalState2) => {
|
|
305
309
|
ToolApprovalState2["Always"] = "always";
|
|
@@ -483,19 +487,22 @@ const parseUsageReport = (model, report) => {
|
|
|
483
487
|
};
|
|
484
488
|
const normalizeBaseDir = (baseDir, os2 = process.platform === "win32" ? OS.Windows : process.platform === "darwin" ? OS.MacOS : OS.Linux) => {
|
|
485
489
|
if (os2 === OS.Windows) {
|
|
486
|
-
return baseDir.toLowerCase();
|
|
490
|
+
return baseDir.toLowerCase().replace(/\\+$/, "");
|
|
487
491
|
} else {
|
|
488
492
|
const wslPrefix = "\\\\wsl.localhost\\";
|
|
489
493
|
if (baseDir.startsWith(wslPrefix)) {
|
|
490
494
|
const thirdBackslashIndex = baseDir.indexOf("\\", wslPrefix.length);
|
|
491
495
|
if (thirdBackslashIndex !== -1) {
|
|
492
496
|
const actualPath = baseDir.substring(thirdBackslashIndex + 1);
|
|
493
|
-
return "/" + actualPath.replace(/\\/g, "/");
|
|
497
|
+
return ("/" + actualPath.replace(/\\/g, "/")).replace(/\/+$/, "");
|
|
494
498
|
}
|
|
495
499
|
}
|
|
496
|
-
return baseDir;
|
|
500
|
+
return baseDir.replace(/\/+$/, "");
|
|
497
501
|
}
|
|
498
502
|
};
|
|
503
|
+
const compareBaseDirs$1 = (baseDir1, baseDir2, os2) => {
|
|
504
|
+
return normalizeBaseDir(baseDir1, os2) === normalizeBaseDir(baseDir2, os2);
|
|
505
|
+
};
|
|
499
506
|
const fileExists = async (fileName) => {
|
|
500
507
|
return await fs.stat(fileName).catch(() => null) !== null;
|
|
501
508
|
};
|
|
@@ -812,6 +819,7 @@ const POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
812
819
|
process.env.AIDER_DESK_HEADLESS === "true";
|
|
813
820
|
const AUTH_USERNAME = process.env.AIDER_DESK_USERNAME;
|
|
814
821
|
const AUTH_PASSWORD = process.env.AIDER_DESK_PASSWORD;
|
|
822
|
+
const CORS_ALLOWED_ORIGINS = process.env.AIDER_DESK_CORS_ALLOWED_ORIGINS;
|
|
815
823
|
const PROBE_BINARY_PATH = path.join(
|
|
816
824
|
RESOURCES_DIR,
|
|
817
825
|
process.platform === "win32" ? "win" : process.platform === "darwin" ? "macos" : "linux",
|
|
@@ -1434,6 +1442,7 @@ const search = async (options) => {
|
|
|
1434
1442
|
args.push(`${flag} ${String(value)}`);
|
|
1435
1443
|
}
|
|
1436
1444
|
}
|
|
1445
|
+
args.push("--");
|
|
1437
1446
|
args.push(`"${options.query}"`);
|
|
1438
1447
|
if (options.path) {
|
|
1439
1448
|
args.push(`"${options.path}"`);
|
|
@@ -1518,11 +1527,11 @@ const DEFAULT_PROVIDER_MODELS = {
|
|
|
1518
1527
|
bedrock: "global.anthropic.claude-sonnet-4-6",
|
|
1519
1528
|
cerebras: "qwen-3-235b-a22b-instruct-2507",
|
|
1520
1529
|
"claude-agent-sdk": "sonnet",
|
|
1521
|
-
deepseek: "deepseek-
|
|
1530
|
+
deepseek: "deepseek-v4-pro",
|
|
1522
1531
|
gemini: "gemini-pro-latest",
|
|
1523
1532
|
"gemini-cli": "gemini-2.5-pro",
|
|
1524
1533
|
groq: "moonshotai/kimi-k2-instruct-0905",
|
|
1525
|
-
"kimi-plan": "
|
|
1534
|
+
"kimi-plan": "k2p6",
|
|
1526
1535
|
openai: "gpt-5.4",
|
|
1527
1536
|
openrouter: "anthropic/claude-sonnet-4.6",
|
|
1528
1537
|
opencode: "claude-sonnet-4-6",
|
|
@@ -1842,7 +1851,8 @@ const getDefaultProviderParams = (providerName) => {
|
|
|
1842
1851
|
case "deepseek":
|
|
1843
1852
|
provider = {
|
|
1844
1853
|
name: "deepseek",
|
|
1845
|
-
apiKey: ""
|
|
1854
|
+
apiKey: "",
|
|
1855
|
+
thinkingEnabled: true
|
|
1846
1856
|
};
|
|
1847
1857
|
break;
|
|
1848
1858
|
case "bedrock":
|
|
@@ -2361,7 +2371,10 @@ const getDefaultProjectSettings = (store, providerModels, baseDir, defaultAgentP
|
|
|
2361
2371
|
currentMode: "agent",
|
|
2362
2372
|
agentProfileId: defaultAgentProfileId,
|
|
2363
2373
|
autoApproveLocked: false,
|
|
2364
|
-
updatedFilesGroupMode: "flat"
|
|
2374
|
+
updatedFilesGroupMode: "flat",
|
|
2375
|
+
disabledRuleFiles: [],
|
|
2376
|
+
contextSidebarSectionsOrder: [],
|
|
2377
|
+
contextSidebarSectionsHidden: []
|
|
2365
2378
|
};
|
|
2366
2379
|
};
|
|
2367
2380
|
const filenamify = filenamifyImport.default;
|
|
@@ -3048,19 +3061,16 @@ const scrapeWeb = async (url, timeout = 6e4, abortSignal, format = "markdown") =
|
|
|
3048
3061
|
const scraper = new WebScraper();
|
|
3049
3062
|
return await scraper.scrape(url, timeout, abortSignal, format);
|
|
3050
3063
|
};
|
|
3051
|
-
const
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
}
|
|
3062
|
-
});
|
|
3063
|
-
return newLock;
|
|
3064
|
+
const THINKING_RESPONSE_STAR_TAG = "---\n► **THINKING**\n";
|
|
3065
|
+
const ANSWER_RESPONSE_START_TAG = "---\n► **ANSWER**\n";
|
|
3066
|
+
const extractPromptContextFromToolResult = (toolResult) => {
|
|
3067
|
+
if (toolResult && typeof toolResult === "object" && "promptContext" in toolResult) {
|
|
3068
|
+
return toolResult.promptContext;
|
|
3069
|
+
}
|
|
3070
|
+
return void 0;
|
|
3071
|
+
};
|
|
3072
|
+
const findLastUserMessage = (messages) => {
|
|
3073
|
+
return [...messages].reverse().find((msg) => msg.role === MessageRole.User);
|
|
3064
3074
|
};
|
|
3065
3075
|
const expandTilde = (filePath) => {
|
|
3066
3076
|
if (filePath.startsWith("~/") || filePath === "~") {
|
|
@@ -3068,11 +3078,25 @@ const expandTilde = (filePath) => {
|
|
|
3068
3078
|
}
|
|
3069
3079
|
return filePath;
|
|
3070
3080
|
};
|
|
3071
|
-
const readFileContent = async (absolutePath, withLines = false, lineOffset = 0, lineLimit = 1e3) => {
|
|
3081
|
+
const readFileContent = async (absolutePath, withLines = false, lineOffset = 0, lineLimit = 1e3, sizeLimit = 0.05 * lineLimit) => {
|
|
3072
3082
|
const fileContentBuffer = await fs.readFile(absolutePath);
|
|
3073
3083
|
if (istextorbinary.isBinary(absolutePath, fileContentBuffer)) {
|
|
3074
3084
|
throw new Error("Binary files cannot be read.");
|
|
3075
3085
|
}
|
|
3086
|
+
const fileSizeKB = fileContentBuffer.length / 1024;
|
|
3087
|
+
if (fileSizeKB > sizeLimit) {
|
|
3088
|
+
const truncatedBytes = fileContentBuffer.subarray(0, Math.floor(sizeLimit * 1024));
|
|
3089
|
+
const truncatedContent = truncatedBytes.toString("utf8");
|
|
3090
|
+
const truncatedLines = truncatedContent.split("\n");
|
|
3091
|
+
if (withLines) {
|
|
3092
|
+
return truncatedLines.map((line, index) => `${index + 1}|${line}`).join("\n") + `
|
|
3093
|
+
|
|
3094
|
+
File size limit (${sizeLimit.toFixed(1)} KB) exceeded. Use shell commands (e.g., head, tail, grep) to read specific parts.`;
|
|
3095
|
+
}
|
|
3096
|
+
return truncatedContent + `
|
|
3097
|
+
|
|
3098
|
+
File size limit (${sizeLimit.toFixed(1)} KB) exceeded. Use shell commands (e.g., head, tail, grep) to read specific parts.`;
|
|
3099
|
+
}
|
|
3076
3100
|
const fileContent = fileContentBuffer.toString("utf8");
|
|
3077
3101
|
const lines = fileContent.split("\n");
|
|
3078
3102
|
const totalLines = lines.length;
|
|
@@ -3088,6 +3112,42 @@ const readFileContent = async (absolutePath, withLines = false, lineOffset = 0,
|
|
|
3088
3112
|
}
|
|
3089
3113
|
return limitedLines.join("\n");
|
|
3090
3114
|
};
|
|
3115
|
+
const truncateToolResult = async (content, maxLines = 1e3, maxSizeKB = 50) => {
|
|
3116
|
+
const lines = content.split("\n");
|
|
3117
|
+
const sizeBytes = Buffer.byteLength(content, "utf8");
|
|
3118
|
+
const sizeKB = sizeBytes / 1024;
|
|
3119
|
+
if (lines.length <= maxLines && sizeKB <= maxSizeKB) {
|
|
3120
|
+
return content;
|
|
3121
|
+
}
|
|
3122
|
+
const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
|
|
3123
|
+
const tmpFileName = `aider-desk-tool-result-${id}.txt`;
|
|
3124
|
+
const tmpFilePath = path.join(os.tmpdir(), tmpFileName);
|
|
3125
|
+
await fs.writeFile(tmpFilePath, content, "utf8");
|
|
3126
|
+
const previewLines = lines.slice(0, maxLines);
|
|
3127
|
+
const reasons = [];
|
|
3128
|
+
if (lines.length > maxLines) {
|
|
3129
|
+
reasons.push(`${lines.length} lines exceeded limit of ${maxLines}`);
|
|
3130
|
+
}
|
|
3131
|
+
if (sizeKB > maxSizeKB) {
|
|
3132
|
+
reasons.push(`${sizeKB.toFixed(1)} KB exceeded limit of ${maxSizeKB} KB`);
|
|
3133
|
+
}
|
|
3134
|
+
return previewLines.join("\n") + `
|
|
3135
|
+
... Content truncated (${reasons.join(", ")}). Full content saved to ${tmpFilePath}.`;
|
|
3136
|
+
};
|
|
3137
|
+
const fileLocks = /* @__PURE__ */ new Map();
|
|
3138
|
+
const withFileLock = (filePath, operation) => {
|
|
3139
|
+
logger.debug("Acquiring file lock:", { filePath });
|
|
3140
|
+
const currentLock = fileLocks.get(filePath) || Promise.resolve();
|
|
3141
|
+
const newLock = currentLock.then(operation, operation);
|
|
3142
|
+
fileLocks.set(filePath, newLock);
|
|
3143
|
+
newLock.finally(() => {
|
|
3144
|
+
if (fileLocks.get(filePath) === newLock) {
|
|
3145
|
+
logger.debug("Releasing file lock:", { filePath });
|
|
3146
|
+
fileLocks.delete(filePath);
|
|
3147
|
+
}
|
|
3148
|
+
});
|
|
3149
|
+
return newLock;
|
|
3150
|
+
};
|
|
3091
3151
|
const createPowerToolset = (task, profile, promptContext, abortSignal) => {
|
|
3092
3152
|
const approvalManager = new ApprovalManager(task, profile);
|
|
3093
3153
|
const fileEditTool = ai.tool({
|
|
@@ -3406,10 +3466,10 @@ Do not use escape characters \\ in the string like \\n or \\" and others. Do not
|
|
|
3406
3466
|
encoding: "utf8",
|
|
3407
3467
|
signal: abortSignal
|
|
3408
3468
|
});
|
|
3409
|
-
const
|
|
3469
|
+
const lines2 = fileContent.split("\n");
|
|
3410
3470
|
const relativeFilePath = path.relative(task.getTaskDir(), absoluteFilePath);
|
|
3411
|
-
for (let index = 0; index <
|
|
3412
|
-
const line =
|
|
3471
|
+
for (let index = 0; index < lines2.length; index++) {
|
|
3472
|
+
const line = lines2[index];
|
|
3413
3473
|
if (searchRegex.test(line)) {
|
|
3414
3474
|
if (results.length >= maxResults) {
|
|
3415
3475
|
break;
|
|
@@ -3421,8 +3481,8 @@ Do not use escape characters \\ in the string like \\n or \\" and others. Do not
|
|
|
3421
3481
|
};
|
|
3422
3482
|
if (contextLines > 0) {
|
|
3423
3483
|
const start = Math.max(0, index - contextLines);
|
|
3424
|
-
const end = Math.min(
|
|
3425
|
-
matchResult.context =
|
|
3484
|
+
const end = Math.min(lines2.length - 1, index + contextLines);
|
|
3485
|
+
matchResult.context = lines2.slice(start, end + 1);
|
|
3426
3486
|
}
|
|
3427
3487
|
results.push(matchResult);
|
|
3428
3488
|
}
|
|
@@ -3431,7 +3491,40 @@ Do not use escape characters \\ in the string like \\n or \\" and others. Do not
|
|
|
3431
3491
|
if (results.length === 0) {
|
|
3432
3492
|
return `No matches found for pattern '${searchTerm}' in files matching '${filePattern}'.`;
|
|
3433
3493
|
}
|
|
3434
|
-
|
|
3494
|
+
const grouped = {};
|
|
3495
|
+
for (const r of results) {
|
|
3496
|
+
if (!grouped[r.filePath]) {
|
|
3497
|
+
grouped[r.filePath] = [];
|
|
3498
|
+
}
|
|
3499
|
+
grouped[r.filePath].push(r);
|
|
3500
|
+
}
|
|
3501
|
+
const lines = [];
|
|
3502
|
+
lines.push(`## Grep Results: \`${searchTerm}\` in \`${filePattern}\` (${results.length} matches)`);
|
|
3503
|
+
lines.push("");
|
|
3504
|
+
for (const [filePath, matches] of Object.entries(grouped)) {
|
|
3505
|
+
lines.push(`### ${filePath} (${matches.length} ${matches.length === 1 ? "match" : "matches"})`);
|
|
3506
|
+
for (const match of matches) {
|
|
3507
|
+
const escapedContent = match.lineContent.replace(/`/g, "\\`");
|
|
3508
|
+
lines.push(`- **L${match.lineNumber}:** \`${escapedContent}\``);
|
|
3509
|
+
if (match.context && match.context.length > 0) {
|
|
3510
|
+
lines.push(" ```");
|
|
3511
|
+
for (const ctxLine of match.context) {
|
|
3512
|
+
lines.push(` ${ctxLine}`);
|
|
3513
|
+
}
|
|
3514
|
+
lines.push(" ```");
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
lines.push("");
|
|
3518
|
+
}
|
|
3519
|
+
const notices = [];
|
|
3520
|
+
if (results.length >= maxResults) {
|
|
3521
|
+
notices.push(`${maxResults} matches limit reached. Use maxResults=${maxResults * 2} for more, or refine pattern`);
|
|
3522
|
+
}
|
|
3523
|
+
if (notices.length > 0) {
|
|
3524
|
+
lines.push("---");
|
|
3525
|
+
lines.push(`[${notices.join(". ")}]`);
|
|
3526
|
+
}
|
|
3527
|
+
return lines.join("\n");
|
|
3435
3528
|
} catch (error) {
|
|
3436
3529
|
if (isAbortError(error)) {
|
|
3437
3530
|
return "Operation was cancelled by user.";
|
|
@@ -3505,24 +3598,26 @@ Timeout: ${timeout}ms`;
|
|
|
3505
3598
|
abortSignal?.removeEventListener("abort", abortListener);
|
|
3506
3599
|
}
|
|
3507
3600
|
};
|
|
3508
|
-
const resolveWithResult = () => {
|
|
3601
|
+
const resolveWithResult = async () => {
|
|
3509
3602
|
if (isResolved) {
|
|
3510
3603
|
return;
|
|
3511
3604
|
}
|
|
3512
3605
|
isResolved = true;
|
|
3513
3606
|
cleanup();
|
|
3514
|
-
|
|
3607
|
+
const truncatedStdout = await truncateToolResult(stdout);
|
|
3608
|
+
const truncatedStderr = await truncateToolResult(stderr);
|
|
3609
|
+
resolve({ stdout: truncatedStdout, stderr: truncatedStderr, exitCode });
|
|
3515
3610
|
};
|
|
3516
3611
|
abortListener = () => {
|
|
3517
3612
|
if (isResolved) {
|
|
3518
3613
|
return;
|
|
3519
3614
|
}
|
|
3520
3615
|
if (childProcess?.pid) {
|
|
3521
|
-
treeKill(childProcess.pid, "
|
|
3616
|
+
treeKill(childProcess.pid, "SIGKILL");
|
|
3522
3617
|
}
|
|
3523
3618
|
stderr = "Operation was cancelled by user.";
|
|
3524
3619
|
exitCode = 130;
|
|
3525
|
-
resolveWithResult();
|
|
3620
|
+
void resolveWithResult();
|
|
3526
3621
|
};
|
|
3527
3622
|
abortSignal?.addEventListener("abort", abortListener);
|
|
3528
3623
|
try {
|
|
@@ -3539,13 +3634,16 @@ Timeout: ${timeout}ms`;
|
|
|
3539
3634
|
return;
|
|
3540
3635
|
}
|
|
3541
3636
|
if (childProcess?.pid) {
|
|
3542
|
-
treeKill(childProcess.pid, "
|
|
3637
|
+
treeKill(childProcess.pid, "SIGKILL");
|
|
3543
3638
|
}
|
|
3544
3639
|
stderr = `Error: Command timed out after ${timeout}ms. Consider increasing the timeout parameter.`;
|
|
3545
3640
|
exitCode = 124;
|
|
3546
|
-
resolveWithResult();
|
|
3641
|
+
void resolveWithResult();
|
|
3547
3642
|
}, timeout);
|
|
3548
3643
|
childProcess.stdout?.on("data", (data) => {
|
|
3644
|
+
if (isResolved) {
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3549
3647
|
const chunk = data.toString("utf-8");
|
|
3550
3648
|
stdout += chunk;
|
|
3551
3649
|
task.addToolMessage(
|
|
@@ -3562,6 +3660,9 @@ Timeout: ${timeout}ms`;
|
|
|
3562
3660
|
);
|
|
3563
3661
|
});
|
|
3564
3662
|
childProcess.stderr?.on("data", (data) => {
|
|
3663
|
+
if (isResolved) {
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3565
3666
|
const chunk = data.toString("utf-8");
|
|
3566
3667
|
stderr += chunk;
|
|
3567
3668
|
task.addToolMessage(
|
|
@@ -3581,7 +3682,7 @@ Timeout: ${timeout}ms`;
|
|
|
3581
3682
|
if (!isResolved) {
|
|
3582
3683
|
stderr = error.message;
|
|
3583
3684
|
exitCode = 1;
|
|
3584
|
-
resolveWithResult();
|
|
3685
|
+
void resolveWithResult();
|
|
3585
3686
|
}
|
|
3586
3687
|
});
|
|
3587
3688
|
childProcess.on("exit", (code, signal) => {
|
|
@@ -3593,14 +3694,14 @@ Timeout: ${timeout}ms`;
|
|
|
3593
3694
|
} else {
|
|
3594
3695
|
exitCode = 1;
|
|
3595
3696
|
}
|
|
3596
|
-
resolveWithResult();
|
|
3697
|
+
void resolveWithResult();
|
|
3597
3698
|
}
|
|
3598
3699
|
});
|
|
3599
3700
|
} catch (error) {
|
|
3600
3701
|
if (!isResolved) {
|
|
3601
3702
|
stderr = error instanceof Error ? error.message : String(error);
|
|
3602
3703
|
exitCode = 1;
|
|
3603
|
-
resolveWithResult();
|
|
3704
|
+
void resolveWithResult();
|
|
3604
3705
|
}
|
|
3605
3706
|
}
|
|
3606
3707
|
});
|
|
@@ -4073,7 +4174,8 @@ Parent Task ID: ${parentTaskId || "none (top-level task)"}` : ""}`;
|
|
|
4073
4174
|
const newTask = await task.getProject().createNewTask({
|
|
4074
4175
|
parentId: parentTaskId || null,
|
|
4075
4176
|
name: name || "",
|
|
4076
|
-
autoApprove
|
|
4177
|
+
autoApprove,
|
|
4178
|
+
workingMode: worktree ? "worktree" : "local"
|
|
4077
4179
|
});
|
|
4078
4180
|
const updates = {};
|
|
4079
4181
|
if (agentProfileId) {
|
|
@@ -4085,9 +4187,6 @@ Parent Task ID: ${parentTaskId || "none (top-level task)"}` : ""}`;
|
|
|
4085
4187
|
updates.model = modelParts.join("/");
|
|
4086
4188
|
updates.mainModel = modelId;
|
|
4087
4189
|
}
|
|
4088
|
-
if (worktree) {
|
|
4089
|
-
updates.workingMode = "worktree";
|
|
4090
|
-
}
|
|
4091
4190
|
const taskInstance = task.getProject().getTask(newTask.id);
|
|
4092
4191
|
if (!taskInstance) {
|
|
4093
4192
|
throw new Error(`Failed to get task instance for newly created task ${newTask.id}`);
|
|
@@ -4699,64 +4798,6 @@ New content: "${content}"`;
|
|
|
4699
4798
|
}
|
|
4700
4799
|
return filteredTools;
|
|
4701
4800
|
};
|
|
4702
|
-
const SKILLS_DIR_NAME = "skills";
|
|
4703
|
-
const SKILL_MARKDOWN_FILE = "SKILL.md";
|
|
4704
|
-
const parseSkillFrontMatter = (markdown) => {
|
|
4705
|
-
const parsed = yamlFrontMatter.loadFront(markdown);
|
|
4706
|
-
const name = typeof parsed.name === "string" ? parsed.name : void 0;
|
|
4707
|
-
const description = typeof parsed.description === "string" ? parsed.description : void 0;
|
|
4708
|
-
if (!name || !description) {
|
|
4709
|
-
return null;
|
|
4710
|
-
}
|
|
4711
|
-
return { name, description };
|
|
4712
|
-
};
|
|
4713
|
-
const safeReadDir = async (dirPath) => {
|
|
4714
|
-
try {
|
|
4715
|
-
return await fs.readdir(dirPath);
|
|
4716
|
-
} catch {
|
|
4717
|
-
return [];
|
|
4718
|
-
}
|
|
4719
|
-
};
|
|
4720
|
-
const safeStat = async (filePath) => {
|
|
4721
|
-
try {
|
|
4722
|
-
return await fs.stat(filePath);
|
|
4723
|
-
} catch {
|
|
4724
|
-
return null;
|
|
4725
|
-
}
|
|
4726
|
-
};
|
|
4727
|
-
const loadSkillsFromDir = async (skillsRootDir, location) => {
|
|
4728
|
-
const entries = await safeReadDir(skillsRootDir);
|
|
4729
|
-
const skills = [];
|
|
4730
|
-
for (const entry of entries) {
|
|
4731
|
-
const dirPath = path.join(skillsRootDir, entry);
|
|
4732
|
-
const stat = await safeStat(dirPath);
|
|
4733
|
-
if (!stat?.isDirectory()) {
|
|
4734
|
-
continue;
|
|
4735
|
-
}
|
|
4736
|
-
const skillMdPath = path.join(dirPath, SKILL_MARKDOWN_FILE);
|
|
4737
|
-
const skillMdStat = await safeStat(skillMdPath);
|
|
4738
|
-
if (!skillMdStat?.isFile()) {
|
|
4739
|
-
continue;
|
|
4740
|
-
}
|
|
4741
|
-
let markdown;
|
|
4742
|
-
try {
|
|
4743
|
-
markdown = await fs.readFile(skillMdPath, "utf8");
|
|
4744
|
-
} catch {
|
|
4745
|
-
continue;
|
|
4746
|
-
}
|
|
4747
|
-
const parsed = parseSkillFrontMatter(markdown);
|
|
4748
|
-
if (!parsed) {
|
|
4749
|
-
continue;
|
|
4750
|
-
}
|
|
4751
|
-
skills.push({
|
|
4752
|
-
name: parsed.name,
|
|
4753
|
-
description: parsed.description,
|
|
4754
|
-
location,
|
|
4755
|
-
dirPath
|
|
4756
|
-
});
|
|
4757
|
-
}
|
|
4758
|
-
return skills;
|
|
4759
|
-
};
|
|
4760
4801
|
const getActivateSkillDescription = (skills) => {
|
|
4761
4802
|
const instructions = 'Execute a skill within the main conversation\n\n<skills_instructions>\nWhen users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.\n\nHow to invoke:\n- Use this tool with the skill name only (no arguments)\n- Example: {"skill": "pdf"}\n\nImportant:\n- When a skill is relevant, you must invoke this tool IMMEDIATELY as your first action\n- NEVER just announce or mention a skill in your text response without actually calling this tool\n- Only use skills listed in <available_skills> below\n- Do not invoke a skill that is already running\n</skills_instructions>';
|
|
4762
4803
|
const availableSkills = skills.map((skill) => {
|
|
@@ -4780,15 +4821,10 @@ ${availableSkills}
|
|
|
4780
4821
|
};
|
|
4781
4822
|
const createSkillsToolset = async (task, profile, promptContext) => {
|
|
4782
4823
|
const approvalManager = new ApprovalManager(task, profile);
|
|
4824
|
+
const skillManager = task.getSkillManager();
|
|
4783
4825
|
const generateActivateSkillDescription = async () => {
|
|
4784
|
-
const
|
|
4785
|
-
|
|
4786
|
-
const [globalSkills, projectSkills, builtinSkills] = await Promise.all([
|
|
4787
|
-
loadSkillsFromDir(globalSkillsDir, "global"),
|
|
4788
|
-
loadSkillsFromDir(projectSkillsDir, "project"),
|
|
4789
|
-
loadSkillsFromDir(AIDER_DESK_BUILTIN_SKILLS_DIR, "builtin")
|
|
4790
|
-
]);
|
|
4791
|
-
return getActivateSkillDescription([...projectSkills, ...globalSkills, ...builtinSkills]);
|
|
4826
|
+
const skills = await skillManager.loadAllSkills();
|
|
4827
|
+
return getActivateSkillDescription(skills);
|
|
4792
4828
|
};
|
|
4793
4829
|
const activateSkillTool = ai.tool({
|
|
4794
4830
|
description: await generateActivateSkillDescription(),
|
|
@@ -4806,30 +4842,21 @@ const createSkillsToolset = async (task, profile, promptContext) => {
|
|
|
4806
4842
|
if (!isApproved) {
|
|
4807
4843
|
return `Activating skill denied by user. Reason: ${userInput}`;
|
|
4808
4844
|
}
|
|
4809
|
-
const
|
|
4810
|
-
const projectSkillsDir = path.join(task.getProjectDir(), AIDER_DESK_DIR, SKILLS_DIR_NAME);
|
|
4811
|
-
const [globalSkills, projectSkills, builtinSkills] = await Promise.all([
|
|
4812
|
-
loadSkillsFromDir(globalSkillsDir, "global"),
|
|
4813
|
-
loadSkillsFromDir(projectSkillsDir, "project"),
|
|
4814
|
-
loadSkillsFromDir(AIDER_DESK_BUILTIN_SKILLS_DIR, "builtin")
|
|
4815
|
-
]);
|
|
4816
|
-
const allSkills = [...projectSkills, ...globalSkills, ...builtinSkills];
|
|
4845
|
+
const allSkills = await skillManager.loadAllSkills();
|
|
4817
4846
|
const requested = allSkills.find((s) => s.name === skill);
|
|
4818
4847
|
if (!requested) {
|
|
4819
4848
|
const available = allSkills.map((s) => s.name).join(", ");
|
|
4820
4849
|
return `Skill '${skill}' not found. Available skills: ${available || "(none)"}.`;
|
|
4821
4850
|
}
|
|
4822
|
-
const
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
content = await fs.readFile(skillMdPath, "utf8");
|
|
4826
|
-
} catch {
|
|
4827
|
-
return `Failed to read skill content from ${skillMdPath}.`;
|
|
4851
|
+
const content = await skillManager.getSkillContent(skill);
|
|
4852
|
+
if (!content) {
|
|
4853
|
+
return `Skill '${requested.name}' has no content or dirPath.`;
|
|
4828
4854
|
}
|
|
4855
|
+
const dirInfo = requested.dirPath ? `
|
|
4856
|
+
Skill directory is ${requested.dirPath} - use it as parent directory for relative paths mentioned in the skill description.` : "";
|
|
4829
4857
|
return `${content}
|
|
4830
4858
|
|
|
4831
|
-
Skill '${requested.name}' activated
|
|
4832
|
-
Skill directory is ${requested.dirPath} - use it as parent directory for relative paths mentioned in the skill description.`;
|
|
4859
|
+
Skill '${requested.name}' activated.${dirInfo}`;
|
|
4833
4860
|
}
|
|
4834
4861
|
});
|
|
4835
4862
|
const allTools = {
|
|
@@ -5266,17 +5293,6 @@ class McpManager {
|
|
|
5266
5293
|
}
|
|
5267
5294
|
}
|
|
5268
5295
|
}
|
|
5269
|
-
const THINKING_RESPONSE_STAR_TAG = "---\n► **THINKING**\n";
|
|
5270
|
-
const ANSWER_RESPONSE_START_TAG = "---\n► **ANSWER**\n";
|
|
5271
|
-
const extractPromptContextFromToolResult = (toolResult) => {
|
|
5272
|
-
if (toolResult && typeof toolResult === "object" && "promptContext" in toolResult) {
|
|
5273
|
-
return toolResult.promptContext;
|
|
5274
|
-
}
|
|
5275
|
-
return void 0;
|
|
5276
|
-
};
|
|
5277
|
-
const findLastUserMessage = (messages) => {
|
|
5278
|
-
return [...messages].reverse().find((msg) => msg.role === MessageRole.User);
|
|
5279
|
-
};
|
|
5280
5296
|
const extractReasoningMiddleware = function extractReasoningMiddleware2({
|
|
5281
5297
|
tagName,
|
|
5282
5298
|
separator = "\n"
|
|
@@ -6246,6 +6262,9 @@ ${fileList}`
|
|
|
6246
6262
|
options.abortSignal = abortSignal;
|
|
6247
6263
|
}
|
|
6248
6264
|
const result = await toolDef.execute(effectiveInput, options);
|
|
6265
|
+
if (options.abortSignal?.aborted) {
|
|
6266
|
+
return result;
|
|
6267
|
+
}
|
|
6249
6268
|
const toolFinishedExtensionResult = await this.extensionManager.dispatchEvent(
|
|
6250
6269
|
"onToolFinished",
|
|
6251
6270
|
{ toolName, input: effectiveInput, output: result },
|
|
@@ -6295,6 +6314,14 @@ ${fileList}`
|
|
|
6295
6314
|
}
|
|
6296
6315
|
);
|
|
6297
6316
|
logger.debug(`Tool ${toolDef.name} returned response`, { response });
|
|
6317
|
+
if (response && typeof response === "object" && "content" in response && Array.isArray(response.content)) {
|
|
6318
|
+
for (let i = 0; i < response.content.length; i++) {
|
|
6319
|
+
const part = response.content[i];
|
|
6320
|
+
if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") {
|
|
6321
|
+
part.text = await truncateToolResult(part.text);
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6298
6325
|
this.lastToolCallTime = Date.now();
|
|
6299
6326
|
return response;
|
|
6300
6327
|
} catch (error) {
|
|
@@ -6475,7 +6502,7 @@ ${fileList}`
|
|
|
6475
6502
|
this.abortControllers.set(controllerId, newController);
|
|
6476
6503
|
}
|
|
6477
6504
|
const effectiveAbortSignal = abortSignal || (controllerId ? this.abortControllers.get(controllerId)?.signal : void 0);
|
|
6478
|
-
const cacheControl = this.modelManager.getCacheControl(
|
|
6505
|
+
const cacheControl = this.modelManager.getCacheControl(provider, modelName);
|
|
6479
6506
|
const providerOptions = this.modelManager.getProviderOptions(provider, modelName);
|
|
6480
6507
|
const providerParameters = this.modelManager.getProviderParameters(provider, modelName);
|
|
6481
6508
|
const firstUserMessage = contextMessages.length > 0 ? contextMessages[0] : null;
|
|
@@ -7178,6 +7205,7 @@ ${fileList}`
|
|
|
7178
7205
|
}
|
|
7179
7206
|
const settings = this.store.getSettings();
|
|
7180
7207
|
const model = await this.modelManager.createLlm(provider, modelName, settings, projectDir, void 0, systemPrompt, void 0);
|
|
7208
|
+
const cacheControl = this.modelManager.getCacheControl(provider, modelName);
|
|
7181
7209
|
const providerOptions = this.modelManager.getProviderOptions(provider, modelName);
|
|
7182
7210
|
const providerParameters = this.modelManager.getProviderParameters(provider, modelName);
|
|
7183
7211
|
const controllerId = uuid.v4();
|
|
@@ -7202,7 +7230,7 @@ ${fileList}`
|
|
|
7202
7230
|
const result = await ai.generateText({
|
|
7203
7231
|
model,
|
|
7204
7232
|
system: systemPrompt,
|
|
7205
|
-
messages: await optimizeMessages(messages),
|
|
7233
|
+
messages: await optimizeMessages(messages, cacheControl),
|
|
7206
7234
|
abortSignal: effectiveAbortSignal,
|
|
7207
7235
|
providerOptions,
|
|
7208
7236
|
...providerParameters
|
|
@@ -7233,7 +7261,7 @@ ${fileList}`
|
|
|
7233
7261
|
const messages = await this.prepareMessages(task, profile, await task.getContextMessages(), await task.getContextFiles());
|
|
7234
7262
|
const toolSet = await this.getAvailableTools(task, "agent", profile, provider, profile.model);
|
|
7235
7263
|
const systemPrompt = await this.promptsManager.getSystemPrompt(this.store.getSettings(), task, profile);
|
|
7236
|
-
const cacheControl = this.modelManager.getCacheControl(
|
|
7264
|
+
const cacheControl = this.modelManager.getCacheControl(provider, profile.model);
|
|
7237
7265
|
const lastUserIndex = messages.map((m) => m.role).lastIndexOf("user");
|
|
7238
7266
|
const userRequestMessageIndex = lastUserIndex >= 0 ? lastUserIndex : 0;
|
|
7239
7267
|
const optimizedMessages = await optimizeMessages(
|
|
@@ -8467,9 +8495,10 @@ const UpdateOpenProjectsOrderSchema = zod.z.object({
|
|
|
8467
8495
|
const LoadInputHistorySchema = zod.z.object({
|
|
8468
8496
|
projectDir: zod.z.string().min(1, "Project directory is required")
|
|
8469
8497
|
});
|
|
8470
|
-
const
|
|
8498
|
+
const RedoUserPromptSchema = zod.z.object({
|
|
8471
8499
|
projectDir: zod.z.string().min(1, "Project directory is required"),
|
|
8472
8500
|
taskId: zod.z.string().min(1, "Task id is required"),
|
|
8501
|
+
messageId: zod.z.string().min(1, "Message id is required"),
|
|
8473
8502
|
mode: zod.z.string().min(1, "Mode is required"),
|
|
8474
8503
|
updatedPrompt: zod.z.string().optional()
|
|
8475
8504
|
});
|
|
@@ -8602,6 +8631,11 @@ const MergeWorktreeToMainSchema = zod.z.object({
|
|
|
8602
8631
|
targetBranch: zod.z.string().optional(),
|
|
8603
8632
|
commitMessage: zod.z.string().optional()
|
|
8604
8633
|
});
|
|
8634
|
+
const MergeAndSwitchToLocalSchema = zod.z.object({
|
|
8635
|
+
projectDir: zod.z.string().min(1, "Project directory is required"),
|
|
8636
|
+
taskId: zod.z.string().min(1, "Task id is required"),
|
|
8637
|
+
targetBranch: zod.z.string().optional()
|
|
8638
|
+
});
|
|
8605
8639
|
const ApplyUncommittedChangesSchema = zod.z.object({
|
|
8606
8640
|
projectDir: zod.z.string().min(1, "Project directory is required"),
|
|
8607
8641
|
taskId: zod.z.string().min(1, "Task id is required"),
|
|
@@ -8656,6 +8690,11 @@ const ResolveWorktreeConflictsWithAgentSchema = zod.z.object({
|
|
|
8656
8690
|
projectDir: zod.z.string().min(1, "Project directory is required"),
|
|
8657
8691
|
taskId: zod.z.string().min(1, "Task id is required")
|
|
8658
8692
|
});
|
|
8693
|
+
const RenameWorktreeBranchSchema = zod.z.object({
|
|
8694
|
+
projectDir: zod.z.string().min(1, "Project directory is required"),
|
|
8695
|
+
taskId: zod.z.string().min(1, "Task id is required"),
|
|
8696
|
+
newBranchName: zod.z.string().min(1, "New branch name is required")
|
|
8697
|
+
});
|
|
8659
8698
|
class ProjectApi extends BaseApi {
|
|
8660
8699
|
constructor(eventsHandler) {
|
|
8661
8700
|
super();
|
|
@@ -8684,13 +8723,13 @@ class ProjectApi extends BaseApi {
|
|
|
8684
8723
|
router.post(
|
|
8685
8724
|
"/project/redo-prompt",
|
|
8686
8725
|
this.handleRequest(async (req, res) => {
|
|
8687
|
-
const parsed = this.validateRequest(
|
|
8726
|
+
const parsed = this.validateRequest(RedoUserPromptSchema, req.body, res);
|
|
8688
8727
|
if (!parsed) {
|
|
8689
8728
|
return;
|
|
8690
8729
|
}
|
|
8691
|
-
const { projectDir, taskId, mode, updatedPrompt } = parsed;
|
|
8692
|
-
await this.eventsHandler.
|
|
8693
|
-
res.status(200).json({ message: "Redo
|
|
8730
|
+
const { projectDir, taskId, messageId, mode, updatedPrompt } = parsed;
|
|
8731
|
+
await this.eventsHandler.redoUserPrompt(projectDir, taskId, messageId, mode, updatedPrompt);
|
|
8732
|
+
res.status(200).json({ message: "Redo user prompt initiated" });
|
|
8694
8733
|
})
|
|
8695
8734
|
);
|
|
8696
8735
|
router.post(
|
|
@@ -9022,6 +9061,18 @@ class ProjectApi extends BaseApi {
|
|
|
9022
9061
|
res.status(200).json({ message: "Worktree merged" });
|
|
9023
9062
|
})
|
|
9024
9063
|
);
|
|
9064
|
+
router.post(
|
|
9065
|
+
"/project/worktree/merge-and-switch-to-local",
|
|
9066
|
+
this.handleRequest(async (req, res) => {
|
|
9067
|
+
const parsed = this.validateRequest(MergeAndSwitchToLocalSchema, req.body, res);
|
|
9068
|
+
if (!parsed) {
|
|
9069
|
+
return;
|
|
9070
|
+
}
|
|
9071
|
+
const { projectDir, taskId, targetBranch } = parsed;
|
|
9072
|
+
await this.eventsHandler.mergeAndSwitchToLocal(projectDir, taskId, targetBranch);
|
|
9073
|
+
res.status(200).json({ message: "Worktree merged and switched to local" });
|
|
9074
|
+
})
|
|
9075
|
+
);
|
|
9025
9076
|
router.post(
|
|
9026
9077
|
"/project/worktree/apply-uncommitted",
|
|
9027
9078
|
this.handleRequest(async (req, res) => {
|
|
@@ -9166,6 +9217,18 @@ class ProjectApi extends BaseApi {
|
|
|
9166
9217
|
res.status(200).json({ message: "Conflicts resolved" });
|
|
9167
9218
|
})
|
|
9168
9219
|
);
|
|
9220
|
+
router.post(
|
|
9221
|
+
"/project/worktree/rename-branch",
|
|
9222
|
+
this.handleRequest(async (req, res) => {
|
|
9223
|
+
const parsed = this.validateRequest(RenameWorktreeBranchSchema, req.body, res);
|
|
9224
|
+
if (!parsed) {
|
|
9225
|
+
return;
|
|
9226
|
+
}
|
|
9227
|
+
const { projectDir, taskId, newBranchName } = parsed;
|
|
9228
|
+
await this.eventsHandler.renameWorktreeBranch(projectDir, taskId, newBranchName);
|
|
9229
|
+
res.status(200).json({ message: "Branch renamed" });
|
|
9230
|
+
})
|
|
9231
|
+
);
|
|
9169
9232
|
router.post(
|
|
9170
9233
|
"/project/update-order",
|
|
9171
9234
|
this.handleRequest(async (req, res) => {
|
|
@@ -10119,6 +10182,80 @@ class ExtensionsApi extends BaseApi {
|
|
|
10119
10182
|
);
|
|
10120
10183
|
}
|
|
10121
10184
|
}
|
|
10185
|
+
const GetSkillsSchema = zod.z.object({
|
|
10186
|
+
projectDir: zod.z.string().min(1, "Project directory is required"),
|
|
10187
|
+
taskId: zod.z.string().min(1, "Task ID is required")
|
|
10188
|
+
});
|
|
10189
|
+
const ActivateSkillSchema = zod.z.object({
|
|
10190
|
+
projectDir: zod.z.string().min(1, "Project directory is required"),
|
|
10191
|
+
taskId: zod.z.string().min(1, "Task ID is required"),
|
|
10192
|
+
skillName: zod.z.string().min(1, "Skill name is required")
|
|
10193
|
+
});
|
|
10194
|
+
class SkillsApi extends BaseApi {
|
|
10195
|
+
constructor(eventsHandler) {
|
|
10196
|
+
super();
|
|
10197
|
+
this.eventsHandler = eventsHandler;
|
|
10198
|
+
}
|
|
10199
|
+
registerRoutes(router) {
|
|
10200
|
+
router.get(
|
|
10201
|
+
"/skills",
|
|
10202
|
+
this.handleRequest(async (req, res) => {
|
|
10203
|
+
const parsed = this.validateRequest(GetSkillsSchema, req.query, res);
|
|
10204
|
+
if (!parsed) {
|
|
10205
|
+
return;
|
|
10206
|
+
}
|
|
10207
|
+
const { projectDir, taskId } = parsed;
|
|
10208
|
+
const skills = await this.eventsHandler.getSkills(projectDir, taskId);
|
|
10209
|
+
res.status(200).json(skills);
|
|
10210
|
+
})
|
|
10211
|
+
);
|
|
10212
|
+
router.post(
|
|
10213
|
+
"/skills/activate",
|
|
10214
|
+
this.handleRequest(async (req, res) => {
|
|
10215
|
+
const parsed = this.validateRequest(ActivateSkillSchema, req.body, res);
|
|
10216
|
+
if (!parsed) {
|
|
10217
|
+
return;
|
|
10218
|
+
}
|
|
10219
|
+
const { projectDir, taskId, skillName } = parsed;
|
|
10220
|
+
await this.eventsHandler.activateSkill(projectDir, taskId, skillName);
|
|
10221
|
+
res.status(200).json({ success: true });
|
|
10222
|
+
})
|
|
10223
|
+
);
|
|
10224
|
+
router.post(
|
|
10225
|
+
"/skills/deactivate",
|
|
10226
|
+
this.handleRequest(async (req, res) => {
|
|
10227
|
+
const parsed = this.validateRequest(ActivateSkillSchema, req.body, res);
|
|
10228
|
+
if (!parsed) {
|
|
10229
|
+
return;
|
|
10230
|
+
}
|
|
10231
|
+
const { projectDir, taskId, skillName } = parsed;
|
|
10232
|
+
await this.eventsHandler.deactivateSkill(projectDir, taskId, skillName);
|
|
10233
|
+
res.status(200).json({ success: true });
|
|
10234
|
+
})
|
|
10235
|
+
);
|
|
10236
|
+
}
|
|
10237
|
+
}
|
|
10238
|
+
const createCorsOriginValidator = (store) => (origin, callback) => {
|
|
10239
|
+
if (CORS_ALLOWED_ORIGINS) {
|
|
10240
|
+
const origins = CORS_ALLOWED_ORIGINS.split(",").map((o) => o.trim()).filter(Boolean);
|
|
10241
|
+
if (!origin || origins.includes(origin)) {
|
|
10242
|
+
callback(null, true);
|
|
10243
|
+
} else {
|
|
10244
|
+
callback(null, false);
|
|
10245
|
+
}
|
|
10246
|
+
return;
|
|
10247
|
+
}
|
|
10248
|
+
const corsSettings = store.getSettings().server.cors;
|
|
10249
|
+
if (!corsSettings.enabled || corsSettings.origins.length === 0) {
|
|
10250
|
+
callback(null, false);
|
|
10251
|
+
return;
|
|
10252
|
+
}
|
|
10253
|
+
if (!origin || corsSettings.origins.includes(origin)) {
|
|
10254
|
+
callback(null, true);
|
|
10255
|
+
} else {
|
|
10256
|
+
callback(null, false);
|
|
10257
|
+
}
|
|
10258
|
+
};
|
|
10122
10259
|
const REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
10123
10260
|
class ServerController {
|
|
10124
10261
|
constructor(server, projectManager, eventsHandler, store, pythonInstaller) {
|
|
@@ -10191,11 +10328,15 @@ class ServerController {
|
|
|
10191
10328
|
new VoiceApi(this.eventsHandler).registerRoutes(apiRouter);
|
|
10192
10329
|
new TerminalApi(this.eventsHandler).registerRoutes(apiRouter);
|
|
10193
10330
|
new ExtensionsApi(this.eventsHandler).registerRoutes(apiRouter);
|
|
10331
|
+
new SkillsApi(this.eventsHandler).registerRoutes(apiRouter);
|
|
10194
10332
|
this.app.use("/api", apiRouter);
|
|
10195
10333
|
}
|
|
10334
|
+
setupCors() {
|
|
10335
|
+
this.app.use(cors({ origin: createCorsOriginValidator(this.store) }));
|
|
10336
|
+
}
|
|
10196
10337
|
init() {
|
|
10197
10338
|
this.app.use(express.json({ limit: "50mb" }));
|
|
10198
|
-
this.
|
|
10339
|
+
this.setupCors();
|
|
10199
10340
|
this.app.use(this.serverGuardMiddleware.bind(this));
|
|
10200
10341
|
this.app.use(this.timeoutMiddleware.bind(this));
|
|
10201
10342
|
this.app.use(this.basicAuthMiddleware.bind(this));
|
|
@@ -10462,9 +10603,10 @@ const isUnsubscribeEventsMessage = (message) => {
|
|
|
10462
10603
|
return message.action === "unsubscribe-events";
|
|
10463
10604
|
};
|
|
10464
10605
|
class ConnectorManager {
|
|
10465
|
-
constructor(httpServer, projectManager, eventManager) {
|
|
10606
|
+
constructor(httpServer, projectManager, eventManager, store) {
|
|
10466
10607
|
this.projectManager = projectManager;
|
|
10467
10608
|
this.eventManager = eventManager;
|
|
10609
|
+
this.store = store;
|
|
10468
10610
|
this.init(httpServer);
|
|
10469
10611
|
}
|
|
10470
10612
|
io = null;
|
|
@@ -10472,7 +10614,7 @@ class ConnectorManager {
|
|
|
10472
10614
|
init(httpServer) {
|
|
10473
10615
|
this.io = new socket_io.Server(httpServer, {
|
|
10474
10616
|
cors: {
|
|
10475
|
-
origin:
|
|
10617
|
+
origin: createCorsOriginValidator(this.store),
|
|
10476
10618
|
methods: ["GET", "POST"]
|
|
10477
10619
|
},
|
|
10478
10620
|
pingTimeout: 6e5,
|
|
@@ -11306,6 +11448,15 @@ class ContextManager {
|
|
|
11306
11448
|
}
|
|
11307
11449
|
return this.removeByToolCallId(messageId);
|
|
11308
11450
|
}
|
|
11451
|
+
removeMessagesByIds(ids) {
|
|
11452
|
+
for (const id of ids) {
|
|
11453
|
+
const index = this.messages.findIndex((msg) => msg.id === id);
|
|
11454
|
+
if (index !== -1) {
|
|
11455
|
+
this.messages.splice(index, 1);
|
|
11456
|
+
}
|
|
11457
|
+
}
|
|
11458
|
+
this.autosave();
|
|
11459
|
+
}
|
|
11309
11460
|
removeMessageByIndex(index, messageId) {
|
|
11310
11461
|
const removedIds = [];
|
|
11311
11462
|
removedIds.push(messageId);
|
|
@@ -11435,16 +11586,17 @@ class ContextManager {
|
|
|
11435
11586
|
}
|
|
11436
11587
|
this.autosave();
|
|
11437
11588
|
}
|
|
11438
|
-
|
|
11439
|
-
const
|
|
11440
|
-
if (
|
|
11441
|
-
logger.warn("No user message found to remove up to.", {
|
|
11442
|
-
taskId: this.taskId
|
|
11589
|
+
removeMessagesUpToUserMessage(messageId) {
|
|
11590
|
+
const userMessageIndex = this.messages.findIndex((msg) => msg.id === messageId && msg.role === MessageRole.User);
|
|
11591
|
+
if (userMessageIndex === -1) {
|
|
11592
|
+
logger.warn("No user message found with the given ID to remove up to.", {
|
|
11593
|
+
taskId: this.taskId,
|
|
11594
|
+
messageId
|
|
11443
11595
|
});
|
|
11444
11596
|
return [];
|
|
11445
11597
|
}
|
|
11446
|
-
const removedMessages = this.messages.splice(
|
|
11447
|
-
logger.debug(`Task ${this.taskId}: Removed ${removedMessages.length} messages up to
|
|
11598
|
+
const removedMessages = this.messages.splice(userMessageIndex);
|
|
11599
|
+
logger.debug(`Task ${this.taskId}: Removed ${removedMessages.length} messages up to user message ${messageId}. Total messages: ${this.messages.length}`);
|
|
11448
11600
|
this.autosave();
|
|
11449
11601
|
return removedMessages;
|
|
11450
11602
|
}
|
|
@@ -12584,6 +12736,156 @@ class AiderManager {
|
|
|
12584
12736
|
connectors.filter((connector) => connector.listenTo.includes("update-env-vars")).forEach((connector) => connector.sendUpdateEnvVarsMessage(environmentVariables));
|
|
12585
12737
|
}
|
|
12586
12738
|
}
|
|
12739
|
+
const SKILLS_DIR_NAME = "skills";
|
|
12740
|
+
const SKILL_MARKDOWN_FILE = "SKILL.md";
|
|
12741
|
+
const TOOL_NAME = `${SKILLS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${SKILLS_TOOL_ACTIVATE_SKILL}`;
|
|
12742
|
+
const parseSkillFrontMatter = (markdown) => {
|
|
12743
|
+
const parsed = yamlFrontMatter.loadFront(markdown);
|
|
12744
|
+
const name = typeof parsed.name === "string" ? parsed.name : void 0;
|
|
12745
|
+
const description = typeof parsed.description === "string" ? parsed.description : void 0;
|
|
12746
|
+
if (!name || !description) {
|
|
12747
|
+
return null;
|
|
12748
|
+
}
|
|
12749
|
+
return { name, description };
|
|
12750
|
+
};
|
|
12751
|
+
const safeReadDir = async (dirPath) => {
|
|
12752
|
+
try {
|
|
12753
|
+
return await fs.readdir(dirPath);
|
|
12754
|
+
} catch {
|
|
12755
|
+
return [];
|
|
12756
|
+
}
|
|
12757
|
+
};
|
|
12758
|
+
const safeStat = async (filePath) => {
|
|
12759
|
+
try {
|
|
12760
|
+
return await fs.stat(filePath);
|
|
12761
|
+
} catch {
|
|
12762
|
+
return null;
|
|
12763
|
+
}
|
|
12764
|
+
};
|
|
12765
|
+
const loadSkillsFromDir = async (skillsRootDir, location) => {
|
|
12766
|
+
const entries = await safeReadDir(skillsRootDir);
|
|
12767
|
+
const skills = [];
|
|
12768
|
+
for (const entry of entries) {
|
|
12769
|
+
const dirPath = path.join(skillsRootDir, entry);
|
|
12770
|
+
const stat = await safeStat(dirPath);
|
|
12771
|
+
if (!stat?.isDirectory()) {
|
|
12772
|
+
continue;
|
|
12773
|
+
}
|
|
12774
|
+
const skillMdPath = path.join(dirPath, SKILL_MARKDOWN_FILE);
|
|
12775
|
+
const skillMdStat = await safeStat(skillMdPath);
|
|
12776
|
+
if (!skillMdStat?.isFile()) {
|
|
12777
|
+
continue;
|
|
12778
|
+
}
|
|
12779
|
+
let markdown;
|
|
12780
|
+
try {
|
|
12781
|
+
markdown = await fs.readFile(skillMdPath, "utf8");
|
|
12782
|
+
} catch {
|
|
12783
|
+
continue;
|
|
12784
|
+
}
|
|
12785
|
+
const parsed = parseSkillFrontMatter(markdown);
|
|
12786
|
+
if (!parsed) {
|
|
12787
|
+
continue;
|
|
12788
|
+
}
|
|
12789
|
+
skills.push({
|
|
12790
|
+
name: parsed.name,
|
|
12791
|
+
description: parsed.description,
|
|
12792
|
+
location,
|
|
12793
|
+
dirPath
|
|
12794
|
+
});
|
|
12795
|
+
}
|
|
12796
|
+
return skills;
|
|
12797
|
+
};
|
|
12798
|
+
class SkillManager {
|
|
12799
|
+
extensionManager;
|
|
12800
|
+
projectDir;
|
|
12801
|
+
constructor(projectDir, extensionManager) {
|
|
12802
|
+
this.projectDir = projectDir;
|
|
12803
|
+
this.extensionManager = extensionManager;
|
|
12804
|
+
}
|
|
12805
|
+
async loadAllSkills() {
|
|
12806
|
+
const globalSkillsDir = path.join(os.homedir(), AIDER_DESK_DIR, SKILLS_DIR_NAME);
|
|
12807
|
+
const projectSkillsDir = path.join(this.projectDir, AIDER_DESK_DIR, SKILLS_DIR_NAME);
|
|
12808
|
+
const extensionSkills = this.extensionManager ? this.extensionManager.getSkills({ baseDir: this.projectDir }, { getTaskDir: () => this.projectDir }) : [];
|
|
12809
|
+
const [globalSkills, projectSkills, builtinSkills] = await Promise.all([
|
|
12810
|
+
loadSkillsFromDir(globalSkillsDir, "global"),
|
|
12811
|
+
loadSkillsFromDir(projectSkillsDir, "project"),
|
|
12812
|
+
loadSkillsFromDir(AIDER_DESK_BUILTIN_SKILLS_DIR, "builtin")
|
|
12813
|
+
]);
|
|
12814
|
+
return [...projectSkills, ...globalSkills, ...builtinSkills, ...extensionSkills];
|
|
12815
|
+
}
|
|
12816
|
+
async getSkills(contextMessages) {
|
|
12817
|
+
const skills = await this.loadAllSkills();
|
|
12818
|
+
const activatedSkillNames = contextMessages ? this.getActivatedSkillNames(contextMessages) : /* @__PURE__ */ new Set();
|
|
12819
|
+
return skills.map((skill) => ({
|
|
12820
|
+
...skill,
|
|
12821
|
+
activated: activatedSkillNames.has(skill.name)
|
|
12822
|
+
}));
|
|
12823
|
+
}
|
|
12824
|
+
async getSkillContent(skillName) {
|
|
12825
|
+
const skills = await this.loadAllSkills();
|
|
12826
|
+
const skill = skills.find((s) => s.name === skillName);
|
|
12827
|
+
if (!skill) {
|
|
12828
|
+
return null;
|
|
12829
|
+
}
|
|
12830
|
+
if (skill.content) {
|
|
12831
|
+
return skill.content;
|
|
12832
|
+
}
|
|
12833
|
+
if (skill.dirPath) {
|
|
12834
|
+
const skillMdPath = path.join(skill.dirPath, SKILL_MARKDOWN_FILE);
|
|
12835
|
+
try {
|
|
12836
|
+
return await fs.readFile(skillMdPath, "utf8");
|
|
12837
|
+
} catch {
|
|
12838
|
+
return null;
|
|
12839
|
+
}
|
|
12840
|
+
}
|
|
12841
|
+
return null;
|
|
12842
|
+
}
|
|
12843
|
+
getActivatedSkillNames(contextMessages) {
|
|
12844
|
+
const activatedNames = /* @__PURE__ */ new Set();
|
|
12845
|
+
for (const message of contextMessages) {
|
|
12846
|
+
if (message.role === "assistant" && Array.isArray(message.content)) {
|
|
12847
|
+
for (const part of message.content) {
|
|
12848
|
+
if (part.type === "tool-call" && part.toolName === TOOL_NAME && part.input?.skill) {
|
|
12849
|
+
activatedNames.add(part.input.skill);
|
|
12850
|
+
}
|
|
12851
|
+
}
|
|
12852
|
+
}
|
|
12853
|
+
}
|
|
12854
|
+
return activatedNames;
|
|
12855
|
+
}
|
|
12856
|
+
buildActivateSkillMessages(skillName, content) {
|
|
12857
|
+
const toolCallId = uuid.v4();
|
|
12858
|
+
const assistantMessage = {
|
|
12859
|
+
id: uuid.v4(),
|
|
12860
|
+
role: "assistant",
|
|
12861
|
+
content: [
|
|
12862
|
+
{
|
|
12863
|
+
type: "text",
|
|
12864
|
+
text: "User requested the skill activation."
|
|
12865
|
+
},
|
|
12866
|
+
{
|
|
12867
|
+
type: "tool-call",
|
|
12868
|
+
toolCallId,
|
|
12869
|
+
toolName: TOOL_NAME,
|
|
12870
|
+
input: { skill: skillName }
|
|
12871
|
+
}
|
|
12872
|
+
]
|
|
12873
|
+
};
|
|
12874
|
+
const toolMessage = {
|
|
12875
|
+
id: uuid.v4(),
|
|
12876
|
+
role: "tool",
|
|
12877
|
+
content: [
|
|
12878
|
+
{
|
|
12879
|
+
type: "tool-result",
|
|
12880
|
+
toolCallId,
|
|
12881
|
+
toolName: TOOL_NAME,
|
|
12882
|
+
output: { type: "text", value: content }
|
|
12883
|
+
}
|
|
12884
|
+
]
|
|
12885
|
+
};
|
|
12886
|
+
return [assistantMessage, toolMessage];
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12587
12889
|
class GitError extends Error {
|
|
12588
12890
|
name = "GitError";
|
|
12589
12891
|
gitCommands;
|
|
@@ -12661,7 +12963,7 @@ class WorktreeManager {
|
|
|
12661
12963
|
await execWithShellPath('git commit -m "Initial commit" --allow-empty', { cwd: projectPath });
|
|
12662
12964
|
}
|
|
12663
12965
|
let baseCommit;
|
|
12664
|
-
let
|
|
12966
|
+
let newBranchName;
|
|
12665
12967
|
const baseRef = baseBranch;
|
|
12666
12968
|
if (branch) {
|
|
12667
12969
|
let branchExists = false;
|
|
@@ -12675,7 +12977,7 @@ class WorktreeManager {
|
|
|
12675
12977
|
baseCommit = (await execWithShellPath(`git rev-parse ${branch}`, {
|
|
12676
12978
|
cwd: projectPath
|
|
12677
12979
|
})).stdout.trim();
|
|
12678
|
-
|
|
12980
|
+
newBranchName = branch;
|
|
12679
12981
|
} else {
|
|
12680
12982
|
if (baseBranch !== "HEAD") {
|
|
12681
12983
|
try {
|
|
@@ -12688,7 +12990,7 @@ class WorktreeManager {
|
|
|
12688
12990
|
cwd: projectPath
|
|
12689
12991
|
})).stdout.trim();
|
|
12690
12992
|
await execWithShellPath(`git worktree add -b ${branch} "${worktreePath}" ${baseRef}`, { cwd: projectPath });
|
|
12691
|
-
|
|
12993
|
+
newBranchName = branch;
|
|
12692
12994
|
}
|
|
12693
12995
|
} else {
|
|
12694
12996
|
await execWithShellPath(`git worktree add "${worktreePath}" ${baseRef}`, { cwd: projectPath });
|
|
@@ -12697,25 +12999,38 @@ class WorktreeManager {
|
|
|
12697
12999
|
})).stdout.trim();
|
|
12698
13000
|
if (baseRef === "HEAD") {
|
|
12699
13001
|
try {
|
|
12700
|
-
|
|
13002
|
+
newBranchName = (await execWithShellPath("git rev-parse --abbrev-ref HEAD", {
|
|
12701
13003
|
cwd: projectPath
|
|
12702
13004
|
})).stdout.trim();
|
|
12703
|
-
if (
|
|
12704
|
-
|
|
13005
|
+
if (newBranchName === "HEAD") {
|
|
13006
|
+
newBranchName = "DETACHED HEAD";
|
|
12705
13007
|
}
|
|
12706
13008
|
} catch {
|
|
12707
|
-
|
|
13009
|
+
newBranchName = "DETACHED HEAD";
|
|
12708
13010
|
}
|
|
12709
13011
|
} else {
|
|
12710
|
-
|
|
13012
|
+
newBranchName = `${baseRef} (DETACHED)`;
|
|
12711
13013
|
}
|
|
12712
13014
|
logger.info(`Worktree created in DETACHED HEAD mode from commit: ${baseCommit}`);
|
|
12713
13015
|
}
|
|
13016
|
+
let resolvedBaseBranch;
|
|
13017
|
+
if (baseRef === "HEAD") {
|
|
13018
|
+
try {
|
|
13019
|
+
const result = await execWithShellPath("git rev-parse --abbrev-ref HEAD", { cwd: projectPath });
|
|
13020
|
+
const headBranch = result.stdout.trim();
|
|
13021
|
+
resolvedBaseBranch = headBranch !== "HEAD" ? headBranch : void 0;
|
|
13022
|
+
} catch {
|
|
13023
|
+
resolvedBaseBranch = void 0;
|
|
13024
|
+
}
|
|
13025
|
+
} else {
|
|
13026
|
+
resolvedBaseBranch = baseRef;
|
|
13027
|
+
}
|
|
12714
13028
|
logger.info(`Worktree created successfully at: ${worktreePath}`);
|
|
12715
13029
|
return {
|
|
12716
13030
|
path: worktreePath,
|
|
12717
13031
|
baseCommit,
|
|
12718
|
-
baseBranch:
|
|
13032
|
+
baseBranch: resolvedBaseBranch,
|
|
13033
|
+
branch: newBranchName
|
|
12719
13034
|
};
|
|
12720
13035
|
} catch (error) {
|
|
12721
13036
|
logger.error("Failed to create worktree:", error);
|
|
@@ -12723,6 +13038,29 @@ class WorktreeManager {
|
|
|
12723
13038
|
}
|
|
12724
13039
|
});
|
|
12725
13040
|
}
|
|
13041
|
+
async renameBranch(projectPath, oldBranch, newBranch) {
|
|
13042
|
+
try {
|
|
13043
|
+
const finalBranchName = await this.findUniqueBranchName(projectPath, newBranch);
|
|
13044
|
+
await execWithShellPath(`git branch -m ${oldBranch} ${finalBranchName}`, { cwd: projectPath });
|
|
13045
|
+
logger.info(`Renamed branch: ${oldBranch} -> ${finalBranchName}`);
|
|
13046
|
+
return finalBranchName;
|
|
13047
|
+
} catch (error) {
|
|
13048
|
+
logger.warn(`Failed to rename branch ${oldBranch} to ${newBranch}:`, error);
|
|
13049
|
+
return newBranch;
|
|
13050
|
+
}
|
|
13051
|
+
}
|
|
13052
|
+
async findUniqueBranchName(projectPath, baseName) {
|
|
13053
|
+
const existingBranches = await this.listBranches(projectPath);
|
|
13054
|
+
const branchNames = new Set(existingBranches.map((b) => b.name));
|
|
13055
|
+
if (!branchNames.has(baseName)) {
|
|
13056
|
+
return baseName;
|
|
13057
|
+
}
|
|
13058
|
+
let suffix = 2;
|
|
13059
|
+
while (branchNames.has(`${baseName}-${suffix}`)) {
|
|
13060
|
+
suffix++;
|
|
13061
|
+
}
|
|
13062
|
+
return `${baseName}-${suffix}`;
|
|
13063
|
+
}
|
|
12726
13064
|
async createSymlinks(projectPath, worktreePath, folderNames) {
|
|
12727
13065
|
if (folderNames.length === 0) {
|
|
12728
13066
|
logger.debug("No symlink folders configured, skipping symlink creation");
|
|
@@ -12776,14 +13114,14 @@ class WorktreeManager {
|
|
|
12776
13114
|
return await withLock(`worktree-remove-${projectDir}-${worktree.path}`, async () => {
|
|
12777
13115
|
try {
|
|
12778
13116
|
await execWithShellPath(`git worktree remove "${worktree.path}" --force`, { cwd: projectDir });
|
|
12779
|
-
if (worktree.
|
|
13117
|
+
if (worktree.branch) {
|
|
12780
13118
|
try {
|
|
12781
|
-
await execWithShellPath(`git branch -D ${worktree.
|
|
13119
|
+
await execWithShellPath(`git branch -D ${worktree.branch}`, {
|
|
12782
13120
|
cwd: projectDir
|
|
12783
13121
|
});
|
|
12784
|
-
logger.info(`Deleted task branch: ${worktree.
|
|
13122
|
+
logger.info(`Deleted task branch: ${worktree.branch}`);
|
|
12785
13123
|
} catch (error) {
|
|
12786
|
-
logger.debug(`Could not delete branch ${worktree.
|
|
13124
|
+
logger.debug(`Could not delete branch ${worktree.branch}:`, error);
|
|
12787
13125
|
}
|
|
12788
13126
|
}
|
|
12789
13127
|
} catch (error) {
|
|
@@ -12810,12 +13148,12 @@ class WorktreeManager {
|
|
|
12810
13148
|
...currentWorktree
|
|
12811
13149
|
});
|
|
12812
13150
|
}
|
|
12813
|
-
currentWorktree = { path: line.substring(9),
|
|
13151
|
+
currentWorktree = { path: line.substring(9), branch: "" };
|
|
12814
13152
|
} else if (line.startsWith("branch ")) {
|
|
12815
13153
|
currentWorktree = {
|
|
12816
13154
|
...currentWorktree || {},
|
|
12817
13155
|
path: currentWorktree ? currentWorktree.path : "",
|
|
12818
|
-
|
|
13156
|
+
branch: line.substring(7).replace("refs/heads/", "")
|
|
12819
13157
|
};
|
|
12820
13158
|
} else if (line.startsWith("HEAD ")) {
|
|
12821
13159
|
currentWorktree = {
|
|
@@ -12827,7 +13165,7 @@ class WorktreeManager {
|
|
|
12827
13165
|
currentWorktree = {
|
|
12828
13166
|
...currentWorktree || {},
|
|
12829
13167
|
path: currentWorktree ? currentWorktree.path : "",
|
|
12830
|
-
|
|
13168
|
+
branch: void 0
|
|
12831
13169
|
};
|
|
12832
13170
|
} else if (line.startsWith("prunable")) {
|
|
12833
13171
|
currentWorktree = {
|
|
@@ -12856,7 +13194,7 @@ class WorktreeManager {
|
|
|
12856
13194
|
cwd: projectPath
|
|
12857
13195
|
});
|
|
12858
13196
|
const worktrees = await this.listWorktrees(projectPath);
|
|
12859
|
-
const worktreeBranches = new Set(worktrees.map((w) => w.
|
|
13197
|
+
const worktreeBranches = new Set(worktrees.map((w) => w.branch));
|
|
12860
13198
|
const branches = [];
|
|
12861
13199
|
const lines = branchOutput.split("\n").filter((line) => line.trim());
|
|
12862
13200
|
for (const line of lines) {
|
|
@@ -12910,6 +13248,22 @@ class WorktreeManager {
|
|
|
12910
13248
|
logger.warn("getEffectiveMainBranch is deprecated, use getProjectMainBranch instead");
|
|
12911
13249
|
return await this.getProjectMainBranch(project.path);
|
|
12912
13250
|
}
|
|
13251
|
+
async getHeadCommit(worktreePath) {
|
|
13252
|
+
try {
|
|
13253
|
+
const { stdout } = await execWithShellPath("git rev-parse HEAD", { cwd: worktreePath });
|
|
13254
|
+
return stdout.trim();
|
|
13255
|
+
} catch {
|
|
13256
|
+
return void 0;
|
|
13257
|
+
}
|
|
13258
|
+
}
|
|
13259
|
+
async getBranchesContainingCommit(projectPath, commit) {
|
|
13260
|
+
try {
|
|
13261
|
+
const { stdout } = await execWithShellPath(`git branch --contains ${commit} --format='%(refname:short)'`, { cwd: projectPath });
|
|
13262
|
+
return stdout.trim().split("\n").map((b) => b.trim()).filter((b) => b);
|
|
13263
|
+
} catch {
|
|
13264
|
+
return [];
|
|
13265
|
+
}
|
|
13266
|
+
}
|
|
12913
13267
|
async hasChangesToRebase(worktreePath, mainBranch) {
|
|
12914
13268
|
try {
|
|
12915
13269
|
let stdout = "0";
|
|
@@ -12991,7 +13345,7 @@ class WorktreeManager {
|
|
|
12991
13345
|
};
|
|
12992
13346
|
}
|
|
12993
13347
|
}
|
|
12994
|
-
async rebaseMainIntoWorktree(worktreePath, mainBranch) {
|
|
13348
|
+
async rebaseMainIntoWorktree(worktreePath, mainBranch, baseCommit) {
|
|
12995
13349
|
return await withLock(`git-rebase-${worktreePath}`, async () => {
|
|
12996
13350
|
const executedCommands = [];
|
|
12997
13351
|
let lastOutput = "";
|
|
@@ -13010,7 +13364,7 @@ class WorktreeManager {
|
|
|
13010
13364
|
});
|
|
13011
13365
|
logger.info("Created temporary commit for uncommitted changes");
|
|
13012
13366
|
}
|
|
13013
|
-
const command = `git rebase ${mainBranch}`;
|
|
13367
|
+
const command = baseCommit ? `git rebase --onto ${mainBranch} ${baseCommit}` : `git rebase ${mainBranch}`;
|
|
13014
13368
|
executedCommands.push(`${command} (in ${worktreePath})`);
|
|
13015
13369
|
const rebaseResult = await execWithShellPath(command, {
|
|
13016
13370
|
cwd: worktreePath
|
|
@@ -13085,7 +13439,7 @@ class WorktreeManager {
|
|
|
13085
13439
|
}
|
|
13086
13440
|
});
|
|
13087
13441
|
}
|
|
13088
|
-
async squashAndMergeWorktreeToMain(projectPath, worktreePath, mainBranch, commitMessage) {
|
|
13442
|
+
async squashAndMergeWorktreeToMain(projectPath, worktreePath, mainBranch, commitMessage, baseCommit) {
|
|
13089
13443
|
const executedCommands = [];
|
|
13090
13444
|
let lastOutput = "";
|
|
13091
13445
|
try {
|
|
@@ -13101,8 +13455,8 @@ class WorktreeManager {
|
|
|
13101
13455
|
if (!commits.trim()) {
|
|
13102
13456
|
return;
|
|
13103
13457
|
}
|
|
13104
|
-
command = `git rebase ${mainBranch}`;
|
|
13105
|
-
executedCommands.push(
|
|
13458
|
+
command = baseCommit ? `git rebase --onto ${mainBranch} ${baseCommit}` : `git rebase ${mainBranch}`;
|
|
13459
|
+
executedCommands.push(`${command} (in ${worktreePath})`);
|
|
13106
13460
|
try {
|
|
13107
13461
|
const rebaseWorktreeResult = await execWithShellPath(command, {
|
|
13108
13462
|
cwd: worktreePath
|
|
@@ -13411,10 +13765,12 @@ Git output: ${err.stderr || err.stdout || err.message}`
|
|
|
13411
13765
|
*/
|
|
13412
13766
|
async hasUncommittedChanges(path2) {
|
|
13413
13767
|
try {
|
|
13414
|
-
const { stdout } = await execWithShellPath("git status --porcelain=v1", {
|
|
13768
|
+
const { stdout } = await execWithShellPath("git status --porcelain=v1 -z", {
|
|
13415
13769
|
cwd: path2
|
|
13416
13770
|
});
|
|
13417
|
-
|
|
13771
|
+
const filePaths = this.parseGitStatusEntries(stdout);
|
|
13772
|
+
const realChanges = filePaths.filter((filePath) => !this.isSymlinkPath(path2, filePath));
|
|
13773
|
+
return realChanges.length > 0;
|
|
13418
13774
|
} catch (error) {
|
|
13419
13775
|
logger.error("Failed to check for uncommitted changes:", error);
|
|
13420
13776
|
return false;
|
|
@@ -13506,7 +13862,7 @@ Git output: ${err.stderr || err.stdout || err.message}`
|
|
|
13506
13862
|
* Merge worktree to main branch with uncommitted changes support
|
|
13507
13863
|
* Returns MergeState for potential revert
|
|
13508
13864
|
*/
|
|
13509
|
-
async mergeWorktreeToMainWithUncommitted(projectPath, taskId, worktreePath, squash, commitMessage, targetBranch, symlinkFolders = []) {
|
|
13865
|
+
async mergeWorktreeToMainWithUncommitted(projectPath, taskId, worktreePath, squash, commitMessage, targetBranch, symlinkFolders = [], baseCommit) {
|
|
13510
13866
|
return await withLock(`git-merge-worktree-${worktreePath}`, async () => {
|
|
13511
13867
|
const timestamp = Date.now();
|
|
13512
13868
|
const worktreeStashId = `worktree-${taskId.length > 24 ? taskId.substring(24) : taskId}-merge-${timestamp}`;
|
|
@@ -13534,7 +13890,7 @@ Git output: ${err.stderr || err.stdout || err.message}`
|
|
|
13534
13890
|
if (!commitMessage) {
|
|
13535
13891
|
throw new Error("Commit message is required for squash merge");
|
|
13536
13892
|
}
|
|
13537
|
-
await this.squashAndMergeWorktreeToMain(projectPath, worktreePath, mainBranch, commitMessage);
|
|
13893
|
+
await this.squashAndMergeWorktreeToMain(projectPath, worktreePath, mainBranch, commitMessage, baseCommit);
|
|
13538
13894
|
} else {
|
|
13539
13895
|
await this.mergeWorktreeToMain(projectPath, worktreePath, mainBranch);
|
|
13540
13896
|
}
|
|
@@ -13588,10 +13944,15 @@ Git output: ${err.stderr || err.stdout || err.message}`
|
|
|
13588
13944
|
* Check if worktree has uncommitted changes or unmerged commits
|
|
13589
13945
|
* Returns information about unsaved work in the worktree
|
|
13590
13946
|
*/
|
|
13591
|
-
async checkWorktreeForUnmergedWork(projectPath, worktreePath, targetBranch) {
|
|
13947
|
+
async checkWorktreeForUnmergedWork(projectPath, worktreePath, targetBranch, symlinkFolders = []) {
|
|
13592
13948
|
try {
|
|
13593
|
-
const hasUncommittedChanges = await this.hasUncommittedChanges(worktreePath);
|
|
13594
13949
|
const { files: uncommittedFiles } = await this.getUncommittedFiles(worktreePath);
|
|
13950
|
+
const filteredUncommittedFiles = uncommittedFiles.filter((file) => {
|
|
13951
|
+
const pathPart = file.replace(/^..\s*/, "").trim();
|
|
13952
|
+
const normalizedPath = pathPart.replace(/\\/g, "/");
|
|
13953
|
+
return !symlinkFolders.some((folder) => normalizedPath.startsWith(`${folder}/`) || normalizedPath === folder);
|
|
13954
|
+
});
|
|
13955
|
+
const actualHasUncommittedChanges = filteredUncommittedFiles.length > 0;
|
|
13595
13956
|
const effectiveTargetBranch = targetBranch || await this.getProjectMainBranch(projectPath);
|
|
13596
13957
|
let unmergedCommitCount = 0;
|
|
13597
13958
|
let unmergedCommits = [];
|
|
@@ -13605,11 +13966,11 @@ Git output: ${err.stderr || err.stdout || err.message}`
|
|
|
13605
13966
|
}
|
|
13606
13967
|
const hasUnmergedCommits = unmergedCommitCount > 0;
|
|
13607
13968
|
return {
|
|
13608
|
-
hasUncommittedChanges,
|
|
13969
|
+
hasUncommittedChanges: actualHasUncommittedChanges,
|
|
13609
13970
|
hasUnmergedCommits,
|
|
13610
13971
|
unmergedCommitCount,
|
|
13611
13972
|
unmergedCommits,
|
|
13612
|
-
uncommittedFiles
|
|
13973
|
+
uncommittedFiles: filteredUncommittedFiles
|
|
13613
13974
|
};
|
|
13614
13975
|
} catch (error) {
|
|
13615
13976
|
logger.error("Failed to check worktree for unmerged work:", error);
|
|
@@ -13634,12 +13995,25 @@ Git output: ${err.stderr || err.stdout || err.message}`
|
|
|
13634
13995
|
const { stdout } = await execWithShellPath("git status --porcelain=v1 -z", {
|
|
13635
13996
|
cwd: worktreePath
|
|
13636
13997
|
});
|
|
13637
|
-
const
|
|
13998
|
+
const filePaths = this.parseGitStatusEntries(stdout);
|
|
13999
|
+
const files = filePaths.filter((filePath) => !this.isSymlinkPath(worktreePath, filePath));
|
|
13638
14000
|
return {
|
|
13639
14001
|
count: files.length,
|
|
13640
14002
|
files: Array.from(new Set(files))
|
|
13641
14003
|
};
|
|
13642
14004
|
}
|
|
14005
|
+
parseGitStatusEntries(stdout) {
|
|
14006
|
+
return stdout.split("\0").map((entry) => entry.substring(3).trim()).filter((filePath) => filePath.length > 0);
|
|
14007
|
+
}
|
|
14008
|
+
isSymlinkPath(cwd, filePath) {
|
|
14009
|
+
try {
|
|
14010
|
+
const fullPath = path.join(cwd, filePath);
|
|
14011
|
+
const stat = fs$1.lstatSync(fullPath);
|
|
14012
|
+
return stat.isSymbolicLink();
|
|
14013
|
+
} catch {
|
|
14014
|
+
return false;
|
|
14015
|
+
}
|
|
14016
|
+
}
|
|
13643
14017
|
/**
|
|
13644
14018
|
* Get updated files with per-commit and uncommitted diffs.
|
|
13645
14019
|
*
|
|
@@ -14282,6 +14656,7 @@ class Task {
|
|
|
14282
14656
|
};
|
|
14283
14657
|
this.taskDataPath = path.join(this.project.baseDir, AIDER_DESK_TASKS_DIR, this.taskId, "settings.json");
|
|
14284
14658
|
this.contextManager = new ContextManager(this, this.taskId);
|
|
14659
|
+
this.skillManager = new SkillManager(project.baseDir, extensionManager);
|
|
14285
14660
|
this.agent = new Agent(
|
|
14286
14661
|
this.store,
|
|
14287
14662
|
this.agentProfileManager,
|
|
@@ -14327,6 +14702,7 @@ class Task {
|
|
|
14327
14702
|
contextManager;
|
|
14328
14703
|
agent;
|
|
14329
14704
|
aiderManager;
|
|
14705
|
+
skillManager;
|
|
14330
14706
|
task;
|
|
14331
14707
|
async getTaskAgentProfile() {
|
|
14332
14708
|
let agentProfileId = this.task.agentProfileId;
|
|
@@ -14380,10 +14756,56 @@ class Task {
|
|
|
14380
14756
|
* Generate a branch name from task name (first 7 words, separated by '-')
|
|
14381
14757
|
*/
|
|
14382
14758
|
generateBranchName() {
|
|
14759
|
+
const settings = this.store.getSettings();
|
|
14760
|
+
const branchPrefix = settings.taskSettings.worktreeBranchPrefix || WORKTREE_BRANCH_PREFIX;
|
|
14383
14761
|
const words = this.task.name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").split(/\s+/).filter((word) => word.length > 0).slice(0, 7);
|
|
14384
14762
|
const branchName = words.join("-");
|
|
14385
14763
|
const cleanBranchName = branchName.replace(/^[.-]+/, "").replace(/-+/g, "-").replace(/-$/, "");
|
|
14386
|
-
|
|
14764
|
+
const fallbackId = /^[0-9a-f]{8}-/i.test(this.taskId) ? this.taskId.split("-")[0] : this.taskId;
|
|
14765
|
+
return `${branchPrefix}${cleanBranchName || fallbackId}`;
|
|
14766
|
+
}
|
|
14767
|
+
async renameWorktreeBranchIfNeeded() {
|
|
14768
|
+
if (this.task.workingMode !== "worktree" || !this.task.worktree?.branch) {
|
|
14769
|
+
return;
|
|
14770
|
+
}
|
|
14771
|
+
const settings = this.store.getSettings();
|
|
14772
|
+
if (!settings.taskSettings.renameBranchOnNameGeneration) {
|
|
14773
|
+
return;
|
|
14774
|
+
}
|
|
14775
|
+
const oldBranch = this.task.worktree.branch;
|
|
14776
|
+
const newBranch = this.generateBranchName();
|
|
14777
|
+
if (oldBranch === newBranch) {
|
|
14778
|
+
return;
|
|
14779
|
+
}
|
|
14780
|
+
await this.renameWorktreeBranch(newBranch);
|
|
14781
|
+
}
|
|
14782
|
+
/**
|
|
14783
|
+
* @deprecated This migration ensures older task data has the `branch` field
|
|
14784
|
+
* and `baseBranch` stores the branch the worktree was created from.
|
|
14785
|
+
* Can be removed once all users have migrated past v0.64.0.
|
|
14786
|
+
*/
|
|
14787
|
+
async migrateWorktreeData() {
|
|
14788
|
+
if (!this.task.worktree || this.task.worktree.branch) {
|
|
14789
|
+
return;
|
|
14790
|
+
}
|
|
14791
|
+
const currentBranch = this.task.worktree.baseBranch;
|
|
14792
|
+
this.task.worktree.branch = currentBranch;
|
|
14793
|
+
let resolvedBase = "";
|
|
14794
|
+
if (this.task.worktree.baseCommit) {
|
|
14795
|
+
const branches = await this.worktreeManager.getBranchesContainingCommit(this.project.baseDir, this.task.worktree.baseCommit);
|
|
14796
|
+
if (branches.length === 1) {
|
|
14797
|
+
resolvedBase = branches[0];
|
|
14798
|
+
}
|
|
14799
|
+
}
|
|
14800
|
+
if (!resolvedBase) {
|
|
14801
|
+
try {
|
|
14802
|
+
resolvedBase = await this.worktreeManager.getProjectMainBranch(this.project.baseDir);
|
|
14803
|
+
} catch {
|
|
14804
|
+
resolvedBase = "";
|
|
14805
|
+
}
|
|
14806
|
+
}
|
|
14807
|
+
this.task.worktree.baseBranch = resolvedBase || void 0;
|
|
14808
|
+
await this.saveTask({ worktree: this.task.worktree });
|
|
14387
14809
|
}
|
|
14388
14810
|
isInternal() {
|
|
14389
14811
|
return this.taskId === INTERNAL_TASK_ID;
|
|
@@ -14459,14 +14881,18 @@ class Task {
|
|
|
14459
14881
|
worktreePath: this.task.worktree.path
|
|
14460
14882
|
});
|
|
14461
14883
|
} else {
|
|
14462
|
-
|
|
14463
|
-
this.task.worktree = await this.worktreeManager.createWorktree(this.project.baseDir, this.taskId, branchName);
|
|
14884
|
+
await this.initWorktree();
|
|
14464
14885
|
void this.sendUpdatedFilesUpdated();
|
|
14886
|
+
void this.sendWorktreeIntegrationStatusUpdated();
|
|
14465
14887
|
}
|
|
14466
14888
|
} else if (workingMode === "local") {
|
|
14467
14889
|
if (existingWorktree) {
|
|
14468
|
-
|
|
14890
|
+
const isShared = this.project.isWorktreeSharedWithOtherTasks(existingWorktree.path, this.taskId);
|
|
14891
|
+
if (!isShared) {
|
|
14892
|
+
await this.worktreeManager.removeWorktree(this.project.baseDir, existingWorktree);
|
|
14893
|
+
}
|
|
14469
14894
|
void this.sendUpdatedFilesUpdated();
|
|
14895
|
+
void this.sendWorktreeIntegrationStatusUpdated();
|
|
14470
14896
|
}
|
|
14471
14897
|
} else {
|
|
14472
14898
|
logger.debug("Empty workingMode, setting to local", {
|
|
@@ -14483,6 +14909,7 @@ class Task {
|
|
|
14483
14909
|
this.task.workingMode = "local";
|
|
14484
14910
|
}
|
|
14485
14911
|
}
|
|
14912
|
+
await this.migrateWorktreeData();
|
|
14486
14913
|
if (await fileExists(this.getTaskDir())) {
|
|
14487
14914
|
this.git = simpleGit.simpleGit(this.getTaskDir());
|
|
14488
14915
|
}
|
|
@@ -14857,7 +15284,9 @@ class Task {
|
|
|
14857
15284
|
await this.saveTask({
|
|
14858
15285
|
name: this.task.name || this.getTaskNameFromPrompt(prompt || ""),
|
|
14859
15286
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14860
|
-
state: DefaultTaskState.InProgress
|
|
15287
|
+
state: DefaultTaskState.InProgress,
|
|
15288
|
+
provider: this.task.provider || profile.provider,
|
|
15289
|
+
model: this.task.model || profile.model
|
|
14861
15290
|
});
|
|
14862
15291
|
const agentMessages = await this.agent.runAgent(this, profile, prompt, mode, promptContext, contextMessages, contextFiles, systemPrompt);
|
|
14863
15292
|
if (agentMessages.length > 0) {
|
|
@@ -14868,6 +15297,7 @@ class Task {
|
|
|
14868
15297
|
void this.sendRequestContextInfo();
|
|
14869
15298
|
void this.sendWorktreeIntegrationStatusUpdated();
|
|
14870
15299
|
void this.sendUpdatedFilesUpdated();
|
|
15300
|
+
void this.sendSkillsUpdated();
|
|
14871
15301
|
this.resolveAgentRunPromises();
|
|
14872
15302
|
if (waitForCurrentAgentToFinish) {
|
|
14873
15303
|
await this.runNextQueuedPrompt();
|
|
@@ -14898,14 +15328,11 @@ class Task {
|
|
|
14898
15328
|
const settings = this.store.getSettings();
|
|
14899
15329
|
if (settings.taskSettings.autoGenerateTaskName) {
|
|
14900
15330
|
this.generateTaskNameInBackground(prompt).then((taskName) => {
|
|
14901
|
-
|
|
14902
|
-
|
|
14903
|
-
} else {
|
|
14904
|
-
void this.saveTask({ name: fallbackName });
|
|
14905
|
-
}
|
|
15331
|
+
const newName = taskName || fallbackName;
|
|
15332
|
+
void this.saveTask({ name: newName }).then(() => this.renameWorktreeBranchIfNeeded());
|
|
14906
15333
|
}).catch((error) => {
|
|
14907
15334
|
logger.warn("Failed to generate task name:", error);
|
|
14908
|
-
void this.saveTask({ name: fallbackName });
|
|
15335
|
+
void this.saveTask({ name: fallbackName }).then(() => this.renameWorktreeBranchIfNeeded());
|
|
14909
15336
|
});
|
|
14910
15337
|
return "<<generating>>";
|
|
14911
15338
|
} else {
|
|
@@ -15681,10 +16108,10 @@ ${contentText}</agent-response>`;
|
|
|
15681
16108
|
return contextFiles;
|
|
15682
16109
|
}
|
|
15683
16110
|
const profile = await this.getTaskAgentProfile();
|
|
15684
|
-
const ruleFiles = await this.getRuleFilesAsContextFiles(profile || void 0);
|
|
16111
|
+
const ruleFiles = await this.getRuleFilesAsContextFiles(profile || void 0, true);
|
|
15685
16112
|
return [...contextFiles, ...ruleFiles];
|
|
15686
16113
|
}
|
|
15687
|
-
async getRuleFilesAsContextFiles(profile) {
|
|
16114
|
+
async getRuleFilesAsContextFiles(profile, includeDisabled = false) {
|
|
15688
16115
|
const ruleFiles = [];
|
|
15689
16116
|
const homeDir = os.homedir();
|
|
15690
16117
|
if (await fileExists(AIDER_DESK_GLOBAL_RULES_DIR)) {
|
|
@@ -15755,7 +16182,8 @@ ${contentText}</agent-response>`;
|
|
|
15755
16182
|
}
|
|
15756
16183
|
}
|
|
15757
16184
|
const extensionResult = await this.extensionManager.dispatchEvent("onRuleFilesRetrieved", { files: ruleFiles }, this.project, this);
|
|
15758
|
-
|
|
16185
|
+
const disabledRuleFiles = this.project.getProjectSettings().disabledRuleFiles ?? [];
|
|
16186
|
+
return includeDisabled ? extensionResult.files : extensionResult.files.filter((f) => !disabledRuleFiles.includes(f.path));
|
|
15759
16187
|
}
|
|
15760
16188
|
getRepoMap() {
|
|
15761
16189
|
return this.aiderManager.getRepoMap();
|
|
@@ -15784,6 +16212,92 @@ ${contentText}</agent-response>`;
|
|
|
15784
16212
|
getContextMessages() {
|
|
15785
16213
|
return this.contextManager.getContextMessages();
|
|
15786
16214
|
}
|
|
16215
|
+
getSkillManager() {
|
|
16216
|
+
return this.skillManager;
|
|
16217
|
+
}
|
|
16218
|
+
async getSkills() {
|
|
16219
|
+
const contextMessages = await this.contextManager.getContextMessages();
|
|
16220
|
+
return this.skillManager.getSkills(contextMessages);
|
|
16221
|
+
}
|
|
16222
|
+
async activateSkill(skillName) {
|
|
16223
|
+
const contextMessages = await this.contextManager.getContextMessages();
|
|
16224
|
+
const activatedNames = this.skillManager.getActivatedSkillNames(contextMessages);
|
|
16225
|
+
if (activatedNames.has(skillName)) {
|
|
16226
|
+
logger.debug("Skill already activated, skipping", { skillName });
|
|
16227
|
+
return;
|
|
16228
|
+
}
|
|
16229
|
+
const content = await this.skillManager.getSkillContent(skillName);
|
|
16230
|
+
if (!content) {
|
|
16231
|
+
throw new Error(`Skill '${skillName}' not found`);
|
|
16232
|
+
}
|
|
16233
|
+
const [assistantMessage, toolMessage] = this.skillManager.buildActivateSkillMessages(skillName, content);
|
|
16234
|
+
this.contextManager.addContextMessage(assistantMessage);
|
|
16235
|
+
this.contextManager.addContextMessage(toolMessage);
|
|
16236
|
+
await this.processResponseMessage(
|
|
16237
|
+
{
|
|
16238
|
+
id: assistantMessage.id,
|
|
16239
|
+
action: "response",
|
|
16240
|
+
content: "User requested the skill activation.",
|
|
16241
|
+
finished: true
|
|
16242
|
+
},
|
|
16243
|
+
false
|
|
16244
|
+
);
|
|
16245
|
+
const toolCallId = toolMessage.content[0].toolCallId;
|
|
16246
|
+
this.addToolMessage(
|
|
16247
|
+
toolCallId,
|
|
16248
|
+
SKILLS_TOOL_GROUP_NAME,
|
|
16249
|
+
SKILLS_TOOL_ACTIVATE_SKILL,
|
|
16250
|
+
{ skill: skillName },
|
|
16251
|
+
JSON.stringify(content),
|
|
16252
|
+
void 0,
|
|
16253
|
+
void 0,
|
|
16254
|
+
false,
|
|
16255
|
+
true
|
|
16256
|
+
);
|
|
16257
|
+
await this.updateContextInfo();
|
|
16258
|
+
}
|
|
16259
|
+
async deactivateSkill(skillName) {
|
|
16260
|
+
const contextMessages = await this.contextManager.getContextMessages();
|
|
16261
|
+
const toolName = `${SKILLS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${SKILLS_TOOL_ACTIVATE_SKILL}`;
|
|
16262
|
+
for (let i = contextMessages.length - 1; i >= 0; i--) {
|
|
16263
|
+
const message = contextMessages[i];
|
|
16264
|
+
if (message.role === "assistant" && Array.isArray(message.content)) {
|
|
16265
|
+
const toolCallPart = message.content.find(
|
|
16266
|
+
(part) => part.type === "tool-call" && part.toolName === toolName && part.input?.skill === skillName
|
|
16267
|
+
);
|
|
16268
|
+
if (!toolCallPart || toolCallPart.type !== "tool-call") {
|
|
16269
|
+
continue;
|
|
16270
|
+
}
|
|
16271
|
+
const toolCallId = toolCallPart.toolCallId;
|
|
16272
|
+
const toolMessage = contextMessages.find(
|
|
16273
|
+
(msg) => msg.role === "tool" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "tool-result" && part.toolCallId === toolCallId)
|
|
16274
|
+
);
|
|
16275
|
+
const removedIds = [];
|
|
16276
|
+
const idsToRemoveFromContext = [];
|
|
16277
|
+
if (toolMessage) {
|
|
16278
|
+
removedIds.push(toolMessage.id);
|
|
16279
|
+
removedIds.push(toolCallId);
|
|
16280
|
+
idsToRemoveFromContext.push(toolMessage.id);
|
|
16281
|
+
}
|
|
16282
|
+
const otherToolCalls = message.content.filter((part) => part.type === "tool-call" && part !== toolCallPart);
|
|
16283
|
+
if (otherToolCalls.length > 0) {
|
|
16284
|
+
this.contextManager.removeMessageById(toolCallId);
|
|
16285
|
+
} else {
|
|
16286
|
+
removedIds.push(message.id);
|
|
16287
|
+
idsToRemoveFromContext.push(message.id);
|
|
16288
|
+
this.contextManager.removeMessagesByIds(idsToRemoveFromContext);
|
|
16289
|
+
}
|
|
16290
|
+
if (removedIds.length > 0) {
|
|
16291
|
+
await this.reloadConnectorMessages();
|
|
16292
|
+
await this.updateContextInfo();
|
|
16293
|
+
void this.sendSkillsUpdated();
|
|
16294
|
+
}
|
|
16295
|
+
return removedIds;
|
|
16296
|
+
}
|
|
16297
|
+
}
|
|
16298
|
+
logger.debug(`No activation found for skill '${skillName}' to deactivate.`);
|
|
16299
|
+
return [];
|
|
16300
|
+
}
|
|
15787
16301
|
async addRoleContextMessage(role, content, usageReport) {
|
|
15788
16302
|
logger.debug("Adding role message to session:", {
|
|
15789
16303
|
baseDir: this.project.baseDir,
|
|
@@ -16064,12 +16578,18 @@ ${contentText}</agent-response>`;
|
|
|
16064
16578
|
const removedIds = this.contextManager.removeMessageById(messageId);
|
|
16065
16579
|
await this.reloadConnectorMessages();
|
|
16066
16580
|
await this.updateContextInfo();
|
|
16581
|
+
if (removedIds.length > 0) {
|
|
16582
|
+
void this.sendSkillsUpdated();
|
|
16583
|
+
}
|
|
16067
16584
|
return removedIds;
|
|
16068
16585
|
}
|
|
16069
16586
|
async removeMessagesUpTo(messageId) {
|
|
16070
16587
|
const removedIds = this.contextManager.removeMessagesAfter(messageId);
|
|
16071
16588
|
await this.reloadConnectorMessages();
|
|
16072
16589
|
await this.updateContextInfo();
|
|
16590
|
+
if (removedIds.length > 0) {
|
|
16591
|
+
void this.sendSkillsUpdated();
|
|
16592
|
+
}
|
|
16073
16593
|
return removedIds;
|
|
16074
16594
|
}
|
|
16075
16595
|
sendTaskMessageRemoved(messageIds) {
|
|
@@ -16077,26 +16597,27 @@ ${contentText}</agent-response>`;
|
|
|
16077
16597
|
this.eventManager.sendTaskMessageRemoved(this.project.baseDir, this.taskId, messageIds);
|
|
16078
16598
|
}
|
|
16079
16599
|
}
|
|
16080
|
-
async
|
|
16081
|
-
logger.info("Redoing
|
|
16600
|
+
async redoUserPrompt(messageId, mode, updatedPrompt) {
|
|
16601
|
+
logger.info("Redoing user prompt:", {
|
|
16082
16602
|
baseDir: this.project.baseDir,
|
|
16603
|
+
messageId,
|
|
16083
16604
|
mode,
|
|
16084
16605
|
hasUpdatedPrompt: !!updatedPrompt
|
|
16085
16606
|
});
|
|
16086
|
-
const removedMessages = this.contextManager.
|
|
16087
|
-
const
|
|
16088
|
-
if (!
|
|
16089
|
-
logger.warn("Could not find
|
|
16607
|
+
const removedMessages = this.contextManager.removeMessagesUpToUserMessage(messageId);
|
|
16608
|
+
const originalUserMessage = removedMessages[0];
|
|
16609
|
+
if (!originalUserMessage || originalUserMessage.role !== MessageRole.User) {
|
|
16610
|
+
logger.warn("Could not find the specified user message to redo.", { messageId });
|
|
16090
16611
|
return;
|
|
16091
16612
|
}
|
|
16092
|
-
const promptToRun = updatedPrompt ??
|
|
16613
|
+
const promptToRun = updatedPrompt ?? originalUserMessage.content;
|
|
16093
16614
|
if (promptToRun) {
|
|
16094
16615
|
logger.info("Found message content to run, reloading and re-running prompt.", {
|
|
16095
16616
|
remainingMessagesCount: (await this.contextManager.getContextMessages()).length
|
|
16096
16617
|
});
|
|
16097
|
-
this.sendTaskMessageRemoved(removedMessages.slice(
|
|
16618
|
+
this.sendTaskMessageRemoved(removedMessages.slice(1).map((msg) => msg.id));
|
|
16098
16619
|
await this.updateContextInfo();
|
|
16099
|
-
void this.runPrompt(promptToRun, mode, false,
|
|
16620
|
+
void this.runPrompt(promptToRun, mode, false, originalUserMessage.id);
|
|
16100
16621
|
} else {
|
|
16101
16622
|
logger.warn("Could not find a previous user message to redo or an updated prompt to run.");
|
|
16102
16623
|
}
|
|
@@ -16123,7 +16644,7 @@ ${contentText}</agent-response>`;
|
|
|
16123
16644
|
if (lastMessage && lastMessage.role === MessageRole.User) {
|
|
16124
16645
|
logger.info("Last message is from user, redoing prompt");
|
|
16125
16646
|
this.addLogMessage("loading", "Resuming task...");
|
|
16126
|
-
void this.
|
|
16647
|
+
void this.redoUserPrompt(lastMessage.id, mode);
|
|
16127
16648
|
} else {
|
|
16128
16649
|
logger.info("Last message is not from user, sending Continue prompt");
|
|
16129
16650
|
void this.runPrompt("Continue", mode, false);
|
|
@@ -16455,9 +16976,13 @@ ${contentText}</agent-response>`;
|
|
|
16455
16976
|
async projectSettingsChanged(oldSettings, newSettings) {
|
|
16456
16977
|
const modeChanged = oldSettings.currentMode !== newSettings.currentMode;
|
|
16457
16978
|
const agentProfileIdChanged = oldSettings.agentProfileId !== newSettings.agentProfileId;
|
|
16458
|
-
|
|
16979
|
+
const disabledRulesChanged = JSON.stringify(oldSettings.disabledRuleFiles) !== JSON.stringify(newSettings.disabledRuleFiles);
|
|
16980
|
+
if (agentProfileIdChanged || modeChanged || disabledRulesChanged) {
|
|
16459
16981
|
void this.sendContextFilesUpdated();
|
|
16460
16982
|
}
|
|
16983
|
+
if (disabledRulesChanged) {
|
|
16984
|
+
void this.updateAgentEstimatedTokens();
|
|
16985
|
+
}
|
|
16461
16986
|
}
|
|
16462
16987
|
sendUpdateEnvVars(environmentVariables) {
|
|
16463
16988
|
this.aiderManager.sendUpdateEnvVars(environmentVariables);
|
|
@@ -16734,6 +17259,18 @@ ${error.stderr}`,
|
|
|
16734
17259
|
});
|
|
16735
17260
|
this.eventManager.sendUpdatedFilesUpdated(this.project.baseDir, this.taskId, updatedFiles);
|
|
16736
17261
|
}
|
|
17262
|
+
async sendSkillsUpdated() {
|
|
17263
|
+
const skills = await this.getSkills();
|
|
17264
|
+
this.eventManager.sendSkillsUpdated(this.project.baseDir, this.taskId, skills);
|
|
17265
|
+
}
|
|
17266
|
+
async initWorktree() {
|
|
17267
|
+
const branchName = this.generateBranchName();
|
|
17268
|
+
this.task.worktree = await this.worktreeManager.createWorktree(this.project.baseDir, this.taskId, branchName);
|
|
17269
|
+
const settings = this.store.getSettings();
|
|
17270
|
+
if (settings.taskSettings.worktreeSymlinkFolders && settings.taskSettings.worktreeSymlinkFolders.length > 0) {
|
|
17271
|
+
await this.worktreeManager.createSymlinks(this.project.baseDir, this.task.worktree.path, settings.taskSettings.worktreeSymlinkFolders);
|
|
17272
|
+
}
|
|
17273
|
+
}
|
|
16737
17274
|
async applyWorkingMode(mode) {
|
|
16738
17275
|
logger.info("Applying workingMode configuration", {
|
|
16739
17276
|
baseDir: this.project.baseDir,
|
|
@@ -16744,17 +17281,15 @@ ${error.stderr}`,
|
|
|
16744
17281
|
const currentWorktree = await this.worktreeManager.getTaskWorktree(this.project.baseDir, this.taskId);
|
|
16745
17282
|
if (mode === "worktree") {
|
|
16746
17283
|
if (!currentWorktree) {
|
|
16747
|
-
|
|
16748
|
-
this.task.worktree = await this.worktreeManager.createWorktree(this.project.baseDir, this.taskId, branchName);
|
|
16749
|
-
const settings = this.store.getSettings();
|
|
16750
|
-
if (settings.taskSettings.worktreeSymlinkFolders && settings.taskSettings.worktreeSymlinkFolders.length > 0) {
|
|
16751
|
-
await this.worktreeManager.createSymlinks(this.project.baseDir, this.task.worktree.path, settings.taskSettings.worktreeSymlinkFolders);
|
|
16752
|
-
}
|
|
17284
|
+
await this.initWorktree();
|
|
16753
17285
|
}
|
|
16754
17286
|
this.task.workingMode = mode;
|
|
16755
17287
|
} else if (mode === "local") {
|
|
16756
17288
|
if (currentWorktree) {
|
|
16757
|
-
|
|
17289
|
+
const isShared = this.project.isWorktreeSharedWithOtherTasks(currentWorktree.path, this.taskId);
|
|
17290
|
+
if (!isShared) {
|
|
17291
|
+
await this.worktreeManager.removeWorktree(this.project.baseDir, currentWorktree);
|
|
17292
|
+
}
|
|
16758
17293
|
}
|
|
16759
17294
|
this.task.worktree = void 0;
|
|
16760
17295
|
this.task.lastMergeState = void 0;
|
|
@@ -16826,7 +17361,8 @@ Only answer with the commit message, nothing else.`,
|
|
|
16826
17361
|
squash,
|
|
16827
17362
|
effectiveCommitMessage || this.task.name || `Task ${this.taskId} changes`,
|
|
16828
17363
|
targetBranch,
|
|
16829
|
-
symlinkFolders
|
|
17364
|
+
symlinkFolders,
|
|
17365
|
+
this.task.worktree.baseCommit
|
|
16830
17366
|
);
|
|
16831
17367
|
await this.saveTask({ lastMergeState: mergeState });
|
|
16832
17368
|
this.addLogMessage(
|
|
@@ -16848,6 +17384,47 @@ Only answer with the commit message, nothing else.`,
|
|
|
16848
17384
|
await this.sendUpdatedFilesUpdated();
|
|
16849
17385
|
await this.sendWorktreeIntegrationStatusUpdated();
|
|
16850
17386
|
}
|
|
17387
|
+
async mergeAndSwitchToLocal(targetBranch) {
|
|
17388
|
+
if (!this.task.worktree) {
|
|
17389
|
+
throw new Error("No worktree exists for this task");
|
|
17390
|
+
}
|
|
17391
|
+
logger.info("Merging worktree and switching to local mode", {
|
|
17392
|
+
baseDir: this.project.baseDir,
|
|
17393
|
+
taskId: this.taskId
|
|
17394
|
+
});
|
|
17395
|
+
await this.waitForCurrentPromptToFinish();
|
|
17396
|
+
try {
|
|
17397
|
+
const effectiveTargetBranch = targetBranch || await this.worktreeManager.getProjectMainBranch(this.project.baseDir);
|
|
17398
|
+
this.addLogMessage("loading", `Merging worktree to ${effectiveTargetBranch} branch and switching to local mode...`);
|
|
17399
|
+
const settings = this.store.getSettings();
|
|
17400
|
+
const symlinkFolders = settings.taskSettings.worktreeSymlinkFolders || [];
|
|
17401
|
+
const mergeState = await this.worktreeManager.mergeWorktreeToMainWithUncommitted(
|
|
17402
|
+
this.project.baseDir,
|
|
17403
|
+
this.task.id,
|
|
17404
|
+
this.task.worktree.path,
|
|
17405
|
+
false,
|
|
17406
|
+
this.task.name || `Task ${this.taskId} changes`,
|
|
17407
|
+
targetBranch,
|
|
17408
|
+
symlinkFolders
|
|
17409
|
+
);
|
|
17410
|
+
await this.saveTask({ lastMergeState: mergeState });
|
|
17411
|
+
this.addLogMessage("info", `Successfully merged worktree to ${effectiveTargetBranch} branch`, true);
|
|
17412
|
+
await this.updateTask({ workingMode: "local" });
|
|
17413
|
+
} catch (error) {
|
|
17414
|
+
logger.error("Failed to merge worktree and switch to local:", { error });
|
|
17415
|
+
const isConflict = error instanceof GitError && (error.gitOutput?.toLowerCase().includes("resolve all conflicts") || error.message?.toLowerCase().includes("conflicts must be resolved first") || error.gitOutput?.toLowerCase().includes("conflicts must be resolved first"));
|
|
17416
|
+
this.addLogMessage(
|
|
17417
|
+
"error",
|
|
17418
|
+
isConflict ? "worktree.mergeConflicts" : error instanceof GitError ? error.getErrorDetails() : `Failed to merge worktree: ${error instanceof Error ? error.message : String(error)}`,
|
|
17419
|
+
true,
|
|
17420
|
+
void 0,
|
|
17421
|
+
isConflict ? ["rebase-worktree"] : void 0
|
|
17422
|
+
);
|
|
17423
|
+
await this.sendUpdatedFilesUpdated();
|
|
17424
|
+
await this.sendWorktreeIntegrationStatusUpdated();
|
|
17425
|
+
throw error;
|
|
17426
|
+
}
|
|
17427
|
+
}
|
|
16851
17428
|
async applyUncommittedChanges(targetBranch) {
|
|
16852
17429
|
if (!this.task.worktree) {
|
|
16853
17430
|
throw new Error("No worktree exists for this task");
|
|
@@ -16977,10 +17554,18 @@ ${diff}
|
|
|
16977
17554
|
taskId: this.taskId,
|
|
16978
17555
|
amend
|
|
16979
17556
|
});
|
|
17557
|
+
const beforeResult = await this.extensionManager.dispatchEvent("onBeforeCommit", { message, amend }, this.project, this);
|
|
17558
|
+
if (beforeResult.blocked) {
|
|
17559
|
+
logger.debug("Commit blocked by extension");
|
|
17560
|
+
return;
|
|
17561
|
+
}
|
|
17562
|
+
message = beforeResult.message;
|
|
17563
|
+
amend = beforeResult.amend;
|
|
16980
17564
|
const taskDir = this.getTaskDir();
|
|
16981
17565
|
await this.worktreeManager.commitChanges(taskDir, message, amend);
|
|
16982
17566
|
await this.sendUpdatedFilesUpdated();
|
|
16983
17567
|
await this.sendWorktreeIntegrationStatusUpdated();
|
|
17568
|
+
await this.extensionManager.dispatchEvent("onAfterCommit", { message, amend }, this.project, this);
|
|
16984
17569
|
}
|
|
16985
17570
|
async getWorktreeIntegrationStatus(targetBranch) {
|
|
16986
17571
|
if (!this.task.worktree) {
|
|
@@ -16988,12 +17573,16 @@ ${diff}
|
|
|
16988
17573
|
}
|
|
16989
17574
|
const effectiveTargetBranch = targetBranch || await this.worktreeManager.getProjectMainBranch(this.project.baseDir);
|
|
16990
17575
|
const worktreePath = this.task.worktree.path;
|
|
17576
|
+
const settings = this.store.getSettings();
|
|
17577
|
+
const symlinkFolders = settings.taskSettings.worktreeSymlinkFolders || [];
|
|
16991
17578
|
const [unmergedWork, predictedConflicts, rebaseState] = await Promise.all([
|
|
16992
|
-
this.worktreeManager.checkWorktreeForUnmergedWork(this.project.baseDir, worktreePath, effectiveTargetBranch),
|
|
17579
|
+
this.worktreeManager.checkWorktreeForUnmergedWork(this.project.baseDir, worktreePath, effectiveTargetBranch, symlinkFolders),
|
|
16993
17580
|
this.worktreeManager.checkForRebaseConflicts(worktreePath, effectiveTargetBranch),
|
|
16994
17581
|
this.worktreeManager.getRebaseState(worktreePath)
|
|
16995
17582
|
]);
|
|
16996
17583
|
return {
|
|
17584
|
+
currentBranch: this.task.worktree.branch || "",
|
|
17585
|
+
baseBranch: this.task.worktree.baseBranch || "",
|
|
16997
17586
|
targetBranch: effectiveTargetBranch,
|
|
16998
17587
|
aheadCommits: {
|
|
16999
17588
|
count: unmergedWork.unmergedCommitCount,
|
|
@@ -17020,8 +17609,12 @@ ${diff}
|
|
|
17020
17609
|
await this.waitForCurrentPromptToFinish();
|
|
17021
17610
|
try {
|
|
17022
17611
|
this.addLogMessage("loading", `Rebasing worktree from ${effectiveFromBranch}...`);
|
|
17023
|
-
const { success, error } = await this.worktreeManager.rebaseMainIntoWorktree(this.task.worktree.path, effectiveFromBranch);
|
|
17612
|
+
const { success, error } = await this.worktreeManager.rebaseMainIntoWorktree(this.task.worktree.path, effectiveFromBranch, this.task.worktree.baseCommit);
|
|
17024
17613
|
if (success) {
|
|
17614
|
+
const newHead = await this.worktreeManager.getHeadCommit(this.task.worktree.path);
|
|
17615
|
+
if (newHead) {
|
|
17616
|
+
await this.saveTask({ worktree: { ...this.task.worktree, baseCommit: newHead, baseBranch: effectiveFromBranch } });
|
|
17617
|
+
}
|
|
17025
17618
|
this.addLogMessage("info", "Worktree rebased successfully", true);
|
|
17026
17619
|
return;
|
|
17027
17620
|
}
|
|
@@ -17062,6 +17655,19 @@ ${diff}
|
|
|
17062
17655
|
await this.sendUpdatedFilesUpdated();
|
|
17063
17656
|
await this.sendWorktreeIntegrationStatusUpdated();
|
|
17064
17657
|
}
|
|
17658
|
+
async renameWorktreeBranch(newBranchName) {
|
|
17659
|
+
if (!this.task.worktree) {
|
|
17660
|
+
throw new Error("No worktree exists for this task");
|
|
17661
|
+
}
|
|
17662
|
+
const oldBranchName = this.task.worktree.branch;
|
|
17663
|
+
if (!oldBranchName) {
|
|
17664
|
+
throw new Error("Cannot determine current branch name");
|
|
17665
|
+
}
|
|
17666
|
+
const actualBranchName = await this.worktreeManager.renameBranch(this.project.baseDir, oldBranchName, newBranchName);
|
|
17667
|
+
this.task.worktree.branch = actualBranchName;
|
|
17668
|
+
await this.saveTask({ worktree: this.task.worktree });
|
|
17669
|
+
void this.sendWorktreeIntegrationStatusUpdated();
|
|
17670
|
+
}
|
|
17065
17671
|
async executeConflictResolution(directoryPath, directoryName) {
|
|
17066
17672
|
const activeProfile = await this.getTaskAgentProfile();
|
|
17067
17673
|
if (!activeProfile) {
|
|
@@ -17727,6 +18333,17 @@ class Project {
|
|
|
17727
18333
|
getAgentProfiles() {
|
|
17728
18334
|
return this.agentProfileManager.getProjectProfiles(this);
|
|
17729
18335
|
}
|
|
18336
|
+
/**
|
|
18337
|
+
* Checks if any other task (excluding the specified taskId) uses the given worktree path.
|
|
18338
|
+
*/
|
|
18339
|
+
isWorktreeSharedWithOtherTasks(worktreePath, excludeTaskId) {
|
|
18340
|
+
for (const [id, task] of this.tasks.entries()) {
|
|
18341
|
+
if (id !== excludeTaskId && task.task.worktree?.path === worktreePath) {
|
|
18342
|
+
return true;
|
|
18343
|
+
}
|
|
18344
|
+
}
|
|
18345
|
+
return false;
|
|
18346
|
+
}
|
|
17730
18347
|
async deleteTaskInternal(taskId) {
|
|
17731
18348
|
const taskDir = path.join(this.baseDir, ".aider-desk", "tasks", taskId);
|
|
17732
18349
|
const task = this.tasks.get(taskId);
|
|
@@ -17735,6 +18352,19 @@ class Project {
|
|
|
17735
18352
|
this.tasks.delete(taskId);
|
|
17736
18353
|
this.eventManager.sendTaskDeleted(task.task);
|
|
17737
18354
|
}
|
|
18355
|
+
const taskData = task?.task;
|
|
18356
|
+
if (taskData?.worktree && !this.isWorktreeSharedWithOtherTasks(taskData.worktree.path, taskId)) {
|
|
18357
|
+
try {
|
|
18358
|
+
await this.worktreeManager.removeWorktree(this.baseDir, taskData.worktree);
|
|
18359
|
+
} catch (error) {
|
|
18360
|
+
logger.warn("Failed to remove worktree during task deletion", {
|
|
18361
|
+
baseDir: this.baseDir,
|
|
18362
|
+
taskId,
|
|
18363
|
+
worktreePath: taskData.worktree.path,
|
|
18364
|
+
error: error instanceof Error ? error.message : String(error)
|
|
18365
|
+
});
|
|
18366
|
+
}
|
|
18367
|
+
}
|
|
17738
18368
|
await fs.rm(taskDir, { recursive: true, force: true });
|
|
17739
18369
|
}
|
|
17740
18370
|
async deleteTask(taskId) {
|
|
@@ -17769,8 +18399,12 @@ class Project {
|
|
|
17769
18399
|
if (!sourceTask) {
|
|
17770
18400
|
throw new Error(`Task with id ${taskId} not found`);
|
|
17771
18401
|
}
|
|
18402
|
+
const hasWorktree = sourceTask.task.worktree && sourceTask.task.workingMode === "worktree";
|
|
17772
18403
|
const newTask = await this.prepareTask(void 0, {
|
|
17773
18404
|
...sourceTask.task,
|
|
18405
|
+
// When the source task has a worktree, make the duplicate a subtask
|
|
18406
|
+
// so both tasks sharing the same worktree are clearly related
|
|
18407
|
+
...hasWorktree ? { parentId: sourceTask.task.parentId || taskId } : {},
|
|
17774
18408
|
state: sourceTask.task.state === DefaultTaskState.InProgress ? DefaultTaskState.Todo : sourceTask.task.state
|
|
17775
18409
|
});
|
|
17776
18410
|
await newTask.init();
|
|
@@ -17982,6 +18616,15 @@ class EventManager {
|
|
|
17982
18616
|
this.sendToWindows("updated-files-updated", data);
|
|
17983
18617
|
this.broadcastToEventConnectors("updated-files-updated", data);
|
|
17984
18618
|
}
|
|
18619
|
+
sendSkillsUpdated(baseDir, taskId, skills) {
|
|
18620
|
+
const data = {
|
|
18621
|
+
baseDir,
|
|
18622
|
+
taskId,
|
|
18623
|
+
skills
|
|
18624
|
+
};
|
|
18625
|
+
this.sendToWindows("skills-updated", data);
|
|
18626
|
+
this.broadcastToEventConnectors("skills-updated", data);
|
|
18627
|
+
}
|
|
17985
18628
|
// Response events
|
|
17986
18629
|
sendResponseChunk(data) {
|
|
17987
18630
|
this.sendToWindows("response-chunk", data);
|
|
@@ -19382,6 +20025,34 @@ const createDeepseekLlm = (profile, model, settings, projectDir) => {
|
|
|
19382
20025
|
});
|
|
19383
20026
|
return deepseekProvider(model.id);
|
|
19384
20027
|
};
|
|
20028
|
+
const getDeepseekProviderOptions = (llmProvider, model) => {
|
|
20029
|
+
if (!isDeepseekProvider(llmProvider)) {
|
|
20030
|
+
return void 0;
|
|
20031
|
+
}
|
|
20032
|
+
const providerOverrides = model.providerOverrides;
|
|
20033
|
+
const thinkingEnabled = providerOverrides?.thinkingEnabled ?? llmProvider.thinkingEnabled ?? true;
|
|
20034
|
+
const reasoningEffort = providerOverrides?.reasoningEffort ?? llmProvider.reasoningEffort ?? "high";
|
|
20035
|
+
return {
|
|
20036
|
+
deepseek: {
|
|
20037
|
+
thinking: { type: thinkingEnabled ? "enabled" : "disabled" },
|
|
20038
|
+
...thinkingEnabled && { reasoningEffort }
|
|
20039
|
+
}
|
|
20040
|
+
};
|
|
20041
|
+
};
|
|
20042
|
+
const getDeepseekProviderParameters = (llmProvider, model) => {
|
|
20043
|
+
if (!isDeepseekProvider(llmProvider)) {
|
|
20044
|
+
return {};
|
|
20045
|
+
}
|
|
20046
|
+
const providerOverrides = model.providerOverrides;
|
|
20047
|
+
const thinkingEnabled = providerOverrides?.thinkingEnabled ?? llmProvider.thinkingEnabled ?? true;
|
|
20048
|
+
if (thinkingEnabled) {
|
|
20049
|
+
return {
|
|
20050
|
+
temperature: void 0,
|
|
20051
|
+
topP: void 0
|
|
20052
|
+
};
|
|
20053
|
+
}
|
|
20054
|
+
return {};
|
|
20055
|
+
};
|
|
19385
20056
|
const deepseekProviderStrategy = {
|
|
19386
20057
|
// Core LLM functions
|
|
19387
20058
|
createLlm: createDeepseekLlm,
|
|
@@ -19390,7 +20061,9 @@ const deepseekProviderStrategy = {
|
|
|
19390
20061
|
loadModels: loadDeepseekModels,
|
|
19391
20062
|
hasEnvVars: hasDeepseekEnvVars,
|
|
19392
20063
|
getAiderMapping: getDeepseekAiderMapping,
|
|
19393
|
-
getModelInfo: getDefaultModelInfo
|
|
20064
|
+
getModelInfo: getDefaultModelInfo,
|
|
20065
|
+
getProviderOptions: getDeepseekProviderOptions,
|
|
20066
|
+
getProviderParameters: getDeepseekProviderParameters
|
|
19394
20067
|
};
|
|
19395
20068
|
const loadGeminiModels = async (profile, settings) => {
|
|
19396
20069
|
if (!isGeminiProvider(profile.provider)) {
|
|
@@ -20015,7 +20688,6 @@ const alibabaPlanProviderStrategy = {
|
|
|
20015
20688
|
getProviderOptions: getAlibabaPlanProviderOptions
|
|
20016
20689
|
};
|
|
20017
20690
|
const KIMI_PLAN_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
20018
|
-
const KIMI_PLAN_MODEL_ID = "k2p5";
|
|
20019
20691
|
const loadKimiPlanModels = async (profile, settings) => {
|
|
20020
20692
|
if (!isKimiPlanProvider(profile.provider)) {
|
|
20021
20693
|
return { models: [], success: false };
|
|
@@ -20029,7 +20701,19 @@ const loadKimiPlanModels = async (profile, settings) => {
|
|
|
20029
20701
|
}
|
|
20030
20702
|
const models = [
|
|
20031
20703
|
{
|
|
20032
|
-
id:
|
|
20704
|
+
id: "kimi-k2-thinking",
|
|
20705
|
+
providerId: profile.id,
|
|
20706
|
+
maxInputTokens: 262144,
|
|
20707
|
+
maxOutputTokensLimit: 32768
|
|
20708
|
+
},
|
|
20709
|
+
{
|
|
20710
|
+
id: "k2p5",
|
|
20711
|
+
providerId: profile.id,
|
|
20712
|
+
maxInputTokens: 262144,
|
|
20713
|
+
maxOutputTokensLimit: 32768
|
|
20714
|
+
},
|
|
20715
|
+
{
|
|
20716
|
+
id: "k2p6",
|
|
20033
20717
|
providerId: profile.id,
|
|
20034
20718
|
maxInputTokens: 262144,
|
|
20035
20719
|
maxOutputTokensLimit: 32768
|
|
@@ -21346,9 +22030,9 @@ const getOpenRouterUsageReport = (task, provider, model, usage, providerMetadata
|
|
|
21346
22030
|
agentTotalCost: task.task.agentTotalCost + messageCost
|
|
21347
22031
|
};
|
|
21348
22032
|
};
|
|
21349
|
-
const getOpenRouterCacheControl = (
|
|
22033
|
+
const getOpenRouterCacheControl = (llmProvider, model) => {
|
|
21350
22034
|
if (isOpenRouterProvider(llmProvider)) {
|
|
21351
|
-
if (
|
|
22035
|
+
if (model.id?.startsWith("anthropic/")) {
|
|
21352
22036
|
return {
|
|
21353
22037
|
providerOptions: {
|
|
21354
22038
|
openrouter: {
|
|
@@ -21537,9 +22221,9 @@ const normalizeRequestyMessages = (_provider, model, messages) => {
|
|
|
21537
22221
|
}
|
|
21538
22222
|
return messages;
|
|
21539
22223
|
};
|
|
21540
|
-
const getRequestyCacheControl = (
|
|
22224
|
+
const getRequestyCacheControl = (llmProvider, model) => {
|
|
21541
22225
|
if (isRequestyProvider(llmProvider) && !llmProvider.useAutoCache) {
|
|
21542
|
-
if (
|
|
22226
|
+
if (model.id?.startsWith("anthropic/")) {
|
|
21543
22227
|
return {
|
|
21544
22228
|
providerOptions: {
|
|
21545
22229
|
requesty: {
|
|
@@ -21928,6 +22612,8 @@ const zaiPlanProviderStrategy = {
|
|
|
21928
22612
|
};
|
|
21929
22613
|
const MODELS_META_URL = "https://models.dev/api.json";
|
|
21930
22614
|
const MODELS_FILE = path.join(AIDER_DESK_DATA_DIR, "models.json");
|
|
22615
|
+
const PROVIDER_MODELS_CACHE_FILE = path.join(AIDER_DESK_CACHE_DIR, "provider-models.json");
|
|
22616
|
+
const PROVIDER_MODELS_CACHE_VERSION = 1;
|
|
21931
22617
|
class ModelManager {
|
|
21932
22618
|
constructor(store, eventManager) {
|
|
21933
22619
|
this.store = store;
|
|
@@ -21976,9 +22662,20 @@ class ModelManager {
|
|
|
21976
22662
|
this.updateEnvVarsProviders();
|
|
21977
22663
|
await this.loadModelsInfo();
|
|
21978
22664
|
await this.loadModelOverrides();
|
|
21979
|
-
await this.
|
|
22665
|
+
const cacheLoaded = await this.loadProviderModelsFromCache();
|
|
22666
|
+
if (cacheLoaded) {
|
|
22667
|
+
this.eventManager.sendProviderModelsUpdated({
|
|
22668
|
+
models: Object.values(this.providerModels).flat(),
|
|
22669
|
+
loading: true,
|
|
22670
|
+
errors: this.providerErrors
|
|
22671
|
+
});
|
|
22672
|
+
this.loadProviderModelsInBackground(this.getProviders().filter((p) => !p.disabled));
|
|
22673
|
+
} else {
|
|
22674
|
+
await this.loadProviderModels(this.getProviders().filter((p) => !p.disabled));
|
|
22675
|
+
}
|
|
21980
22676
|
logger.info("ModelInfoManager initialized successfully.", {
|
|
21981
|
-
modelCount: Object.keys(this.modelsInfo).length
|
|
22677
|
+
modelCount: Object.keys(this.modelsInfo).length,
|
|
22678
|
+
cacheLoaded
|
|
21982
22679
|
});
|
|
21983
22680
|
} catch (error) {
|
|
21984
22681
|
logger.error("Error initializing ModelInfoManager:", error);
|
|
@@ -22025,6 +22722,44 @@ class ModelManager {
|
|
|
22025
22722
|
await freshDataPromise;
|
|
22026
22723
|
}
|
|
22027
22724
|
}
|
|
22725
|
+
async loadProviderModelsFromCache() {
|
|
22726
|
+
try {
|
|
22727
|
+
const cacheData = await fs$1.promises.readFile(PROVIDER_MODELS_CACHE_FILE, "utf-8");
|
|
22728
|
+
const cache = JSON.parse(cacheData);
|
|
22729
|
+
if (cache.version !== PROVIDER_MODELS_CACHE_VERSION) {
|
|
22730
|
+
logger.info("Provider models cache version mismatch, ignoring cache");
|
|
22731
|
+
return false;
|
|
22732
|
+
}
|
|
22733
|
+
this.providerModels = cache.providerModels;
|
|
22734
|
+
this.providerErrors = cache.providerErrors;
|
|
22735
|
+
logger.info("Loaded provider models from cache", {
|
|
22736
|
+
providerCount: Object.keys(cache.providerModels).length
|
|
22737
|
+
});
|
|
22738
|
+
return true;
|
|
22739
|
+
} catch {
|
|
22740
|
+
logger.info("Provider models cache not found or invalid");
|
|
22741
|
+
return false;
|
|
22742
|
+
}
|
|
22743
|
+
}
|
|
22744
|
+
async saveProviderModelsToCache() {
|
|
22745
|
+
try {
|
|
22746
|
+
const cache = {
|
|
22747
|
+
version: PROVIDER_MODELS_CACHE_VERSION,
|
|
22748
|
+
providerModels: this.providerModels,
|
|
22749
|
+
providerErrors: this.providerErrors
|
|
22750
|
+
};
|
|
22751
|
+
await fs$1.promises.mkdir(AIDER_DESK_CACHE_DIR, { recursive: true });
|
|
22752
|
+
await fs$1.promises.writeFile(PROVIDER_MODELS_CACHE_FILE, JSON.stringify(cache));
|
|
22753
|
+
logger.info("Saved provider models to cache");
|
|
22754
|
+
} catch (error) {
|
|
22755
|
+
logger.error("Failed to save provider models to cache:", error);
|
|
22756
|
+
}
|
|
22757
|
+
}
|
|
22758
|
+
loadProviderModelsInBackground(providers) {
|
|
22759
|
+
this.loadProviderModels(providers).catch((error) => {
|
|
22760
|
+
logger.error("Background loading of provider models failed:", error);
|
|
22761
|
+
});
|
|
22762
|
+
}
|
|
22028
22763
|
processModelsMeta(data) {
|
|
22029
22764
|
for (const providerId in data) {
|
|
22030
22765
|
const providerData = data[providerId];
|
|
@@ -22067,6 +22802,21 @@ class ModelManager {
|
|
|
22067
22802
|
}
|
|
22068
22803
|
return Array.from(changed);
|
|
22069
22804
|
}
|
|
22805
|
+
async loadModelsWithRetry(strategy, profile, retryCount = 3) {
|
|
22806
|
+
let lastResponse;
|
|
22807
|
+
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
|
22808
|
+
if (attempt > 0) {
|
|
22809
|
+
const delayMs = Math.pow(2, attempt - 1) * 1e3;
|
|
22810
|
+
logger.info(`Retrying load models for provider profile ${profile.id} in ${delayMs}ms (attempt ${attempt + 1}/${retryCount + 1})`);
|
|
22811
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
22812
|
+
}
|
|
22813
|
+
lastResponse = await strategy.loadModels(profile, this.store.getSettings());
|
|
22814
|
+
if (lastResponse.success) {
|
|
22815
|
+
return lastResponse;
|
|
22816
|
+
}
|
|
22817
|
+
}
|
|
22818
|
+
return lastResponse;
|
|
22819
|
+
}
|
|
22070
22820
|
async providersChanged(oldProviders, newProviders) {
|
|
22071
22821
|
await this.initPromise;
|
|
22072
22822
|
const removedProviders = oldProviders.filter((p) => !newProviders.find((np) => np.id === p.id));
|
|
@@ -22108,7 +22858,7 @@ class ModelManager {
|
|
|
22108
22858
|
continue;
|
|
22109
22859
|
}
|
|
22110
22860
|
let providerModels = [];
|
|
22111
|
-
const response = await
|
|
22861
|
+
const response = await this.loadModelsWithRetry(strategy, profile);
|
|
22112
22862
|
delete this.providerErrors[profile.id];
|
|
22113
22863
|
if (response.success) {
|
|
22114
22864
|
providerModels.push(...response.models);
|
|
@@ -22137,6 +22887,7 @@ class ModelManager {
|
|
|
22137
22887
|
errors: this.providerErrors
|
|
22138
22888
|
});
|
|
22139
22889
|
this.eventManager.sendSettingsUpdated(this.store.getSettings());
|
|
22890
|
+
await this.saveProviderModelsToCache();
|
|
22140
22891
|
}
|
|
22141
22892
|
enrichWithModelInfo(models, profile, strategy) {
|
|
22142
22893
|
const enrichedModels = [...models];
|
|
@@ -22404,12 +23155,22 @@ class ModelManager {
|
|
|
22404
23155
|
}
|
|
22405
23156
|
return strategy.getUsageReport(task, provider, modelObj, usage, providerMetadata);
|
|
22406
23157
|
}
|
|
22407
|
-
getCacheControl(
|
|
23158
|
+
getCacheControl(provider, modelId) {
|
|
23159
|
+
const llmProvider = provider.provider;
|
|
22408
23160
|
const strategy = this.providerRegistry[llmProvider.name];
|
|
22409
23161
|
if (!strategy?.getCacheControl) {
|
|
22410
23162
|
return void 0;
|
|
22411
23163
|
}
|
|
22412
|
-
|
|
23164
|
+
const models = this.providerModels[provider.id] || [];
|
|
23165
|
+
const modelObj = models.find((m) => m.id === modelId);
|
|
23166
|
+
if (!modelObj) {
|
|
23167
|
+
const fallbackModel = {
|
|
23168
|
+
id: modelId,
|
|
23169
|
+
providerId: provider.id
|
|
23170
|
+
};
|
|
23171
|
+
return strategy.getCacheControl(llmProvider, fallbackModel);
|
|
23172
|
+
}
|
|
23173
|
+
return strategy.getCacheControl(llmProvider, modelObj);
|
|
22413
23174
|
}
|
|
22414
23175
|
isStreamingDisabled(provider, modelId) {
|
|
22415
23176
|
const llmProvider = provider.provider;
|
|
@@ -22543,7 +23304,7 @@ class ModelManager {
|
|
|
22543
23304
|
getProviderOptions: provider.strategy.getProviderOptions ? (_provider, model) => provider.strategy.getProviderOptions(model) : void 0,
|
|
22544
23305
|
getProviderTools: provider.strategy.getProviderTools ? (_provider, model) => provider.strategy.getProviderTools(model) : void 0,
|
|
22545
23306
|
getProviderParameters: provider.strategy.getProviderParameters ? (_provider, model) => provider.strategy.getProviderParameters(model) : void 0,
|
|
22546
|
-
getCacheControl: provider.strategy.getCacheControl ? (
|
|
23307
|
+
getCacheControl: provider.strategy.getCacheControl ? (_provider, model) => provider.strategy.getCacheControl(model) : void 0,
|
|
22547
23308
|
hasEnvVars: () => false,
|
|
22548
23309
|
getAiderMapping: provider.strategy.getAiderMapping ? provider.strategy.getAiderMapping : (_provider, modelId) => ({
|
|
22549
23310
|
modelName: modelId,
|
|
@@ -23802,8 +24563,16 @@ class TaskContextImpl {
|
|
|
23802
24563
|
async loadContextMessages(messages) {
|
|
23803
24564
|
await this.task.loadContextMessages(messages);
|
|
23804
24565
|
}
|
|
24566
|
+
async redoUserPrompt(messageId, mode, updatedPrompt) {
|
|
24567
|
+
await this.task.redoUserPrompt(messageId, mode || "agent", updatedPrompt);
|
|
24568
|
+
}
|
|
23805
24569
|
async redoLastUserPrompt(mode, updatedPrompt) {
|
|
23806
|
-
await this.task.
|
|
24570
|
+
const messages = await this.task.getContextMessages();
|
|
24571
|
+
const lastUserMessage = messages.findLast((msg) => msg.role === "user");
|
|
24572
|
+
if (!lastUserMessage) {
|
|
24573
|
+
return;
|
|
24574
|
+
}
|
|
24575
|
+
await this.task.redoUserPrompt(lastUserMessage.id, mode || "agent", updatedPrompt);
|
|
23807
24576
|
}
|
|
23808
24577
|
async removeMessagesUpTo(messageId) {
|
|
23809
24578
|
await this.task.removeMessagesUpTo(messageId);
|
|
@@ -24015,7 +24784,11 @@ class ExtensionContextImpl {
|
|
|
24015
24784
|
this.memoryManager = memoryManager;
|
|
24016
24785
|
this.project = project;
|
|
24017
24786
|
this.task = task;
|
|
24787
|
+
this.taskContext = this.task ? new TaskContextImpl(this.task) : null;
|
|
24788
|
+
this.projectContext = this.project ? new ProjectContextImpl(this.project) : null;
|
|
24018
24789
|
}
|
|
24790
|
+
taskContext;
|
|
24791
|
+
projectContext;
|
|
24019
24792
|
log(message, type = "info") {
|
|
24020
24793
|
const logFn = logger[type];
|
|
24021
24794
|
logFn(`[Extension:${this.extensionName}] ${message}`);
|
|
@@ -24024,13 +24797,13 @@ class ExtensionContextImpl {
|
|
|
24024
24797
|
return this.project?.baseDir ?? "";
|
|
24025
24798
|
}
|
|
24026
24799
|
getTaskContext() {
|
|
24027
|
-
return this.
|
|
24800
|
+
return this.taskContext;
|
|
24028
24801
|
}
|
|
24029
24802
|
getProjectContext() {
|
|
24030
|
-
if (!this.
|
|
24803
|
+
if (!this.projectContext) {
|
|
24031
24804
|
throw new Error("Project context not available");
|
|
24032
24805
|
}
|
|
24033
|
-
return
|
|
24806
|
+
return this.projectContext;
|
|
24034
24807
|
}
|
|
24035
24808
|
async getModelConfigs() {
|
|
24036
24809
|
if (!this.modelManager) {
|
|
@@ -24464,7 +25237,7 @@ class ExtensionManager {
|
|
|
24464
25237
|
filterEnabledExtensions(extensions) {
|
|
24465
25238
|
const settings = this.store.getSettings();
|
|
24466
25239
|
const disabledExtensions = settings.extensions?.disabled || [];
|
|
24467
|
-
return extensions.filter((ext) => !disabledExtensions.includes(ext.
|
|
25240
|
+
return extensions.filter((ext) => !disabledExtensions.includes(ext.filePath));
|
|
24468
25241
|
}
|
|
24469
25242
|
/**
|
|
24470
25243
|
* Handle settings changes. Detects when extensions with UI components
|
|
@@ -24482,7 +25255,7 @@ class ExtensionManager {
|
|
|
24482
25255
|
const changedExtensions = [...newlyDisabled, ...newlyEnabled];
|
|
24483
25256
|
if (changedExtensions.length > 0) {
|
|
24484
25257
|
const allExtensions = this.registry.getExtensions();
|
|
24485
|
-
const hasUIComponentsChange = allExtensions.some((ext) => changedExtensions.includes(ext.
|
|
25258
|
+
const hasUIComponentsChange = allExtensions.some((ext) => changedExtensions.includes(ext.filePath) && ext.instance.getUIComponents !== void 0);
|
|
24486
25259
|
if (hasUIComponentsChange) {
|
|
24487
25260
|
logger.debug("[Extensions] Extensions with UI components changed, triggering UI refresh");
|
|
24488
25261
|
this.eventManager.sendExtensionUIRefresh({ reloadComponents: true });
|
|
@@ -24510,6 +25283,7 @@ class ExtensionManager {
|
|
|
24510
25283
|
this.registry.clear();
|
|
24511
25284
|
await this.loadExtensionsForDir(AIDER_DESK_GLOBAL_EXTENSIONS_DIR);
|
|
24512
25285
|
this.initialized = true;
|
|
25286
|
+
this.migrateDisabledExtensions();
|
|
24513
25287
|
await this.startHotReloadWatcher();
|
|
24514
25288
|
this.captureExtensionsTelemetry();
|
|
24515
25289
|
this.preloadAvailableExtensions().catch((error) => {
|
|
@@ -24640,13 +25414,51 @@ class ExtensionManager {
|
|
|
24640
25414
|
isInitialized() {
|
|
24641
25415
|
return this.initialized;
|
|
24642
25416
|
}
|
|
25417
|
+
/**
|
|
25418
|
+
* @deprecated Migration helper: converts old name-based disabled list to filePath-based.
|
|
25419
|
+
* Will be removed in a future version.
|
|
25420
|
+
*/
|
|
25421
|
+
migrateDisabledExtensions() {
|
|
25422
|
+
const settings = this.store.getSettings();
|
|
25423
|
+
const disabled = settings.extensions?.disabled;
|
|
25424
|
+
if (!disabled || disabled.length === 0) {
|
|
25425
|
+
return;
|
|
25426
|
+
}
|
|
25427
|
+
const allExtensions = this.registry.getExtensions();
|
|
25428
|
+
const filePaths = new Set(allExtensions.map((ext) => ext.filePath));
|
|
25429
|
+
const migrated = [];
|
|
25430
|
+
let changed = false;
|
|
25431
|
+
for (const item of disabled) {
|
|
25432
|
+
if (filePaths.has(item)) {
|
|
25433
|
+
migrated.push(item);
|
|
25434
|
+
} else {
|
|
25435
|
+
const match = allExtensions.find((ext) => ext.metadata.name === item);
|
|
25436
|
+
if (match) {
|
|
25437
|
+
migrated.push(match.filePath);
|
|
25438
|
+
changed = true;
|
|
25439
|
+
} else {
|
|
25440
|
+
migrated.push(item);
|
|
25441
|
+
}
|
|
25442
|
+
}
|
|
25443
|
+
}
|
|
25444
|
+
if (changed) {
|
|
25445
|
+
logger.info("[Extensions] Migrated disabled extensions from name-based to filePath-based identifiers");
|
|
25446
|
+
this.store.saveSettings({
|
|
25447
|
+
...settings,
|
|
25448
|
+
extensions: {
|
|
25449
|
+
...settings.extensions,
|
|
25450
|
+
disabled: migrated
|
|
25451
|
+
}
|
|
25452
|
+
});
|
|
25453
|
+
}
|
|
25454
|
+
}
|
|
24643
25455
|
captureExtensionsTelemetry() {
|
|
24644
25456
|
const allExtensions = this.registry.getExtensions();
|
|
24645
25457
|
const settings = this.store.getSettings();
|
|
24646
25458
|
const disabledExtensions = settings.extensions?.disabled || [];
|
|
24647
25459
|
const globalExtensions = allExtensions.filter((ext) => !ext.projectDir).length;
|
|
24648
25460
|
const projectExtensions = allExtensions.filter((ext) => ext.projectDir).length;
|
|
24649
|
-
const enabledCount = allExtensions.filter((ext) => !disabledExtensions.includes(ext.
|
|
25461
|
+
const enabledCount = allExtensions.filter((ext) => !disabledExtensions.includes(ext.filePath)).length;
|
|
24650
25462
|
this.telemetryManager.captureExtensionsLoaded(allExtensions.length, globalExtensions, projectExtensions, enabledCount, disabledExtensions.length);
|
|
24651
25463
|
}
|
|
24652
25464
|
async dispose() {
|
|
@@ -25170,6 +25982,42 @@ class ExtensionManager {
|
|
|
25170
25982
|
}
|
|
25171
25983
|
return collectedProviders;
|
|
25172
25984
|
}
|
|
25985
|
+
getSkills(project, task) {
|
|
25986
|
+
const collectedSkills = [];
|
|
25987
|
+
const allExtensions = this.registry.getExtensions(project.baseDir);
|
|
25988
|
+
const extensions = this.filterEnabledExtensions(allExtensions);
|
|
25989
|
+
for (const loaded of extensions) {
|
|
25990
|
+
const { instance, metadata } = loaded;
|
|
25991
|
+
if (!instance.getSkills) {
|
|
25992
|
+
continue;
|
|
25993
|
+
}
|
|
25994
|
+
try {
|
|
25995
|
+
const context = new ExtensionContextImpl(loaded.id, metadata.name, this.store, this.modelManager, this.eventManager, this.memoryManager, project, task);
|
|
25996
|
+
const skills = instance.getSkills(context);
|
|
25997
|
+
if (!Array.isArray(skills)) {
|
|
25998
|
+
logger.error(`[Extensions] Extension '${metadata.name}' getSkills() did not return an array`);
|
|
25999
|
+
continue;
|
|
26000
|
+
}
|
|
26001
|
+
for (const skill of skills) {
|
|
26002
|
+
if (!skill.name || !skill.description) {
|
|
26003
|
+
logger.error(`[Extensions] Invalid skill from extension '${metadata.name}': missing name or description`);
|
|
26004
|
+
continue;
|
|
26005
|
+
}
|
|
26006
|
+
if (!skill.dirPath && !skill.content) {
|
|
26007
|
+
logger.error(`[Extensions] Invalid skill from extension '${metadata.name}': must have dirPath or content`);
|
|
26008
|
+
continue;
|
|
26009
|
+
}
|
|
26010
|
+
collectedSkills.push({
|
|
26011
|
+
...skill,
|
|
26012
|
+
location: "extension"
|
|
26013
|
+
});
|
|
26014
|
+
}
|
|
26015
|
+
} catch (error) {
|
|
26016
|
+
logger.error(`[Extensions] Failed to get skills from extension '${metadata.name}':`, error);
|
|
26017
|
+
}
|
|
26018
|
+
}
|
|
26019
|
+
return collectedSkills;
|
|
26020
|
+
}
|
|
25173
26021
|
getUIComponents(project, task) {
|
|
25174
26022
|
const collectedComponents = [];
|
|
25175
26023
|
const allExtensions = this.registry.getExtensions(project?.baseDir);
|
|
@@ -25610,6 +26458,15 @@ class ExtensionManager {
|
|
|
25610
26458
|
throw new Error("Invalid GitHub repository URL");
|
|
25611
26459
|
}
|
|
25612
26460
|
await this.unloadExtensionsForDir(targetDir);
|
|
26461
|
+
const parsedExistingPath = path.parse(existingExtension.filePath);
|
|
26462
|
+
const existingIsFolder = parsedExistingPath.name === "index";
|
|
26463
|
+
if (extension.type === "folder" && !existingIsFolder) {
|
|
26464
|
+
await fs.unlink(existingExtension.filePath);
|
|
26465
|
+
logger.debug(`[Extensions] Removed old single-file extension: ${existingExtension.filePath}`);
|
|
26466
|
+
} else if (extension.type === "single" && existingIsFolder) {
|
|
26467
|
+
await fs.rm(parsedExistingPath.dir, { recursive: true, force: true });
|
|
26468
|
+
logger.debug(`[Extensions] Removed old folder extension: ${parsedExistingPath.dir}`);
|
|
26469
|
+
}
|
|
25613
26470
|
if (extension.type === "single" && extension.file) {
|
|
25614
26471
|
const url = `${githubRawBase}/${extension.file}`;
|
|
25615
26472
|
const response = await fetch(url);
|
|
@@ -25780,7 +26637,7 @@ class EventsHandler {
|
|
|
25780
26637
|
}
|
|
25781
26638
|
async addOpenProject(baseDir) {
|
|
25782
26639
|
const projects = this.store.getOpenProjects();
|
|
25783
|
-
const existingProject = projects.find((p) =>
|
|
26640
|
+
const existingProject = projects.find((p) => compareBaseDirs$1(p.baseDir, baseDir));
|
|
25784
26641
|
if (!existingProject) {
|
|
25785
26642
|
logger.info("EventsHandler: addOpenProject", { baseDir });
|
|
25786
26643
|
const providerModels = await this.modelManager.getProviderModels();
|
|
@@ -25797,7 +26654,7 @@ class EventsHandler {
|
|
|
25797
26654
|
}
|
|
25798
26655
|
removeOpenProject(baseDir) {
|
|
25799
26656
|
const projects = this.store.getOpenProjects();
|
|
25800
|
-
const updatedProjects = projects.filter((project) =>
|
|
26657
|
+
const updatedProjects = projects.filter((project) => !compareBaseDirs$1(project.baseDir, baseDir));
|
|
25801
26658
|
if (updatedProjects.length > 0) {
|
|
25802
26659
|
if (!updatedProjects.some((p) => p.active)) {
|
|
25803
26660
|
updatedProjects[updatedProjects.length - 1].active = true;
|
|
@@ -25847,8 +26704,8 @@ class EventsHandler {
|
|
|
25847
26704
|
const removedIds = await this.projectManager.getProject(baseDir).getTask(taskId)?.removeMessagesUpTo(messageId) ?? [];
|
|
25848
26705
|
this.eventManager.sendTaskMessageRemoved(baseDir, taskId, removedIds);
|
|
25849
26706
|
}
|
|
25850
|
-
async
|
|
25851
|
-
void this.projectManager.getProject(baseDir).getTask(taskId)?.
|
|
26707
|
+
async redoUserPrompt(baseDir, taskId, messageId, mode, updatedPrompt) {
|
|
26708
|
+
void this.projectManager.getProject(baseDir).getTask(taskId)?.redoUserPrompt(messageId, mode, updatedPrompt);
|
|
25852
26709
|
}
|
|
25853
26710
|
async resumeTask(baseDir, taskId) {
|
|
25854
26711
|
void this.projectManager.getProject(baseDir).getTask(taskId)?.resumeTask();
|
|
@@ -26077,6 +26934,13 @@ class EventsHandler {
|
|
|
26077
26934
|
}
|
|
26078
26935
|
await task.mergeWorktreeToMain(squash, targetBranch, commitMessage);
|
|
26079
26936
|
}
|
|
26937
|
+
async mergeAndSwitchToLocal(baseDir, taskId, targetBranch) {
|
|
26938
|
+
const task = this.projectManager.getProject(baseDir).getTask(taskId);
|
|
26939
|
+
if (!task) {
|
|
26940
|
+
throw new Error(`Task ${taskId} not found`);
|
|
26941
|
+
}
|
|
26942
|
+
await task.mergeAndSwitchToLocal(targetBranch);
|
|
26943
|
+
}
|
|
26080
26944
|
async applyUncommittedChanges(baseDir, taskId, targetBranch) {
|
|
26081
26945
|
const task = this.projectManager.getProject(baseDir).getTask(taskId);
|
|
26082
26946
|
if (!task) {
|
|
@@ -26159,6 +27023,13 @@ class EventsHandler {
|
|
|
26159
27023
|
}
|
|
26160
27024
|
await task.resolveConflictsWithAgent();
|
|
26161
27025
|
}
|
|
27026
|
+
async renameWorktreeBranch(baseDir, taskId, newBranchName) {
|
|
27027
|
+
const task = this.projectManager.getProject(baseDir).getTask(taskId);
|
|
27028
|
+
if (!task) {
|
|
27029
|
+
throw new Error(`Task ${taskId} not found`);
|
|
27030
|
+
}
|
|
27031
|
+
await task.renameWorktreeBranch(newBranchName);
|
|
27032
|
+
}
|
|
26162
27033
|
async scrapeWeb(baseDir, taskId, url, filePath) {
|
|
26163
27034
|
const content = await scrapeWeb(url);
|
|
26164
27035
|
const project = this.projectManager.getProject(baseDir);
|
|
@@ -26437,6 +27308,17 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
26437
27308
|
async initProjectRulesFile(baseDir, taskId, args) {
|
|
26438
27309
|
return this.projectManager.getProject(baseDir).getTask(taskId)?.initProjectAgentsFile(args);
|
|
26439
27310
|
}
|
|
27311
|
+
async getSkills(baseDir, taskId) {
|
|
27312
|
+
return await this.projectManager.getProject(baseDir).getTask(taskId)?.getSkills() || [];
|
|
27313
|
+
}
|
|
27314
|
+
async activateSkill(baseDir, taskId, skillName) {
|
|
27315
|
+
await this.projectManager.getProject(baseDir).getTask(taskId)?.activateSkill(skillName);
|
|
27316
|
+
void this.projectManager.getProject(baseDir).getTask(taskId)?.sendSkillsUpdated();
|
|
27317
|
+
}
|
|
27318
|
+
async deactivateSkill(baseDir, taskId, skillName) {
|
|
27319
|
+
const removedIds = await this.projectManager.getProject(baseDir).getTask(taskId)?.deactivateSkill(skillName) ?? [];
|
|
27320
|
+
this.eventManager.sendTaskMessageRemoved(baseDir, taskId, removedIds);
|
|
27321
|
+
}
|
|
26440
27322
|
async enableServer(username, password) {
|
|
26441
27323
|
const currentSettings = this.store.getSettings();
|
|
26442
27324
|
const updatedSettings = {
|
|
@@ -26932,6 +27814,7 @@ class PromptsManager {
|
|
|
26932
27814
|
};
|
|
26933
27815
|
getRulesContent = async (task, agentProfile) => {
|
|
26934
27816
|
const ruleFiles = await task.getRuleFilesAsContextFiles(agentProfile);
|
|
27817
|
+
logger.debug("Rule files for prompt content:", { ruleFiles });
|
|
26935
27818
|
const ruleFilesContent = await Promise.all(
|
|
26936
27819
|
ruleFiles.map(async (file) => {
|
|
26937
27820
|
try {
|
|
@@ -27332,7 +28215,7 @@ const initManagers = async (store, windowManager) => {
|
|
|
27332
28215
|
windowManager
|
|
27333
28216
|
);
|
|
27334
28217
|
const serverController = new ServerController(httpServer, projectManager, eventsHandler, store, pythonInstaller);
|
|
27335
|
-
const connectorManager = new ConnectorManager(httpServer, projectManager, eventManager);
|
|
28218
|
+
const connectorManager = new ConnectorManager(httpServer, projectManager, eventManager, store);
|
|
27336
28219
|
httpServer.listen(SERVER_PORT);
|
|
27337
28220
|
logger.info(`AiderDesk headless server listening on http://localhost:${SERVER_PORT}`);
|
|
27338
28221
|
let cleanedUp = false;
|
|
@@ -27920,6 +28803,10 @@ const DEFAULT_SETTINGS = {
|
|
|
27920
28803
|
enabled: false,
|
|
27921
28804
|
username: "",
|
|
27922
28805
|
password: ""
|
|
28806
|
+
},
|
|
28807
|
+
cors: {
|
|
28808
|
+
enabled: false,
|
|
28809
|
+
origins: []
|
|
27923
28810
|
}
|
|
27924
28811
|
},
|
|
27925
28812
|
memory: {
|
|
@@ -27935,7 +28822,9 @@ const DEFAULT_SETTINGS = {
|
|
|
27935
28822
|
worktreeSymlinkFolders: ["node_modules", "vendor", "__pycache__", ".venv", "venv"],
|
|
27936
28823
|
contextCompactingThreshold: 0,
|
|
27937
28824
|
contextCompactionType: ContextCompactionType.Compact,
|
|
27938
|
-
defaultWorkingMode: "local"
|
|
28825
|
+
defaultWorkingMode: "local",
|
|
28826
|
+
worktreeBranchPrefix: "aider-desk/task/",
|
|
28827
|
+
renameBranchOnNameGeneration: true
|
|
27939
28828
|
},
|
|
27940
28829
|
extensions: {
|
|
27941
28830
|
repositories: [AIDER_DESK_EXTENSIONS_REPO_URL],
|
|
@@ -27999,7 +28888,14 @@ class Store {
|
|
|
27999
28888
|
}
|
|
28000
28889
|
},
|
|
28001
28890
|
mcpServers: settings.mcpServers || DEFAULT_SETTINGS.mcpServers,
|
|
28002
|
-
server:
|
|
28891
|
+
server: {
|
|
28892
|
+
...DEFAULT_SETTINGS.server,
|
|
28893
|
+
...settings.server,
|
|
28894
|
+
cors: {
|
|
28895
|
+
...DEFAULT_SETTINGS.server.cors,
|
|
28896
|
+
...settings.server?.cors
|
|
28897
|
+
}
|
|
28898
|
+
},
|
|
28003
28899
|
memory: {
|
|
28004
28900
|
...DEFAULT_SETTINGS.memory,
|
|
28005
28901
|
...settings?.memory
|