@aiderdesk/aiderdesk 0.59.0 → 0.61.0-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/out/renderer/assets/{_basePickBy-BXFYYah3.js → _basePickBy-BWoXHZ8Z.js} +2 -2
  2. package/out/renderer/assets/{_baseUniq-Cd1ra5R6.js → _baseUniq-Z8t1Ab1g.js} +1 -1
  3. package/out/renderer/assets/{arc-CzRQcQ8z.js → arc-CwESpVYE.js} +1 -1
  4. package/out/renderer/assets/{architectureDiagram-2XIMDMQ5-CfLvNO9v.js → architectureDiagram-2XIMDMQ5-C490EQr5.js} +6 -6
  5. package/out/renderer/assets/{blockDiagram-WCTKOSBZ-BLFI6WIC.js → blockDiagram-WCTKOSBZ-DbHssxrQ.js} +6 -6
  6. package/out/renderer/assets/{c4Diagram-IC4MRINW-D2fK1fs0.js → c4Diagram-IC4MRINW-BxE3GUKu.js} +2 -2
  7. package/out/renderer/assets/{channel-DrJKu4_6.js → channel-C9hJq_Xr.js} +1 -1
  8. package/out/renderer/assets/{chunk-4BX2VUAB-CceHu94f.js → chunk-4BX2VUAB-ChcKNpL6.js} +1 -1
  9. package/out/renderer/assets/{chunk-55IACEB6-Ceu45XlD.js → chunk-55IACEB6-oLT9lXTt.js} +1 -1
  10. package/out/renderer/assets/{chunk-FMBD7UC4-BKzuqZXn.js → chunk-FMBD7UC4-B5k8rETe.js} +1 -1
  11. package/out/renderer/assets/{chunk-JSJVCQXG-iYOtJ8I-.js → chunk-JSJVCQXG-DtjSx6Cj.js} +1 -1
  12. package/out/renderer/assets/{chunk-KX2RTZJC-bNHhs5xZ.js → chunk-KX2RTZJC-D32tV7H-.js} +1 -1
  13. package/out/renderer/assets/{chunk-NQ4KR5QH-C0IrVSiA.js → chunk-NQ4KR5QH-Bmqo2FpL.js} +3 -3
  14. package/out/renderer/assets/{chunk-QZHKN3VN-CQapQOOT.js → chunk-QZHKN3VN-8BpGifjS.js} +1 -1
  15. package/out/renderer/assets/{chunk-WL4C6EOR-DUYtOMll.js → chunk-WL4C6EOR-vhby2fZi.js} +5 -5
  16. package/out/renderer/assets/{classDiagram-VBA2DB6C-UeXpMFb8.js → classDiagram-VBA2DB6C-BxRSSb9e.js} +6 -6
  17. package/out/renderer/assets/{classDiagram-v2-RAHNMMFH-UeXpMFb8.js → classDiagram-v2-RAHNMMFH-BxRSSb9e.js} +6 -6
  18. package/out/renderer/assets/{clone-CFgMCpt9.js → clone-D9a-uIZa.js} +1 -1
  19. package/out/renderer/assets/{cose-bilkent-S5V4N54A-DnX12BL_.js → cose-bilkent-S5V4N54A-CUqqQ-6R.js} +1 -1
  20. package/out/renderer/assets/{dagre-KLK3FWXG-7-_OG7Kj.js → dagre-KLK3FWXG-ORIwMMhq.js} +6 -6
  21. package/out/renderer/assets/{diagram-E7M64L7V-DumXVEqI.js → diagram-E7M64L7V-bS9HcoDQ.js} +7 -7
  22. package/out/renderer/assets/{diagram-IFDJBPK2-UTrn1bff.js → diagram-IFDJBPK2-BRlTIR0J.js} +6 -6
  23. package/out/renderer/assets/{diagram-P4PSJMXO-Bk1LZhwV.js → diagram-P4PSJMXO-jsjGwH-p.js} +6 -6
  24. package/out/renderer/assets/{erDiagram-INFDFZHY-CMAH31O-.js → erDiagram-INFDFZHY-DGlgjHOQ.js} +4 -4
  25. package/out/renderer/assets/{flowDiagram-PKNHOUZH-uQFfaMJM.js → flowDiagram-PKNHOUZH-CzLC87bM.js} +6 -6
  26. package/out/renderer/assets/{ganttDiagram-A5KZAMGK-quRvalRu.js → ganttDiagram-A5KZAMGK-BbDv36wT.js} +1 -1
  27. package/out/renderer/assets/{gitGraphDiagram-K3NZZRJ6-CfwRyzfx.js → gitGraphDiagram-K3NZZRJ6-CBQnBjEi.js} +7 -7
  28. package/out/renderer/assets/{graph-W5JPvpWW.js → graph-Dl5N6maZ.js} +2 -2
  29. package/out/renderer/assets/{index-Bhs67jG4.js → index-6qedlt0q.js} +2 -2
  30. package/out/renderer/assets/{index-C1ZBVNfw.js → index-86jDpUJn.js} +2 -2
  31. package/out/renderer/assets/{index-Wwk_5mUs.js → index-BRjO8ber.js} +5 -5
  32. package/out/renderer/assets/{index-DjoaBGM_.js → index-BVgw7j0d.js} +2 -2
  33. package/out/renderer/assets/{index--j4qFXVt.js → index-BhkyI1q3.js} +2 -2
  34. package/out/renderer/assets/{index-BK7nk5p7.js → index-BqrmLPkg.js} +5 -5
  35. package/out/renderer/assets/{index-C3Glc2Zo.js → index-BuPbw4xM.js} +2 -2
  36. package/out/renderer/assets/{index-C327GPg5.js → index-CTO-LPBg.js} +5 -5
  37. package/out/renderer/assets/{index-CIjEi4HS.js → index-CTO4SzlI.js} +2 -2
  38. package/out/renderer/assets/{index-D6kgjSLw.js → index-CZ9IQK_0.js} +5 -5
  39. package/out/renderer/assets/{index-JCZJkVpl.js → index-CZxsVxBZ.js} +1 -1
  40. package/out/renderer/assets/{index-Bb7XYl8_.js → index-DIXV-3Xn.js} +2 -2
  41. package/out/renderer/assets/{index-DdsfZljo.js → index-DNzOtZX5.js} +2 -2
  42. package/out/renderer/assets/{index-33dprpTM.js → index-DZtJe7oC.js} +2 -2
  43. package/out/renderer/assets/{index-B4cKzfxL.js → index-DaWjZz_g.js} +5 -5
  44. package/out/renderer/assets/{index-BJyiVzcz.js → index-De056HHR.js} +3 -3
  45. package/out/renderer/assets/{index-CL49sSsZ.js → index-Dk3wSDSN.js} +5 -5
  46. package/out/renderer/assets/{index-CJqBFUF8.js → index-Dy4VRsnA.js} +4 -4
  47. package/out/renderer/assets/{index-BijwEwO9.js → index-FnnayPBc.js} +2 -2
  48. package/out/renderer/assets/{index-0KKkNp35.js → index-MDHavDF9.js} +2146 -2507
  49. package/out/renderer/assets/{index-HpcqfYbh.js → index-MZb_zy6R.js} +2 -2
  50. package/out/renderer/assets/{index-Do6DM72m.js → index-chzQl8rJ.js} +2 -2
  51. package/out/renderer/assets/{index-ChkFNtFm.js → index-s-Owx3Pm.js} +3 -3
  52. package/out/renderer/assets/{infoDiagram-LFFYTUFH-CLlREcM7.js → infoDiagram-LFFYTUFH-GtEDBEmz.js} +5 -5
  53. package/out/renderer/assets/{ishikawaDiagram-PHBUUO56-C8MCvpMI.js → ishikawaDiagram-PHBUUO56-Uj90gr3I.js} +1 -1
  54. package/out/renderer/assets/{journeyDiagram-4ABVD52K-D3AyVALG.js → journeyDiagram-4ABVD52K-DekFuOwS.js} +4 -4
  55. package/out/renderer/assets/{kanban-definition-K7BYSVSG-D6esJ1Mv.js → kanban-definition-K7BYSVSG-Hfz2L6NS.js} +2 -2
  56. package/out/renderer/assets/{layout-B-GkLewD.js → layout-B9K2VT39.js} +4 -4
  57. package/out/renderer/assets/{mindmap-definition-YRQLILUH-DiFIDGDM.js → mindmap-definition-YRQLILUH-DDcS7_GH.js} +3 -3
  58. package/out/renderer/assets/{pieDiagram-SKSYHLDU-DjzUpI9a.js → pieDiagram-SKSYHLDU-DwjEcJ0Q.js} +7 -7
  59. package/out/renderer/assets/{quadrantDiagram-337W2JSQ-BKx0O-wq.js → quadrantDiagram-337W2JSQ-CpGqN7vo.js} +1 -1
  60. package/out/renderer/assets/{requirementDiagram-Z7DCOOCP-qRcPUPwh.js → requirementDiagram-Z7DCOOCP-qDRUf1ip.js} +3 -3
  61. package/out/renderer/assets/{sankeyDiagram-WA2Y5GQK-CE8REafI.js → sankeyDiagram-WA2Y5GQK-DRKS8C1H.js} +1 -1
  62. package/out/renderer/assets/{sequenceDiagram-2WXFIKYE-a4AELpAg.js → sequenceDiagram-2WXFIKYE-DhVN2ug2.js} +3 -3
  63. package/out/renderer/assets/{stateDiagram-RAJIS63D-CXiPRAnM.js → stateDiagram-RAJIS63D-BpP4eSqu.js} +8 -8
  64. package/out/renderer/assets/{stateDiagram-v2-FVOUBMTO-CZKt-kP1.js → stateDiagram-v2-FVOUBMTO-B_qQJqra.js} +4 -4
  65. package/out/renderer/assets/{timeline-definition-YZTLITO2-BY3z14HB.js → timeline-definition-YZTLITO2-UoWXE_76.js} +2 -2
  66. package/out/renderer/assets/{treemap-KZPCXAKY-DZOzveSW.js → treemap-KZPCXAKY-W2a2L6ff.js} +4 -4
  67. package/out/renderer/assets/{vennDiagram-LZ73GAT5-Bus0Wjc8.js → vennDiagram-LZ73GAT5-Dp1FZ609.js} +1 -1
  68. package/out/renderer/assets/{xychartDiagram-JWTSCODW-BdGw2_Lw.js → xychartDiagram-JWTSCODW-CTzYHTbD.js} +1 -1
  69. package/out/renderer/index.html +1 -1
  70. package/out/resources/prompts/init-project.hbs +61 -49
  71. package/out/resources/prompts/system-prompt.hbs +5 -0
  72. package/out/resources/prompts/workflow.hbs +10 -0
  73. package/out/runner.js +446 -549
  74. package/package.json +1 -1
package/out/runner.js CHANGED
@@ -772,8 +772,6 @@ const AIDER_DESK_RULES_DIR = "rules";
772
772
  const AIDER_DESK_PROJECT_RULES_DIR = path.join(AIDER_DESK_DIR, AIDER_DESK_RULES_DIR);
773
773
  const AIDER_DESK_GLOBAL_RULES_DIR = path.join(os.homedir(), AIDER_DESK_DIR, AIDER_DESK_RULES_DIR);
774
774
  const AIDER_DESK_COMMANDS_DIR = path.join(AIDER_DESK_DIR, "commands");
775
- const AIDER_DESK_HOOKS_DIR = path.join(AIDER_DESK_DIR, "hooks");
776
- const AIDER_DESK_GLOBAL_HOOKS_DIR = path.join(os.homedir(), AIDER_DESK_DIR, "hooks");
777
775
  const AIDER_DESK_EXTENSIONS_DIR = path.join(AIDER_DESK_DIR, "extensions");
778
776
  const AIDER_DESK_GLOBAL_EXTENSIONS_DIR = path.join(os.homedir(), AIDER_DESK_DIR, "extensions");
779
777
  const AIDER_DESK_PROMPTS_DIR = path.join(AIDER_DESK_DIR, "prompts");
@@ -784,6 +782,7 @@ const AIDER_DESK_TMP_DIR = path.join(AIDER_DESK_DIR, "tmp");
784
782
  const AIDER_DESK_WATCH_FILES_LOCK = path.join(AIDER_DESK_DIR, "watch-files.lock");
785
783
  const WORKTREE_BRANCH_PREFIX = "aider-desk/task/";
786
784
  const AIDER_DESK_MEMORY_FILE = path.join(AIDER_DESK_DATA_DIR, "memory.db");
785
+ const EXTENSIONS_REPOS_CACHE_DIR = path.join(AIDER_DESK_CACHE_DIR, "extensions");
787
786
  const POSTHOG_PUBLIC_API_KEY = "phc_AF4zkjrcziXLh8PBFsRSvVr4VZ38p3ezsdX0KDYuElI";
788
787
  const POSTHOG_HOST = "https://eu.i.posthog.com";
789
788
  process.env.AIDER_DESK_HEADLESS === "true";
@@ -852,10 +851,6 @@ class ApprovalManager {
852
851
  return this.handleApproval(key, text, subject);
853
852
  }
854
853
  async handleApproval(key, text, subject) {
855
- const hookResult = await this.task.hookManager.trigger("onHandleApproval", { key, text, subject }, this.task, this.task.project);
856
- if (typeof hookResult.result === "boolean") {
857
- return [hookResult.result, void 0];
858
- }
859
854
  const extensionResult = await this.task.dispatchExtensionEvent("onHandleApproval", { key, text, subject });
860
855
  if (extensionResult.blocked) {
861
856
  return [false, void 0];
@@ -2315,7 +2310,7 @@ const getTelemetryEnvironmentVariablesForAider = (settings, baseDir) => {
2315
2310
  ...getLangfuseEnvironmentVariables(baseDir, settings)
2316
2311
  };
2317
2312
  };
2318
- const getDefaultProjectSettings = (store, providerModels, baseDir, defaultAgentProfileId = DEFAULT_AGENT_PROFILE.id) => {
2313
+ const getDefaultProjectSettings = (store, providerModels, baseDir, defaultAgentProfileId = DEFAULT_AGENT_PROFILE.id, providers) => {
2319
2314
  const openProjects = store.getOpenProjects();
2320
2315
  const activeProject = openProjects.find((p) => p.active);
2321
2316
  if (activeProject) {
@@ -2324,7 +2319,7 @@ const getDefaultProjectSettings = (store, providerModels, baseDir, defaultAgentP
2324
2319
  };
2325
2320
  }
2326
2321
  return {
2327
- mainModel: determineMainModel(store.getSettings(), store.getProviders(), providerModels, baseDir),
2322
+ mainModel: determineMainModel(store.getSettings(), providers ?? store.getProviders(), providerModels, baseDir),
2328
2323
  weakModel: determineWeakModel(baseDir),
2329
2324
  modelEditFormats: {},
2330
2325
  currentMode: "agent",
@@ -5783,7 +5778,13 @@ class Agent {
5783
5778
  const fileInfos = await Promise.all(
5784
5779
  files.map(async (file) => {
5785
5780
  try {
5786
- const filePath = path.resolve(task.getTaskDir(), file.path);
5781
+ const filePath = await task.resolveContextFilePath(file.path);
5782
+ if (!filePath) {
5783
+ logger.error("Could not resolve context file path:", {
5784
+ path: file.path
5785
+ });
5786
+ return null;
5787
+ }
5787
5788
  const fileContentBuffer = await fs.readFile(filePath);
5788
5789
  if (istextorbinary.isBinary(filePath, fileContentBuffer)) {
5789
5790
  try {
@@ -5827,9 +5828,13 @@ class Agent {
5827
5828
  );
5828
5829
  fileInfos.filter(Boolean).forEach((file) => {
5829
5830
  if (file.isImage && file.imageBase64) {
5831
+ imageParts.push({
5832
+ type: "text",
5833
+ text: `Here is image ${path.basename(file.path)} for your reference.`
5834
+ });
5830
5835
  imageParts.push({
5831
5836
  type: "image",
5832
- image: `data:${file.mimeType};base64,${file.imageBase64}`,
5837
+ image: file.imageBase64,
5833
5838
  mediaType: file.mimeType
5834
5839
  });
5835
5840
  } else if (!file.isImage && file.content) {
@@ -5905,16 +5910,27 @@ ${file.content}</content-with-line-numbers>
5905
5910
  const nonImageFiles = [];
5906
5911
  for (const file of contextFiles) {
5907
5912
  try {
5908
- const filePath = path.resolve(task.getTaskDir(), file.path);
5913
+ const filePath = await task.resolveContextFilePath(file.path);
5914
+ if (!filePath) {
5915
+ logger.error("Could not resolve context file path for working files:", {
5916
+ path: file.path
5917
+ });
5918
+ nonImageFiles.push(file);
5919
+ continue;
5920
+ }
5909
5921
  const fileContentBuffer = await fs.readFile(filePath);
5910
5922
  if (istextorbinary.isBinary(filePath, fileContentBuffer)) {
5911
5923
  try {
5912
5924
  const detected = await fileType.fileTypeFromBuffer(fileContentBuffer);
5913
5925
  if (detected?.mime.startsWith("image/")) {
5914
5926
  const imageBase64 = fileContentBuffer.toString("base64");
5927
+ imageParts.push({
5928
+ type: "text",
5929
+ text: `Here is image ${path.basename(file.path)} for your reference.`
5930
+ });
5915
5931
  imageParts.push({
5916
5932
  type: "image",
5917
- image: `data:${detected.mime};base64,${imageBase64}`,
5933
+ image: imageBase64,
5918
5934
  mediaType: detected.mime
5919
5935
  });
5920
5936
  continue;
@@ -6052,12 +6068,7 @@ ${fileList}`
6052
6068
  wrappedToolSet[toolName] = {
6053
6069
  ...toolDef,
6054
6070
  execute: async (input, options) => {
6055
- const hookResult = await task.hookManager.trigger("onToolCalled", { toolName, args: input }, task, task.project);
6056
- if (hookResult.blocked) {
6057
- logger.warn(`Tool execution blocked by hook: ${toolName}`);
6058
- return "Tool execution blocked by hook.";
6059
- }
6060
- let effectiveInput = hookResult.event.args ?? input;
6071
+ let effectiveInput = input;
6061
6072
  const [serverName, messageToolName] = extractServerNameToolName(toolName);
6062
6073
  task.addToolMessage(options.toolCallId, serverName, messageToolName, effectiveInput, void 0, void 0, promptContext);
6063
6074
  const toolCalledExtensionResult = await this.extensionManager.dispatchEvent(
@@ -6074,7 +6085,6 @@ ${fileList}`
6074
6085
  options.abortSignal = abortSignal;
6075
6086
  }
6076
6087
  const result = await toolDef.execute(effectiveInput, options);
6077
- const toolFinishedHookResult = await task.hookManager.trigger("onToolFinished", { toolName, args: effectiveInput, result }, task, task.project);
6078
6088
  const toolFinishedExtensionResult = await this.extensionManager.dispatchEvent(
6079
6089
  "onToolFinished",
6080
6090
  { toolName, input: effectiveInput, output: result },
@@ -6083,8 +6093,6 @@ ${fileList}`
6083
6093
  );
6084
6094
  if (toolFinishedExtensionResult.output) {
6085
6095
  return toolFinishedExtensionResult.output;
6086
- } else if (toolFinishedHookResult.event.result) {
6087
- return toolFinishedHookResult.event.result;
6088
6096
  } else {
6089
6097
  return result;
6090
6098
  }
@@ -6230,18 +6238,6 @@ ${fileList}`
6230
6238
  async runAgent(task, profile, prompt, mode = "agent", promptContext, initialContextMessages, initialContextFiles, systemPrompt, includeInContext = true, abortSignal) {
6231
6239
  let contextMessages = initialContextMessages ?? await task.getContextMessages();
6232
6240
  let contextFiles = initialContextFiles ?? await task.getContextFiles();
6233
- const hookResult = await task.hookManager.trigger("onAgentStarted", { prompt, contextMessages, contextFiles }, task, task.project);
6234
- if (hookResult.blocked) {
6235
- logger.debug("Agent execution blocked by hook");
6236
- return [];
6237
- }
6238
- prompt = hookResult.event.prompt;
6239
- if (hookResult.event.contextMessages) {
6240
- contextMessages = hookResult.event.contextMessages;
6241
- }
6242
- if (hookResult.event.contextFiles) {
6243
- contextFiles = hookResult.event.contextFiles;
6244
- }
6245
6241
  if (!systemPrompt) {
6246
6242
  systemPrompt = await this.promptsManager.getSystemPrompt(this.store.getSettings(), task, profile);
6247
6243
  }
@@ -6254,7 +6250,7 @@ ${fileList}`
6254
6250
  const settings = this.store.getSettings();
6255
6251
  const projectProfiles = this.agentProfileManager.getProjectProfiles(task.project);
6256
6252
  let resultMessages = userRequestMessage ? [userRequestMessage] : [];
6257
- const providers = this.store.getProviders();
6253
+ const providers = this.modelManager.getProviders();
6258
6254
  let provider = providers.find((p) => p.id === profile.provider);
6259
6255
  let modelName = profile.model;
6260
6256
  if (provider) {
@@ -6555,13 +6551,6 @@ ${fileList}`
6555
6551
  return;
6556
6552
  }
6557
6553
  responseMessages = await this.processStep(currentResponseId, stepResult, task, provider, modelName, promptContext, abortSignal);
6558
- const hookResult3 = await task.hookManager.trigger("onAgentStepFinished", { stepResult, finishReason, responseMessages }, task, task.project);
6559
- if (hookResult3?.event?.finishReason) {
6560
- finishReason = hookResult3.event.finishReason;
6561
- }
6562
- if (hookResult3?.event?.responseMessages) {
6563
- responseMessages = hookResult3.event.responseMessages;
6564
- }
6565
6554
  const extensionResult2 = await this.extensionManager.dispatchEvent(
6566
6555
  "onAgentStepFinished",
6567
6556
  {
@@ -6772,10 +6761,6 @@ ${fileList}`
6772
6761
  break;
6773
6762
  }
6774
6763
  }
6775
- const hookResult2 = await task.hookManager.trigger("onAgentFinished", { aborted: false, contextMessages, resultMessages }, task, task.project);
6776
- if (hookResult2?.event?.resultMessages) {
6777
- resultMessages = hookResult2.event.resultMessages;
6778
- }
6779
6764
  const extensionResult = await this.extensionManager.dispatchEvent(
6780
6765
  "onAgentFinished",
6781
6766
  {
@@ -6794,10 +6779,6 @@ ${fileList}`
6794
6779
  if (streamingMessageIds.size > 0) {
6795
6780
  this.removeUnfinishedStreamingMessages(task, streamingMessageIds);
6796
6781
  }
6797
- const hookResult2 = await task.hookManager.trigger("onAgentFinished", { aborted: true, contextMessages, resultMessages }, task, task.project);
6798
- if (hookResult2?.event?.resultMessages) {
6799
- resultMessages = hookResult2.event.resultMessages;
6800
- }
6801
6782
  const extensionResult = await this.extensionManager.dispatchEvent(
6802
6783
  "onAgentFinished",
6803
6784
  {
@@ -6830,20 +6811,40 @@ ${fileList}`
6830
6811
  return resultMessages;
6831
6812
  }
6832
6813
  filterResultMessages(resultMessages) {
6833
- return resultMessages.filter((message) => {
6834
- if (message.role === "tool" && message.id.startsWith(`${HELPERS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}`)) {
6835
- return false;
6814
+ const helpersPrefix = `${HELPERS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}`;
6815
+ return resultMessages.reduce((acc, message) => {
6816
+ if (message.role === "tool") {
6817
+ const nonHelpersContent = message.content.filter((part) => !part.toolName.startsWith(helpersPrefix));
6818
+ if (nonHelpersContent.length === 0) {
6819
+ return acc;
6820
+ }
6821
+ if (nonHelpersContent.length === message.content.length) {
6822
+ acc.push(message);
6823
+ } else {
6824
+ acc.push({ ...message, content: nonHelpersContent });
6825
+ }
6826
+ return acc;
6836
6827
  }
6837
6828
  if (message.role === "assistant") {
6838
- if (Array.isArray(message.content) && message.content.some(
6839
- (content) => content.type === "tool-call" && content.toolName.startsWith(`${HELPERS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}`)
6840
- )) {
6841
- return false;
6829
+ if (!Array.isArray(message.content)) {
6830
+ acc.push(message);
6831
+ return acc;
6832
+ }
6833
+ const hasHelpersToolCall = message.content.some((part) => part.type === "tool-call" && part.toolName.startsWith(helpersPrefix));
6834
+ if (!hasHelpersToolCall) {
6835
+ acc.push(message);
6836
+ return acc;
6842
6837
  }
6843
- return true;
6838
+ const nonHelpersContent = message.content.filter((part) => !(part.type === "tool-call" && part.toolName.startsWith(helpersPrefix)));
6839
+ if (nonHelpersContent.length === 0) {
6840
+ return acc;
6841
+ }
6842
+ acc.push({ ...message, content: nonHelpersContent });
6843
+ return acc;
6844
6844
  }
6845
- return true;
6846
- });
6845
+ acc.push(message);
6846
+ return acc;
6847
+ }, []);
6847
6848
  }
6848
6849
  async getContextFilesAsToolCallMessages(task, profile, contextFiles) {
6849
6850
  const messages = [];
@@ -6950,16 +6951,16 @@ ${fileList}`
6950
6951
  }
6951
6952
  }
6952
6953
  for (const imageData of imageFiles) {
6953
- fileMessages.push({
6954
- role: "assistant",
6955
- content: `Provide content of image file: ${imageData.relativePath}`
6956
- });
6957
6954
  const imageMessage = {
6958
6955
  role: "user",
6959
6956
  content: [
6957
+ {
6958
+ type: "text",
6959
+ text: `Here is content of image file ${path.basename(imageData.relativePath)}`
6960
+ },
6960
6961
  {
6961
6962
  type: "image",
6962
- image: `data:${imageData.mimeType};base64,${imageData.imageBase64}`,
6963
+ image: imageData.imageBase64,
6963
6964
  mediaType: imageData.mimeType
6964
6965
  }
6965
6966
  ]
@@ -7006,7 +7007,7 @@ ${fileList}`
7006
7007
  }
7007
7008
  async generateText(modelId, systemPrompt, prompt, projectDir, messages = [], abortable = true, abortSignal) {
7008
7009
  const [providerId, modelName] = extractProviderModel(modelId);
7009
- const providers = this.store.getProviders();
7010
+ const providers = this.modelManager.getProviders();
7010
7011
  const provider = providers.find((p) => p.id === providerId);
7011
7012
  if (!provider) {
7012
7013
  throw new Error(`Provider ${providerId} not found`);
@@ -7059,7 +7060,7 @@ ${fileList}`
7059
7060
  }
7060
7061
  async estimateTokens(task, profile) {
7061
7062
  try {
7062
- const providers = this.store.getProviders();
7063
+ const providers = this.modelManager.getProviders();
7063
7064
  const provider = providers.find((p) => p.id === profile.provider);
7064
7065
  if (!provider) {
7065
7066
  logger.warn(`Estimation failed: Provider ${profile.provider} not found`);
@@ -8328,7 +8329,8 @@ const RunCommandSchema = zod.z.object({
8328
8329
  });
8329
8330
  const InitProjectRulesFileSchema = zod.z.object({
8330
8331
  projectDir: zod.z.string().min(1, "Project directory is required"),
8331
- taskId: zod.z.string().min(1, "Task id is required")
8332
+ taskId: zod.z.string().min(1, "Task id is required"),
8333
+ args: zod.z.string().optional()
8332
8334
  });
8333
8335
  const CreateNewTaskSchema = zod.z.object({
8334
8336
  projectDir: zod.z.string().min(1, "Project directory is required"),
@@ -8440,9 +8442,9 @@ const GenerateCommitMessageSchema = zod.z.object({
8440
8442
  const CommitChangesSchema = zod.z.object({
8441
8443
  projectDir: zod.z.string().min(1, "Project directory is required"),
8442
8444
  taskId: zod.z.string().min(1, "Task id is required"),
8443
- message: zod.z.string().min(1, "Commit message is required"),
8445
+ message: zod.z.string(),
8444
8446
  amend: zod.z.boolean()
8445
- });
8447
+ }).refine((data) => data.amend || data.message.trim().length > 0, { message: "Commit message is required", path: ["message"] });
8446
8448
  const ListBranchesSchema = zod.z.object({
8447
8449
  projectDir: zod.z.string().min(1, "Project directory is required")
8448
8450
  });
@@ -8601,8 +8603,8 @@ class ProjectApi extends BaseApi {
8601
8603
  if (!parsed) {
8602
8604
  return;
8603
8605
  }
8604
- const { projectDir, taskId } = parsed;
8605
- await this.eventsHandler.initProjectRulesFile(projectDir, taskId);
8606
+ const { projectDir, taskId, args } = parsed;
8607
+ await this.eventsHandler.initProjectRulesFile(projectDir, taskId, args);
8606
8608
  res.status(200).json({ message: "Project rules file initialized" });
8607
8609
  })
8608
8610
  );
@@ -9746,6 +9748,11 @@ const UninstallExtensionSchema = zod.z.object({
9746
9748
  extensionId: zod.z.string().min(1, "Extension ID is required"),
9747
9749
  projectDir: zod.z.string().optional()
9748
9750
  });
9751
+ const UpdateExtensionSchema = zod.z.object({
9752
+ extensionId: zod.z.string().min(1, "Extension ID is required"),
9753
+ repositoryUrl: zod.z.string().url("Repository URL must be a valid URL"),
9754
+ projectDir: zod.z.string().optional()
9755
+ });
9749
9756
  class ExtensionsApi extends BaseApi {
9750
9757
  constructor(eventsHandler) {
9751
9758
  super();
@@ -9808,6 +9815,22 @@ class ExtensionsApi extends BaseApi {
9808
9815
  }
9809
9816
  })
9810
9817
  );
9818
+ router.post(
9819
+ "/extensions/update",
9820
+ this.handleRequest(async (req, res) => {
9821
+ const parsed = this.validateRequest(UpdateExtensionSchema, req.body, res);
9822
+ if (!parsed) {
9823
+ return;
9824
+ }
9825
+ const { extensionId, repositoryUrl, projectDir } = parsed;
9826
+ const success = await this.eventsHandler.updateExtension(extensionId, repositoryUrl, projectDir);
9827
+ if (success) {
9828
+ res.status(200).json({ message: "Extension updated successfully", success });
9829
+ } else {
9830
+ res.status(500).json({ message: "Failed to update extension", success });
9831
+ }
9832
+ })
9833
+ );
9811
9834
  router.get(
9812
9835
  "/extensions/ui-components",
9813
9836
  this.handleRequest(async (req, res) => {
@@ -10849,12 +10872,18 @@ class ContextManager {
10849
10872
  return isFileIgnored(this.task.getTaskDir(), contextFile.path);
10850
10873
  }
10851
10874
  async addContextFile(contextFile) {
10852
- const absolutePath = path.resolve(this.task.getTaskDir(), contextFile.path);
10853
- const isDir = await isDirectory(absolutePath);
10854
- const alreadyAdded = this.files.find((file) => path.resolve(this.task.getTaskDir(), file.path) === absolutePath);
10855
- if (alreadyAdded) {
10875
+ const absolutePath = await this.resolveContextFilePath(contextFile.path);
10876
+ if (!absolutePath) {
10877
+ logger.debug("Skipping non-existing file for task:", {
10878
+ taskId: this.taskId,
10879
+ path: contextFile.path
10880
+ });
10856
10881
  return [];
10857
10882
  }
10883
+ if (this.isContextFileAlreadyAdded(absolutePath)) {
10884
+ return [];
10885
+ }
10886
+ const isDir = await isDirectory(absolutePath);
10858
10887
  if (isDir) {
10859
10888
  logger.debug("Recursively adding files in directory to task context:", {
10860
10889
  taskId: this.taskId,
@@ -10882,14 +10911,6 @@ class ContextManager {
10882
10911
  }
10883
10912
  return addedFiles;
10884
10913
  } else {
10885
- if (!await fileExists(absolutePath)) {
10886
- logger.debug("Skipping non-existing file for task:", {
10887
- taskId: this.taskId,
10888
- path: contextFile.path,
10889
- absolutePath
10890
- });
10891
- return [];
10892
- }
10893
10914
  if (await this.isFileIgnored(contextFile)) {
10894
10915
  logger.debug("Skipping ignored file for task:", {
10895
10916
  taskId: this.taskId,
@@ -10910,6 +10931,24 @@ class ContextManager {
10910
10931
  return [newContextFile];
10911
10932
  }
10912
10933
  }
10934
+ /**
10935
+ * Resolves a relative file path by delegating to Task.resolveContextFilePath
10936
+ * which tries taskDir first, then falls back to projectDir.
10937
+ */
10938
+ async resolveContextFilePath(relativePath) {
10939
+ return this.task.resolveContextFilePath(relativePath);
10940
+ }
10941
+ /**
10942
+ * Checks whether a resolved absolute path matches any already-added context file.
10943
+ * Accounts for files that may have been resolved from either taskDir or projectDir.
10944
+ */
10945
+ isContextFileAlreadyAdded(absolutePath) {
10946
+ return this.files.some((file) => {
10947
+ const taskDirResolved = path.resolve(this.task.getTaskDir(), file.path);
10948
+ const projectDirResolved = path.resolve(this.task.getProjectDir(), file.path);
10949
+ return taskDirResolved === absolutePath || projectDirResolved === absolutePath;
10950
+ });
10951
+ }
10913
10952
  dropContextFile(filePath) {
10914
10953
  const absolutePath = path.resolve(this.task.getTaskDir(), filePath);
10915
10954
  const droppedFiles = [];
@@ -12249,6 +12288,7 @@ class WorktreeManager {
12249
12288
  await fs.mkdir(worktreePath, { recursive: true });
12250
12289
  } catch (error) {
12251
12290
  logger.error("Failed to create worktree directory:", error);
12291
+ throw error;
12252
12292
  }
12253
12293
  }
12254
12294
  async createWorktree(projectPath, taskId, branch, baseBranch = "HEAD") {
@@ -13268,8 +13308,16 @@ Git output: ${err.stderr || err.stdout || err.message}`
13268
13308
  try {
13269
13309
  const commitMap = /* @__PURE__ */ new Map();
13270
13310
  let diffCommand = "git diff --numstat -z HEAD";
13311
+ let mergeBase = "";
13271
13312
  if (workingMode === "worktree" && mainBranch) {
13272
- diffCommand = `git diff --numstat -z ${mainBranch}`;
13313
+ mergeBase = mainBranch;
13314
+ try {
13315
+ const { stdout: baseOutput } = await execWithShellPath(`git merge-base HEAD ${mainBranch}`, { cwd: worktreePath });
13316
+ mergeBase = baseOutput.trim();
13317
+ } catch (mergeBaseError) {
13318
+ logger.warn("Failed to get merge-base, falling back to main branch:", mergeBaseError);
13319
+ }
13320
+ diffCommand = `git diff --numstat -z ${mergeBase}`;
13273
13321
  try {
13274
13322
  logger.debug("Getting commit information for worktree", { worktreePath, mainBranch });
13275
13323
  const { stdout: logOutput } = await execWithShellPath(`git log --pretty=format:'%H|%s' --name-only ${mainBranch}..HEAD`, { cwd: worktreePath });
@@ -13324,7 +13372,7 @@ Git output: ${err.stderr || err.stdout || err.message}`
13324
13372
  }
13325
13373
  }
13326
13374
  const escapedPath = filePath.replace(/"/g, '\\"');
13327
- const gitDiffCmd = workingMode === "worktree" && mainBranch ? `git diff --unified=3 ${mainBranch} -- "${escapedPath}"` : `git diff --unified=3 HEAD -- "${escapedPath}"`;
13375
+ const gitDiffCmd = workingMode === "worktree" && mainBranch ? `git diff --unified=3 ${mergeBase} -- "${escapedPath}"` : `git diff --unified=3 HEAD -- "${escapedPath}"`;
13328
13376
  const { stdout: diffOutput } = await execWithShellPath(gitDiffCmd, {
13329
13377
  cwd: worktreePath,
13330
13378
  maxBuffer: 10 * 1024 * 1024
@@ -13358,24 +13406,22 @@ Git output: ${err.stderr || err.stdout || err.message}`
13358
13406
  try {
13359
13407
  logger.info(`Restoring file: ${filePath}`, { worktreePath });
13360
13408
  const escapedPath = filePath.replace(/"/g, '\\"');
13361
- let isTracked = false;
13409
+ let existsInHead = false;
13362
13410
  try {
13363
- await execWithShellPath(`git ls-files --error-unmatch -- "${escapedPath}"`, { cwd: worktreePath });
13364
- isTracked = true;
13365
- } catch (error) {
13366
- const errorMessage = error instanceof Error ? error.message : String(error);
13367
- const isUntrackedFileError = errorMessage.includes("did not match");
13368
- if (isUntrackedFileError) {
13369
- isTracked = false;
13370
- } else {
13371
- throw error;
13372
- }
13411
+ await execWithShellPath(`git cat-file -e HEAD:"${escapedPath}"`, { cwd: worktreePath });
13412
+ existsInHead = true;
13413
+ } catch {
13414
+ existsInHead = false;
13373
13415
  }
13374
- if (isTracked) {
13375
- await execWithShellPath(`git restore -- "${escapedPath}"`, {
13416
+ if (existsInHead) {
13417
+ await execWithShellPath(`git restore --staged --worktree --source=HEAD -- "${escapedPath}"`, {
13376
13418
  cwd: worktreePath
13377
13419
  });
13378
13420
  } else {
13421
+ try {
13422
+ await execWithShellPath(`git rm --cached -f -- "${escapedPath}"`, { cwd: worktreePath });
13423
+ } catch {
13424
+ }
13379
13425
  const absolutePath = path.join(worktreePath, filePath);
13380
13426
  try {
13381
13427
  const stats = await fs.lstat(absolutePath);
@@ -13699,7 +13745,7 @@ const getTaskFinishedNotificationText = (task) => {
13699
13745
  ${TaskStateEmoji[task.state] || ""} ${task.state.toLowerCase().split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ")}` : ""}`;
13700
13746
  };
13701
13747
  class Task {
13702
- constructor(project, taskId, store, mcpManager, customCommandManager, agentProfileManager, telemetryManager, dataManager, eventManager, modelManager, worktreeManager, memoryManager, hookManager, promptsManager, extensionManager, initialTaskData) {
13748
+ constructor(project, taskId, store, mcpManager, customCommandManager, agentProfileManager, telemetryManager, dataManager, eventManager, modelManager, worktreeManager, memoryManager, promptsManager, extensionManager, initialTaskData) {
13703
13749
  this.project = project;
13704
13750
  this.taskId = taskId;
13705
13751
  this.store = store;
@@ -13712,7 +13758,6 @@ class Task {
13712
13758
  this.modelManager = modelManager;
13713
13759
  this.worktreeManager = worktreeManager;
13714
13760
  this.memoryManager = memoryManager;
13715
- this.hookManager = hookManager;
13716
13761
  this.promptsManager = promptsManager;
13717
13762
  this.extensionManager = extensionManager;
13718
13763
  this.task = {
@@ -13902,10 +13947,12 @@ class Task {
13902
13947
  } else {
13903
13948
  const branchName = this.generateBranchName();
13904
13949
  this.task.worktree = await this.worktreeManager.createWorktree(this.project.baseDir, this.taskId, branchName);
13950
+ void this.sendUpdatedFilesUpdated();
13905
13951
  }
13906
13952
  } else if (workingMode === "local") {
13907
13953
  if (existingWorktree) {
13908
13954
  await this.worktreeManager.removeWorktree(this.project.baseDir, existingWorktree);
13955
+ void this.sendUpdatedFilesUpdated();
13909
13956
  }
13910
13957
  } else {
13911
13958
  logger.debug("Empty workingMode, setting to local", {
@@ -13933,7 +13980,6 @@ class Task {
13933
13980
  await this.updateAutocompletionData();
13934
13981
  this.eventManager.sendTaskInitialized(this.task);
13935
13982
  this.initialized = true;
13936
- await this.hookManager.trigger("onTaskInitialized", { task: this.task }, this, this.project);
13937
13983
  await this.extensionManager.dispatchEvent("onTaskInitialized", { task: this.task }, this.project, this);
13938
13984
  }
13939
13985
  async load() {
@@ -14005,6 +14051,30 @@ class Task {
14005
14051
  getTaskDir() {
14006
14052
  return this.task.worktree ? this.task.worktree.path : this.project.baseDir;
14007
14053
  }
14054
+ /**
14055
+ * Resolves a relative file path against taskDir first, then falls back to projectDir.
14056
+ * This handles git worktree cases where files may exist in the project directory
14057
+ * but not in the worktree (taskDir).
14058
+ *
14059
+ * @param relativePath - Relative file path to resolve
14060
+ * @returns The resolved absolute path if the file/dir exists in either location, or null otherwise
14061
+ */
14062
+ async resolveContextFilePath(relativePath) {
14063
+ const taskDirAbsolutePath = path.resolve(this.getTaskDir(), relativePath);
14064
+ if (await fileExists(taskDirAbsolutePath) || await isDirectory(taskDirAbsolutePath)) {
14065
+ return taskDirAbsolutePath;
14066
+ }
14067
+ const projectDirAbsolutePath = path.resolve(this.getProjectDir(), relativePath);
14068
+ if (await fileExists(projectDirAbsolutePath) || await isDirectory(projectDirAbsolutePath)) {
14069
+ logger.debug("File not found in taskDir, falling back to projectDir:", {
14070
+ taskId: this.taskId,
14071
+ relativePath,
14072
+ projectDirAbsolutePath
14073
+ });
14074
+ return projectDirAbsolutePath;
14075
+ }
14076
+ return null;
14077
+ }
14008
14078
  /**
14009
14079
  * Dispatches an extension event through the ExtensionManager.
14010
14080
  * This public method provides controlled access to extension event dispatch
@@ -14038,7 +14108,6 @@ class Task {
14038
14108
  baseDir: this.project.baseDir,
14039
14109
  taskId: this.taskId
14040
14110
  });
14041
- await this.hookManager.trigger("onTaskClosed", { task: this.task }, this, this.project);
14042
14111
  await this.extensionManager.dispatchEvent("onTaskClosed", { task: this.task }, this.project, this);
14043
14112
  if (clearContext) {
14044
14113
  this.eventManager.sendClearTask(this.project.baseDir, this.taskId, true, true);
@@ -14125,13 +14194,6 @@ class Task {
14125
14194
  this.eventManager.sendQueuedPromptsUpdated(this.project.baseDir, this.taskId, this.queuedPrompts);
14126
14195
  return [];
14127
14196
  }
14128
- const hookResult = await this.hookManager.trigger("onPromptSubmitted", { prompt, mode }, this, this.project);
14129
- if (hookResult.blocked) {
14130
- logger.debug("Prompt blocked by hook");
14131
- return [];
14132
- }
14133
- prompt = hookResult.event.prompt;
14134
- mode = hookResult.event.mode;
14135
14197
  let promptContext = {
14136
14198
  id: userMessageId
14137
14199
  };
@@ -14206,14 +14268,6 @@ class Task {
14206
14268
  }
14207
14269
  async runPromptInAider(mode, prompt, promptContext, sendNotification = true) {
14208
14270
  await this.waitForCurrentPromptToFinish();
14209
- await this.hookManager.trigger("onPromptStarted", { prompt, mode }, this, this.project);
14210
- const aiderHookResult = await this.hookManager.trigger("onAiderPromptStarted", { prompt, mode }, this, this.project);
14211
- if (aiderHookResult.blocked) {
14212
- logger.debug("Aider prompt blocked by hook");
14213
- return [];
14214
- }
14215
- prompt = aiderHookResult.event.prompt;
14216
- mode = aiderHookResult.event.mode;
14217
14271
  await this.saveTask({
14218
14272
  name: this.task.name || this.getTaskNameFromPrompt(prompt),
14219
14273
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -14268,11 +14322,7 @@ class Task {
14268
14322
  this.contextManager.addContextMessage(assistantMessage);
14269
14323
  }
14270
14324
  }
14271
- void this.sendRequestContextInfo();
14272
- void this.sendWorktreeIntegrationStatusUpdated();
14273
- void this.sendUpdatedFilesUpdated();
14274
- await this.hookManager.trigger("onAiderPromptFinished", { responses }, this, this.project);
14275
- await this.hookManager.trigger("onPromptFinished", { responses }, this, this.project);
14325
+ this.sendStateUpdated();
14276
14326
  if (this.task.state === DefaultTaskState.InProgress) {
14277
14327
  await this.saveTask({
14278
14328
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -14287,7 +14337,6 @@ class Task {
14287
14337
  return responses;
14288
14338
  }
14289
14339
  async runPromptInAgent(profile, mode, prompt, promptContext = { id: uuid.v4() }, contextMessages, contextFiles, systemPrompt, waitForCurrentAgentToFinish = true, sendNotification = true) {
14290
- await this.hookManager.trigger("onPromptStarted", { prompt, mode: "agent" }, this, this.project);
14291
14340
  if (waitForCurrentAgentToFinish) {
14292
14341
  await this.waitForCurrentAgentToFinish();
14293
14342
  }
@@ -14302,7 +14351,6 @@ class Task {
14302
14351
  this.sendAddMessage(message.role, message.content, false);
14303
14352
  });
14304
14353
  }
14305
- await this.hookManager.trigger("onPromptFinished", { responses: [] }, this, this.project);
14306
14354
  void this.sendRequestContextInfo();
14307
14355
  void this.sendWorktreeIntegrationStatusUpdated();
14308
14356
  void this.sendUpdatedFilesUpdated();
@@ -14445,12 +14493,6 @@ ${contentText}</agent-response>`;
14445
14493
  this.registerSubagentAbortController(interruptId, abortController);
14446
14494
  }
14447
14495
  try {
14448
- const hookResult = await this.hookManager.trigger("onSubagentStarted", { subagentId: profile.id, prompt }, this, this.project);
14449
- if (hookResult.blocked) {
14450
- logger.debug("Subagent execution blocked by hook");
14451
- return [];
14452
- }
14453
- prompt = hookResult.event.prompt;
14454
14496
  if (!contextMessages) {
14455
14497
  contextMessages = await this.getContextMessages();
14456
14498
  }
@@ -14491,10 +14533,6 @@ ${contentText}</agent-response>`;
14491
14533
  false,
14492
14534
  abortController?.signal
14493
14535
  );
14494
- const finishedHookResult = await this.hookManager.trigger("onSubagentFinished", { subagentId: profile.id, resultMessages }, this, this.project);
14495
- if (finishedHookResult.event.resultMessages) {
14496
- resultMessages = finishedHookResult.event.resultMessages;
14497
- }
14498
14536
  const subagentFinishedExtensionResult = await this.extensionManager.dispatchEvent(
14499
14537
  "onSubagentFinished",
14500
14538
  { subagentProfile: profile, resultMessages },
@@ -14542,10 +14580,6 @@ ${contentText}</agent-response>`;
14542
14580
  }
14543
14581
  }
14544
14582
  async processResponseMessage(message, saveToDb = true) {
14545
- const hookResult = await this.hookManager.trigger("onResponseMessageProcessed", { message }, this, this.project);
14546
- if (hookResult.result) {
14547
- message = { ...message, ...hookResult.result };
14548
- }
14549
14583
  if (!message.finished) {
14550
14584
  const sendResponseChunk = async (chunk) => {
14551
14585
  let data = {
@@ -14679,7 +14713,6 @@ ${contentText}</agent-response>`;
14679
14713
  if (!this.currentQuestion) {
14680
14714
  return false;
14681
14715
  }
14682
- void this.hookManager.trigger("onQuestionAnswered", { question: this.currentQuestion, answer, userInput }, this, this.project);
14683
14716
  const extensionResult = await this.extensionManager.dispatchEvent(
14684
14717
  "onQuestionAnswered",
14685
14718
  { question: this.currentQuestion, answer, userInput },
@@ -14733,12 +14766,6 @@ ${contentText}</agent-response>`;
14733
14766
  async addFiles(...contextFiles) {
14734
14767
  const addedFiles = [];
14735
14768
  for (let contextFile of contextFiles) {
14736
- const hookResult = await this.hookManager.trigger("onFileAdded", { file: contextFile }, this, this.project);
14737
- if (hookResult.blocked) {
14738
- logger.debug("File addition blocked by hook");
14739
- return false;
14740
- }
14741
- contextFile = hookResult.event.file;
14742
14769
  const extensionResult = await this.extensionManager.dispatchEvent("onFilesAdded", { files: [contextFile] }, this.project, this);
14743
14770
  if (extensionResult.files.length === 0) {
14744
14771
  logger.debug("File addition blocked by extension (empty files array)");
@@ -14767,7 +14794,6 @@ ${contentText}</agent-response>`;
14767
14794
  this.findMessageConnectors("add-file").forEach((connector) => connector.sendAddFileMessage(contextFile, noUpdate));
14768
14795
  }
14769
14796
  async dropFile(filePath) {
14770
- await this.hookManager.trigger("onFileDropped", { filePath }, this, this.project);
14771
14797
  await this.extensionManager.dispatchEvent("onFilesDropped", { files: [{ path: filePath }] }, this.project, this);
14772
14798
  const normalizedPath = this.normalizeFilePath(filePath);
14773
14799
  logger.info("Dropping file or folder:", { path: normalizedPath });
@@ -14800,18 +14826,18 @@ ${contentText}</agent-response>`;
14800
14826
  this.addLogMessage("warning", `Failed to add new file ${absolutePath} to git staging area: ${gitErrorMessage}`, false, promptContext);
14801
14827
  }
14802
14828
  }
14829
+ sendStateUpdated() {
14830
+ void this.sendRequestContextInfo();
14831
+ void this.sendWorktreeIntegrationStatusUpdated();
14832
+ void this.sendUpdatedFilesUpdated();
14833
+ void this.sendContextFilesUpdated();
14834
+ }
14803
14835
  async sendContextFilesUpdated() {
14804
14836
  const mode = this.getCurrentMode();
14805
14837
  const allFiles = await this.getContextFiles(!AIDER_MODES.includes(mode));
14806
14838
  this.eventManager.sendContextFilesUpdated(this.project.baseDir, this.taskId, allFiles);
14807
14839
  }
14808
14840
  async runCommand(command, addToHistory = true) {
14809
- const hookResult = await this.hookManager.trigger("onCommandExecuted", { command }, this, this.project);
14810
- if (hookResult.blocked) {
14811
- logger.debug("Command execution blocked by hook");
14812
- return;
14813
- }
14814
- command = hookResult.event.command;
14815
14841
  const extensionResult = await this.extensionManager.dispatchEvent("onCommandExecuted", { command }, this.project, this);
14816
14842
  if (extensionResult.blocked) {
14817
14843
  logger.debug("Command execution blocked by extension");
@@ -14883,14 +14909,6 @@ ${contentText}</agent-response>`;
14883
14909
  void this.updateContextInfo(true, true);
14884
14910
  }
14885
14911
  async askQuestion(questionData, awaitAnswer = true) {
14886
- const hookResult = await this.hookManager.trigger("onQuestionAsked", { question: questionData }, this, this.project);
14887
- if (hookResult.result && typeof hookResult.result === "string") {
14888
- logger.info("Question answered by hook", {
14889
- question: questionData.text,
14890
- answer: hookResult.result
14891
- });
14892
- return [hookResult.result, void 0];
14893
- }
14894
14912
  const extensionResult = await this.extensionManager.dispatchEvent("onQuestionAsked", { question: questionData }, this.project, this);
14895
14913
  if (extensionResult.answer) {
14896
14914
  logger.info("Question answered by extension", {
@@ -15987,7 +16005,7 @@ ${contentText}</agent-response>`;
15987
16005
  });
15988
16006
  return [];
15989
16007
  }
15990
- async initProjectAgentsFile() {
16008
+ async initProjectAgentsFile(args) {
15991
16009
  logger.info("Initializing AGENTS.md file", {
15992
16010
  baseDir: this.project.baseDir
15993
16011
  });
@@ -16006,24 +16024,15 @@ ${contentText}</agent-response>`;
16006
16024
  provider: activeProfile.provider,
16007
16025
  model: activeProfile.model
16008
16026
  };
16009
- await this.runPromptInAgent(initProjectRulesAgentProfile, "init-project-agents-file", await this.promptsManager.getInitProjectPrompt(this));
16027
+ const systemPrompt = await this.promptsManager.getInitProjectSystemPrompt(this);
16028
+ const userPrompt = args ? `/init ${args}` : "/init";
16029
+ await this.runPromptInAgent(initProjectRulesAgentProfile, "init-project-agents-file", userPrompt, { id: uuid.v4() }, void 0, void 0, systemPrompt);
16010
16030
  const projectAgentsPath = path.join(this.project.baseDir, "AGENTS.md");
16011
16031
  const projectAgentsFileExists = await fileExists(projectAgentsPath);
16012
16032
  if (projectAgentsFileExists) {
16013
16033
  logger.info("AGENTS.md file created successfully", {
16014
16034
  path: projectAgentsPath
16015
16035
  });
16016
- this.addLogMessage("info", "AGENTS.md has been successfully initialized.");
16017
- const [answer] = await this.askQuestion({
16018
- baseDir: this.project.baseDir,
16019
- taskId: this.taskId,
16020
- text: "Do you want to add AGENTS.md as read-only file for Aider (in .aider.conf.yml)?",
16021
- defaultAnswer: "y",
16022
- internal: false
16023
- });
16024
- if (answer === "y") {
16025
- await this.addProjectAgentsToAiderConfig();
16026
- }
16027
16036
  } else {
16028
16037
  logger.warn("AGENTS.md file was not created");
16029
16038
  this.addLogMessage("warning", "AGENTS.md file was not created.");
@@ -16037,37 +16046,6 @@ ${contentText}</agent-response>`;
16037
16046
  this.contextManager.setContextMessages(messages, false);
16038
16047
  }
16039
16048
  }
16040
- async addProjectAgentsToAiderConfig() {
16041
- const aiderConfigPath = path.join(this.project.baseDir, ".aider.conf.yml");
16042
- const projectAgentsRelativePath = "AGENTS.md";
16043
- try {
16044
- let config = {};
16045
- if (await fileExists(aiderConfigPath)) {
16046
- const configContent = await fs.readFile(aiderConfigPath, "utf8");
16047
- config = YAML.parse(configContent) || {};
16048
- }
16049
- if (!config.read) {
16050
- config.read = [];
16051
- } else if (!Array.isArray(config.read)) {
16052
- config.read = [config.read];
16053
- }
16054
- if (!config.read.includes(projectAgentsRelativePath)) {
16055
- config.read.push(projectAgentsRelativePath);
16056
- const yamlContent = YAML.stringify(config);
16057
- await fs.writeFile(aiderConfigPath, yamlContent, "utf8");
16058
- logger.info("Added AGENTS.md to .aider.conf.yml", {
16059
- path: aiderConfigPath
16060
- });
16061
- this.addLogMessage("info", `Added ${projectAgentsRelativePath} to .aider.conf.yml`);
16062
- } else {
16063
- logger.info("AGENTS.md already exists in .aider.conf.yml");
16064
- }
16065
- } catch (error) {
16066
- logger.error("Error updating .aider.conf.yml:", error);
16067
- this.addLogMessage("error", `Failed to update .aider.conf.yml: ${error instanceof Error ? error.message : String(error)}`);
16068
- throw error;
16069
- }
16070
- }
16071
16049
  async runExtensionCommand(extensionCommand, args, mode) {
16072
16050
  const { command } = extensionCommand;
16073
16051
  logger.info("Running extension command:", {
@@ -16939,7 +16917,7 @@ const migrateSessionFile = async (project, sessionsDirPath, tasksDirPath, sessio
16939
16917
  }
16940
16918
  };
16941
16919
  class Project {
16942
- constructor(baseDir, store, mcpManager, telemetryManager, dataManager, eventManager, modelManager, worktreeManager, agentProfileManager, memoryManager, hookManager, promptsManager, extensionManager) {
16920
+ constructor(baseDir, store, mcpManager, telemetryManager, dataManager, eventManager, modelManager, worktreeManager, agentProfileManager, memoryManager, promptsManager, extensionManager) {
16943
16921
  this.baseDir = baseDir;
16944
16922
  this.store = store;
16945
16923
  this.mcpManager = mcpManager;
@@ -16950,7 +16928,6 @@ class Project {
16950
16928
  this.worktreeManager = worktreeManager;
16951
16929
  this.agentProfileManager = agentProfileManager;
16952
16930
  this.memoryManager = memoryManager;
16953
- this.hookManager = hookManager;
16954
16931
  this.promptsManager = promptsManager;
16955
16932
  this.extensionManager = extensionManager;
16956
16933
  this.customCommandManager = new CustomCommandManager(this, this.eventManager, this.extensionManager);
@@ -16992,6 +16969,7 @@ class Project {
16992
16969
  }
16993
16970
  }
16994
16971
  const sourceTask = parentTask || this.getMostRecentTask();
16972
+ const defaultWorkingMode = this.store.getSettings().taskSettings.defaultWorkingMode || "local";
16995
16973
  let initialTaskData;
16996
16974
  if (sourceTask) {
16997
16975
  initialTaskData = {
@@ -17005,6 +16983,7 @@ class Project {
17005
16983
  provider: parentTask?.task.provider,
17006
16984
  model: parentTask?.task.model,
17007
16985
  weakModelLocked: sourceTask.task.weakModelLocked,
16986
+ workingMode: defaultWorkingMode,
17008
16987
  ...parentTask ? {
17009
16988
  workingMode: parentTask.task.workingMode,
17010
16989
  worktree: parentTask.task.worktree
@@ -17014,9 +16993,10 @@ class Project {
17014
16993
  } else {
17015
16994
  const providerModels = await this.modelManager.getProviderModels();
17016
16995
  initialTaskData = {
17017
- mainModel: determineMainModel(this.store.getSettings(), this.store.getProviders(), providerModels.models || [], this.baseDir),
16996
+ mainModel: determineMainModel(this.store.getSettings(), this.modelManager.getProviders(), providerModels.models || [], this.baseDir),
17018
16997
  weakModel: determineWeakModel(this.baseDir),
17019
16998
  currentMode: "agent",
16999
+ workingMode: defaultWorkingMode,
17020
17000
  ...normalizedParams
17021
17001
  };
17022
17002
  }
@@ -17056,13 +17036,11 @@ class Project {
17056
17036
  this.modelManager,
17057
17037
  this.worktreeManager,
17058
17038
  this.memoryManager,
17059
- this.hookManager,
17060
17039
  this.promptsManager,
17061
17040
  this.extensionManager,
17062
17041
  initialTaskData
17063
17042
  );
17064
17043
  this.tasks.set(taskId, task);
17065
- void task.hookManager.trigger("onTaskCreated", { task: task.task }, task, this);
17066
17044
  const extResult = await this.extensionManager.dispatchEvent("onTaskPrepared", { task: task.task }, this, task);
17067
17045
  if (extResult.task) {
17068
17046
  Object.assign(task.task, extResult.task);
@@ -17299,7 +17277,6 @@ class Project {
17299
17277
  await this.extensionManager.dispatchEvent("onProjectStopped", { baseDir: this.baseDir }, this);
17300
17278
  this.customCommandManager.dispose();
17301
17279
  this.agentProfileManager.removeProject(this.baseDir);
17302
- await this.hookManager.stopWatchingProject(this.baseDir);
17303
17280
  await this.promptsManager.unwatchProject(this.baseDir);
17304
17281
  this.extensionManager.stopProjectWatcher(this.baseDir);
17305
17282
  await Promise.all(Array.from(this.tasks.values()).map((task) => task.close()));
@@ -17316,7 +17293,7 @@ class Project {
17316
17293
  }
17317
17294
  }
17318
17295
  class ProjectManager {
17319
- constructor(store, mcpManager, telemetryManager, dataManager, eventManager, modelManager, worktreeManager, agentProfileManager, memoryManager, hookManager, promptsManager, extensionManager) {
17296
+ constructor(store, mcpManager, telemetryManager, dataManager, eventManager, modelManager, worktreeManager, agentProfileManager, memoryManager, promptsManager, extensionManager) {
17320
17297
  this.store = store;
17321
17298
  this.mcpManager = mcpManager;
17322
17299
  this.telemetryManager = telemetryManager;
@@ -17325,7 +17302,6 @@ class ProjectManager {
17325
17302
  this.modelManager = modelManager;
17326
17303
  this.agentProfileManager = agentProfileManager;
17327
17304
  this.memoryManager = memoryManager;
17328
- this.hookManager = hookManager;
17329
17305
  this.promptsManager = promptsManager;
17330
17306
  this.extensionManager = extensionManager;
17331
17307
  this.worktreeManager = worktreeManager;
@@ -17348,7 +17324,6 @@ class ProjectManager {
17348
17324
  this.worktreeManager,
17349
17325
  this.agentProfileManager,
17350
17326
  this.memoryManager,
17351
- this.hookManager,
17352
17327
  this.promptsManager,
17353
17328
  this.extensionManager
17354
17329
  );
@@ -21301,13 +21276,14 @@ class ModelManager {
21301
21276
  "vertex-ai": vertexAiProviderStrategy,
21302
21277
  "zai-plan": zaiPlanProviderStrategy
21303
21278
  };
21279
+ extensionProviders = /* @__PURE__ */ new Map();
21304
21280
  async init() {
21305
21281
  try {
21306
21282
  logger.info("Initializing ModelInfoManager...");
21307
21283
  this.updateEnvVarsProviders();
21308
21284
  await this.loadModelsInfo();
21309
21285
  await this.loadModelOverrides();
21310
- await this.loadProviderModels(this.store.getProviders().filter((p) => !p.disabled));
21286
+ await this.loadProviderModels(this.getProviders().filter((p) => !p.disabled));
21311
21287
  logger.info("ModelInfoManager initialized successfully.", {
21312
21288
  modelCount: Object.keys(this.modelsInfo).length
21313
21289
  });
@@ -21545,7 +21521,8 @@ class ModelManager {
21545
21521
  this.providerModels = {};
21546
21522
  this.providerErrors = {};
21547
21523
  }
21548
- await this.loadProviderModels(this.store.getProviders().filter((p) => !p.disabled));
21524
+ const allProviders = this.getProviders();
21525
+ await this.loadProviderModels(allProviders.filter((p) => !p.disabled));
21549
21526
  }
21550
21527
  return {
21551
21528
  models: Object.values(this.providerModels).flat(),
@@ -21604,7 +21581,7 @@ class ModelManager {
21604
21581
  logger.info(`Added model override: ${providerId}/${modelId}`);
21605
21582
  }
21606
21583
  await this.saveModelOverrides();
21607
- await this.loadProviderModels(this.store.getProviders().filter((provider) => provider.id === providerId));
21584
+ await this.loadProviderModels(this.getProviders().filter((provider) => provider.id === providerId));
21608
21585
  }
21609
21586
  async deleteModel(providerId, modelId) {
21610
21587
  await this.initPromise;
@@ -21616,7 +21593,7 @@ class ModelManager {
21616
21593
  if (this.modelOverrides.length < initialLength) {
21617
21594
  await this.saveModelOverrides();
21618
21595
  logger.info(`Deleted model override: ${providerId}/${modelId}`);
21619
- await this.loadProviderModels(this.store.getProviders().filter((provider) => provider.id === providerId));
21596
+ await this.loadProviderModels(this.getProviders().filter((provider) => provider.id === providerId));
21620
21597
  } else {
21621
21598
  logger.warn(`Model override not found for deletion: ${providerId}/${modelId}`);
21622
21599
  }
@@ -21644,12 +21621,12 @@ class ModelManager {
21644
21621
  affectedProviderIds.add(providerId);
21645
21622
  }
21646
21623
  await this.saveModelOverrides();
21647
- const affectedProviders = this.store.getProviders().filter((provider) => affectedProviderIds.has(provider.id));
21624
+ const affectedProviders = this.getProviders().filter((provider) => affectedProviderIds.has(provider.id));
21648
21625
  await this.loadProviderModels(affectedProviders);
21649
21626
  logger.info(`Bulk updated ${modelUpdates.length} model overrides for ${affectedProviderIds.size} providers`);
21650
21627
  }
21651
21628
  getAiderModelMapping(modelName, projectDir) {
21652
- const providers = this.store.getProviders();
21629
+ const providers = this.getProviders();
21653
21630
  const [providerId, modelId] = extractProviderModel(modelName);
21654
21631
  if (!providerId || !modelId) {
21655
21632
  logger.error("Invalid provider/model format:", modelName);
@@ -21862,6 +21839,75 @@ class ModelManager {
21862
21839
  }
21863
21840
  return strategy.isRetryable(error);
21864
21841
  }
21842
+ registerExtensionProviders(providers) {
21843
+ const newProfiles = [];
21844
+ for (const registered of providers) {
21845
+ const { provider } = registered;
21846
+ this.providerRegistry[provider.provider.name] = {
21847
+ ...provider.strategy,
21848
+ createLlm: (profile2, model, settings, projectDir) => provider.strategy.createLlm(profile2, model, settings, projectDir),
21849
+ getUsageReport: provider.strategy.getUsageReport || getDefaultUsageReport,
21850
+ getProviderOptions: provider.strategy.getProviderOptions ? (_provider, model) => provider.strategy.getProviderOptions(model) : void 0,
21851
+ getProviderTools: provider.strategy.getProviderTools ? (_provider, model) => provider.strategy.getProviderTools(model) : void 0,
21852
+ getProviderParameters: provider.strategy.getProviderParameters ? (_provider, model) => provider.strategy.getProviderParameters(model) : void 0,
21853
+ getCacheControl: provider.strategy.getCacheControl ? (profile2) => provider.strategy.getCacheControl(profile2) : void 0,
21854
+ hasEnvVars: () => false,
21855
+ getAiderMapping: provider.strategy.getAiderMapping ? provider.strategy.getAiderMapping : (_provider, modelId) => ({
21856
+ modelName: modelId,
21857
+ environmentVariables: {}
21858
+ })
21859
+ };
21860
+ const profile = {
21861
+ id: provider.id,
21862
+ name: provider.name,
21863
+ provider: provider.provider,
21864
+ headers: provider.headers,
21865
+ extensionId: registered.extensionId
21866
+ };
21867
+ logger.info(`Registering extension provider: ${profile.name} (ID: ${profile.id}) from extension ${registered.extensionId}`);
21868
+ this.extensionProviders.set(`${registered.extensionId}:${provider.id}`, {
21869
+ provider,
21870
+ profile
21871
+ });
21872
+ newProfiles.push(profile);
21873
+ }
21874
+ if (newProfiles.length > 0) {
21875
+ logger.info(`[Models] Registered ${newProfiles.length} extension provider(s)`);
21876
+ void this.loadProviderModels(newProfiles);
21877
+ this.eventManager.sendProvidersUpdated(this.getProviders());
21878
+ }
21879
+ }
21880
+ unregisterExtensionProviders(extensionId) {
21881
+ const toRemove = [];
21882
+ for (const [key, entry] of this.extensionProviders) {
21883
+ if (entry.profile.extensionId === extensionId) {
21884
+ toRemove.push(key);
21885
+ }
21886
+ }
21887
+ if (toRemove.length === 0) {
21888
+ return;
21889
+ }
21890
+ for (const key of toRemove) {
21891
+ const entry = this.extensionProviders.get(key);
21892
+ delete this.providerRegistry[entry.provider.provider.name];
21893
+ delete this.providerModels[entry.profile.id];
21894
+ delete this.providerErrors[entry.profile.id];
21895
+ this.extensionProviders.delete(key);
21896
+ }
21897
+ logger.info(`[Models] Unregistered ${toRemove.length} extension provider(s) for extension ${extensionId}`);
21898
+ this.eventManager.sendProviderModelsUpdated({
21899
+ models: Object.values(this.providerModels).flat(),
21900
+ loading: false,
21901
+ errors: this.providerErrors
21902
+ });
21903
+ this.eventManager.sendProvidersUpdated(this.getProviders());
21904
+ }
21905
+ getProviders() {
21906
+ return [...this.store.getProviders(), ...this.getExtensionProviderProfiles()];
21907
+ }
21908
+ getExtensionProviderProfiles() {
21909
+ return [...this.extensionProviders.values()].map((e) => e.profile);
21910
+ }
21865
21911
  }
21866
21912
  class DataManager {
21867
21913
  db;
@@ -22011,9 +22057,8 @@ class DataManager {
22011
22057
  }
22012
22058
  }
22013
22059
  class TerminalManager {
22014
- constructor(eventManager, worktreeManager, telemetryManager) {
22060
+ constructor(eventManager, telemetryManager) {
22015
22061
  this.eventManager = eventManager;
22016
- this.worktreeManager = worktreeManager;
22017
22062
  this.telemetryManager = telemetryManager;
22018
22063
  }
22019
22064
  terminals = /* @__PURE__ */ new Map();
@@ -22036,11 +22081,10 @@ class TerminalManager {
22036
22081
  }
22037
22082
  return [];
22038
22083
  }
22039
- async createTerminal(baseDir, taskId, cols = 80, rows = 24) {
22084
+ async createTerminal(baseDir, taskDir, taskId, cols = 80, rows = 24) {
22040
22085
  const terminalId = uuid.v4();
22041
22086
  try {
22042
- const worktree = await this.worktreeManager.getTaskWorktree(baseDir, taskId);
22043
- const cwd = worktree ? worktree.path : baseDir;
22087
+ const cwd = taskDir;
22044
22088
  const shell = this.getShellCommand();
22045
22089
  const args = this.getShellArgs();
22046
22090
  logger.info("Creating terminal:", { terminalId, baseDir, cwd, shell, args, cols, rows });
@@ -23393,7 +23437,9 @@ class ExtensionFetcher {
23393
23437
  return cached.extensions;
23394
23438
  }
23395
23439
  try {
23396
- const extensions = await this.fetchExtensionsViaGitClone(repoUrl);
23440
+ const repoDir = await this.ensureRepoCloned(repoUrl);
23441
+ const extensionsPath = this.getExtensionsPath(repoUrl, repoDir);
23442
+ const extensions = await this.scanForExtensions(extensionsPath, repoUrl);
23397
23443
  this.cache.set(repoUrl, {
23398
23444
  timestamp: now,
23399
23445
  extensions
@@ -23408,37 +23454,39 @@ class ExtensionFetcher {
23408
23454
  throw error;
23409
23455
  }
23410
23456
  }
23411
- async fetchExtensionsViaGitClone(repoUrl) {
23412
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ext-repo-"));
23457
+ getRepoDirName(repoUrl) {
23413
23458
  try {
23414
- const cloneUrl = this.getCloneUrl(repoUrl);
23415
- if (!cloneUrl) {
23416
- throw new Error(`Invalid repository URL: ${repoUrl}`);
23417
- }
23418
- const extensionSubPath = this.getExtensionSubPath(repoUrl);
23419
- if (extensionSubPath) {
23420
- logger.info(`[ExtensionFetcher] Sparse cloning repository to ${tempDir}, path: ${extensionSubPath}`);
23421
- await execWithShellPath(`git clone --depth 1 --sparse "${cloneUrl}" "${tempDir}"`, {
23422
- cwd: os.tmpdir()
23423
- });
23424
- await execWithShellPath(`git -C "${tempDir}" sparse-checkout set "${extensionSubPath}"`, {
23425
- cwd: tempDir
23426
- });
23427
- } else {
23428
- logger.info(`[ExtensionFetcher] Cloning repository to ${tempDir}`);
23429
- await execWithShellPath(`git clone --depth 1 "${cloneUrl}" "${tempDir}"`, {
23430
- cwd: os.tmpdir()
23431
- });
23459
+ const url = new URL(repoUrl);
23460
+ const pathParts = url.pathname.split("/").filter(Boolean);
23461
+ if (pathParts.length >= 2) {
23462
+ return `${pathParts[0]}-${pathParts[1]}`;
23432
23463
  }
23433
- const extensionsPath = this.getExtensionsPath(repoUrl, tempDir);
23434
- return await this.scanForExtensions(extensionsPath, repoUrl);
23435
- } finally {
23464
+ } catch {
23465
+ }
23466
+ return repoUrl.replace(/[^a-zA-Z0-9]/g, "-");
23467
+ }
23468
+ async ensureRepoCloned(repoUrl) {
23469
+ const cloneUrl = this.getCloneUrl(repoUrl);
23470
+ if (!cloneUrl) {
23471
+ throw new Error(`Invalid repository URL: ${repoUrl}`);
23472
+ }
23473
+ const dirName = this.getRepoDirName(repoUrl);
23474
+ const repoDir = path.join(EXTENSIONS_REPOS_CACHE_DIR, dirName);
23475
+ const dirExists = await this.fileExists(path.join(repoDir, ".git"));
23476
+ if (dirExists) {
23436
23477
  try {
23437
- await fs.rm(tempDir, { recursive: true, force: true });
23438
- } catch (cleanupError) {
23439
- logger.warn(`[ExtensionFetcher] Failed to cleanup temp directory ${tempDir}:`, cleanupError);
23478
+ logger.debug(`[ExtensionFetcher] Pulling existing repository cache at ${repoDir}`);
23479
+ await execWithShellPath(`git -C "${repoDir}" pull`, { cwd: repoDir });
23480
+ return repoDir;
23481
+ } catch (pullError) {
23482
+ logger.warn(`[ExtensionFetcher] git pull failed for ${repoDir}, removing and re-cloning:`, pullError);
23483
+ await fs.rm(repoDir, { recursive: true, force: true });
23440
23484
  }
23441
23485
  }
23486
+ await fs.mkdir(EXTENSIONS_REPOS_CACHE_DIR, { recursive: true });
23487
+ logger.info(`[ExtensionFetcher] Cloning repository to ${repoDir}`);
23488
+ await execWithShellPath(`git clone "${cloneUrl}" "${repoDir}"`, { cwd: EXTENSIONS_REPOS_CACHE_DIR });
23489
+ return repoDir;
23442
23490
  }
23443
23491
  getCloneUrl(webUrl) {
23444
23492
  try {
@@ -23470,17 +23518,11 @@ class ExtensionFetcher {
23470
23518
  }
23471
23519
  }
23472
23520
  getExtensionsPath(repoUrl, clonedRepoPath) {
23473
- try {
23474
- const url = new URL(repoUrl);
23475
- const pathParts = url.pathname.split("/").filter(Boolean);
23476
- if (pathParts.length > 4 && pathParts[2] === "tree") {
23477
- const extensionPath = pathParts.slice(4).join("/");
23478
- return path.join(clonedRepoPath, extensionPath);
23479
- }
23480
- return clonedRepoPath;
23481
- } catch {
23482
- return clonedRepoPath;
23521
+ const subPath = this.getExtensionSubPath(repoUrl);
23522
+ if (subPath) {
23523
+ return path.join(clonedRepoPath, subPath);
23483
23524
  }
23525
+ return clonedRepoPath;
23484
23526
  }
23485
23527
  async scanForExtensions(extensionsPath, repoUrl) {
23486
23528
  const extensions = [];
@@ -23850,6 +23892,10 @@ class ExtensionManager {
23850
23892
  reloadComponents: true
23851
23893
  });
23852
23894
  }
23895
+ const providers = this.getProviders(project);
23896
+ if (providers.length > 0) {
23897
+ this.modelManager.registerExtensionProviders(providers);
23898
+ }
23853
23899
  this.debouncedNotifyListeners();
23854
23900
  }
23855
23901
  getExtensions(projectDir) {
@@ -23908,6 +23954,7 @@ class ExtensionManager {
23908
23954
  }
23909
23955
  }
23910
23956
  this.registry.unregister(filePath);
23957
+ this.modelManager.unregisterExtensionProviders(extension.id);
23911
23958
  logger.debug(`[Extensions] Unloaded extension: ${filePath}`);
23912
23959
  }
23913
23960
  findExtensionByPath(filePath) {
@@ -24336,6 +24383,39 @@ class ExtensionManager {
24336
24383
  errors
24337
24384
  };
24338
24385
  }
24386
+ getProviders(project) {
24387
+ const collectedProviders = [];
24388
+ const allExtensions = this.registry.getExtensions(project?.baseDir);
24389
+ const extensions = this.filterEnabledExtensions(allExtensions);
24390
+ for (const loaded of extensions) {
24391
+ const { instance, metadata } = loaded;
24392
+ if (!instance.getProviders) {
24393
+ continue;
24394
+ }
24395
+ try {
24396
+ const context = new ExtensionContextImpl(loaded.id, metadata.name, this.store, this.modelManager, this.eventManager, project, void 0);
24397
+ const providers = instance.getProviders(context);
24398
+ if (!Array.isArray(providers)) {
24399
+ logger.error(`[Extensions] Extension '${metadata.name}' getProviders() did not return an array`);
24400
+ continue;
24401
+ }
24402
+ for (const provider of providers) {
24403
+ if (!provider.id || !provider.name || !provider.strategy) {
24404
+ logger.error(`[Extensions] Invalid provider from extension '${metadata.name}': missing id, name or strategy`);
24405
+ continue;
24406
+ }
24407
+ collectedProviders.push({
24408
+ extensionId: loaded.id,
24409
+ extensionName: metadata.name,
24410
+ provider
24411
+ });
24412
+ }
24413
+ } catch (error) {
24414
+ logger.error(`[Extensions] Failed to get providers from extension '${metadata.name}':`, error);
24415
+ }
24416
+ }
24417
+ return collectedProviders;
24418
+ }
24339
24419
  getUIComponents(project, task) {
24340
24420
  const collectedComponents = [];
24341
24421
  const allExtensions = this.registry.getExtensions(project?.baseDir);
@@ -24506,7 +24586,7 @@ class ExtensionManager {
24506
24586
  const allExtensions = [];
24507
24587
  for (const repoUrl of repositories) {
24508
24588
  try {
24509
- const extensions = await this.fetcher.fetchExtensionsViaGitClone(repoUrl);
24589
+ const extensions = await this.fetcher.fetchExtensionsFromRepo(repoUrl, true);
24510
24590
  allExtensions.push(...extensions);
24511
24591
  } catch (error) {
24512
24592
  logger.error(`[ExtensionManager] Failed to fetch extensions from ${repoUrl}:`, error);
@@ -24549,33 +24629,19 @@ class ExtensionManager {
24549
24629
  await fs.writeFile(targetPath, code, "utf-8");
24550
24630
  logger.debug(`[Extensions] Installed single-file extension: ${extension.file}`);
24551
24631
  } else if (extension.type === "folder" && extension.folder) {
24552
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ext-install-"));
24553
- try {
24554
- const cloneUrl = this.getCloneUrl(repositoryUrl);
24555
- if (!cloneUrl) {
24556
- throw new Error("Invalid repository URL for cloning");
24557
- }
24558
- logger.debug(`[Extensions] Cloning repository to ${tempDir}`);
24559
- await execWithShellPath(`git clone --depth 1 "${cloneUrl}" "${tempDir}"`);
24560
- const extensionsPath = this.getExtensionsPath(repositoryUrl, tempDir);
24561
- const sourcePath = path.join(extensionsPath, extension.folder);
24562
- const targetPath = path.join(targetDir, extension.folder);
24563
- if (!await this.fileExists(sourcePath)) {
24564
- throw new Error(`Extension folder not found in repository: ${extension.folder}`);
24565
- }
24566
- await fs.cp(sourcePath, targetPath, { recursive: true });
24567
- if (extension.hasDependencies) {
24568
- logger.debug(`[Extensions] Installing dependencies for ${extension.name}...`);
24569
- await this.installDependencies(targetPath);
24570
- }
24571
- logger.debug(`[Extensions] Installed folder extension: ${extension.folder}`);
24572
- } finally {
24573
- try {
24574
- await fs.rm(tempDir, { recursive: true, force: true });
24575
- } catch (cleanupError) {
24576
- logger.warn(`[Extensions] Failed to cleanup temp directory ${tempDir}:`, cleanupError);
24577
- }
24632
+ const repoDir = await this.fetcher.ensureRepoCloned(repositoryUrl);
24633
+ const extensionsPath = this.fetcher.getExtensionsPath(repositoryUrl, repoDir);
24634
+ const sourcePath = path.join(extensionsPath, extension.folder);
24635
+ const targetPath = path.join(targetDir, extension.folder);
24636
+ if (!await this.fileExists(sourcePath)) {
24637
+ throw new Error(`Extension folder not found in repository: ${extension.folder}`);
24578
24638
  }
24639
+ await fs.cp(sourcePath, targetPath, { recursive: true });
24640
+ if (extension.hasDependencies) {
24641
+ logger.debug(`[Extensions] Installing dependencies for ${extension.name}...`);
24642
+ await this.installDependencies(targetPath);
24643
+ }
24644
+ logger.debug(`[Extensions] Installed folder extension: ${extension.folder}`);
24579
24645
  }
24580
24646
  await this.loadExtensionsForDir(targetDir, project);
24581
24647
  logger.info(`[Extensions] Successfully installed ${extension.name}`);
@@ -24606,36 +24672,6 @@ class ExtensionManager {
24606
24672
  child.on("error", reject);
24607
24673
  });
24608
24674
  }
24609
- getCloneUrl(webUrl) {
24610
- try {
24611
- const url = new URL(webUrl);
24612
- if (url.hostname !== "github.com") {
24613
- return null;
24614
- }
24615
- const pathParts = url.pathname.split("/").filter(Boolean);
24616
- if (pathParts.length < 2) {
24617
- return null;
24618
- }
24619
- const owner = pathParts[0];
24620
- const repo = pathParts[1];
24621
- return `https://github.com/${owner}/${repo}.git`;
24622
- } catch {
24623
- return null;
24624
- }
24625
- }
24626
- getExtensionsPath(repoUrl, clonedRepoPath) {
24627
- try {
24628
- const url = new URL(repoUrl);
24629
- const pathParts = url.pathname.split("/").filter(Boolean);
24630
- if (pathParts.length > 4 && pathParts[2] === "tree") {
24631
- const extensionPath = pathParts.slice(4).join("/");
24632
- return path.join(clonedRepoPath, extensionPath);
24633
- }
24634
- return clonedRepoPath;
24635
- } catch {
24636
- return clonedRepoPath;
24637
- }
24638
- }
24639
24675
  /**
24640
24676
  * Uninstall an extension
24641
24677
  * @param extensionId - Extension identifier (filePath)
@@ -24674,10 +24710,72 @@ class ExtensionManager {
24674
24710
  return false;
24675
24711
  }
24676
24712
  }
24713
+ /**
24714
+ * Update an extension from a repository, overwriting existing files
24715
+ * @param extensionId - Extension identifier (filePath of installed extension)
24716
+ * @param repositoryUrl - Repository URL where the extension is located
24717
+ * @param project - Optional project for project-level extension
24718
+ * @returns true if update succeeded
24719
+ */
24720
+ async updateExtension(extensionId, repositoryUrl, project) {
24721
+ const projectDir = project?.baseDir;
24722
+ try {
24723
+ logger.debug(`[Extensions] Updating extension '${extensionId}' from ${repositoryUrl}`);
24724
+ const existingExtension = this.registry.getExtension(extensionId);
24725
+ if (!existingExtension) {
24726
+ throw new Error(`Extension '${extensionId}' not found`);
24727
+ }
24728
+ if (!existingExtension.filePath) {
24729
+ throw new Error(`Extension '${extensionId}' has no file path`);
24730
+ }
24731
+ const targetDir = projectDir ? path.join(projectDir, AIDER_DESK_EXTENSIONS_DIR) : AIDER_DESK_GLOBAL_EXTENSIONS_DIR;
24732
+ const availableExtensions = await this.getAvailableExtensions([repositoryUrl]);
24733
+ const extension = availableExtensions.find((ext) => ext.id === existingExtension.id);
24734
+ if (!extension) {
24735
+ throw new Error(`Extension '${existingExtension.id}' not found in repository`);
24736
+ }
24737
+ const githubRawBase = this.fetcher.getRawUrl(repositoryUrl);
24738
+ if (!githubRawBase) {
24739
+ throw new Error("Invalid GitHub repository URL");
24740
+ }
24741
+ await this.unloadExtensionsForDir(targetDir);
24742
+ if (extension.type === "single" && extension.file) {
24743
+ const url = `${githubRawBase}/${extension.file}`;
24744
+ const response = await fetch(url);
24745
+ if (!response.ok) {
24746
+ throw new Error(`Failed to download: ${response.statusText}`);
24747
+ }
24748
+ const code = await response.text();
24749
+ const targetPath = path.join(targetDir, extension.file);
24750
+ await fs.writeFile(targetPath, code, "utf-8");
24751
+ logger.debug(`[Extensions] Updated single-file extension: ${extension.file}`);
24752
+ } else if (extension.type === "folder" && extension.folder) {
24753
+ const repoDir = await this.fetcher.ensureRepoCloned(repositoryUrl);
24754
+ const extensionsPath = this.fetcher.getExtensionsPath(repositoryUrl, repoDir);
24755
+ const sourcePath = path.join(extensionsPath, extension.folder);
24756
+ const targetPath = path.join(targetDir, extension.folder);
24757
+ if (!await this.fileExists(sourcePath)) {
24758
+ throw new Error(`Extension folder not found in repository: ${extension.folder}`);
24759
+ }
24760
+ await fs.cp(sourcePath, targetPath, { recursive: true });
24761
+ const packageJsonPath = path.join(targetPath, "package.json");
24762
+ if (await this.fileExists(packageJsonPath)) {
24763
+ logger.debug(`[Extensions] Installing dependencies for ${extension.name}...`);
24764
+ await this.installDependencies(targetPath);
24765
+ }
24766
+ logger.debug(`[Extensions] Updated folder extension: ${extension.folder}`);
24767
+ }
24768
+ await this.loadExtensionsForDir(targetDir, project);
24769
+ logger.info(`[Extensions] Successfully updated ${extension.name}`);
24770
+ return true;
24771
+ } catch (error) {
24772
+ logger.error(`[Extensions] Failed to update extension '${extensionId}':`, error);
24773
+ return false;
24774
+ }
24775
+ }
24677
24776
  /**
24678
24777
  * Dispatches an event to all loaded and initialized extensions.
24679
- * Runs in parallel with HookManager triggers, allowing extensions to respond
24680
- * to the same events as hooks. Events can be modified or blocked by extensions.
24778
+ * Events can be modified or blocked by extensions.
24681
24779
  *
24682
24780
  * @param eventName - The name of the event to dispatch
24683
24781
  * @param event - The event payload
@@ -25062,7 +25160,9 @@ class EventsHandler {
25062
25160
  }
25063
25161
  async createTerminal(baseDir, taskId, cols, rows) {
25064
25162
  try {
25065
- return await this.terminalManager.createTerminal(baseDir, taskId, cols, rows);
25163
+ const task = this.projectManager.getProject(baseDir).getTask(taskId);
25164
+ const taskDir = task?.getTaskDir() ?? baseDir;
25165
+ return await this.terminalManager.createTerminal(baseDir, taskDir, taskId, cols, rows);
25066
25166
  } catch (error) {
25067
25167
  logger.error("Failed to create terminal:", { baseDir, error });
25068
25168
  throw error;
@@ -25358,14 +25458,14 @@ ${error instanceof Error ? error.message : String(error)}`);
25358
25458
  return providerModels;
25359
25459
  }
25360
25460
  getProviders() {
25361
- return this.store.getProviders();
25461
+ return this.modelManager.getProviders();
25362
25462
  }
25363
25463
  async updateProviders(providers) {
25364
25464
  const oldProviders = this.store.getProviders();
25365
25465
  this.store.setProviders(providers);
25366
25466
  await this.modelManager.providersChanged(oldProviders, providers);
25367
25467
  this.projectManager.modelsUpdated();
25368
- this.eventManager.sendProvidersUpdated(providers);
25468
+ this.eventManager.sendProvidersUpdated(this.modelManager.getProviders());
25369
25469
  }
25370
25470
  async upsertModel(providerId, modelId, model) {
25371
25471
  await this.modelManager.upsertModel(providerId, modelId, model);
@@ -25453,8 +25553,8 @@ ${error instanceof Error ? error.message : String(error)}`);
25453
25553
  async clearAllTodos(baseDir, taskId) {
25454
25554
  return await this.projectManager.getProject(baseDir).getTask(taskId)?.clearAllTodos() || [];
25455
25555
  }
25456
- async initProjectRulesFile(baseDir, taskId) {
25457
- return this.projectManager.getProject(baseDir).getTask(taskId)?.initProjectAgentsFile();
25556
+ async initProjectRulesFile(baseDir, taskId, args) {
25557
+ return this.projectManager.getProject(baseDir).getTask(taskId)?.initProjectAgentsFile(args);
25458
25558
  }
25459
25559
  async enableServer(username, password) {
25460
25560
  const currentSettings = this.store.getSettings();
@@ -25571,6 +25671,16 @@ ${error instanceof Error ? error.message : String(error)}`);
25571
25671
  async uninstallExtension(extensionId, projectDir) {
25572
25672
  return await this.extensionManager.uninstallExtension(extensionId, projectDir);
25573
25673
  }
25674
+ async updateExtension(extensionId, repositoryUrl, projectDir) {
25675
+ let project = void 0;
25676
+ if (projectDir) {
25677
+ project = this.projectManager.getProject(projectDir);
25678
+ if (!project) {
25679
+ throw new Error("Project not found");
25680
+ }
25681
+ }
25682
+ return await this.extensionManager.updateExtension(extensionId, repositoryUrl, project);
25683
+ }
25574
25684
  getUIComponents(placement, projectDir, taskId) {
25575
25685
  const project = projectDir ? this.projectManager.getProject(projectDir) : void 0;
25576
25686
  const task = taskId && project ? project.getTask(taskId) ?? void 0 : void 0;
@@ -25609,216 +25719,6 @@ ${error instanceof Error ? error.message : String(error)}`);
25609
25719
  return await this.extensionManager.executeUIExtensionAction(extensionId, componentId, action, args, project, task);
25610
25720
  }
25611
25721
  }
25612
- class HookContextImpl {
25613
- constructor(task, project) {
25614
- this.task = task;
25615
- this.project = project;
25616
- }
25617
- addInfoMessage(message) {
25618
- this.task.addLogMessage("info", message);
25619
- }
25620
- addWarningMessage(message) {
25621
- this.task.addLogMessage("warning", message);
25622
- }
25623
- addErrorMessage(message) {
25624
- this.task.addLogMessage("error", message);
25625
- }
25626
- addLoadingMessage(message, finished = false) {
25627
- this.task.addLogMessage("loading", message, finished);
25628
- }
25629
- async setTaskName(name) {
25630
- await this.task.saveTask({ name });
25631
- }
25632
- async addContextMessage(role, content) {
25633
- await this.task.addRoleContextMessage(role === "user" ? MessageRole.User : MessageRole.Assistant, content);
25634
- }
25635
- get taskData() {
25636
- return this.task.task;
25637
- }
25638
- get projectDir() {
25639
- return this.task.getProjectDir();
25640
- }
25641
- }
25642
- class HookManager {
25643
- globalHooks = [];
25644
- projectHooksCache = /* @__PURE__ */ new Map();
25645
- globalInitialized = false;
25646
- globalWatcher = null;
25647
- projectWatchers = /* @__PURE__ */ new Map();
25648
- constructor() {
25649
- }
25650
- async init() {
25651
- if (this.globalInitialized) {
25652
- return;
25653
- }
25654
- this.globalHooks = await this.loadHooksFromDir(AIDER_DESK_GLOBAL_HOOKS_DIR);
25655
- await this.setupGlobalWatcher();
25656
- this.globalInitialized = true;
25657
- }
25658
- async setupGlobalWatcher() {
25659
- if (this.globalWatcher) {
25660
- await this.globalWatcher.close();
25661
- }
25662
- this.globalWatcher = await this.setupWatcherForDir(AIDER_DESK_GLOBAL_HOOKS_DIR, async () => {
25663
- logger.debug("Global hooks changed, reloading...");
25664
- this.globalHooks = await this.loadHooksFromDir(AIDER_DESK_GLOBAL_HOOKS_DIR);
25665
- });
25666
- }
25667
- async reloadProjectHooks(projectDir) {
25668
- const projectHooksDir = path.join(projectDir, AIDER_DESK_HOOKS_DIR);
25669
- const hooks = await this.loadHooksFromDir(projectHooksDir);
25670
- this.projectHooksCache.set(projectDir, hooks);
25671
- if (!this.projectWatchers.has(projectDir)) {
25672
- const watcher = await this.setupWatcherForDir(projectHooksDir, async () => {
25673
- logger.debug(`Project hooks changed for ${projectDir}, reloading...`);
25674
- const updatedHooks = await this.loadHooksFromDir(projectHooksDir);
25675
- this.projectHooksCache.set(projectDir, updatedHooks);
25676
- });
25677
- if (watcher) {
25678
- this.projectWatchers.set(projectDir, watcher);
25679
- }
25680
- }
25681
- logger.debug(`Reloaded hooks for project: ${projectDir}`);
25682
- }
25683
- async stopWatchingProject(projectDir) {
25684
- const watcher = this.projectWatchers.get(projectDir);
25685
- if (watcher) {
25686
- await watcher.close();
25687
- this.projectWatchers.delete(projectDir);
25688
- }
25689
- this.projectHooksCache.delete(projectDir);
25690
- logger.debug(`Stopped watching hooks for project: ${projectDir}`);
25691
- }
25692
- async setupWatcherForDir(dir, onChange) {
25693
- try {
25694
- const dirExists = await fs.access(dir).then(() => true).catch(() => false);
25695
- if (!dirExists) {
25696
- await fs.mkdir(dir, { recursive: true });
25697
- }
25698
- const debouncedOnChange = debounce(onChange, 1e3);
25699
- const watcher = chokidar.watch(dir, {
25700
- persistent: true,
25701
- usePolling: true,
25702
- ignoreInitial: true
25703
- });
25704
- watcher.on("add", debouncedOnChange).on("change", debouncedOnChange).on("unlink", debouncedOnChange).on("error", (error) => {
25705
- logger.error(`Watcher error for hooks directory ${dir}:`, error);
25706
- });
25707
- return watcher;
25708
- } catch (error) {
25709
- logger.error(`Failed to setup watcher for hooks directory ${dir}:`, error);
25710
- return null;
25711
- }
25712
- }
25713
- async loadHooksFromDir(dir) {
25714
- const hooks = [];
25715
- try {
25716
- const dirExists = await fs.access(dir).then(() => true).catch(() => false);
25717
- if (!dirExists) {
25718
- return hooks;
25719
- }
25720
- const files = await fs.readdir(dir);
25721
- for (const file of files) {
25722
- if (file.endsWith(".js")) {
25723
- const filePath = path.join(dir, file);
25724
- try {
25725
- const resolvedPath = require.resolve(filePath);
25726
- delete require.cache[resolvedPath];
25727
- const hookFile = require(filePath);
25728
- hooks.push(hookFile);
25729
- logger.debug(`Loaded hook file: ${filePath}`);
25730
- } catch (error) {
25731
- logger.error(`Failed to load hook file ${filePath}:`, error);
25732
- }
25733
- }
25734
- }
25735
- } catch (error) {
25736
- logger.error(`Failed to load hooks from ${dir}:`, error);
25737
- }
25738
- return hooks;
25739
- }
25740
- async trigger(hookName, event, task, project) {
25741
- if (!this.globalInitialized) {
25742
- await this.init();
25743
- }
25744
- let projectHooks = this.projectHooksCache.get(project.baseDir);
25745
- if (projectHooks === void 0) {
25746
- await this.reloadProjectHooks(project.baseDir);
25747
- projectHooks = this.projectHooksCache.get(project.baseDir) || [];
25748
- }
25749
- const allHooks = [...this.globalHooks, ...projectHooks];
25750
- const context = new HookContextImpl(task, project);
25751
- let currentEvent = { ...event };
25752
- let blocked = false;
25753
- let hookResult = void 0;
25754
- for (const hook of allHooks) {
25755
- logger.debug(`Executing hook ${hookName}`, {
25756
- hookName,
25757
- event,
25758
- hook
25759
- });
25760
- const hookFn = hook[hookName];
25761
- if (typeof hookFn === "function") {
25762
- logger.debug(`Hook function found for ${hookName}`, {
25763
- hookName,
25764
- event,
25765
- hook
25766
- });
25767
- try {
25768
- const result = await hookFn(currentEvent, context);
25769
- logger.debug(`Hook function result for ${hookName}`, {
25770
- hookName,
25771
- event,
25772
- hook,
25773
- result
25774
- });
25775
- if (result === false) {
25776
- if (hookName === "onHandleApproval") {
25777
- hookResult = false;
25778
- } else {
25779
- blocked = true;
25780
- }
25781
- break;
25782
- }
25783
- if (result === true && hookName === "onHandleApproval") {
25784
- hookResult = true;
25785
- break;
25786
- }
25787
- if (typeof result === "string" && hookName === "onQuestionAsked") {
25788
- hookResult = result;
25789
- break;
25790
- }
25791
- if (result && typeof result === "object") {
25792
- if (hookName === "onResponseMessageProcessed") {
25793
- hookResult = result;
25794
- } else {
25795
- currentEvent = { ...currentEvent, ...result };
25796
- }
25797
- }
25798
- if (result && hookName === "onToolFinished") {
25799
- hookResult = result;
25800
- currentEvent = { ...currentEvent, result };
25801
- }
25802
- } catch (error) {
25803
- logger.error(`Error executing hook ${hookName}:`, error);
25804
- }
25805
- }
25806
- }
25807
- return { event: currentEvent, blocked, result: hookResult };
25808
- }
25809
- async dispose() {
25810
- if (this.globalWatcher) {
25811
- await this.globalWatcher.close();
25812
- this.globalWatcher = null;
25813
- }
25814
- for (const watcher of this.projectWatchers.values()) {
25815
- await watcher.close();
25816
- }
25817
- this.projectWatchers.clear();
25818
- this.projectHooksCache.clear();
25819
- logger.debug("HookManager disposed");
25820
- }
25821
- }
25822
25722
  const registerConditionalHelpers = () => {
25823
25723
  Handlebars.registerHelper("equals", (v1, v2) => v1 === v2);
25824
25724
  Handlebars.registerHelper("not", (v) => !v);
@@ -26164,7 +26064,7 @@ ${content}
26164
26064
  );
26165
26065
  return ruleFilesContent.filter(Boolean).join("\n");
26166
26066
  };
26167
- getInitProjectPrompt = async (task) => {
26067
+ getInitProjectSystemPrompt = async (task) => {
26168
26068
  const data = {};
26169
26069
  return await this.render("init-project", data, task.getProjectDir(), task);
26170
26070
  };
@@ -26231,10 +26131,6 @@ const initManagers = async (store, windowManager) => {
26231
26131
  memoryManager.init().catch((error) => {
26232
26132
  logger.error("[Memory] Memory system initialization failed, continuing without memories:", error);
26233
26133
  });
26234
- const hookManager = new HookManager();
26235
- hookManager.init().catch((error) => {
26236
- logger.error("[Hooks] Hook system initialization failed:", error);
26237
- });
26238
26134
  const extensionManager = new ExtensionManager(store, modelManager, eventManager, telemetryManager);
26239
26135
  extensionManager.init().catch((error) => {
26240
26136
  logger.error("[Extensions] Extension system initialization failed, continuing without extensions:", error);
@@ -26258,11 +26154,10 @@ const initManagers = async (store, windowManager) => {
26258
26154
  worktreeManager,
26259
26155
  agentProfileManager,
26260
26156
  memoryManager,
26261
- hookManager,
26262
26157
  promptsManager,
26263
26158
  extensionManager
26264
26159
  );
26265
- const terminalManager = new TerminalManager(eventManager, worktreeManager, telemetryManager);
26160
+ const terminalManager = new TerminalManager(eventManager, telemetryManager);
26266
26161
  const versionsManager = new VersionsManager(eventManager, store);
26267
26162
  const httpServer = http.createServer();
26268
26163
  const cloudflareTunnelManager = new CloudflareTunnelManager();
@@ -26303,7 +26198,6 @@ const initManagers = async (store, windowManager) => {
26303
26198
  mcpManager.close(),
26304
26199
  telemetryManager.destroy(),
26305
26200
  agentProfileManager.dispose(),
26306
- hookManager.dispose(),
26307
26201
  promptsManager.dispose(),
26308
26202
  extensionManager.dispose()
26309
26203
  ]);
@@ -26936,10 +26830,12 @@ const migrateV12ToV13 = (settings) => {
26936
26830
  };
26937
26831
  };
26938
26832
  const migrateProvidersV13toV14 = (settings) => {
26939
- const providers = Object.entries(settings.llmProviders).map(([name, provider]) => ({
26940
- id: name,
26941
- provider
26942
- }));
26833
+ const providers = Object.entries(settings.llmProviders).map(
26834
+ ([name, provider]) => ({
26835
+ id: name,
26836
+ provider
26837
+ })
26838
+ );
26943
26839
  return providers;
26944
26840
  };
26945
26841
  const migrateSettingsV14toV15 = (settings) => {
@@ -27107,7 +27003,8 @@ const DEFAULT_SETTINGS = {
27107
27003
  showTaskStateActions: true,
27108
27004
  worktreeSymlinkFolders: ["node_modules", "vendor", "__pycache__", ".venv", "venv"],
27109
27005
  contextCompactingThreshold: 0,
27110
- contextCompactionType: ContextCompactionType.Compact
27006
+ contextCompactionType: ContextCompactionType.Compact,
27007
+ defaultWorkingMode: "local"
27111
27008
  },
27112
27009
  extensions: {
27113
27010
  repositories: [AIDER_DESK_EXTENSIONS_REPO_URL],
@@ -27404,7 +27301,7 @@ const addProjectsFromEnv = async (store, modelManager, agentProfileManager) => {
27404
27301
  const defaultAgentProfileId = agentProfileManager.getDefaultAgentProfileId();
27405
27302
  const newProject = {
27406
27303
  baseDir: projectPath.endsWith("/") ? projectPath.slice(0, -1) : projectPath,
27407
- settings: getDefaultProjectSettings(store, providerModels.models || [], projectPath, defaultAgentProfileId),
27304
+ settings: getDefaultProjectSettings(store, providerModels.models || [], projectPath, defaultAgentProfileId, modelManager.getProviders()),
27408
27305
  active: false
27409
27306
  };
27410
27307
  projectsFromEnv.push(newProject);