@aiderdesk/aiderdesk 0.64.0 → 0.67.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.
Files changed (61) hide show
  1. package/out/cli.js +1 -0
  2. package/out/{open-telemetry-CcefKvbB.js → open-telemetry-baOvr6sK.js} +1 -1
  3. package/out/renderer/assets/{arc-DoIK-bD2.js → arc-BnPy4nhx.js} +1 -1
  4. package/out/renderer/assets/{architectureDiagram-Q4EWVU46-B8_dgBXp.js → architectureDiagram-3BPJPVTR-_SwpsbSs.js} +11 -9
  5. package/out/renderer/assets/{blockDiagram-DXYQGD6D-BDOvGPDN.js → blockDiagram-GPEHLZMM-D8Bc_xCY.js} +218 -30
  6. package/out/renderer/assets/{c4Diagram-AHTNJAMY-1ABZnJ2v.js → c4Diagram-AAUBKEIU-Ddic5IpI.js} +2 -2
  7. package/out/renderer/assets/{channel-Cr_H2zdE.js → channel-I2sEi2rW.js} +1 -1
  8. package/out/renderer/assets/{chunk-EDXVE4YY-Dt80V_EG.js → chunk-2J33WTMH-Cqrw7IHw.js} +1 -1
  9. package/out/renderer/assets/{chunk-4BX2VUAB-d88VZY9C.js → chunk-4BX2VUAB-DKY4go9E.js} +1 -1
  10. package/out/renderer/assets/{chunk-55IACEB6-BO1oJBQV.js → chunk-55IACEB6-COO6Gc0F.js} +1 -1
  11. package/out/renderer/assets/{chunk-4TB4RGXK-DLcMuHVI.js → chunk-727SXJPM-Bu8zhvGn.js} +245 -149
  12. package/out/renderer/assets/{chunk-OYMX7WX6-DBFhtMcs.js → chunk-AQP2D5EJ-JHXEk1U1.js} +92 -78
  13. package/out/renderer/assets/{chunk-FMBD7UC4-D5MNbIWZ.js → chunk-FMBD7UC4-BqbYNISq.js} +1 -1
  14. package/out/renderer/assets/{chunk-YZCP3GAM-gAcMGuhT.js → chunk-ND2GUHAM-D29ZgnDp.js} +1 -1
  15. package/out/renderer/assets/{chunk-QZHKN3VN-Bxwt_pyh.js → chunk-QZHKN3VN-B-pbtAbR.js} +1 -1
  16. package/out/renderer/assets/{classDiagram-6PBFFD2Q-B7lgamMP.js → classDiagram-4FO5ZUOK-B7_tIdbP.js} +6 -6
  17. package/out/renderer/assets/{classDiagram-v2-HSJHXN6E-B7lgamMP.js → classDiagram-v2-Q7XG4LA2-B7_tIdbP.js} +6 -6
  18. package/out/renderer/assets/{cose-bilkent-S5V4N54A-BZNBIG2x.js → cose-bilkent-S5V4N54A-CGNgxRV6.js} +1 -1
  19. package/out/renderer/assets/{dagre-KV5264BT-C3hXUNb-.js → dagre-BM42HDAG-DB-tI7Ml.js} +17 -6
  20. package/out/renderer/assets/{diagram-MMDJMWI5-BcI1Ek4N.js → diagram-2AECGRRQ-Cdwrgy7R.js} +3 -5
  21. package/out/renderer/assets/{diagram-5BDNPKRD-DNh45EqP.js → diagram-5GNKFQAL-BnvZ78_N.js} +4 -6
  22. package/out/renderer/assets/diagram-KO2AKTUF-VUrfwRCk.js +632 -0
  23. package/out/renderer/assets/{diagram-TYMM5635-DuHcW-s7.js → diagram-LMA3HP47-_Vo8GrFC.js} +3 -5
  24. package/out/renderer/assets/{diagram-G4DWMVQ6-8lhqJfPk.js → diagram-OG6HWLK6-CoLz9kio.js} +4 -6
  25. package/out/renderer/assets/{erDiagram-SMLLAGMA-I6Q9HYdF.js → erDiagram-TEJ5UH35-B3Bg3gW1.js} +4 -4
  26. package/out/renderer/assets/{flowDiagram-DWJPFMVM-BzRjtX5C.js → flowDiagram-I6XJVG4X-BgyAk0Xe.js} +6 -6
  27. package/out/renderer/assets/{ganttDiagram-T4ZO3ILL-DVkem_IA.js → ganttDiagram-6RSMTGT7-NnVRwQPs.js} +7 -1
  28. package/out/renderer/assets/{gitGraphDiagram-UUTBAWPF-BYpvdMpK.js → gitGraphDiagram-PVQCEYII-C4vrRnDw.js} +4 -6
  29. package/out/renderer/assets/{graph-CAtr5PoG.js → graph-BZvTCUpv.js} +490 -135
  30. package/out/renderer/assets/{index-CNL53LoL.js → index-P63PgYUG.js} +12163 -8228
  31. package/out/renderer/assets/{index-Duw36zwk.css → index-zdiQSGqQ.css} +135 -27
  32. package/out/renderer/assets/{infoDiagram-42DDH7IO-BcmBthOY.js → infoDiagram-5YYISTIA-zTriWVJJ.js} +3 -5
  33. package/out/renderer/assets/{ishikawaDiagram-UXIWVN3A-moTWny-V.js → ishikawaDiagram-YF4QCWOH-DHO6Yeea.js} +1 -1
  34. package/out/renderer/assets/{journeyDiagram-VCZTEJTY-DOW8zaZt.js → journeyDiagram-JHISSGLW-CZsRcg2X.js} +4 -4
  35. package/out/renderer/assets/{kanban-definition-6JOO6SKY-DpJjTob4.js → kanban-definition-UN3LZRKU-DSpAeh8p.js} +2 -2
  36. package/out/renderer/assets/{layout-BvH51Ui9.js → layout-B5A8fT-Z.js} +459 -32
  37. package/out/renderer/assets/{mindmap-definition-QFDTVHPH-DggFFNHq.js → mindmap-definition-RKZ34NQL-Bns9Ab8p.js} +3 -3
  38. package/out/renderer/assets/{pieDiagram-DEJITSTG-BED4dnMF.js → pieDiagram-4H26LBE5-BX36DY7W.js} +4 -6
  39. package/out/renderer/assets/{quadrantDiagram-34T5L4WZ-RpQ3qNU5.js → quadrantDiagram-W4KKPZXB-DuD2qSni.js} +22 -20
  40. package/out/renderer/assets/{requirementDiagram-MS252O5E-VQt4zBMB.js → requirementDiagram-4Y6WPE33-Th2uknlw.js} +3 -3
  41. package/out/renderer/assets/{sankeyDiagram-XADWPNL6-DywR7qAk.js → sankeyDiagram-5OEKKPKP-DP3WODC7.js} +80 -11
  42. package/out/renderer/assets/{sequenceDiagram-FGHM5R23-CVPfZD4e.js → sequenceDiagram-3UESZ5HK-BvpGEq-v.js} +21 -9
  43. package/out/renderer/assets/{stateDiagram-FHFEXIEX-BrH8Q8ZG.js → stateDiagram-AJRCARHV-Btrfma35.js} +6 -8
  44. package/out/renderer/assets/{stateDiagram-v2-QKLJ7IA2-BTWk2K0H.js → stateDiagram-v2-BHNVJYJU-v-8JiozM.js} +4 -4
  45. package/out/renderer/assets/{timeline-definition-GMOUNBTQ-DwDUCrTb.js → timeline-definition-PNZ67QCA-prZy7cGx.js} +2 -2
  46. package/out/renderer/assets/{vennDiagram-DHZGUBPP-Bjvr7yGM.js → vennDiagram-CIIHVFJN-XCVp5OjS.js} +1 -1
  47. package/out/renderer/assets/{wardley-RL74JXVD-Bo-sW7uQ.js → wardley-L42UT6IY-FZSNLjCs.js} +25605 -19118
  48. package/out/renderer/assets/{wardleyDiagram-NUSXRM2D-DRW_1PCJ.js → wardleyDiagram-YWT4CUSO-Cla_7ryL.js} +112 -38
  49. package/out/renderer/assets/worker-CfJUABHG.js +12626 -0
  50. package/out/renderer/assets/{xychartDiagram-5P7HB3ND-Ds-qS4nC.js → xychartDiagram-2RQKCTM6-CKvwGbhk.js} +1 -1
  51. package/out/renderer/index.html +2 -2
  52. package/out/renderer/progress.html +4 -48
  53. package/out/resources/connector/connector.py +5 -0
  54. package/out/resources/mcp-server/aider-desk-mcp-server.js +1051 -501
  55. package/out/resources/prompts/workflow.hbs +2 -2
  56. package/out/runner.js +2169 -327
  57. package/package.json +42 -21
  58. package/scripts/generate-package.mjs +10 -2
  59. package/out/renderer/assets/_baseUniq-C6Q8LpuQ.js +0 -381
  60. package/out/renderer/assets/clone-DKkqtIT8.js +0 -8
  61. package/out/renderer/assets/min-CowxrbD6.js +0 -41
package/out/runner.js CHANGED
@@ -31,22 +31,25 @@ const node_sqlite = require("node:sqlite");
31
31
  const path = require("path");
32
32
  const os = require("os");
33
33
  const http = require("http");
34
- const gpt4o = require("gpt-tokenizer/model/gpt-4o");
35
34
  const istextorbinary = require("istextorbinary");
36
35
  const child_process = require("child_process");
36
+ const util = require("util");
37
37
  const treeKill = require("tree-kill");
38
+ const ai = require("ai");
38
39
  const glob = require("glob");
39
40
  const fs$1 = require("fs");
40
- const util = require("util");
41
41
  const dotenvx = require("@dotenvx/dotenvx");
42
42
  const YAML = require("yaml");
43
43
  const posthogNode = require("posthog-node");
44
44
  const langfuseVercel = require("langfuse-vercel");
45
+ const otel = require("@posthog/ai/otel");
45
46
  const simpleGit = require("simple-git");
46
47
  const filenamifyImport = require("filenamify");
47
48
  const slugify = require("slugify");
49
+ const AdmZip = require("adm-zip");
48
50
  const Turndown = require("turndown");
49
51
  const cheerio = require("cheerio");
52
+ const gpt4o = require("gpt-tokenizer/model/gpt-4o");
50
53
  const uuid = require("uuid");
51
54
  const index_js = require("@modelcontextprotocol/sdk/client/index.js");
52
55
  const stdio_js = require("@modelcontextprotocol/sdk/client/stdio.js");
@@ -63,7 +66,6 @@ const debounce = require("lodash/debounce.js");
63
66
  const crypto = require("crypto");
64
67
  const undici = require("undici");
65
68
  const globalAgent = require("global-agent");
66
- const ai = require("ai");
67
69
  const fileType = require("file-type");
68
70
  const anthropic = require("@ai-sdk/anthropic");
69
71
  const azure = require("@ai-sdk/azure");
@@ -229,14 +231,16 @@ const TASKS_TOOL_GET_TASK_MESSAGE = "get_task_message";
229
231
  const TASKS_TOOL_CREATE_TASK = "create_task";
230
232
  const TASKS_TOOL_DELETE_TASK = "delete_task";
231
233
  const TASKS_TOOL_SEARCH_TASK = "search_task";
234
+ const TASKS_TOOL_RUN_PROMPT = "run_prompt";
232
235
  const TASKS_TOOL_SEARCH_PARENT_TASK = "search_parent_task";
233
236
  const TASKS_TOOL_DESCRIPTIONS = {
234
237
  [TASKS_TOOL_LIST_TASKS]: "List all tasks in the current project. Returns basic information for each task including id, name, and creation/update timestamps. Use this to get an overview of all available tasks before performing specific task operations.",
235
238
  [TASKS_TOOL_GET_TASK]: "Get comprehensive details about a specific task by its ID. Returns task metadata, current state, list of context files with their read-only status, and the total count of context messages. Use this to understand a task's configuration and context before working with it or its messages.",
236
239
  [TASKS_TOOL_GET_TASK_MESSAGE]: "Retrieve a specific message from a task's conversation history by message index and task ID. The first message (index 0) is always the user's initial prompt, and subsequent messages alternate between user and assistant. Use this to examine the conversation flow, understand previous interactions, or extract specific information from the task history.",
237
- [TASKS_TOOL_CREATE_TASK]: "Create a new task in the current project with an initial prompt. Optionally specify an agent profile ID to use different capabilities, a model ID to override the default model, or a parentTaskId to create a subtask of another task. The parentTaskId parameter is only available for top-level tasks; if the current task is itself a subtask, you cannot create subtasks from it. The new task will start with the provided prompt as its first user message. Use this to begin new work streams, separate different aspects of a project, or break down complex tasks into manageable subtasks. Use execute to create a task and wait for the task to be finished. Use executeInBackground to create a task in the background without waiting for the task to be finished.",
240
+ [TASKS_TOOL_CREATE_TASK]: "Create a new task in the current project with an initial prompt. Optionally use user-provided agentProfileId to use a different agent, a modelId to override the model, or a parentTaskId (if available) to create a subtask of another task. The new task will start with the provided prompt as its first user message. Use this to begin new work streams, separate different aspects of a project, or break down complex tasks into manageable subtasks. Use execute to create a task and wait for the task to be finished. Use executeInBackground to create a task in the background without waiting for the task to be finished.",
238
241
  [TASKS_TOOL_DELETE_TASK]: "Permanently delete a task and all its associated data including messages, context files, and metadata. This action cannot be undone. Note that you cannot delete the currently active task. Use this to clean up completed or abandoned tasks, but be cautious as this removes all task history permanently.",
239
242
  [TASKS_TOOL_SEARCH_TASK]: "Search content within a specific task using semantic search. Use natural language queries with 2-5 descriptive words including key concepts and context. Searches through task conversation history and context files. Use this to find relevant information, discussions, or code snippets within a task.",
243
+ [TASKS_TOOL_RUN_PROMPT]: "Run a prompt on an existing task. The task must already exist (use create_task first if needed). Use this to send additional instructions to a task, continue work on an existing task, or delegate follow-up work. By default, waits for the prompt to complete before returning. Use executeInBackground to run without waiting.",
240
244
  [TASKS_TOOL_SEARCH_PARENT_TASK]: "Search content within parent task using semantic search. Use natural language queries with 2-5 descriptive words including key concepts and context. Automatically searches parent task conversation history and context files."
241
245
  };
242
246
  const WorktreeSchema = zod.z.object({
@@ -255,6 +259,13 @@ const MergeStateSchema = zod.z.object({
255
259
  });
256
260
  const AIDER_MODES = ["code", "ask", "architect", "context"];
257
261
  const AIDER_COMMANDS = ["commit", "map", "map-refresh", "tokens", "test"];
262
+ var AutonomyMode = /* @__PURE__ */ ((AutonomyMode2) => {
263
+ AutonomyMode2["Manual"] = "manual";
264
+ AutonomyMode2["Guided"] = "guided";
265
+ AutonomyMode2["Autonomous"] = "autonomous";
266
+ return AutonomyMode2;
267
+ })(AutonomyMode || {});
268
+ const DEFAULT_AUTONOMY_MODE = "guided";
258
269
  var DiffViewMode = /* @__PURE__ */ ((DiffViewMode2) => {
259
270
  DiffViewMode2["SideBySide"] = "side-by-side";
260
271
  DiffViewMode2["Unified"] = "unified";
@@ -297,9 +308,8 @@ const ProjectSettingsSchema = zod.z.object({
297
308
  reasoningEffort: zod.z.string().optional(),
298
309
  thinkingTokens: zod.z.string().optional(),
299
310
  currentMode: zod.z.string(),
300
- contextCompactingThreshold: zod.z.number().optional(),
301
311
  weakModelLocked: zod.z.boolean().optional(),
302
- autoApproveLocked: zod.z.boolean().optional(),
312
+ autonomyModeLocked: zod.z.boolean().optional(),
303
313
  updatedFilesGroupMode: zod.z.enum(["grouped", "flat"]).default("flat"),
304
314
  disabledRuleFiles: zod.z.array(zod.z.string()).default([]),
305
315
  contextSidebarSectionsOrder: zod.z.array(zod.z.string()).default([]),
@@ -335,8 +345,15 @@ var MemoryEmbeddingProvider = /* @__PURE__ */ ((MemoryEmbeddingProvider2) => {
335
345
  var ContextCompactionType = /* @__PURE__ */ ((ContextCompactionType2) => {
336
346
  ContextCompactionType2["Compact"] = "compact";
337
347
  ContextCompactionType2["Handoff"] = "handoff";
348
+ ContextCompactionType2["Smart"] = "smart";
338
349
  return ContextCompactionType2;
339
350
  })(ContextCompactionType || {});
351
+ var FileWatchMode = /* @__PURE__ */ ((FileWatchMode2) => {
352
+ FileWatchMode2["Auto"] = "auto";
353
+ FileWatchMode2["Native"] = "native";
354
+ FileWatchMode2["Polling"] = "polling";
355
+ return FileWatchMode2;
356
+ })(FileWatchMode || {});
340
357
  var MemoryEmbeddingProgressPhase = /* @__PURE__ */ ((MemoryEmbeddingProgressPhase2) => {
341
358
  MemoryEmbeddingProgressPhase2["Idle"] = "idle";
342
359
  MemoryEmbeddingProgressPhase2["LoadingModel"] = "loading-model";
@@ -370,7 +387,7 @@ const TaskDataSchema = zod.z.object({
370
387
  lastMergeState: MergeStateSchema.optional(),
371
388
  aiderTotalCost: zod.z.number(),
372
389
  agentTotalCost: zod.z.number(),
373
- autoApprove: zod.z.boolean().optional(),
390
+ autonomyMode: zod.z.nativeEnum(AutonomyMode).optional(),
374
391
  agentProfileId: zod.z.string().optional(),
375
392
  provider: zod.z.string().optional(),
376
393
  model: zod.z.string().optional(),
@@ -380,7 +397,7 @@ const TaskDataSchema = zod.z.object({
380
397
  reasoningEffort: zod.z.string().optional(),
381
398
  thinkingTokens: zod.z.string().optional(),
382
399
  currentMode: zod.z.string().optional(),
383
- contextCompactingThreshold: zod.z.number().optional(),
400
+ contextCompactingThresholdTokens: zod.z.number().optional(),
384
401
  weakModelLocked: zod.z.boolean().optional(),
385
402
  handoff: zod.z.boolean().optional(),
386
403
  lastAgentProviderMetadata: zod.z.unknown().optional(),
@@ -455,6 +472,20 @@ const extractTextContent = (content) => {
455
472
  }
456
473
  return "";
457
474
  };
475
+ const extractImagesFromContent = (content) => {
476
+ if (!Array.isArray(content)) {
477
+ return void 0;
478
+ }
479
+ const images = content.filter((part) => part.type === "image").map((part) => {
480
+ const data = part.image;
481
+ if (typeof data !== "string") {
482
+ return void 0;
483
+ }
484
+ const mediaType = part.mediaType || "image/png";
485
+ return data.startsWith("data:") ? data : `data:${mediaType};base64,${data}`;
486
+ }).filter((v) => v !== void 0);
487
+ return images.length > 0 ? images : void 0;
488
+ };
458
489
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
459
490
  const parseUsageReport = (model, report) => {
460
491
  const sentMatch = report.match(/Tokens: ([\d.]+k?) sent/);
@@ -547,6 +578,7 @@ const extractProviderModel = (modelId) => {
547
578
  const [providerId, ...modelParts] = modelId.split("/");
548
579
  return [providerId, modelParts.join("/")];
549
580
  };
581
+ const isUuid = (id) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
550
582
  const DEFAULT_PAGE_SIZE = 100;
551
583
  class LogBuffer {
552
584
  db = null;
@@ -793,6 +825,7 @@ const AIDER_DESK_CONNECTOR_DIR = path.join(AIDER_DESK_DATA_DIR, "aider-connector
793
825
  const AIDER_DESK_MCP_SERVER_DIR = path.join(AIDER_DESK_DATA_DIR, "mcp-server");
794
826
  const AIDER_DESK_BIN_DIR = path.join(AIDER_DESK_DATA_DIR, "bin");
795
827
  const UV_EXECUTABLE = process.platform === "win32" ? path.join(AIDER_DESK_BIN_DIR, "uv.exe") : path.join(AIDER_DESK_BIN_DIR, "uv");
828
+ const RIPGREP_BINARY_PATH = process.platform === "win32" ? path.join(AIDER_DESK_BIN_DIR, "rg.exe") : path.join(AIDER_DESK_BIN_DIR, "rg");
796
829
  const SERVER_PORT = process.env.AIDER_DESK_PORT ? parseInt(process.env.AIDER_DESK_PORT) : 24337;
797
830
  const PID_FILES_DIR = path.join(AIDER_DESK_DATA_DIR, "aider-processes");
798
831
  const AIDER_DESK_DIR = ".aider-desk";
@@ -816,7 +849,9 @@ const AIDER_DESK_MEMORY_FILE = path.join(AIDER_DESK_DATA_DIR, "memory.db");
816
849
  const EXTENSIONS_REPOS_CACHE_DIR = path.join(AIDER_DESK_CACHE_DIR, "extensions");
817
850
  const POSTHOG_PUBLIC_API_KEY = "phc_AF4zkjrcziXLh8PBFsRSvVr4VZ38p3ezsdX0KDYuElI";
818
851
  const POSTHOG_HOST = "https://eu.i.posthog.com";
819
- process.env.AIDER_DESK_HEADLESS === "true";
852
+ const HEADLESS_MODE = process.env.AIDER_DESK_HEADLESS === "true";
853
+ const APP_TYPE = process.env.AIDER_DESK_APP_TYPE || (HEADLESS_MODE ? "docker" : "electron");
854
+ process.env.AIDER_DESK_DISABLE_MENU === "true";
820
855
  const AUTH_USERNAME = process.env.AIDER_DESK_USERNAME;
821
856
  const AUTH_PASSWORD = process.env.AIDER_DESK_PASSWORD;
822
857
  const CORS_ALLOWED_ORIGINS = process.env.AIDER_DESK_CORS_ALLOWED_ORIGINS;
@@ -866,6 +901,9 @@ if (process.env.NODE_ENV !== "production" || process.env.AIDER_DESK_HEADLESS ===
866
901
  const initEventLogging = (eventManager) => {
867
902
  eventTransport.setEventManager(eventManager);
868
903
  };
904
+ const isAutoApprove = (task) => {
905
+ return task.task.autonomyMode !== AutonomyMode.Manual;
906
+ };
869
907
  class ApprovalManager {
870
908
  constructor(task, profile) {
871
909
  this.task = task;
@@ -875,7 +913,7 @@ class ApprovalManager {
875
913
  async handleToolApproval(toolName, input, key, text, subject) {
876
914
  const extensionResult = await this.task.dispatchExtensionEvent("onToolApproval", { toolName, input });
877
915
  if (extensionResult.blocked) {
878
- return [false, void 0];
916
+ return [false, typeof extensionResult.blocked === "string" ? extensionResult.blocked : void 0];
879
917
  }
880
918
  if (extensionResult.allowed) {
881
919
  return [true, void 0];
@@ -893,7 +931,7 @@ class ApprovalManager {
893
931
  key = extensionResult.key;
894
932
  text = extensionResult.text;
895
933
  subject = extensionResult.subject;
896
- if (this.task.task.autoApprove) {
934
+ if (isAutoApprove(this.task)) {
897
935
  return [true, void 0];
898
936
  }
899
937
  const isApprovedFromSet = this.alwaysApproveForRunKeys.has(key) || (this.profile.toolApprovals[key] || ToolApprovalState.Always) === ToolApprovalState.Always;
@@ -1474,6 +1512,7 @@ const AVAILABLE_PROVIDERS = [
1474
1512
  "lmstudio",
1475
1513
  "minimax",
1476
1514
  "mistral",
1515
+ "neuralwatt",
1477
1516
  "ollama",
1478
1517
  "openai",
1479
1518
  "openai-compatible",
@@ -1519,6 +1558,7 @@ const isOpenCodeProvider = (provider) => provider.name === "opencode";
1519
1558
  const isZaiPlanProvider = (provider) => provider.name === "zai-plan";
1520
1559
  const isMinimaxProvider = (provider) => provider.name === "minimax";
1521
1560
  const isMistralProvider = (provider) => provider.name === "mistral";
1561
+ const isNeuralwattProvider = (provider) => provider.name === "neuralwatt";
1522
1562
  const isSyntheticProvider = (provider) => provider.name === "synthetic";
1523
1563
  const DEFAULT_PROVIDER_MODELS = {
1524
1564
  "alibaba-plan": "qwen3-coder-plus",
@@ -1539,7 +1579,8 @@ const DEFAULT_PROVIDER_MODELS = {
1539
1579
  synthetic: "hf:zai-org/GLM-4.7",
1540
1580
  "zai-plan": "glm-5.1",
1541
1581
  minimax: "MiniMax-M2.7",
1542
- mistral: "mistral-large-latest"
1582
+ mistral: "mistral-large-latest",
1583
+ neuralwatt: "claude-sonnet-4-6"
1543
1584
  };
1544
1585
  const DEFAULT_AIDER_MAIN_MODEL = `anthropic/${DEFAULT_PROVIDER_MODELS.anthropic}`;
1545
1586
  const DEFAULT_AGENT_PROFILE_ID = "default";
@@ -1548,7 +1589,7 @@ const DEFAULT_AGENT_PROFILE = {
1548
1589
  name: "Default Agent",
1549
1590
  provider: "anthropic",
1550
1591
  model: DEFAULT_PROVIDER_MODELS.anthropic,
1551
- maxIterations: 250,
1592
+ maxIterations: 0,
1552
1593
  minTimeBetweenToolCalls: 0,
1553
1594
  toolApprovals: {
1554
1595
  // aider tools
@@ -1869,7 +1910,8 @@ const getDefaultProviderParams = (providerName) => {
1869
1910
  name: "openai-compatible",
1870
1911
  apiKey: "",
1871
1912
  baseUrl: "",
1872
- reasoningEffort: ReasoningEffort.None
1913
+ reasoningEffort: ReasoningEffort.None,
1914
+ trackTokenUsage: true
1873
1915
  };
1874
1916
  break;
1875
1917
  case "litellm":
@@ -1943,6 +1985,12 @@ const getDefaultProviderParams = (providerName) => {
1943
1985
  apiKey: ""
1944
1986
  };
1945
1987
  break;
1988
+ case "neuralwatt":
1989
+ provider = {
1990
+ name: "neuralwatt",
1991
+ apiKey: ""
1992
+ };
1993
+ break;
1946
1994
  case "gemini-cli":
1947
1995
  provider = {
1948
1996
  name: "gemini-cli",
@@ -1984,7 +2032,7 @@ class TelemetryManager {
1984
2032
  }
1985
2033
  async init() {
1986
2034
  try {
1987
- await Promise.resolve().then(() => require("./open-telemetry-CcefKvbB.js"));
2035
+ await Promise.resolve().then(() => require("./open-telemetry-baOvr6sK.js"));
1988
2036
  const app = getElectronApp();
1989
2037
  this.client = new posthogNode.PostHog(POSTHOG_PUBLIC_API_KEY, {
1990
2038
  host: POSTHOG_HOST
@@ -1994,7 +2042,8 @@ class TelemetryManager {
1994
2042
  distinctId: this.distinctId,
1995
2043
  properties: {
1996
2044
  os: process.platform,
1997
- version: app?.getVersion()
2045
+ version: app?.getVersion(),
2046
+ appType: APP_TYPE
1998
2047
  }
1999
2048
  });
2000
2049
  } catch (error) {
@@ -2064,7 +2113,7 @@ class TelemetryManager {
2064
2113
  useTodoTools: profile.useTodoTools,
2065
2114
  includeContextFiles: profile.includeContextFiles,
2066
2115
  includeRepoMap: profile.includeRepoMap,
2067
- autoApprove: task?.autoApprove ?? false,
2116
+ autonomyMode: task?.autonomyMode ?? "guided",
2068
2117
  enabledMcpServersCount: profile.enabledServers.length,
2069
2118
  totalMcpServersCount: Object.keys(this.store.getSettings().mcpServers).length
2070
2119
  }
@@ -2157,6 +2206,24 @@ const getLangfuseEnvironmentVariables = (baseDir, settings) => {
2157
2206
  LANGFUSE_HOST: getEffectiveEnvironmentVariable("LANGFUSE_HOST", settings, baseDir)?.value
2158
2207
  };
2159
2208
  };
2209
+ const initializePostHogExporter = () => {
2210
+ const posthogApiKey = getEffectiveEnvironmentVariable("POSTHOG_API_KEY");
2211
+ const posthogHost = getEffectiveEnvironmentVariable("POSTHOG_HOST");
2212
+ if (posthogApiKey) {
2213
+ logger.info("Initializing PostHog Trace Exporter...");
2214
+ return new otel.PostHogTraceExporter({
2215
+ apiKey: posthogApiKey.value,
2216
+ host: posthogHost?.value || "https://us.i.posthog.com"
2217
+ });
2218
+ }
2219
+ return void 0;
2220
+ };
2221
+ const getPostHogAiderEnvironmentVariables = (baseDir, settings) => {
2222
+ return {
2223
+ POSTHOG_API_KEY: getEffectiveEnvironmentVariable("POSTHOG_API_KEY", settings, baseDir)?.value,
2224
+ POSTHOG_API_URL: getEffectiveEnvironmentVariable("POSTHOG_HOST", settings, baseDir)?.value
2225
+ };
2226
+ };
2160
2227
  const readEnvFile = (filePath) => {
2161
2228
  try {
2162
2229
  if (fs$1.existsSync(filePath)) {
@@ -2205,7 +2272,8 @@ const readApiKeyFromConfFile = (filePath, envVarName) => {
2205
2272
  OPENCODE_API_KEY: ["opencode"],
2206
2273
  REQUESTY_API_KEY: ["requesty"],
2207
2274
  SYNTHETIC_API_KEY: ["synthetic"],
2208
- MISTRAL_API_KEY: ["mistral"]
2275
+ MISTRAL_API_KEY: ["mistral"],
2276
+ NEURALWATT_API_KEY: ["neuralwatt"]
2209
2277
  };
2210
2278
  const providerNames = envVarToProviderName[envVarName] || [envVarName.replace(/_API_KEY$/, "").toLowerCase()];
2211
2279
  const apiKeys = Array.isArray(apiKeyValue) ? apiKeyValue : [apiKeyValue];
@@ -2353,7 +2421,8 @@ const getEnvironmentVariablesForAider = (settings, baseDir) => {
2353
2421
  };
2354
2422
  const getTelemetryEnvironmentVariablesForAider = (settings, baseDir) => {
2355
2423
  return {
2356
- ...getLangfuseEnvironmentVariables(baseDir, settings)
2424
+ ...getLangfuseEnvironmentVariables(baseDir, settings),
2425
+ ...getPostHogAiderEnvironmentVariables(baseDir, settings)
2357
2426
  };
2358
2427
  };
2359
2428
  const getDefaultProjectSettings = (store, providerModels, baseDir, defaultAgentProfileId = DEFAULT_AGENT_PROFILE.id, providers) => {
@@ -2370,7 +2439,7 @@ const getDefaultProjectSettings = (store, providerModels, baseDir, defaultAgentP
2370
2439
  modelEditFormats: {},
2371
2440
  currentMode: "agent",
2372
2441
  agentProfileId: defaultAgentProfileId,
2373
- autoApproveLocked: false,
2442
+ autonomyModeLocked: false,
2374
2443
  updatedFilesGroupMode: "flat",
2375
2444
  disabledRuleFiles: [],
2376
2445
  contextSidebarSectionsOrder: [],
@@ -2843,8 +2912,8 @@ const downloadUv = async () => {
2843
2912
  strip: 1
2844
2913
  });
2845
2914
  } else if (filename.endsWith(".zip")) {
2846
- const AdmZip = (await import("adm-zip")).default;
2847
- const zip = new AdmZip(tempFile);
2915
+ const AdmZip2 = (await import("adm-zip")).default;
2916
+ const zip = new AdmZip2(tempFile);
2848
2917
  zip.extractAllTo(AIDER_DESK_BIN_DIR, true);
2849
2918
  }
2850
2919
  if (platform !== "win32") {
@@ -2907,6 +2976,111 @@ const getLatestPythonLibVersion = async (library) => {
2907
2976
  return null;
2908
2977
  }
2909
2978
  };
2979
+ const RIPGREP_VERSION = "15.1.0";
2980
+ const RIPGREP_BASE_URL = `https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}`;
2981
+ const getRipgrepAssetName = () => {
2982
+ const platform = process.platform;
2983
+ const arch = process.arch;
2984
+ if (platform === "win32") {
2985
+ return arch === "arm64" ? `ripgrep-${RIPGREP_VERSION}-aarch64-pc-windows-msvc.zip` : `ripgrep-${RIPGREP_VERSION}-x86_64-pc-windows-msvc.zip`;
2986
+ }
2987
+ if (platform === "darwin") {
2988
+ return arch === "arm64" ? `ripgrep-${RIPGREP_VERSION}-aarch64-apple-darwin.tar.gz` : `ripgrep-${RIPGREP_VERSION}-x86_64-apple-darwin.tar.gz`;
2989
+ }
2990
+ if (arch === "arm64") {
2991
+ return `ripgrep-${RIPGREP_VERSION}-aarch64-unknown-linux-gnu.tar.gz`;
2992
+ }
2993
+ return `ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl.tar.gz`;
2994
+ };
2995
+ const isRipgrepAvailable = () => {
2996
+ try {
2997
+ if (!fs$1.existsSync(RIPGREP_BINARY_PATH)) {
2998
+ return false;
2999
+ }
3000
+ const result = child_process.execSync(`"${RIPGREP_BINARY_PATH}" --version`, {
3001
+ encoding: "utf8",
3002
+ timeout: 5e3,
3003
+ windowsHide: true
3004
+ });
3005
+ if (!result.includes("ripgrep")) {
3006
+ return false;
3007
+ }
3008
+ logger.debug(`ripgrep is available at ${RIPGREP_BINARY_PATH}: ${result.trim()}`);
3009
+ return true;
3010
+ } catch {
3011
+ return false;
3012
+ }
3013
+ };
3014
+ const downloadRipgrep = async () => {
3015
+ const filename = getRipgrepAssetName();
3016
+ const url = `${RIPGREP_BASE_URL}/${filename}`;
3017
+ if (!fs$1.existsSync(AIDER_DESK_BIN_DIR)) {
3018
+ fs$1.mkdirSync(AIDER_DESK_BIN_DIR, { recursive: true });
3019
+ }
3020
+ const tempFile = path.join(AIDER_DESK_BIN_DIR, filename);
3021
+ const rgExeName = process.platform === "win32" ? "rg.exe" : "rg";
3022
+ try {
3023
+ logger.info(`Downloading ripgrep from ${url}`);
3024
+ const response = await fetch(url);
3025
+ if (!response.ok) {
3026
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
3027
+ }
3028
+ const buffer = Buffer.from(await response.arrayBuffer());
3029
+ fs$1.writeFileSync(tempFile, buffer);
3030
+ if (filename.endsWith(".tar.gz")) {
3031
+ const { extract } = await import("tar");
3032
+ await extract({
3033
+ cwd: AIDER_DESK_BIN_DIR,
3034
+ file: tempFile,
3035
+ strip: 1
3036
+ });
3037
+ } else if (filename.endsWith(".zip")) {
3038
+ const zip = new AdmZip(tempFile);
3039
+ const entry = zip.getEntry(rgExeName) || zip.getEntries().find((e) => e.entryName.endsWith(rgExeName));
3040
+ if (entry) {
3041
+ fs$1.writeFileSync(path.join(AIDER_DESK_BIN_DIR, rgExeName), entry.getData());
3042
+ } else {
3043
+ throw new Error(`Could not find ${rgExeName} in zip archive`);
3044
+ }
3045
+ }
3046
+ if (process.platform !== "win32") {
3047
+ fs$1.chmodSync(path.join(AIDER_DESK_BIN_DIR, rgExeName), 493);
3048
+ }
3049
+ logger.info(`ripgrep downloaded successfully to ${AIDER_DESK_BIN_DIR}`);
3050
+ } finally {
3051
+ if (fs$1.existsSync(tempFile)) {
3052
+ fs$1.unlinkSync(tempFile);
3053
+ }
3054
+ }
3055
+ };
3056
+ let ripgrepEnsured = false;
3057
+ let ripgrepEnsurePromise = null;
3058
+ const ensureRipgrepBinary = async () => {
3059
+ if (ripgrepEnsured) {
3060
+ return true;
3061
+ }
3062
+ if (!ripgrepEnsurePromise) {
3063
+ ripgrepEnsurePromise = (async () => {
3064
+ if (isRipgrepAvailable()) {
3065
+ ripgrepEnsured = true;
3066
+ return;
3067
+ }
3068
+ await downloadRipgrep();
3069
+ if (!isRipgrepAvailable()) {
3070
+ throw new Error("ripgrep binary not found after download");
3071
+ }
3072
+ ripgrepEnsured = true;
3073
+ })();
3074
+ }
3075
+ try {
3076
+ await ripgrepEnsurePromise;
3077
+ return true;
3078
+ } catch (error) {
3079
+ ripgrepEnsurePromise = null;
3080
+ logger.error("Failed to ensure ripgrep binary:", error);
3081
+ return false;
3082
+ }
3083
+ };
2910
3084
  const isAbortError = (error) => {
2911
3085
  if (!(error instanceof Error)) {
2912
3086
  return false;
@@ -3078,18 +3252,25 @@ const expandTilde = (filePath) => {
3078
3252
  }
3079
3253
  return filePath;
3080
3254
  };
3081
- const readFileContent = async (absolutePath, withLines = false, lineOffset = 0, lineLimit = 1e3, sizeLimit = 0.05 * lineLimit) => {
3255
+ const readFileContent = async (absolutePath, withLines = false, lineOffset = 0, lineLimit = 1e3, sizeLimit = Math.max(50, 0.05 * lineLimit)) => {
3082
3256
  const fileContentBuffer = await fs.readFile(absolutePath);
3083
3257
  if (istextorbinary.isBinary(absolutePath, fileContentBuffer)) {
3084
3258
  throw new Error("Binary files cannot be read.");
3085
3259
  }
3086
- const fileSizeKB = fileContentBuffer.length / 1024;
3087
- if (fileSizeKB > sizeLimit) {
3088
- const truncatedBytes = fileContentBuffer.subarray(0, Math.floor(sizeLimit * 1024));
3260
+ const fileContent = fileContentBuffer.toString("utf8");
3261
+ const lines = fileContent.split("\n");
3262
+ const totalLines = lines.length;
3263
+ const startIndex = Math.max(0, lineOffset);
3264
+ const endIndex = Math.min(totalLines, startIndex + lineLimit);
3265
+ const limitedLines = lines.slice(startIndex, endIndex);
3266
+ const limitedContent = limitedLines.join("\n");
3267
+ const limitedSizeKB = Buffer.byteLength(limitedContent, "utf8") / 1024;
3268
+ if (limitedSizeKB > sizeLimit) {
3269
+ const truncatedBytes = Buffer.from(limitedContent, "utf8").subarray(0, Math.floor(sizeLimit * 1024));
3089
3270
  const truncatedContent = truncatedBytes.toString("utf8");
3090
3271
  const truncatedLines = truncatedContent.split("\n");
3091
3272
  if (withLines) {
3092
- return truncatedLines.map((line, index) => `${index + 1}|${line}`).join("\n") + `
3273
+ return truncatedLines.map((line, index) => `${startIndex + index + 1}|${line}`).join("\n") + `
3093
3274
 
3094
3275
  File size limit (${sizeLimit.toFixed(1)} KB) exceeded. Use shell commands (e.g., head, tail, grep) to read specific parts.`;
3095
3276
  }
@@ -3097,33 +3278,30 @@ File size limit (${sizeLimit.toFixed(1)} KB) exceeded. Use shell commands (e.g.,
3097
3278
 
3098
3279
  File size limit (${sizeLimit.toFixed(1)} KB) exceeded. Use shell commands (e.g., head, tail, grep) to read specific parts.`;
3099
3280
  }
3100
- const fileContent = fileContentBuffer.toString("utf8");
3101
- const lines = fileContent.split("\n");
3102
- const totalLines = lines.length;
3103
- const startIndex = Math.max(0, lineOffset);
3104
- const endIndex = Math.min(totalLines, startIndex + lineLimit);
3105
- let limitedLines = lines.slice(startIndex, endIndex);
3281
+ let resultLines = limitedLines;
3106
3282
  if (withLines) {
3107
- limitedLines = limitedLines.map((line, index) => `${startIndex + index + 1}|${line}`);
3283
+ resultLines = limitedLines.map((line, index) => `${startIndex + index + 1}|${line}`);
3108
3284
  }
3109
3285
  if (endIndex < totalLines) {
3110
- limitedLines.push("...");
3111
- limitedLines.push(`Total lines in the file: ${totalLines}`);
3286
+ resultLines = [...resultLines, "...", `Total lines in the file: ${totalLines}`];
3112
3287
  }
3113
- return limitedLines.join("\n");
3288
+ return resultLines.join("\n");
3114
3289
  };
3115
- const truncateToolResult = async (content, maxLines = 1e3, maxSizeKB = 50) => {
3290
+ const truncateToolResult = async (content, maxLines = 1e3, maxSizeKB = 50, maxTokens = 5e4, saveToFile = true, truncationSuffix) => {
3116
3291
  const lines = content.split("\n");
3117
3292
  const sizeBytes = Buffer.byteLength(content, "utf8");
3118
3293
  const sizeKB = sizeBytes / 1024;
3119
- if (lines.length <= maxLines && sizeKB <= maxSizeKB) {
3294
+ const tokenCount = maxTokens === Infinity ? 0 : gpt4o.encode(content).length;
3295
+ if (lines.length <= maxLines && sizeKB <= maxSizeKB && tokenCount <= maxTokens) {
3120
3296
  return content;
3121
3297
  }
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);
3298
+ let tmpFilePath;
3299
+ if (saveToFile) {
3300
+ const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
3301
+ const tmpFileName = `aider-desk-tool-result-${id}.txt`;
3302
+ tmpFilePath = path.join(os.tmpdir(), tmpFileName);
3303
+ await fs.writeFile(tmpFilePath, content, "utf8");
3304
+ }
3127
3305
  const reasons = [];
3128
3306
  if (lines.length > maxLines) {
3129
3307
  reasons.push(`${lines.length} lines exceeded limit of ${maxLines}`);
@@ -3131,9 +3309,141 @@ const truncateToolResult = async (content, maxLines = 1e3, maxSizeKB = 50) => {
3131
3309
  if (sizeKB > maxSizeKB) {
3132
3310
  reasons.push(`${sizeKB.toFixed(1)} KB exceeded limit of ${maxSizeKB} KB`);
3133
3311
  }
3134
- return previewLines.join("\n") + `
3135
- ... Content truncated (${reasons.join(", ")}). Full content saved to ${tmpFilePath}.`;
3312
+ if (tokenCount > maxTokens) {
3313
+ reasons.push(`${tokenCount} tokens exceeded limit of ${maxTokens}`);
3314
+ }
3315
+ const getSuffix = () => {
3316
+ if (truncationSuffix) {
3317
+ return truncationSuffix;
3318
+ }
3319
+ const fileNote = tmpFilePath ? ` Full content saved to ${tmpFilePath}.` : "";
3320
+ return `Content truncated (${reasons.join(", ")}).${fileNote}`;
3321
+ };
3322
+ if (tokenCount > maxTokens) {
3323
+ const headBudget = Math.floor(maxTokens / 2);
3324
+ const tailBudget = maxTokens - headBudget;
3325
+ const headLines = [];
3326
+ let headTokens = 0;
3327
+ for (const line of lines) {
3328
+ const lineTokens = gpt4o.encode(line).length;
3329
+ if (headTokens + lineTokens > headBudget) {
3330
+ break;
3331
+ }
3332
+ headLines.push(line);
3333
+ headTokens += lineTokens;
3334
+ }
3335
+ const tailLines = [];
3336
+ let tailTokens = 0;
3337
+ for (let i = lines.length - 1; i >= 0; i--) {
3338
+ if (headLines.length + tailLines.length >= lines.length) {
3339
+ break;
3340
+ }
3341
+ const lineTokens = gpt4o.encode(lines[i]).length;
3342
+ if (tailTokens + lineTokens > tailBudget) {
3343
+ break;
3344
+ }
3345
+ tailLines.unshift(lines[i]);
3346
+ tailTokens += lineTokens;
3347
+ }
3348
+ const omittedLines = lines.length - headLines.length - tailLines.length;
3349
+ const truncationNotice = `
3350
+
3351
+ ... ${omittedLines} lines omitted (${reasons.join(", ")}). Full content saved to ${tmpFilePath}.
3352
+
3353
+ `;
3354
+ if (truncationSuffix) {
3355
+ const suffixNotice = `
3356
+
3357
+ ... ${omittedLines} lines omitted. ${truncationSuffix}
3358
+
3359
+ `;
3360
+ return headLines.join("\n") + suffixNotice + tailLines.join("\n");
3361
+ }
3362
+ return headLines.join("\n") + truncationNotice + tailLines.join("\n");
3363
+ }
3364
+ let preview;
3365
+ if (sizeKB > maxSizeKB) {
3366
+ const maxBytes = Math.floor(maxSizeKB * 1024);
3367
+ const contentBuffer = Buffer.from(content, "utf8");
3368
+ preview = contentBuffer.subarray(0, maxBytes).toString("utf8");
3369
+ } else {
3370
+ preview = lines.slice(0, maxLines).join("\n");
3371
+ }
3372
+ return preview + `
3373
+ ... ${getSuffix()}`;
3374
+ };
3375
+ const NETWORK_ERROR_CODES = ["ECONNRESET", "EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ENOTFOUND", "ENETUNREACH", "EAI_AGAIN"];
3376
+ const UNDICI_ERROR_PREFIX = "UND_ERR_";
3377
+ const isNetworkError = (error) => {
3378
+ if (!(error instanceof Error)) {
3379
+ return false;
3380
+ }
3381
+ if (error instanceof TypeError && error.message === "terminated") {
3382
+ return true;
3383
+ }
3384
+ if ("code" in error) {
3385
+ const code = error.code;
3386
+ if (typeof code === "string") {
3387
+ if (code.startsWith(UNDICI_ERROR_PREFIX)) {
3388
+ return true;
3389
+ }
3390
+ if (NETWORK_ERROR_CODES.includes(code)) {
3391
+ return true;
3392
+ }
3393
+ }
3394
+ }
3395
+ if (error.cause instanceof Error && isNetworkError(error.cause)) {
3396
+ return true;
3397
+ }
3398
+ return false;
3399
+ };
3400
+ const IMAGE_TOKEN_ESTIMATE = 1e3;
3401
+ const extractToolResultText = (output) => {
3402
+ if (!output || typeof output !== "object") {
3403
+ return "";
3404
+ }
3405
+ const o = output;
3406
+ if (o.type === "text" || o.type === "error-text") {
3407
+ return String(o.value ?? "");
3408
+ }
3409
+ if (o.type === "json" || o.type === "error-json") {
3410
+ return JSON.stringify(o.value);
3411
+ }
3412
+ if (o.type === "content" && Array.isArray(o.value)) {
3413
+ return o.value.map((v) => v.type === "text" ? v.text ?? "" : v.type === "media" ? "[media]" : "").join("\n");
3414
+ }
3415
+ return JSON.stringify(output);
3416
+ };
3417
+ const estimateMessageTokens = (messages) => {
3418
+ let estimatedImageTokens = 0;
3419
+ const textOnlyMessages = messages.map((msg) => {
3420
+ if (typeof msg.content === "string") {
3421
+ return msg;
3422
+ }
3423
+ if (!Array.isArray(msg.content)) {
3424
+ return { role: msg.role, content: "" };
3425
+ }
3426
+ const parts = msg.content;
3427
+ const textParts = [];
3428
+ for (const part of parts) {
3429
+ const type = part.type;
3430
+ if (type === "text" && typeof part.text === "string") {
3431
+ textParts.push(part.text);
3432
+ } else if (type === "tool-call") {
3433
+ textParts.push(JSON.stringify(part.input ?? ""));
3434
+ } else if (type === "tool-result") {
3435
+ textParts.push(extractToolResultText(part.output));
3436
+ } else if (type === "reasoning" && typeof part.text === "string") {
3437
+ textParts.push(part.text);
3438
+ } else if (type === "image") {
3439
+ estimatedImageTokens += IMAGE_TOKEN_ESTIMATE;
3440
+ }
3441
+ }
3442
+ return { role: msg.role, content: textParts.join("\n\n") };
3443
+ });
3444
+ return textOnlyMessages.reduce((sum, msg) => sum + gpt4o.encode(typeof msg.content === "string" ? msg.content : "").length, 0) + estimatedImageTokens;
3136
3445
  };
3446
+ const execFileAsync = util.promisify(child_process.execFile);
3137
3447
  const fileLocks = /* @__PURE__ */ new Map();
3138
3448
  const withFileLock = (filePath, operation) => {
3139
3449
  logger.debug("Acquiring file lock:", { filePath });
@@ -3445,50 +3755,97 @@ Do not use escape characters \\ in the string like \\n or \\" and others. Do not
3445
3755
  if (!isApproved) {
3446
3756
  return `Grep search for '${searchTerm}' in files matching '${filePattern}' denied by user. Reason: ${userInput}`;
3447
3757
  }
3758
+ const rgAvailable = await ensureRipgrepBinary();
3759
+ if (!rgAvailable) {
3760
+ return "Error: ripgrep binary is not available. Please try again or check the logs for details.";
3761
+ }
3448
3762
  try {
3449
- const files = await glob.glob(filePattern, {
3450
- cwd: task.getTaskDir(),
3451
- nodir: true,
3452
- absolute: true,
3453
- signal: abortSignal
3763
+ const rgArgs = ["--no-heading", "--line-number", "--color", "never", "--max-count", String(maxResults)];
3764
+ if (!caseSensitive) {
3765
+ rgArgs.push("-i");
3766
+ }
3767
+ if (contextLines > 0) {
3768
+ rgArgs.push("-C", String(contextLines));
3769
+ }
3770
+ rgArgs.push("-g", filePattern);
3771
+ rgArgs.push("--", searchTerm, ".");
3772
+ const taskDir = task.getTaskDir();
3773
+ logger.debug("Executing ripgrep:", {
3774
+ binary: RIPGREP_BINARY_PATH,
3775
+ args: rgArgs,
3776
+ cwd: taskDir
3454
3777
  });
3455
- if (files.length === 0) {
3456
- return `No files found matching pattern '${filePattern}'.`;
3457
- }
3458
- const filteredFiles = await filterIgnoredFiles(task.getTaskDir(), files);
3459
- if (filteredFiles.length === 0) {
3460
- return `No files found matching pattern '${filePattern}' (all files were ignored).`;
3778
+ const { stdout, stderr } = await execFileAsync(RIPGREP_BINARY_PATH, rgArgs, {
3779
+ cwd: taskDir,
3780
+ env: { ...process.env, TERM: "dumb", PATH: getShellPath() },
3781
+ maxBuffer: 10 * 1024 * 1024,
3782
+ windowsHide: true
3783
+ });
3784
+ logger.debug("ripgrep stdout raw:", { stdout: stdout.substring(0, 2e3) });
3785
+ logger.debug("ripgrep stderr:", { stderr: stderr.substring(0, 2e3) });
3786
+ const outputLines = stdout.split("\n").filter(Boolean);
3787
+ logger.debug("outputLines count:", { count: outputLines.length, lines: outputLines.slice(0, 20) });
3788
+ if (outputLines.length === 0) {
3789
+ return `No matches found for pattern '${searchTerm}' in files matching '${filePattern}'.`;
3461
3790
  }
3462
3791
  const results = [];
3463
- const searchRegex = new RegExp(searchTerm, caseSensitive ? void 0 : "i");
3464
- for (const absoluteFilePath of filteredFiles) {
3465
- const fileContent = await fs.readFile(absoluteFilePath, {
3466
- encoding: "utf8",
3467
- signal: abortSignal
3468
- });
3469
- const lines2 = fileContent.split("\n");
3470
- const relativeFilePath = path.relative(task.getTaskDir(), absoluteFilePath);
3471
- for (let index = 0; index < lines2.length; index++) {
3472
- const line = lines2[index];
3473
- if (searchRegex.test(line)) {
3474
- if (results.length >= maxResults) {
3475
- break;
3476
- }
3477
- const matchResult = {
3478
- filePath: relativeFilePath,
3479
- lineNumber: index + 1,
3480
- lineContent: line
3481
- };
3482
- if (contextLines > 0) {
3483
- const start = Math.max(0, index - contextLines);
3484
- const end = Math.min(lines2.length - 1, index + contextLines);
3485
- matchResult.context = lines2.slice(start, end + 1);
3792
+ const outputLineRegex = /^(.+?)([:-])(\d+)\2(.*)$/;
3793
+ let pendingBeforeContext = [];
3794
+ let afterContextBreak = false;
3795
+ for (let rawLine of outputLines) {
3796
+ if (rawLine.endsWith("\r")) {
3797
+ rawLine = rawLine.slice(0, -1);
3798
+ }
3799
+ if (rawLine === "--") {
3800
+ pendingBeforeContext = [];
3801
+ afterContextBreak = true;
3802
+ continue;
3803
+ }
3804
+ const line = rawLine;
3805
+ const match = outputLineRegex.exec(line);
3806
+ if (!match) {
3807
+ continue;
3808
+ }
3809
+ const [, rawFilePath, separator, lineNumStr, content] = match;
3810
+ const lineNum = parseInt(lineNumStr, 10);
3811
+ if (isNaN(lineNum)) {
3812
+ continue;
3813
+ }
3814
+ const filePath = rawFilePath.startsWith("./") ? rawFilePath.slice(2) : rawFilePath;
3815
+ const isMatch = separator === ":";
3816
+ if (isMatch) {
3817
+ const context = pendingBeforeContext.length > 0 ? [...pendingBeforeContext, content] : void 0;
3818
+ results.push({
3819
+ filePath,
3820
+ lineNumber: lineNum,
3821
+ lineContent: content,
3822
+ context
3823
+ });
3824
+ pendingBeforeContext = [];
3825
+ afterContextBreak = false;
3826
+ } else {
3827
+ if (afterContextBreak) {
3828
+ pendingBeforeContext.push(content);
3829
+ } else {
3830
+ const lastResult = results[results.length - 1];
3831
+ if (lastResult) {
3832
+ if (!lastResult.context) {
3833
+ lastResult.context = [];
3834
+ }
3835
+ lastResult.context.push(content);
3836
+ } else {
3837
+ pendingBeforeContext.push(content);
3486
3838
  }
3487
- results.push(matchResult);
3488
3839
  }
3489
3840
  }
3841
+ if (results.length >= maxResults) {
3842
+ break;
3843
+ }
3490
3844
  }
3491
3845
  if (results.length === 0) {
3846
+ if (stderr.trim()) {
3847
+ return `Error during grep: ${stderr.trim()}`;
3848
+ }
3492
3849
  return `No matches found for pattern '${searchTerm}' in files matching '${filePattern}'.`;
3493
3850
  }
3494
3851
  const grouped = {};
@@ -3498,33 +3855,33 @@ Do not use escape characters \\ in the string like \\n or \\" and others. Do not
3498
3855
  }
3499
3856
  grouped[r.filePath].push(r);
3500
3857
  }
3501
- const lines = [];
3502
- lines.push(`## Grep Results: \`${searchTerm}\` in \`${filePattern}\` (${results.length} matches)`);
3503
- lines.push("");
3858
+ const markdownLines = [];
3859
+ markdownLines.push(`## Grep Results: \`${searchTerm}\` in \`${filePattern}\` (${results.length} matches)`);
3860
+ markdownLines.push("");
3504
3861
  for (const [filePath, matches] of Object.entries(grouped)) {
3505
- lines.push(`### ${filePath} (${matches.length} ${matches.length === 1 ? "match" : "matches"})`);
3862
+ markdownLines.push(`### ${filePath} (${matches.length} ${matches.length === 1 ? "match" : "matches"})`);
3506
3863
  for (const match of matches) {
3507
3864
  const escapedContent = match.lineContent.replace(/`/g, "\\`");
3508
- lines.push(`- **L${match.lineNumber}:** \`${escapedContent}\``);
3865
+ markdownLines.push(`- **L${match.lineNumber}:** \`${escapedContent}\``);
3509
3866
  if (match.context && match.context.length > 0) {
3510
- lines.push(" ```");
3867
+ markdownLines.push(" ```");
3511
3868
  for (const ctxLine of match.context) {
3512
- lines.push(` ${ctxLine}`);
3869
+ markdownLines.push(` ${ctxLine}`);
3513
3870
  }
3514
- lines.push(" ```");
3871
+ markdownLines.push(" ```");
3515
3872
  }
3516
3873
  }
3517
- lines.push("");
3874
+ markdownLines.push("");
3518
3875
  }
3519
3876
  const notices = [];
3520
3877
  if (results.length >= maxResults) {
3521
3878
  notices.push(`${maxResults} matches limit reached. Use maxResults=${maxResults * 2} for more, or refine pattern`);
3522
3879
  }
3523
3880
  if (notices.length > 0) {
3524
- lines.push("---");
3525
- lines.push(`[${notices.join(". ")}]`);
3881
+ markdownLines.push("---");
3882
+ markdownLines.push(`[${notices.join(". ")}]`);
3526
3883
  }
3527
- return lines.join("\n");
3884
+ return await truncateToolResult(markdownLines.join("\n"), 1e3, 50, 1e4);
3528
3885
  } catch (error) {
3529
3886
  if (isAbortError(error)) {
3530
3887
  return "Operation was cancelled by user.";
@@ -3581,6 +3938,18 @@ Timeout: ${timeout}ms`;
3581
3938
  return `Bash command execution denied by user. Reason: ${userInput}`;
3582
3939
  }
3583
3940
  const absoluteCwd = expandedCwd ? path.resolve(task.getTaskDir(), expandedCwd) : task.getTaskDir();
3941
+ const isPosix = process.platform !== "win32";
3942
+ const killProcess = (pid) => {
3943
+ if (isPosix) {
3944
+ try {
3945
+ process.kill(-pid, "SIGKILL");
3946
+ } catch {
3947
+ treeKill(pid, "SIGKILL");
3948
+ }
3949
+ } else {
3950
+ treeKill(pid, "SIGKILL");
3951
+ }
3952
+ };
3584
3953
  return await new Promise((resolve) => {
3585
3954
  let stdout = "";
3586
3955
  let stderr = "";
@@ -3604,8 +3973,8 @@ Timeout: ${timeout}ms`;
3604
3973
  }
3605
3974
  isResolved = true;
3606
3975
  cleanup();
3607
- const truncatedStdout = await truncateToolResult(stdout);
3608
- const truncatedStderr = await truncateToolResult(stderr);
3976
+ const truncatedStdout = await truncateToolResult(stdout, 1e3, 50, 1e4);
3977
+ const truncatedStderr = await truncateToolResult(stderr, 1e3, 50, 1e4);
3609
3978
  resolve({ stdout: truncatedStdout, stderr: truncatedStderr, exitCode });
3610
3979
  };
3611
3980
  abortListener = () => {
@@ -3613,7 +3982,7 @@ Timeout: ${timeout}ms`;
3613
3982
  return;
3614
3983
  }
3615
3984
  if (childProcess?.pid) {
3616
- treeKill(childProcess.pid, "SIGKILL");
3985
+ killProcess(childProcess.pid);
3617
3986
  }
3618
3987
  stderr = "Operation was cancelled by user.";
3619
3988
  exitCode = 130;
@@ -3627,6 +3996,7 @@ Timeout: ${timeout}ms`;
3627
3996
  env: { ...process.env, TERM: "dumb", DEBIAN_FRONTEND: "noninteractive", PATH: getShellPath() },
3628
3997
  stdio: ["ignore", "pipe", "pipe"],
3629
3998
  // Explicitly pipe stdout and stderr to capture output from piped commands
3999
+ detached: isPosix,
3630
4000
  signal: abortSignal
3631
4001
  });
3632
4002
  timeoutHandle = setTimeout(() => {
@@ -3634,7 +4004,7 @@ Timeout: ${timeout}ms`;
3634
4004
  return;
3635
4005
  }
3636
4006
  if (childProcess?.pid) {
3637
- treeKill(childProcess.pid, "SIGKILL");
4007
+ killProcess(childProcess.pid);
3638
4008
  }
3639
4009
  stderr = `Error: Command timed out after ${timeout}ms. Consider increasing the timeout parameter.`;
3640
4010
  exitCode = 124;
@@ -3745,7 +4115,8 @@ Format: ${format}`;
3745
4115
  return `Error: Invalid URL provided: ${url}. Please provide a valid URL.`;
3746
4116
  }
3747
4117
  try {
3748
- return await scrapeWeb(url, timeout, abortSignal, format);
4118
+ const content = await scrapeWeb(url, timeout, abortSignal, format);
4119
+ return await truncateToolResult(content, 1e3, 50, 1e4);
3749
4120
  } catch (error) {
3750
4121
  if (isAbortError(error)) {
3751
4122
  return "Operation was cancelled by user.";
@@ -4014,6 +4385,11 @@ const createTasksToolset = (settings, task, profile, promptContext) => {
4014
4385
  if (state) {
4015
4386
  allTasks = allTasks.filter((t) => t.state === state);
4016
4387
  }
4388
+ allTasks.sort((a, b) => {
4389
+ const dateA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
4390
+ const dateB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
4391
+ return dateB - dateA;
4392
+ });
4017
4393
  const startIndex = Math.max(0, offset);
4018
4394
  const endIndex = startIndex + Math.max(0, limit || allTasks.length);
4019
4395
  const paginatedTasks = allTasks.slice(startIndex, endIndex);
@@ -4130,34 +4506,35 @@ const createTasksToolset = (settings, task, profile, promptContext) => {
4130
4506
  }
4131
4507
  }
4132
4508
  });
4133
- const isSubtask = task.task.parentId !== null;
4134
4509
  const autoGenerateTaskName = settings.taskSettings.autoGenerateTaskName;
4135
4510
  const nameProperty = autoGenerateTaskName ? zod.z.string().optional().describe("Optional concise name for the new task. If not provided, the task name will be auto-generated from the prompt.") : zod.z.string().describe("Concise name for the new task.");
4136
- const CreateTaskInputSchema = zod.z.object({
4137
- prompt: zod.z.string().describe("The initial prompt for the new task"),
4138
- name: nameProperty,
4139
- agentProfileId: zod.z.string().optional().describe("Optional agent profile ID to use for the task. Use only when explicitly requested by the user."),
4140
- modelId: zod.z.string().optional().describe("Optional model ID to use for the task. Use only when explicitly requested by the user."),
4141
- mode: zod.z.string().optional().default("agent").describe("Optional mode to use for the task. Use only when explicitly requested by the user."),
4142
- asSubtask: zod.z.boolean().optional().default(false).describe("If true, the task will be created as a subtask of the current task. Use only when explicitly requested by the user."),
4143
- autoApprove: zod.z.boolean().optional().default(false).describe(
4144
- "If true, the task will be created with auto-approve enabled. Set autoApprove to true when no planning is needed, just execution of the task with all the work."
4145
- ),
4146
- worktree: zod.z.boolean().optional().default(false).describe("If true, the task will be created in worktree mode. Use only when explicitly requested by the user."),
4147
- executeAndWait: zod.z.boolean().optional().default(false).describe("If true, the task will be created and executed immediately and the tool will wait for it to complete before returning."),
4148
- executeInBackground: zod.z.boolean().optional().default(false).describe("If true, the task will be created and executed in the background.")
4149
- });
4150
- const CreateTaskWithParentInputSchema = CreateTaskInputSchema.extend({
4151
- parentTaskId: zod.z.string().nullable().optional().describe(
4152
- `Optional ID of the parent task. If provided, the new task will be created as a subtask of the specified parent. Use the current task's ID (${task.taskId}) to create a subtask of the current task. When asSubtask is true, this tasks ID will be used automatically.`
4153
- )
4154
- });
4511
+ const availableAgents = task.getProject().getAgentProfiles().map((p) => {
4512
+ const slug = deriveDirName(p.name, /* @__PURE__ */ new Set());
4513
+ const shortId = isUuid(p.id) ? p.id.substring(0, 8) : p.id;
4514
+ return `${slug}(${shortId})`;
4515
+ }).join(",");
4155
4516
  const createTaskTool = ai.tool({
4156
4517
  description: TASKS_TOOL_DESCRIPTIONS[TASKS_TOOL_CREATE_TASK],
4157
- inputSchema: isSubtask ? CreateTaskInputSchema : CreateTaskWithParentInputSchema,
4518
+ inputSchema: zod.z.object({
4519
+ prompt: zod.z.string().describe("The initial prompt for the new task"),
4520
+ name: nameProperty,
4521
+ agentProfileId: zod.z.string().optional().describe(`Optional agent profile ID or name. Available agents: ${availableAgents}. Use only when explicitly requested by the user.`),
4522
+ modelId: zod.z.string().optional().describe("Optional model ID in the format `provider/model` to use for the task. Use only when explicitly requested by the user."),
4523
+ mode: zod.z.string().optional().default("agent").describe("Optional mode to use for the task. Use only when explicitly requested by the user."),
4524
+ asSubtask: zod.z.boolean().optional().default(false).describe("If true, the task will be created as a subtask of the current task. Use only when explicitly requested by the user."),
4525
+ parentTaskId: zod.z.string().nullable().optional().describe(
4526
+ "Optional ID of the parent task. If provided, the new task will be created as a subtask of the specified parent. When asSubtask is specified as true, this is not needed as parent ID is resolved automatically."
4527
+ ),
4528
+ autonomyMode: zod.z.enum(AutonomyMode).optional().describe(
4529
+ 'The autonomy mode for the new task. "manual" requires approval for every tool and plan, "guided" (default) auto-approves tools but waits for plan approval from user, "autonomous" runs without interruption.'
4530
+ ),
4531
+ worktree: zod.z.boolean().optional().default(false).describe("If true, the task will be created in worktree mode. Use only when explicitly requested by the user."),
4532
+ executeAndWait: zod.z.boolean().optional().default(false).describe("If true, the task will be created and executed immediately and the tool will wait for it to complete before returning."),
4533
+ executeInBackground: zod.z.boolean().optional().default(false).describe("If true, the task will be created and executed in the background.")
4534
+ }),
4158
4535
  execute: async (input, { toolCallId }) => {
4159
- const { prompt, name, agentProfileId, modelId, mode = "agent", asSubtask = false, autoApprove, worktree, executeAndWait, executeInBackground } = input;
4160
- const parentTaskId = asSubtask ? task.taskId : "parentTaskId" in input ? input.parentTaskId : void 0;
4536
+ const { prompt, name, agentProfileId, modelId, mode = "agent", asSubtask = false, autonomyMode, worktree, executeAndWait, executeInBackground } = input;
4537
+ const parentTaskId = asSubtask ? task.task.parentId || task.taskId : "parentTaskId" in input ? input.parentTaskId : void 0;
4161
4538
  task.addToolMessage(toolCallId, TASKS_TOOL_GROUP_NAME, TASKS_TOOL_CREATE_TASK, input, void 0, void 0, promptContext);
4162
4539
  const toolName = `${TASKS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${TASKS_TOOL_CREATE_TASK}`;
4163
4540
  const questionKey = toolName;
@@ -4174,18 +4551,27 @@ Parent Task ID: ${parentTaskId || "none (top-level task)"}` : ""}`;
4174
4551
  const newTask = await task.getProject().createNewTask({
4175
4552
  parentId: parentTaskId || null,
4176
4553
  name: name || "",
4177
- autoApprove,
4554
+ autonomyMode,
4178
4555
  workingMode: worktree ? "worktree" : "local"
4179
4556
  });
4180
4557
  const updates = {};
4181
4558
  if (agentProfileId) {
4182
- updates.agentProfileId = agentProfileId;
4559
+ const resolvedProfile = task.getProject().resolveAgentProfile(agentProfileId);
4560
+ if (resolvedProfile) {
4561
+ updates.agentProfileId = resolvedProfile.id;
4562
+ } else {
4563
+ const availableProfiles = task.getProject().getAgentProfiles().map((p) => `"${p.name}"`).join(", ");
4564
+ return `Agent profile '${agentProfileId}' not found. Available profiles: ${availableProfiles}`;
4565
+ }
4183
4566
  }
4184
4567
  if (modelId) {
4185
4568
  const [provider, ...modelParts] = modelId.split("/");
4186
4569
  updates.provider = provider;
4187
4570
  updates.model = modelParts.join("/");
4188
4571
  updates.mainModel = modelId;
4572
+ } else {
4573
+ updates.provider = void 0;
4574
+ updates.model = void 0;
4189
4575
  }
4190
4576
  const taskInstance = task.getProject().getTask(newTask.id);
4191
4577
  if (!taskInstance) {
@@ -4243,6 +4629,66 @@ Parent Task ID: ${parentTaskId || "none (top-level task)"}` : ""}`;
4243
4629
  }
4244
4630
  }
4245
4631
  });
4632
+ const runPromptTool = ai.tool({
4633
+ description: TASKS_TOOL_DESCRIPTIONS[TASKS_TOOL_RUN_PROMPT],
4634
+ inputSchema: zod.z.object({
4635
+ taskId: zod.z.string().describe("The ID of the existing task to run the prompt on"),
4636
+ prompt: zod.z.string().describe("The prompt to run on the task"),
4637
+ mode: zod.z.string().optional().describe("Optional mode to use for the prompt. Use only when explicitly requested by the user."),
4638
+ executeAndWait: zod.z.boolean().optional().default(true).describe("If true (default), the tool will wait for the prompt to complete before returning. If false, the prompt will run in the background.")
4639
+ }),
4640
+ execute: async (input, { toolCallId }) => {
4641
+ const { taskId, prompt, mode, executeAndWait = true } = input;
4642
+ task.addToolMessage(
4643
+ toolCallId,
4644
+ TASKS_TOOL_GROUP_NAME,
4645
+ TASKS_TOOL_RUN_PROMPT,
4646
+ { taskId, prompt, mode, executeAndWait },
4647
+ void 0,
4648
+ void 0,
4649
+ promptContext
4650
+ );
4651
+ const toolName = `${TASKS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${TASKS_TOOL_RUN_PROMPT}`;
4652
+ const questionKey = toolName;
4653
+ const questionText = `Approve running prompt on task ${taskId}?`;
4654
+ const questionSubject = `Task ID: ${taskId}
4655
+ Prompt: ${prompt}${executeAndWait ? "\nWait for completion: true" : "\nRun in background: true"}`;
4656
+ const [isApproved, userInput] = await approvalManager.handleToolApproval(toolName, input, questionKey, questionText, questionSubject);
4657
+ if (!isApproved) {
4658
+ return `Running prompt on task denied by user. Reason: ${userInput}`;
4659
+ }
4660
+ try {
4661
+ const targetTask = task.getProject().getTask(taskId);
4662
+ if (!targetTask) {
4663
+ return `Task with ID ${taskId} not found`;
4664
+ }
4665
+ const effectiveMode = mode || targetTask.task.currentMode || "agent";
4666
+ const run = targetTask.runPrompt(prompt, effectiveMode, false);
4667
+ if (executeAndWait) {
4668
+ await run;
4669
+ }
4670
+ const taskState = targetTask.task.state;
4671
+ const contextMessages = await targetTask.getContextMessages();
4672
+ let resultMessage = executeAndWait ? "Prompt has been executed successfully" : "Prompt has been started in the background";
4673
+ if (executeAndWait && taskState === DefaultTaskState.Interrupted) {
4674
+ resultMessage = "Task has been interrupted";
4675
+ } else if (executeAndWait && taskState === DefaultTaskState.Delegated) {
4676
+ resultMessage = "Task has been delegated to a subagent";
4677
+ }
4678
+ return {
4679
+ taskId,
4680
+ result: resultMessage,
4681
+ state: taskState,
4682
+ ...executeAndWait && contextMessages.length > 0 && {
4683
+ lastMessage: contextMessages[contextMessages.length - 1]
4684
+ }
4685
+ };
4686
+ } catch (error) {
4687
+ const errorMessage = error instanceof Error ? error.message : String(error);
4688
+ return `Error running prompt on task: ${errorMessage}`;
4689
+ }
4690
+ }
4691
+ });
4246
4692
  const searchTaskTool = ai.tool({
4247
4693
  description: TASKS_TOOL_DESCRIPTIONS[TASKS_TOOL_SEARCH_TASK],
4248
4694
  inputSchema: zod.z.object({
@@ -4309,6 +4755,7 @@ Please, consider reporting an issue at https://github.com/hotovo/aider-desk/issu
4309
4755
  [`${TASKS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${TASKS_TOOL_GET_TASK_MESSAGE}`]: getTaskMessageTool,
4310
4756
  [`${TASKS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${TASKS_TOOL_CREATE_TASK}`]: createTaskTool,
4311
4757
  [`${TASKS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${TASKS_TOOL_DELETE_TASK}`]: deleteTaskTool,
4758
+ [`${TASKS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${TASKS_TOOL_RUN_PROMPT}`]: runPromptTool,
4312
4759
  [`${TASKS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${TASKS_TOOL_SEARCH_TASK}`]: searchTaskTool
4313
4760
  };
4314
4761
  const filteredTools = {};
@@ -4542,7 +4989,7 @@ const createAiderToolset = (task, profile, promptContext) => {
4542
4989
  }
4543
4990
  task.addToolMessage(toolCallId, AIDER_TOOL_GROUP_NAME, AIDER_TOOL_RUN_PROMPT, { prompt }, void 0, void 0, aiderPromptContext);
4544
4991
  const responses = await task.sendPromptToAider(prompt, aiderPromptContext, "code", [], void 0, {
4545
- autoApprove: task.task.autoApprove,
4992
+ autoApprove: task.task.autonomyMode !== AutonomyMode.Manual,
4546
4993
  denyCommands: true
4547
4994
  });
4548
4995
  task.addLogMessage("loading");
@@ -5748,7 +6195,8 @@ const addImportantReminders = async (task, profile, projectProfiles, userRequest
5748
6195
  ${subagents}`);
5749
6196
  }
5750
6197
  }
5751
- if (!task.task.autoApprove && !profile.isSubagent) {
6198
+ const shouldPresentPlan = task.task.autonomyMode !== AutonomyMode.Autonomous;
6199
+ if (shouldPresentPlan && !profile.isSubagent) {
5752
6200
  reminders.push("Before making any complex changes, present the plan and wait for my approval.");
5753
6201
  }
5754
6202
  if (task.getTaskDir() !== task.getProjectDir()) {
@@ -5774,6 +6222,7 @@ ${reminder}
5774
6222
  "onImportantReminders",
5775
6223
  {
5776
6224
  profile,
6225
+ agentProfile: profile,
5777
6226
  remindersContent
5778
6227
  },
5779
6228
  task.project,
@@ -5791,9 +6240,12 @@ ${reminder}
5791
6240
  ${remindersContent}
5792
6241
  </ThisIsImportant>`;
5793
6242
  }
5794
- const updatedFirstUserMessage = {
6243
+ const updatedFirstUserMessage = typeof userRequestMessage.content === "string" ? {
5795
6244
  ...userRequestMessage,
5796
6245
  content: `${userRequestMessage.content}${remindersContent}`
6246
+ } : {
6247
+ ...userRequestMessage,
6248
+ content: [...userRequestMessage.content, { type: "text", text: remindersContent }]
5797
6249
  };
5798
6250
  const newMessages = [...messages];
5799
6251
  newMessages[userRequestMessageIndex] = updatedFirstUserMessage;
@@ -6237,9 +6689,9 @@ ${fileList}`
6237
6689
  const extensionTools = this.extensionManager.createExtensionToolset(task, mode, profile, toolSet, abortSignal);
6238
6690
  Object.assign(toolSet, extensionTools);
6239
6691
  }
6240
- return this.wrapToolsWithHooks(task, toolSet, abortSignal, promptContext);
6692
+ return this.wrapToolsWithHooks(task, profile, toolSet, abortSignal, promptContext);
6241
6693
  }
6242
- wrapToolsWithHooks(task, toolSet, abortSignal, promptContext) {
6694
+ wrapToolsWithHooks(task, profile, toolSet, abortSignal, promptContext) {
6243
6695
  const wrappedToolSet = {};
6244
6696
  for (const [toolName, toolDef] of Object.entries(toolSet)) {
6245
6697
  wrappedToolSet[toolName] = {
@@ -6250,7 +6702,7 @@ ${fileList}`
6250
6702
  task.addToolMessage(options.toolCallId, serverName, messageToolName, effectiveInput, void 0, void 0, promptContext);
6251
6703
  const toolCalledExtensionResult = await this.extensionManager.dispatchEvent(
6252
6704
  "onToolCalled",
6253
- { toolName, input: effectiveInput, abortSignal: options.abortSignal || abortSignal },
6705
+ { toolName, agentProfile: profile, input: effectiveInput, abortSignal: options.abortSignal || abortSignal },
6254
6706
  task.project,
6255
6707
  task
6256
6708
  );
@@ -6267,7 +6719,7 @@ ${fileList}`
6267
6719
  }
6268
6720
  const toolFinishedExtensionResult = await this.extensionManager.dispatchEvent(
6269
6721
  "onToolFinished",
6270
- { toolName, input: effectiveInput, output: result },
6722
+ { toolName, agentProfile: profile, input: effectiveInput, output: result },
6271
6723
  task.project,
6272
6724
  task
6273
6725
  );
@@ -6423,7 +6875,7 @@ ${fileList}`
6423
6875
  }
6424
6876
  return inputSchema;
6425
6877
  }
6426
- async runAgent(task, profile, prompt, mode = "agent", promptContext, initialContextMessages, initialContextFiles, systemPrompt, includeInContext = true, abortSignal) {
6878
+ async runAgent(task, profile, prompt, mode = "agent", promptContext, initialContextMessages, initialContextFiles, systemPrompt, includeInContext = true, abortSignal, images) {
6427
6879
  let contextMessages = initialContextMessages ?? await task.getContextMessages();
6428
6880
  let contextFiles = initialContextFiles ?? await task.getContextFiles();
6429
6881
  if (!systemPrompt) {
@@ -6432,9 +6884,33 @@ ${fileList}`
6432
6884
  const userRequestMessage = prompt ? {
6433
6885
  id: promptContext?.id || uuid.v4(),
6434
6886
  role: "user",
6435
- content: prompt,
6436
- promptContext
6887
+ content: images && images.length > 0 ? [
6888
+ { type: "text", text: prompt },
6889
+ ...images.map((dataUrl) => {
6890
+ const match = dataUrl.match(/^data:(image\/[^;]+);base64,(.+)$/);
6891
+ return {
6892
+ type: "image",
6893
+ image: match ? match[2] : dataUrl,
6894
+ mediaType: match ? match[1] : void 0
6895
+ };
6896
+ })
6897
+ ] : prompt,
6898
+ promptContext,
6899
+ timestamp: Date.now()
6437
6900
  } : null;
6901
+ if (userRequestMessage) {
6902
+ logger.info("User request message created:", {
6903
+ id: userRequestMessage.id,
6904
+ contentType: typeof userRequestMessage.content === "string" ? "string" : "parts",
6905
+ ...typeof userRequestMessage.content === "string" ? { contentPreview: userRequestMessage.content.substring(0, 200) } : {
6906
+ parts: userRequestMessage.content.map((part) => ({
6907
+ type: part.type,
6908
+ ...part.type === "text" ? { textPreview: part.text?.substring(0, 100) } : {},
6909
+ ...part.type === "image" ? { mediaType: part.mediaType, imagePreview: typeof part.image === "string" ? part.image.substring(0, 80) : typeof part.image } : {}
6910
+ }))
6911
+ }
6912
+ });
6913
+ }
6438
6914
  const settings = this.store.getSettings();
6439
6915
  const projectProfiles = this.agentProfileManager.getProjectProfiles(task.project);
6440
6916
  let resultMessages = userRequestMessage ? [userRequestMessage] : [];
@@ -6653,7 +7129,7 @@ ${fileList}`
6653
7129
  return null;
6654
7130
  }
6655
7131
  };
6656
- const modelSettings = this.modelManager.getModelSettings(provider.provider.name, modelName);
7132
+ const modelSettings = this.modelManager.getModelSettings(provider.id, modelName);
6657
7133
  const effectiveTemperature = profile.temperature ?? modelSettings?.temperature;
6658
7134
  const effectiveMaxOutputTokens = profile.maxTokens ?? modelSettings?.maxOutputTokens;
6659
7135
  logger.debug("Parameters:", {
@@ -6711,9 +7187,9 @@ ${fileList}`
6711
7187
  let iterationCount = 0;
6712
7188
  let retryCount = 0;
6713
7189
  while (true) {
6714
- logger.info(`Starting iteration ${iterationCount}`);
7190
+ logger.info(`Starting iteration ${iterationCount}`, { task: task.taskId });
6715
7191
  iterationCount++;
6716
- if (iterationCount > profile.maxIterations) {
7192
+ if (profile.maxIterations > 0 && iterationCount > profile.maxIterations) {
6717
7193
  logger.warn(`Max iterations (${profile.maxIterations}) reached. Stopping agent.`);
6718
7194
  task.addLogMessage(
6719
7195
  "warning",
@@ -6726,7 +7202,7 @@ ${fileList}`
6726
7202
  let iterationError = null;
6727
7203
  let hasReasoning = false;
6728
7204
  let finishReason = null;
6729
- let responseMessages = [];
7205
+ let currentStepMessages = [];
6730
7206
  let responseMessageIndex = 0;
6731
7207
  const onStepFinish = async (stepResult) => {
6732
7208
  finishReason = stepResult.finishReason;
@@ -6738,7 +7214,7 @@ ${fileList}`
6738
7214
  logger.info("Prompt aborted by user");
6739
7215
  return;
6740
7216
  }
6741
- responseMessages = await this.processStep(currentResponseId, stepResult, task, provider, modelName, promptContext, abortSignal);
7217
+ currentStepMessages = await this.processStep(currentResponseId, stepResult, task, provider, modelName, promptContext, abortSignal);
6742
7218
  const extensionResult2 = await this.extensionManager.dispatchEvent(
6743
7219
  "onAgentStepFinished",
6744
7220
  {
@@ -6747,18 +7223,18 @@ ${fileList}`
6747
7223
  currentResponseId,
6748
7224
  stepResult,
6749
7225
  finishReason,
6750
- responseMessages
7226
+ responseMessages: currentStepMessages
6751
7227
  },
6752
7228
  task.project,
6753
7229
  task
6754
7230
  );
6755
- responseMessages = extensionResult2.responseMessages;
7231
+ currentStepMessages = extensionResult2.responseMessages;
6756
7232
  finishReason = extensionResult2.finishReason;
6757
7233
  currentResponseId = uuid.v4();
6758
7234
  responseMessageIndex = 0;
6759
7235
  hasReasoning = false;
6760
7236
  streamingMessageIds.clear();
6761
- if (responseMessages.length > 0) {
7237
+ if (currentStepMessages.length > 0) {
6762
7238
  retryCount = 0;
6763
7239
  }
6764
7240
  };
@@ -6831,25 +7307,46 @@ ${fileList}`
6831
7307
  onStepFinish,
6832
7308
  experimental_repairToolCall: repairToolCall
6833
7309
  });
6834
- for await (const chunk of result.fullStream) {
6835
- logger.debug("Chunk:", { chunk: chunk.type, responseMessageIndex });
6836
- const responseMessageId = responseMessageIndex > 0 ? `${currentResponseId}-${responseMessageIndex}` : currentResponseId;
6837
- if (chunk.type === "text-start") {
6838
- if (hasReasoning) {
7310
+ try {
7311
+ for await (const chunk of result.fullStream) {
7312
+ logger.debug("Chunk:", { chunk: chunk.type, responseMessageIndex });
7313
+ const responseMessageId = responseMessageIndex > 0 ? `${currentResponseId}-${responseMessageIndex}` : currentResponseId;
7314
+ if (chunk.type === "text-start") {
7315
+ if (hasReasoning) {
7316
+ streamingMessageIds.add(responseMessageId);
7317
+ await task.processResponseMessage({
7318
+ id: responseMessageId,
7319
+ action: "response",
7320
+ content: ANSWER_RESPONSE_START_TAG,
7321
+ finished: false,
7322
+ promptContext
7323
+ });
7324
+ hasReasoning = false;
7325
+ }
7326
+ } else if (chunk.type === "text-end") {
7327
+ responseMessageIndex++;
7328
+ } else if (chunk.type === "text-delta") {
7329
+ if (chunk.text.trim()) {
7330
+ streamingMessageIds.add(responseMessageId);
7331
+ await task.processResponseMessage({
7332
+ id: responseMessageId,
7333
+ action: "response",
7334
+ content: chunk.text,
7335
+ finished: false,
7336
+ promptContext
7337
+ });
7338
+ }
7339
+ } else if (chunk.type === "reasoning-start") {
6839
7340
  streamingMessageIds.add(responseMessageId);
6840
7341
  await task.processResponseMessage({
6841
7342
  id: responseMessageId,
6842
7343
  action: "response",
6843
- content: ANSWER_RESPONSE_START_TAG,
7344
+ content: THINKING_RESPONSE_STAR_TAG,
6844
7345
  finished: false,
6845
7346
  promptContext
6846
7347
  });
6847
- hasReasoning = false;
6848
- }
6849
- } else if (chunk.type === "text-end") {
6850
- responseMessageIndex++;
6851
- } else if (chunk.type === "text-delta") {
6852
- if (chunk.text.trim()) {
7348
+ hasReasoning = true;
7349
+ } else if (chunk.type === "reasoning-delta") {
6853
7350
  streamingMessageIds.add(responseMessageId);
6854
7351
  await task.processResponseMessage({
6855
7352
  id: responseMessageId,
@@ -6858,51 +7355,53 @@ ${fileList}`
6858
7355
  finished: false,
6859
7356
  promptContext
6860
7357
  });
7358
+ } else if (chunk.type === "tool-input-start") {
7359
+ task.addLogMessage("loading", "Preparing tool...", false, promptContext);
7360
+ streamingMessageIds.add(chunk.id);
7361
+ } else if (chunk.type === "tool-call") {
7362
+ task.addLogMessage("loading", "Executing tool...", false, promptContext);
7363
+ streamingMessageIds.add(chunk.toolCallId);
7364
+ } else if (chunk.type === "tool-result") {
7365
+ const [serverName, toolName] = extractServerNameToolName(chunk.toolName);
7366
+ const toolPromptContext = extractPromptContextFromToolResult(chunk.output) ?? promptContext;
7367
+ streamingMessageIds.add(chunk.toolCallId);
7368
+ task.addToolMessage(chunk.toolCallId, serverName, toolName, chunk.input, JSON.stringify(chunk.output), void 0, toolPromptContext);
7369
+ task.addLogMessage("loading", void 0, false, promptContext);
6861
7370
  }
6862
- } else if (chunk.type === "reasoning-start") {
6863
- streamingMessageIds.add(responseMessageId);
6864
- await task.processResponseMessage({
6865
- id: responseMessageId,
6866
- action: "response",
6867
- content: THINKING_RESPONSE_STAR_TAG,
6868
- finished: false,
6869
- promptContext
6870
- });
6871
- hasReasoning = true;
6872
- } else if (chunk.type === "reasoning-delta") {
6873
- streamingMessageIds.add(responseMessageId);
6874
- await task.processResponseMessage({
6875
- id: responseMessageId,
6876
- action: "response",
6877
- content: chunk.text,
6878
- finished: false,
6879
- promptContext
6880
- });
6881
- } else if (chunk.type === "tool-input-start") {
6882
- task.addLogMessage("loading", "Preparing tool...", false, promptContext);
6883
- streamingMessageIds.add(chunk.id);
6884
- } else if (chunk.type === "tool-call") {
6885
- task.addLogMessage("loading", "Executing tool...", false, promptContext);
6886
- streamingMessageIds.add(chunk.toolCallId);
6887
- } else if (chunk.type === "tool-result") {
6888
- const [serverName, toolName] = extractServerNameToolName(chunk.toolName);
6889
- const toolPromptContext = extractPromptContextFromToolResult(chunk.output) ?? promptContext;
6890
- streamingMessageIds.add(chunk.toolCallId);
6891
- task.addToolMessage(chunk.toolCallId, serverName, toolName, chunk.input, JSON.stringify(chunk.output), void 0, toolPromptContext);
6892
- task.addLogMessage("loading", void 0, false, promptContext);
7371
+ }
7372
+ } catch (streamError) {
7373
+ if (effectiveAbortSignal?.aborted) {
7374
+ throw streamError;
7375
+ }
7376
+ if (isNetworkError(streamError) && retryCount < MAX_RETRIES) {
7377
+ logger.warn(`Network error during streaming, retrying (${retryCount + 1}/${MAX_RETRIES})...`, { error: streamError });
7378
+ iterationError = streamError;
7379
+ this.removeUnfinishedStreamingMessages(task, streamingMessageIds);
7380
+ task.addLogMessage("loading", "Network error occured. Retrying...");
7381
+ } else if (isNetworkError(streamError)) {
7382
+ const error = streamError;
7383
+ const underlyingMessage = error.cause instanceof Error ? error.cause.message : error.message;
7384
+ throw new Error(`Network error after ${MAX_RETRIES} retries (check your connection): ${underlyingMessage}`, { cause: streamError });
7385
+ } else {
7386
+ throw streamError;
6893
7387
  }
6894
7388
  }
6895
7389
  }
6896
7390
  if (iterationError) {
6897
- logger.error("Error during prompt:", iterationError);
6898
- if (iterationError instanceof ai.APICallError && iterationError.isRetryable && this.modelManager.isRetryable(resolvedProvider, modelName, iterationError)) {
7391
+ logger.error("Error during iteration:", iterationError);
7392
+ if (isNetworkError(iterationError) && retryCount < MAX_RETRIES) {
7393
+ logger.info(`Network error, retrying (${retryCount + 1}/${MAX_RETRIES})...`);
7394
+ this.removeUnfinishedStreamingMessages(task, streamingMessageIds);
7395
+ retryCount++;
7396
+ continue;
7397
+ } else if (iterationError instanceof ai.APICallError && iterationError.isRetryable && this.modelManager.isRetryable(resolvedProvider, modelName, iterationError)) {
6899
7398
  continue;
6900
7399
  } else {
6901
7400
  break;
6902
7401
  }
6903
7402
  }
6904
- const newMessages = this.filterResultMessages(responseMessages);
6905
- messages.push(...responseMessages);
7403
+ const newMessages = this.filterResultMessages(currentStepMessages);
7404
+ messages.push(...currentStepMessages);
6906
7405
  resultMessages.push(...newMessages);
6907
7406
  if (includeInContext) {
6908
7407
  try {
@@ -6928,7 +7427,7 @@ ${fileList}`
6928
7427
  retryCount++;
6929
7428
  continue;
6930
7429
  }
6931
- const lastMessage = responseMessages[responseMessages.length - 1];
7430
+ const lastMessage = currentStepMessages[currentStepMessages.length - 1];
6932
7431
  if (finishReason === "stop" && lastMessage?.role === "tool") {
6933
7432
  logger.debug('Finish reason is "stop" but last message is a tool call. Retrying...');
6934
7433
  retryCount++;
@@ -6990,6 +7489,9 @@ ${fileList}`
6990
7489
  } else {
6991
7490
  task.addLogMessage("error", `${error instanceof Error ? error.message : String(error)}`, false, promptContext);
6992
7491
  }
7492
+ await task.updateTask({
7493
+ state: DefaultTaskState.Interrupted
7494
+ });
6993
7495
  } finally {
6994
7496
  if (controllerId) {
6995
7497
  this.abortControllers.delete(controllerId);
@@ -7286,11 +7788,9 @@ ${fileList}`
7286
7788
  optimizedMessages.unshift({ role: "system", content: systemPrompt });
7287
7789
  const chatMessages = optimizedMessages.map((msg) => ({
7288
7790
  role: msg.role === "tool" ? "user" : msg.role,
7289
- // Map 'tool' role to user message as gpt-tokenizer does not support tool messages
7290
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
7291
- // Handle potential non-string content if necessary
7791
+ content: msg.content
7292
7792
  }));
7293
- return gpt4o.countTokens(chatMessages);
7793
+ return estimateMessageTokens(chatMessages);
7294
7794
  } catch (error) {
7295
7795
  logger.error(`Error counting tokens: ${error}`);
7296
7796
  return 0;
@@ -7384,32 +7884,80 @@ ${fileList}`
7384
7884
  ...message,
7385
7885
  id: currentResponseId,
7386
7886
  usageReport,
7387
- promptContext
7887
+ promptContext,
7888
+ timestamp: Date.now()
7388
7889
  });
7389
7890
  } else if (message.role === "tool") {
7390
7891
  messages.push({
7391
7892
  ...message,
7392
7893
  // @ts-expect-error the id is there
7393
7894
  id: message.id || uuid.v4(),
7394
- promptContext
7895
+ promptContext,
7896
+ timestamp: Date.now()
7395
7897
  });
7396
7898
  }
7397
7899
  });
7398
7900
  return messages;
7399
7901
  }
7400
7902
  async compactMessagesIfNeeded(task, profile, provider, model, userRequestMessage, contextMessages, contextFiles, messages, resultMessages, promptContext, abortSignal) {
7401
- const contextCompactingThreshold = task.task.contextCompactingThreshold ?? this.store.getProjectSettings(task.getProjectDir())?.contextCompactingThreshold ?? 0;
7402
- const contextCompactionType = profile.isSubagent ? ContextCompactionType.Compact : this.store.getSettings().taskSettings.contextCompactionType ?? ContextCompactionType.Compact;
7403
- const lastAssistantMessage = [...resultMessages].reverse().find((m) => m.role === "assistant");
7404
- const usageReport = lastAssistantMessage?.usageReport;
7405
- const maxTokens = this.modelManager.getModelSettings(provider.provider.name, model)?.maxInputTokens;
7406
- if (contextCompactingThreshold === 0 || !usageReport || !maxTokens) {
7407
- return true;
7408
- }
7409
- const totalTokens = usageReport.sentTokens + usageReport.receivedTokens + (usageReport.cacheReadTokens ?? 0);
7410
- if (maxTokens && totalTokens > maxTokens * contextCompactingThreshold / 100) {
7903
+ const taskSettings = this.store.getSettings().taskSettings;
7904
+ const thresholdConfig = {
7905
+ percentage: profile.autoCompactThresholdPercentage ?? taskSettings.contextCompactingThreshold.percentage,
7906
+ tokens: profile.autoCompactThresholdTokens ?? taskSettings.contextCompactingThreshold.tokens
7907
+ };
7908
+ const taskTokensOverride = task.task.contextCompactingThresholdTokens;
7909
+ let contextCompactionType = profile.autoCompactionType ?? taskSettings.contextCompactionType ?? ContextCompactionType.Compact;
7910
+ if (profile.isSubagent && contextCompactionType === ContextCompactionType.Handoff) {
7911
+ contextCompactionType = ContextCompactionType.Compact;
7912
+ }
7913
+ logger.debug("Compaction threshold", {
7914
+ thresholdConfig,
7915
+ profile: {
7916
+ autoCompactThresholdPercentage: profile.autoCompactThresholdPercentage,
7917
+ autoCompactThresholdTokens: profile.autoCompactThresholdTokens
7918
+ },
7919
+ taskSettings: {
7920
+ contextCompactingThreshold: taskSettings.contextCompactingThreshold
7921
+ },
7922
+ taskTokensOverride,
7923
+ contextCompactionType
7924
+ });
7925
+ const lastAssistantMessage = [...resultMessages].reverse().find((m) => m.role === "assistant");
7926
+ const usageReport = lastAssistantMessage?.usageReport;
7927
+ const maxTokens = this.modelManager.getModelSettings(provider.id, model)?.maxInputTokens;
7928
+ if (!usageReport || !maxTokens) {
7929
+ logger.debug("No usageReport or maxTokens", {
7930
+ usageReport,
7931
+ maxTokens
7932
+ });
7933
+ return true;
7934
+ }
7935
+ const totalTokens = usageReport.sentTokens + usageReport.receivedTokens + (usageReport.cacheReadTokens ?? 0);
7936
+ let effectiveThreshold;
7937
+ let thresholdDescription;
7938
+ if (taskTokensOverride !== void 0) {
7939
+ if (taskTokensOverride === 0) {
7940
+ return true;
7941
+ }
7942
+ effectiveThreshold = taskTokensOverride;
7943
+ thresholdDescription = `task override: ${taskTokensOverride}`;
7944
+ } else {
7945
+ if (thresholdConfig.percentage === 0) {
7946
+ return true;
7947
+ }
7948
+ const percentageThreshold = maxTokens * thresholdConfig.percentage / 100;
7949
+ const tokenThreshold = thresholdConfig.tokens;
7950
+ effectiveThreshold = Math.min(percentageThreshold, tokenThreshold);
7951
+ thresholdDescription = `percentage: ${percentageThreshold}, tokens: ${tokenThreshold}`;
7952
+ }
7953
+ logger.debug("Checking total tokens vs effective threshold", {
7954
+ totalTokens,
7955
+ effectiveThreshold,
7956
+ thresholdDescription
7957
+ });
7958
+ if (totalTokens > effectiveThreshold) {
7411
7959
  logger.info(
7412
- `Token usage ${totalTokens} exceeds threshold of ${contextCompactingThreshold}%. Compacting conversation with type: ${contextCompactionType}.`
7960
+ `Token usage ${totalTokens} exceeds effective threshold of ${effectiveThreshold} (${thresholdDescription}). Compacting conversation with type: ${contextCompactionType}.`
7413
7961
  );
7414
7962
  if (contextCompactionType === ContextCompactionType.Compact) {
7415
7963
  await task.compactConversation(
@@ -7425,15 +7973,24 @@ ${fileList}`
7425
7973
  messages.length = 0;
7426
7974
  resultMessages.length = 0;
7427
7975
  messages.push(...await this.prepareMessages(task, profile, await task.getContextMessages(), contextFiles));
7976
+ const continuationText = `Based on your compacted summary of our previous conversation, please continue our work with my request:
7977
+
7978
+ ${extractTextContent(userRequestMessage.content)}`;
7979
+ const originalImageParts = Array.isArray(userRequestMessage.content) ? userRequestMessage.content.filter((part) => part.type === "image") : [];
7428
7980
  resultMessages.push({
7429
7981
  id: uuid.v4(),
7430
7982
  role: "user",
7431
- content: `Based on your compacted summary of our previous conversation, please continue our work with my request:
7432
-
7433
- ${userRequestMessage.content}`,
7983
+ content: originalImageParts.length > 0 ? [{ type: "text", text: continuationText }, ...originalImageParts] : continuationText,
7434
7984
  promptContext
7435
7985
  });
7436
7986
  messages.push(...resultMessages);
7987
+ } else if (contextCompactionType === ContextCompactionType.Smart) {
7988
+ const compactedMessages = await task.smartCompactConversation([...contextMessages, ...resultMessages], "Previous conversation has been compacted.");
7989
+ contextMessages.length = 0;
7990
+ messages.length = 0;
7991
+ resultMessages.length = 0;
7992
+ contextMessages.push(...compactedMessages);
7993
+ messages.push(...await this.prepareMessages(task, profile, compactedMessages, contextFiles));
7437
7994
  } else if (contextCompactionType === ContextCompactionType.Handoff) {
7438
7995
  await task.handoffConversation(
7439
7996
  "agent",
@@ -7449,6 +8006,52 @@ ${userRequestMessage.content}`,
7449
8006
  return true;
7450
8007
  }
7451
8008
  }
8009
+ let isDockerCache;
8010
+ const isDocker = () => {
8011
+ if (isDockerCache !== void 0) {
8012
+ return isDockerCache;
8013
+ }
8014
+ try {
8015
+ isDockerCache = fs__namespace.existsSync("/.dockerenv") || fs__namespace.existsSync("/run/.containerenv");
8016
+ } catch {
8017
+ isDockerCache = false;
8018
+ }
8019
+ return isDockerCache;
8020
+ };
8021
+ const isNetworkDrive = (drivePath) => {
8022
+ try {
8023
+ const driveLetter = drivePath.charAt(0).toUpperCase();
8024
+ const output = child_process.execSync(`net use ${driveLetter}:`, {
8025
+ encoding: "utf8",
8026
+ timeout: 5e3,
8027
+ stdio: ["pipe", "pipe", "pipe"]
8028
+ });
8029
+ return output.includes("\\\\") || output.includes("//");
8030
+ } catch {
8031
+ return false;
8032
+ }
8033
+ };
8034
+ const shouldUsePolling = (watchPath, mode = FileWatchMode.Auto) => {
8035
+ if (mode === FileWatchMode.Polling) {
8036
+ return true;
8037
+ }
8038
+ if (mode === FileWatchMode.Native) {
8039
+ return false;
8040
+ }
8041
+ if (isDocker()) {
8042
+ logger.debug(`[FileWatch] Docker environment detected, using polling for ${watchPath}`);
8043
+ return true;
8044
+ }
8045
+ if (process.platform === "win32") {
8046
+ const isUncPath = watchPath.startsWith("\\\\") || watchPath.startsWith("//");
8047
+ const isNetworkLetterDrive = /^[A-Za-z]:/.test(watchPath) && isNetworkDrive(watchPath);
8048
+ if (isUncPath || isNetworkLetterDrive) {
8049
+ logger.debug(`[FileWatch] Network path detected (${watchPath}), using polling`);
8050
+ return true;
8051
+ }
8052
+ }
8053
+ return false;
8054
+ };
7452
8055
  const getGlobalAgentsDir = () => path__namespace.join(os.homedir(), AIDER_DESK_AGENTS_DIR);
7453
8056
  const getProjectAgentsDir = (projectDir) => path__namespace.join(projectDir, AIDER_DESK_AGENTS_DIR);
7454
8057
  const getAgentsDirForProfile = (profile) => profile.projectDir ? getProjectAgentsDir(profile.projectDir) : getGlobalAgentsDir();
@@ -7481,9 +8084,10 @@ const getAllRuleFilesForProfile = async (profile, dirName) => {
7481
8084
  return ruleFiles;
7482
8085
  };
7483
8086
  class AgentProfileManager {
7484
- constructor(eventManager, extensionManager) {
8087
+ constructor(eventManager, extensionManager, store) {
7485
8088
  this.eventManager = eventManager;
7486
8089
  this.extensionManager = extensionManager;
8090
+ this.store = store;
7487
8091
  this.extensionsChangeListener = () => {
7488
8092
  this.sendAgentProfilesUpdated();
7489
8093
  };
@@ -7598,12 +8202,12 @@ class AgentProfileManager {
7598
8202
  }
7599
8203
  const watcher = chokidar.watch(agentsDir, {
7600
8204
  persistent: true,
7601
- usePolling: true,
8205
+ usePolling: shouldUsePolling(agentsDir, this.store.getSettings().fileWatchMode),
7602
8206
  ignoreInitial: true
7603
8207
  });
7604
8208
  const rulesWatcher = chokidar.watch(path__namespace.join(agentsDir, "*", AIDER_DESK_RULES_DIR), {
7605
8209
  persistent: true,
7606
- usePolling: true,
8210
+ usePolling: shouldUsePolling(agentsDir, this.store.getSettings().fileWatchMode),
7607
8211
  ignoreInitial: true
7608
8212
  });
7609
8213
  const reloadFunction = () => this.debounceReloadProfiles(agentsDir);
@@ -7874,6 +8478,37 @@ class AgentProfileManager {
7874
8478
  }
7875
8479
  return this.profiles.get(profileId)?.agentProfile;
7876
8480
  }
8481
+ resolveAgentProfile(id) {
8482
+ const lowerId = id.toLowerCase();
8483
+ const directMatch = this.getProfile(id);
8484
+ if (directMatch) {
8485
+ return directMatch;
8486
+ }
8487
+ const allProfiles = this.getAllProfiles();
8488
+ for (const profile of allProfiles) {
8489
+ if (profile.name.toLowerCase() === lowerId) {
8490
+ return profile;
8491
+ }
8492
+ }
8493
+ for (const context of this.profiles.values()) {
8494
+ if (context.dirName.toLowerCase() === lowerId) {
8495
+ return context.agentProfile;
8496
+ }
8497
+ }
8498
+ const slugifiedId = deriveDirName(id, /* @__PURE__ */ new Set()).toLowerCase();
8499
+ for (const context of this.profiles.values()) {
8500
+ if (context.dirName.toLowerCase() === slugifiedId) {
8501
+ return context.agentProfile;
8502
+ }
8503
+ }
8504
+ for (const profile of allProfiles) {
8505
+ const derivedName = deriveDirName(profile.name, /* @__PURE__ */ new Set()).toLowerCase();
8506
+ if (derivedName === slugifiedId) {
8507
+ return profile;
8508
+ }
8509
+ }
8510
+ return null;
8511
+ }
7877
8512
  getOrderedProfiles(profileContexts) {
7878
8513
  return profileContexts.sort((a, b) => {
7879
8514
  const aIsProject = !!a.agentProfile.projectDir;
@@ -7970,6 +8605,22 @@ class AgentProfileManager {
7970
8605
  this.directoryWatchers.clear();
7971
8606
  this.profiles.clear();
7972
8607
  }
8608
+ async settingsChanged(oldSettings, newSettings) {
8609
+ if (oldSettings.fileWatchMode === newSettings.fileWatchMode) {
8610
+ return;
8611
+ }
8612
+ const watchedDirs = Array.from(this.directoryWatchers.keys());
8613
+ for (const watcher of this.directoryWatchers.values()) {
8614
+ await watcher.close();
8615
+ }
8616
+ this.directoryWatchers.clear();
8617
+ for (const dir of watchedDirs) {
8618
+ if (dir.endsWith("-rules")) {
8619
+ continue;
8620
+ }
8621
+ await this.setupWatcherForDirectory(dir);
8622
+ }
8623
+ }
7973
8624
  getDefaultAgentProfileId() {
7974
8625
  for (const defaultProfile of DEFAULT_AGENT_PROFILES) {
7975
8626
  if (this.profiles.has(defaultProfile.id)) {
@@ -8213,7 +8864,8 @@ const RunPromptSchema = zod.z.object({
8213
8864
  projectDir: zod.z.string().min(1, "Project directory is required"),
8214
8865
  taskId: zod.z.string().min(1, "Task ID is required"),
8215
8866
  prompt: zod.z.string().min(1, "Prompt is required"),
8216
- mode: zod.z.string().optional()
8867
+ mode: zod.z.string().optional(),
8868
+ images: zod.z.array(zod.z.string()).optional()
8217
8869
  });
8218
8870
  const SavePromptSchema = zod.z.object({
8219
8871
  projectDir: zod.z.string().min(1, "Project directory is required"),
@@ -8233,8 +8885,8 @@ class PromptApi extends BaseApi {
8233
8885
  if (!parsed) {
8234
8886
  return;
8235
8887
  }
8236
- const { projectDir, taskId, prompt, mode } = parsed;
8237
- const responses = await this.eventsHandler.runPrompt(projectDir, taskId, prompt, mode);
8888
+ const { projectDir, taskId, prompt, mode, images } = parsed;
8889
+ const responses = await this.eventsHandler.runPrompt(projectDir, taskId, prompt, mode, images);
8238
8890
  res.status(200).json(responses);
8239
8891
  })
8240
8892
  );
@@ -8422,6 +9074,10 @@ const ClearContextSchema = zod.z.object({
8422
9074
  projectDir: zod.z.string().min(1, "Project directory is required"),
8423
9075
  taskId: zod.z.string().min(1, "Task id is required")
8424
9076
  });
9077
+ const UndoContextChangeSchema = zod.z.object({
9078
+ projectDir: zod.z.string().min(1, "Project directory is required"),
9079
+ taskId: zod.z.string().min(1, "Task id is required")
9080
+ });
8425
9081
  const AnswerQuestionSchema = zod.z.object({
8426
9082
  projectDir: zod.z.string().min(1, "Project directory is required"),
8427
9083
  taskId: zod.z.string().min(1, "Task id is required"),
@@ -8500,7 +9156,8 @@ const RedoUserPromptSchema = zod.z.object({
8500
9156
  taskId: zod.z.string().min(1, "Task id is required"),
8501
9157
  messageId: zod.z.string().min(1, "Message id is required"),
8502
9158
  mode: zod.z.string().min(1, "Mode is required"),
8503
- updatedPrompt: zod.z.string().optional()
9159
+ updatedPrompt: zod.z.string().optional(),
9160
+ updatedImages: zod.z.array(zod.z.string()).optional()
8504
9161
  });
8505
9162
  const ResumeTaskSchema = zod.z.object({
8506
9163
  projectDir: zod.z.string().min(1, "Project directory is required"),
@@ -8546,7 +9203,8 @@ const InitProjectRulesFileSchema = zod.z.object({
8546
9203
  const CreateNewTaskSchema = zod.z.object({
8547
9204
  projectDir: zod.z.string().min(1, "Project directory is required"),
8548
9205
  parentId: zod.z.string().nullable().optional(),
8549
- name: zod.z.string().optional()
9206
+ name: zod.z.string().optional(),
9207
+ activate: zod.z.boolean().optional()
8550
9208
  });
8551
9209
  const UpdateTaskSchema = zod.z.object({
8552
9210
  projectDir: zod.z.string().min(1, "Project directory is required"),
@@ -8607,6 +9265,10 @@ const HandoffConversationSchema = zod.z.object({
8607
9265
  taskId: zod.z.string().min(1, "Task id is required"),
8608
9266
  focus: zod.z.string().optional()
8609
9267
  });
9268
+ const SmartCompactConversationSchema = zod.z.object({
9269
+ projectDir: zod.z.string().min(1, "Project directory is required"),
9270
+ taskId: zod.z.string().min(1, "Task id is required")
9271
+ });
8610
9272
  const ChangeRequestItemSchema = zod.z.object({
8611
9273
  filename: zod.z.string().min(1, "Filename is required"),
8612
9274
  lineNumber: zod.z.number().int().min(1, "Line number is required"),
@@ -8727,8 +9389,8 @@ class ProjectApi extends BaseApi {
8727
9389
  if (!parsed) {
8728
9390
  return;
8729
9391
  }
8730
- const { projectDir, taskId, messageId, mode, updatedPrompt } = parsed;
8731
- await this.eventsHandler.redoUserPrompt(projectDir, taskId, messageId, mode, updatedPrompt);
9392
+ const { projectDir, taskId, messageId, mode, updatedPrompt, updatedImages } = parsed;
9393
+ await this.eventsHandler.redoUserPrompt(projectDir, taskId, messageId, mode, updatedPrompt, updatedImages);
8732
9394
  res.status(200).json({ message: "Redo user prompt initiated" });
8733
9395
  })
8734
9396
  );
@@ -8840,8 +9502,8 @@ class ProjectApi extends BaseApi {
8840
9502
  if (!parsed) {
8841
9503
  return;
8842
9504
  }
8843
- const { projectDir, parentId, name } = parsed;
8844
- const params = { parentId, name };
9505
+ const { projectDir, parentId, name, activate } = parsed;
9506
+ const params = { parentId, name, activate };
8845
9507
  const task = await this.eventsHandler.createNewTask(projectDir, params);
8846
9508
  res.status(200).json(task);
8847
9509
  })
@@ -9013,6 +9675,30 @@ class ProjectApi extends BaseApi {
9013
9675
  res.status(200).json({ message: "Conversation compacted" });
9014
9676
  })
9015
9677
  );
9678
+ router.post(
9679
+ "/project/smart-compact-conversation",
9680
+ this.handleRequest(async (req, res) => {
9681
+ const parsed = this.validateRequest(SmartCompactConversationSchema, req.body, res);
9682
+ if (!parsed) {
9683
+ return;
9684
+ }
9685
+ const { projectDir, taskId } = parsed;
9686
+ await this.eventsHandler.smartCompactConversation(projectDir, taskId);
9687
+ res.status(200).json({ message: "Conversation smart-compacted" });
9688
+ })
9689
+ );
9690
+ router.post(
9691
+ "/project/undo-context-change",
9692
+ this.handleRequest(async (req, res) => {
9693
+ const parsed = this.validateRequest(UndoContextChangeSchema, req.body, res);
9694
+ if (!parsed) {
9695
+ return;
9696
+ }
9697
+ const { projectDir, taskId } = parsed;
9698
+ const undone = await this.eventsHandler.undoContextChange(projectDir, taskId);
9699
+ res.status(200).json({ undone });
9700
+ })
9701
+ );
9016
9702
  router.post(
9017
9703
  "/project/handoff-conversation",
9018
9704
  this.handleRequest(async (req, res) => {
@@ -10883,10 +11569,11 @@ class ShellCommandError extends Error {
10883
11569
  }
10884
11570
  const execAsync$1 = util.promisify(child_process.exec);
10885
11571
  class CustomCommandManager {
10886
- constructor(project, eventManager, extensionManager) {
11572
+ constructor(project, eventManager, extensionManager, store) {
10887
11573
  this.project = project;
10888
11574
  this.eventManager = eventManager;
10889
11575
  this.extensionManager = extensionManager;
11576
+ this.store = store;
10890
11577
  }
10891
11578
  commands = /* @__PURE__ */ new Map();
10892
11579
  watchers = [];
@@ -10926,7 +11613,7 @@ class CustomCommandManager {
10926
11613
  }
10927
11614
  const watcher = chokidar.watch(commandsDir, {
10928
11615
  persistent: true,
10929
- usePolling: true,
11616
+ usePolling: shouldUsePolling(commandsDir, this.store.getSettings().fileWatchMode),
10930
11617
  ignoreInitial: true
10931
11618
  });
10932
11619
  watcher.on("add", async () => {
@@ -10984,14 +11671,16 @@ class CustomCommandManager {
10984
11671
  const args = Array.isArray(parsed.arguments) ? parsed.arguments : [];
10985
11672
  const template = parsed.__content?.trim() || "";
10986
11673
  const includeContext = typeof parsed.includeContext === "boolean" ? parsed.includeContext : true;
10987
- const autoApprove = typeof parsed.autoApprove === "boolean" ? parsed.autoApprove : void 0;
11674
+ const autonomyMode = typeof parsed.autonomyMode === "string" && ["manual", "guided", "autonomous"].includes(parsed.autonomyMode) ? parsed.autonomyMode : void 0;
11675
+ const skills = typeof parsed.skills === "string" && parsed.skills.trim() ? parsed.skills.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
10988
11676
  commands.set(name, {
10989
11677
  name,
10990
11678
  description: parsed.description || "Not specified",
10991
11679
  arguments: args,
10992
11680
  template,
10993
11681
  includeContext,
10994
- autoApprove
11682
+ autonomyMode,
11683
+ skills
10995
11684
  });
10996
11685
  } catch (err) {
10997
11686
  logger.error(`Failed to parse command file ${filePath}: ${err}`);
@@ -11123,6 +11812,14 @@ class CustomCommandManager {
11123
11812
  this.watchers.forEach((watcher) => watcher.close());
11124
11813
  this.watchers = [];
11125
11814
  }
11815
+ async settingsChanged(oldSettings, newSettings) {
11816
+ if (oldSettings.fileWatchMode === newSettings.fileWatchMode) {
11817
+ return;
11818
+ }
11819
+ this.watchers.forEach((watcher) => watcher.close());
11820
+ this.watchers = [];
11821
+ await this.setupFileWatchers();
11822
+ }
11126
11823
  }
11127
11824
  const migrateContextMessage = (message) => {
11128
11825
  if (Array.isArray(message.content)) {
@@ -11225,10 +11922,20 @@ class ContextManager {
11225
11922
  }
11226
11923
  messages;
11227
11924
  files;
11925
+ undoSnapshot = null;
11228
11926
  loadPromise = null;
11229
11927
  loaded = false;
11230
11928
  autosaveEnabled = false;
11231
11929
  storagePath;
11930
+ hasUndoSnapshot() {
11931
+ return this.undoSnapshot !== null;
11932
+ }
11933
+ undoContextChange() {
11934
+ const snapshot = this.undoSnapshot;
11935
+ this.undoSnapshot = null;
11936
+ this.task.sendContextInfoUpdated();
11937
+ return snapshot;
11938
+ }
11232
11939
  enableAutosave() {
11233
11940
  logger.debug("Enabling autosave for task", { taskId: this.taskId });
11234
11941
  this.autosaveEnabled = true;
@@ -11247,7 +11954,8 @@ class ContextManager {
11247
11954
  id: uuid.v4(),
11248
11955
  role: roleOrMessage,
11249
11956
  content: content || "",
11250
- usageReport
11957
+ usageReport,
11958
+ timestamp: Date.now()
11251
11959
  };
11252
11960
  } else {
11253
11961
  message = roleOrMessage;
@@ -11260,6 +11968,10 @@ class ContextManager {
11260
11968
  }
11261
11969
  this.messages.push(message);
11262
11970
  logger.debug(`Task ${this.taskId}: Added ${message.role} message. Total messages: ${this.messages.length}`);
11971
+ if (this.undoSnapshot !== null) {
11972
+ this.undoSnapshot = null;
11973
+ this.task.sendContextInfoUpdated();
11974
+ }
11263
11975
  this.autosave();
11264
11976
  }
11265
11977
  async isFileIgnored(contextFile) {
@@ -11277,7 +11989,14 @@ class ContextManager {
11277
11989
  });
11278
11990
  return [];
11279
11991
  }
11280
- if (this.isContextFileAlreadyAdded(absolutePath)) {
11992
+ const existingContextFile = this.findExistingContextFile(absolutePath);
11993
+ if (existingContextFile) {
11994
+ const readOnly = contextFile.readOnly ?? false;
11995
+ if (existingContextFile.readOnly !== readOnly) {
11996
+ existingContextFile.readOnly = readOnly;
11997
+ this.autosave();
11998
+ return [existingContextFile];
11999
+ }
11281
12000
  return [];
11282
12001
  }
11283
12002
  const isDir = await isDirectory(absolutePath);
@@ -11336,11 +12055,11 @@ class ContextManager {
11336
12055
  return this.task.resolveContextFilePath(relativePath);
11337
12056
  }
11338
12057
  /**
11339
- * Checks whether a resolved absolute path matches any already-added context file.
12058
+ * Finds an already-added context file matching the given resolved absolute path, if any.
11340
12059
  * Accounts for files that may have been resolved from either taskDir or projectDir.
11341
12060
  */
11342
- isContextFileAlreadyAdded(absolutePath) {
11343
- return this.files.some((file) => {
12061
+ findExistingContextFile(absolutePath) {
12062
+ return this.files.find((file) => {
11344
12063
  const taskDirResolved = path.resolve(this.task.getTaskDir(), file.path);
11345
12064
  const projectDirResolved = path.resolve(this.task.getProjectDir(), file.path);
11346
12065
  return taskDirResolved === absolutePath || projectDirResolved === absolutePath;
@@ -11394,6 +12113,10 @@ class ContextManager {
11394
12113
  messages: contextMessages.length,
11395
12114
  save
11396
12115
  });
12116
+ if (this.messages.length > 0 && this.messages !== contextMessages) {
12117
+ this.undoSnapshot = [...this.messages];
12118
+ this.task.sendContextInfoUpdated();
12119
+ }
11397
12120
  this.messages = contextMessages;
11398
12121
  if (save) {
11399
12122
  this.autosave();
@@ -11423,8 +12146,12 @@ class ContextManager {
11423
12146
  this.autosave();
11424
12147
  }
11425
12148
  }
11426
- clearMessages(save = true) {
12149
+ clearMessages(save = true, createSnapshot = true) {
11427
12150
  logger.debug("Clearing task messages", { taskId: this.taskId });
12151
+ if (createSnapshot && this.messages.length > 0) {
12152
+ this.undoSnapshot = [...this.messages];
12153
+ this.task.sendContextInfoUpdated();
12154
+ }
11428
12155
  this.messages = [];
11429
12156
  if (save) {
11430
12157
  this.autosave();
@@ -11721,6 +12448,34 @@ ${JSON.stringify(part.output.value)}`
11721
12448
  throw error;
11722
12449
  }
11723
12450
  }
12451
+ /**
12452
+ * Creates a backup of the current context.json file before a destructive operation
12453
+ * (e.g., smart compaction). Backups are named context.backup.001.json, context.backup.002.json, etc.
12454
+ * For debugging purposes only.
12455
+ */
12456
+ async backupContext() {
12457
+ try {
12458
+ const dir = path.dirname(this.storagePath);
12459
+ const files = await fs$1.promises.readdir(dir);
12460
+ const backupPattern = /^context\.backup\.(\d+)\.json$/;
12461
+ let maxNumber = 0;
12462
+ for (const file of files) {
12463
+ const match = backupPattern.exec(file);
12464
+ if (match) {
12465
+ const num = parseInt(match[1], 10);
12466
+ if (num > maxNumber) {
12467
+ maxNumber = num;
12468
+ }
12469
+ }
12470
+ }
12471
+ const nextNumber = String(maxNumber + 1).padStart(3, "0");
12472
+ const backupPath = path.join(dir, `context.backup.${nextNumber}.json`);
12473
+ await fs$1.promises.copyFile(this.storagePath, backupPath);
12474
+ logger.debug(`Task context backed up to ${backupPath}`, { taskId: this.taskId });
12475
+ } catch (error) {
12476
+ logger.error("Failed to backup task context:", { error, taskId: this.taskId });
12477
+ }
12478
+ }
11724
12479
  async cleanupContext() {
11725
12480
  const existingFiles = [];
11726
12481
  for (const file of this.files) {
@@ -11794,8 +12549,8 @@ ${JSON.stringify(part.output.value)}`
11794
12549
  this.loaded = true;
11795
12550
  await this.cleanupContext();
11796
12551
  }
11797
- async loadMessages(messages) {
11798
- await this.task.clearContext(false, false);
12552
+ async loadMessages(messages, updateTaskState = true) {
12553
+ await this.task.clearContext(false, false, updateTaskState, false);
11799
12554
  this.messages = messages;
11800
12555
  const messagesData = this.getContextMessagesData(messages);
11801
12556
  for (const messageData of messagesData) {
@@ -11866,7 +12621,8 @@ ${JSON.stringify(part.output.value)}`
11866
12621
  commitMessage: response.commitMessage,
11867
12622
  diff: response.diff,
11868
12623
  usageReport: response.usageReport,
11869
- promptContext: message.promptContext
12624
+ promptContext: message.promptContext,
12625
+ timestamp: message.timestamp
11870
12626
  };
11871
12627
  messagesData.push(responseCompletedData);
11872
12628
  });
@@ -11905,7 +12661,8 @@ ${JSON.stringify(part.output.value)}`
11905
12661
  baseDir: this.task.getProjectDir(),
11906
12662
  taskId: this.taskId,
11907
12663
  usageReport: subMessage.usageReport,
11908
- promptContext: subMessage.promptContext
12664
+ promptContext: subMessage.promptContext,
12665
+ timestamp: subMessage.timestamp
11909
12666
  };
11910
12667
  messagesData.push(responseCompletedData);
11911
12668
  }
@@ -11921,7 +12678,8 @@ ${JSON.stringify(part.output.value)}`
11921
12678
  toolName: subToolName,
11922
12679
  args: subPart.input,
11923
12680
  usageReport: void 0,
11924
- promptContext: subMessage.promptContext
12681
+ promptContext: subMessage.promptContext,
12682
+ timestamp: subMessage.timestamp
11925
12683
  };
11926
12684
  messagesData.push(toolData);
11927
12685
  }
@@ -11935,7 +12693,8 @@ ${JSON.stringify(part.output.value)}`
11935
12693
  baseDir: this.task.getProjectDir(),
11936
12694
  taskId: this.taskId,
11937
12695
  usageReport: subMessage.usageReport,
11938
- promptContext: subMessage.promptContext
12696
+ promptContext: subMessage.promptContext,
12697
+ timestamp: subMessage.timestamp
11939
12698
  };
11940
12699
  messagesData.push(responseCompletedData);
11941
12700
  }
@@ -11978,7 +12737,8 @@ ${JSON.stringify(part.output.value)}`
11978
12737
  commitMessage: message.commitMessage,
11979
12738
  diff: message.diff,
11980
12739
  usageReport: message.usageReport,
11981
- promptContext: message.promptContext
12740
+ promptContext: message.promptContext,
12741
+ timestamp: message.timestamp
11982
12742
  };
11983
12743
  messagesData.push(responseCompletedData);
11984
12744
  reasoning = "";
@@ -12007,7 +12767,8 @@ ${JSON.stringify(part.output.value)}`
12007
12767
  toolName,
12008
12768
  args: toolCall.input,
12009
12769
  usageReport: message.usageReport,
12010
- promptContext: message.promptContext
12770
+ promptContext: message.promptContext,
12771
+ timestamp: message.timestamp
12011
12772
  };
12012
12773
  messagesData.push(toolData);
12013
12774
  } else if (part.type === "tool-result") {
@@ -12041,19 +12802,27 @@ ${JSON.stringify(part.output.value)}`
12041
12802
  taskId: this.taskId,
12042
12803
  reflectedMessage: message.reflectedMessage,
12043
12804
  usageReport: message.usageReport,
12044
- promptContext: message.promptContext
12805
+ promptContext: message.promptContext,
12806
+ timestamp: message.timestamp
12045
12807
  };
12046
12808
  messagesData.push(responseCompletedData);
12047
12809
  }
12048
12810
  } else if (message.role === "user") {
12049
12811
  const content = extractTextContent(message.content);
12812
+ const images = Array.isArray(message.content) ? message.content.filter((part) => part.type === "image").map((part) => {
12813
+ const data = part.image;
12814
+ const mediaType = part.mediaType || "image/png";
12815
+ return data.startsWith("data:") ? data : `data:${mediaType};base64,${data}`;
12816
+ }) : void 0;
12050
12817
  const userMessageData = {
12051
12818
  type: "user",
12052
12819
  id: message.id || uuid.v4(),
12053
12820
  baseDir: this.task.getProjectDir(),
12054
12821
  taskId: this.taskId,
12055
12822
  content,
12056
- promptContext: message.promptContext
12823
+ images: images && images.length > 0 ? images : void 0,
12824
+ promptContext: message.promptContext,
12825
+ timestamp: message.timestamp
12057
12826
  };
12058
12827
  messagesData.push(userMessageData);
12059
12828
  } else if (message.role === "tool" && Array.isArray(message.content)) {
@@ -12811,7 +13580,16 @@ class SkillManager {
12811
13580
  loadSkillsFromDir(projectSkillsDir, "project"),
12812
13581
  loadSkillsFromDir(AIDER_DESK_BUILTIN_SKILLS_DIR, "builtin")
12813
13582
  ]);
12814
- return [...projectSkills, ...globalSkills, ...builtinSkills, ...extensionSkills];
13583
+ const allSkills = [...extensionSkills, ...projectSkills, ...globalSkills, ...builtinSkills];
13584
+ const seen = /* @__PURE__ */ new Set();
13585
+ const deduped = [];
13586
+ for (const skill of allSkills) {
13587
+ if (!seen.has(skill.name)) {
13588
+ seen.add(skill.name);
13589
+ deduped.push(skill);
13590
+ }
13591
+ }
13592
+ return deduped;
12815
13593
  }
12816
13594
  async getSkills(contextMessages) {
12817
13595
  const skills = await this.loadAllSkills();
@@ -12960,7 +13738,13 @@ class WorktreeManager {
12960
13738
  await execWithShellPath("git add -A", { cwd: projectPath });
12961
13739
  } catch {
12962
13740
  }
12963
- await execWithShellPath('git commit -m "Initial commit" --allow-empty', { cwd: projectPath });
13741
+ try {
13742
+ await execWithShellPath('git commit -m "Initial commit" --allow-empty', { cwd: projectPath });
13743
+ } catch {
13744
+ await execWithShellPath('git -c user.name="AiderDesk" -c user.email="aiderdesk@aiderdesk" commit -m "Initial commit" --allow-empty', {
13745
+ cwd: projectPath
13746
+ });
13747
+ }
12964
13748
  }
12965
13749
  let baseCommit;
12966
13750
  let newBranchName;
@@ -14114,6 +14898,11 @@ Git output: ${err.stderr || err.stdout || err.message}`
14114
14898
  logger.warn(`Failed to get files for commit ${commit.hash}:`, commitError);
14115
14899
  }
14116
14900
  }
14901
+ try {
14902
+ await execWithShellPath("git rev-parse HEAD", { cwd: worktreePath });
14903
+ } catch {
14904
+ return files;
14905
+ }
14117
14906
  try {
14118
14907
  const { stdout: uncommittedNumstat } = await execWithShellPath("git diff --numstat -z HEAD", {
14119
14908
  cwd: worktreePath
@@ -14212,6 +15001,11 @@ Git output: ${err.stderr || err.stdout || err.message}`
14212
15001
  * Non-worktree mode: simple uncommitted changes vs HEAD.
14213
15002
  */
14214
15003
  async getNonWorktreeUpdatedFiles(worktreePath) {
15004
+ try {
15005
+ await execWithShellPath("git rev-parse HEAD", { cwd: worktreePath });
15006
+ } catch {
15007
+ return [];
15008
+ }
14215
15009
  const { stdout } = await execWithShellPath("git diff --numstat -z HEAD", {
14216
15010
  cwd: worktreePath
14217
15011
  });
@@ -14606,11 +15400,668 @@ Git output: ${err.stderr || err.stdout || err.message}`
14606
15400
  }
14607
15401
  }
14608
15402
  }
14609
- async close(projectDir) {
14610
- logger.info("Closing worktree manager");
14611
- await this.pruneDeleted(projectDir);
15403
+ async close(projectDir) {
15404
+ logger.info("Closing worktree manager");
15405
+ await this.pruneDeleted(projectDir);
15406
+ }
15407
+ }
15408
+ const extractFilePath = (args) => {
15409
+ if (typeof args === "object" && args !== null && "filePath" in args) {
15410
+ return args.filePath;
15411
+ }
15412
+ return void 0;
15413
+ };
15414
+ const getToolOutputText = (output) => {
15415
+ if (!output) {
15416
+ return "";
15417
+ }
15418
+ if (output.type === "text") {
15419
+ return output.value;
15420
+ }
15421
+ if (output.type === "error-text") {
15422
+ return output.value;
15423
+ }
15424
+ return JSON.stringify(output.value);
15425
+ };
15426
+ const isErrorResult = (output) => {
15427
+ const text = getToolOutputText(output).toLowerCase();
15428
+ return text.includes("denied by user") || text.startsWith("error:") || text.includes("no files found") || text.includes("no matches found") || text.includes("operation was cancelled") || text.includes("warning:") || text.includes("already updated - no changes were needed") || text.includes("failed to");
15429
+ };
15430
+ const isNoOpResult = (output) => {
15431
+ const text = getToolOutputText(output);
15432
+ return text.includes("Already updated - no changes were needed");
15433
+ };
15434
+ const isPowerTool = (serverName) => {
15435
+ return serverName === POWER_TOOL_GROUP_NAME;
15436
+ };
15437
+ const getToolInfoFromToolMessage = (message) => {
15438
+ return message.content.filter((part) => part.type === "tool-result").map((part) => {
15439
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15440
+ return {
15441
+ messageIndex: -1,
15442
+ toolCallId: part.toolCallId,
15443
+ toolName,
15444
+ serverName,
15445
+ input: void 0,
15446
+ output: part.output
15447
+ };
15448
+ });
15449
+ };
15450
+ const findAssistantToolCall = (messages, toolCallId) => {
15451
+ for (let i = 0; i < messages.length; i++) {
15452
+ const msg = messages[i];
15453
+ if (msg.role !== "assistant") {
15454
+ continue;
15455
+ }
15456
+ if (!Array.isArray(msg.content)) {
15457
+ continue;
15458
+ }
15459
+ for (let j = 0; j < msg.content.length; j++) {
15460
+ const part = msg.content[j];
15461
+ if (part.type === "tool-call" && part.toolCallId === toolCallId) {
15462
+ const [, toolName] = extractServerNameToolName(part.toolName);
15463
+ return {
15464
+ messageId: msg.id,
15465
+ messageIndex: i,
15466
+ partIndex: j,
15467
+ toolCallId: part.toolCallId,
15468
+ toolName,
15469
+ input: part.input
15470
+ };
15471
+ }
15472
+ }
15473
+ }
15474
+ return void 0;
15475
+ };
15476
+ const cloneMessages = (messages) => {
15477
+ return messages.map((msg) => {
15478
+ if (msg.role === "tool") {
15479
+ return {
15480
+ ...msg,
15481
+ content: msg.content.map((part) => ({ ...part }))
15482
+ };
15483
+ }
15484
+ return {
15485
+ ...msg,
15486
+ content: Array.isArray(msg.content) ? msg.content.map((part) => ({ ...part })) : msg.content
15487
+ };
15488
+ });
15489
+ };
15490
+ const removeToolCallFromAssistant = (messages, toolCallId) => {
15491
+ const callInfo = findAssistantToolCall(messages, toolCallId);
15492
+ if (!callInfo) {
15493
+ return;
15494
+ }
15495
+ const assistantMsg = messages[callInfo.messageIndex];
15496
+ if (!Array.isArray(assistantMsg.content)) {
15497
+ return;
15498
+ }
15499
+ assistantMsg.content.splice(callInfo.partIndex, 1);
15500
+ const hasToolCalls = assistantMsg.content.some((p) => p.type === "tool-call");
15501
+ if (!hasToolCalls) {
15502
+ messages.splice(callInfo.messageIndex, 1);
15503
+ }
15504
+ };
15505
+ const removeToolResult = (messages, toolCallId) => {
15506
+ const toolMsgIdx = messages.findIndex((m) => m.role === "tool" && m.content.some((p) => p.type === "tool-result" && p.toolCallId === toolCallId));
15507
+ if (toolMsgIdx === -1) {
15508
+ return;
15509
+ }
15510
+ const toolMsg = messages[toolMsgIdx];
15511
+ const partIdx = toolMsg.content.findIndex((p) => p.type === "tool-result" && p.toolCallId === toolCallId);
15512
+ if (partIdx === -1) {
15513
+ return;
15514
+ }
15515
+ toolMsg.content.splice(partIdx, 1);
15516
+ if (toolMsg.content.length === 0) {
15517
+ messages.splice(toolMsgIdx, 1);
15518
+ }
15519
+ };
15520
+ const getProtectedStartIndex = (messages, protectedMessageCount) => {
15521
+ return Math.max(0, messages.length - protectedMessageCount);
15522
+ };
15523
+ const mergeConsecutiveAssistantMessages = (messages) => {
15524
+ for (let i = messages.length - 2; i >= 0; i--) {
15525
+ const current = messages[i];
15526
+ const next = messages[i + 1];
15527
+ if (current.role !== "assistant" || next.role !== "assistant") {
15528
+ continue;
15529
+ }
15530
+ const currentHasToolCalls = Array.isArray(current.content) && current.content.some((p) => p.type === "tool-call");
15531
+ const nextHasToolCalls = Array.isArray(next.content) && next.content.some((p) => p.type === "tool-call");
15532
+ if (currentHasToolCalls || nextHasToolCalls) {
15533
+ continue;
15534
+ }
15535
+ const currentTextParts = Array.isArray(current.content) ? current.content.filter((p) => p.type === "text") : [];
15536
+ const nextTextParts = Array.isArray(next.content) ? next.content.filter((p) => p.type === "text") : [];
15537
+ const mergedText = [...currentTextParts, ...nextTextParts].map((p) => p.text).filter(Boolean).join("\n\n");
15538
+ current.content = mergedText ? [{ type: "text", text: mergedText }] : [];
15539
+ messages.splice(i + 1, 1);
15540
+ }
15541
+ return messages.filter((msg) => {
15542
+ if (msg.role === "assistant" && Array.isArray(msg.content) && msg.content.length === 0) {
15543
+ return false;
15544
+ }
15545
+ return true;
15546
+ });
15547
+ };
15548
+ const truncateNonPowerToolResults = async (messages, protectedMessageCount = 10) => {
15549
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15550
+ for (let i = 0; i < protectedStart; i++) {
15551
+ const msg = messages[i];
15552
+ if (msg.role !== "tool") {
15553
+ continue;
15554
+ }
15555
+ for (let j = 0; j < msg.content.length; j++) {
15556
+ const part = msg.content[j];
15557
+ if (part.type !== "tool-result") {
15558
+ continue;
15559
+ }
15560
+ const [serverName] = extractServerNameToolName(part.toolName);
15561
+ if (isPowerTool(serverName)) {
15562
+ continue;
15563
+ }
15564
+ if (part.output.type !== "text" && part.output.type !== "error-text") {
15565
+ continue;
15566
+ }
15567
+ const outputText = part.output.value;
15568
+ if (!outputText) {
15569
+ continue;
15570
+ }
15571
+ const truncated = await truncateToolResult(
15572
+ outputText,
15573
+ 20,
15574
+ 2,
15575
+ 2e3,
15576
+ false,
15577
+ "Output truncated due to compaction, re-execute the tool if full output is needed."
15578
+ );
15579
+ if (truncated !== outputText) {
15580
+ part.output = {
15581
+ type: part.output.type,
15582
+ value: truncated
15583
+ };
15584
+ }
15585
+ }
15586
+ }
15587
+ return messages;
15588
+ };
15589
+ const smartCompactMessages = async (messages, protectedMessageCount = 10) => {
15590
+ let result = cloneMessages(messages);
15591
+ result = removeErroredTools(result, protectedMessageCount);
15592
+ result = collapseFileEdits(result, protectedMessageCount);
15593
+ result = removeStaleFileReads(result, protectedMessageCount);
15594
+ result = compactFileReads(result, protectedMessageCount);
15595
+ result = removeObsoleteSearches(result, protectedMessageCount);
15596
+ result = compactSemanticSearches(result, protectedMessageCount);
15597
+ result = deduplicateBash(result, protectedMessageCount);
15598
+ result = redactFetchOutputs(result, protectedMessageCount);
15599
+ result = await truncateNonPowerToolResults(result, protectedMessageCount);
15600
+ result = mergeConsecutiveAssistantMessages(result);
15601
+ return result;
15602
+ };
15603
+ const removeErroredTools = (messages, protectedMessageCount = 10) => {
15604
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15605
+ for (let i = protectedStart - 1; i >= 0; i--) {
15606
+ if (i >= messages.length) {
15607
+ continue;
15608
+ }
15609
+ const msg = messages[i];
15610
+ if (msg.role !== "tool") {
15611
+ continue;
15612
+ }
15613
+ const toolInfos = getToolInfoFromToolMessage(msg);
15614
+ for (const info of toolInfos) {
15615
+ if (!isPowerTool(info.serverName)) {
15616
+ continue;
15617
+ }
15618
+ if (isErrorResult(info.output) || isNoOpResult(info.output)) {
15619
+ removeToolCallFromAssistant(messages, info.toolCallId);
15620
+ removeToolResult(messages, info.toolCallId);
15621
+ }
15622
+ }
15623
+ }
15624
+ return messages;
15625
+ };
15626
+ const collapseFileEdits = (messages, protectedMessageCount = 10) => {
15627
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15628
+ const fileEditGroups = /* @__PURE__ */ new Map();
15629
+ for (let i = 0; i < protectedStart; i++) {
15630
+ if (i >= messages.length) {
15631
+ break;
15632
+ }
15633
+ const msg = messages[i];
15634
+ if (msg.role !== "tool") {
15635
+ continue;
15636
+ }
15637
+ for (const part of msg.content) {
15638
+ if (part.type !== "tool-result") {
15639
+ continue;
15640
+ }
15641
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15642
+ if (!isPowerTool(serverName)) {
15643
+ continue;
15644
+ }
15645
+ if (toolName !== POWER_TOOL_FILE_EDIT && toolName !== POWER_TOOL_FILE_WRITE) {
15646
+ continue;
15647
+ }
15648
+ const callInfo = findAssistantToolCall(messages, part.toolCallId);
15649
+ if (!callInfo) {
15650
+ continue;
15651
+ }
15652
+ const filePath = extractFilePath(callInfo.input);
15653
+ if (!filePath) {
15654
+ continue;
15655
+ }
15656
+ if (!fileEditGroups.has(filePath)) {
15657
+ fileEditGroups.set(filePath, []);
15658
+ }
15659
+ fileEditGroups.get(filePath).push({
15660
+ assistantMessageId: callInfo.messageId,
15661
+ toolCallId: part.toolCallId
15662
+ });
15663
+ }
15664
+ }
15665
+ for (const [filePath, edits] of fileEditGroups) {
15666
+ if (edits.length === 0) {
15667
+ continue;
15668
+ }
15669
+ const lastEdit = edits[edits.length - 1];
15670
+ const syntheticMessage = {
15671
+ id: uuid.v4(),
15672
+ role: "assistant",
15673
+ content: [
15674
+ {
15675
+ type: "text",
15676
+ text: `<file-edited path="${filePath}">File was edited. Read the content again if you need to work on it.</file-edited>`
15677
+ }
15678
+ ]
15679
+ };
15680
+ const assistantIndex = messages.findIndex((m) => m.id === lastEdit.assistantMessageId);
15681
+ const insertIndex = assistantIndex !== -1 ? assistantIndex + 1 : 0;
15682
+ messages.splice(insertIndex, 0, syntheticMessage);
15683
+ for (const edit of edits) {
15684
+ removeToolCallFromAssistant(messages, edit.toolCallId);
15685
+ removeToolResult(messages, edit.toolCallId);
15686
+ }
15687
+ }
15688
+ return messages;
15689
+ };
15690
+ const removeStaleFileReads = (messages, protectedMessageCount = 10) => {
15691
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15692
+ const editedFilePaths = /* @__PURE__ */ new Set();
15693
+ for (let i = 0; i < messages.length; i++) {
15694
+ const msg = messages[i];
15695
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
15696
+ for (const part of msg.content) {
15697
+ if (part.type === "text") {
15698
+ const match = part.text.match(/<file-edited path="([^"]+)">/);
15699
+ if (match) {
15700
+ editedFilePaths.add(match[1]);
15701
+ }
15702
+ }
15703
+ }
15704
+ continue;
15705
+ }
15706
+ if (msg.role !== "tool") {
15707
+ continue;
15708
+ }
15709
+ for (const part of msg.content) {
15710
+ if (part.type !== "tool-result") {
15711
+ continue;
15712
+ }
15713
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15714
+ if (!isPowerTool(serverName)) {
15715
+ continue;
15716
+ }
15717
+ if (toolName !== POWER_TOOL_FILE_EDIT && toolName !== POWER_TOOL_FILE_WRITE) {
15718
+ continue;
15719
+ }
15720
+ const callInfo = findAssistantToolCall(messages, part.toolCallId);
15721
+ if (callInfo) {
15722
+ const filePath = extractFilePath(callInfo.input);
15723
+ if (filePath) {
15724
+ editedFilePaths.add(filePath);
15725
+ }
15726
+ }
15727
+ }
15728
+ }
15729
+ const protectedReadFilePaths = /* @__PURE__ */ new Set();
15730
+ for (let i = protectedStart; i < messages.length; i++) {
15731
+ const msg = messages[i];
15732
+ if (msg.role !== "tool") {
15733
+ continue;
15734
+ }
15735
+ for (const part of msg.content) {
15736
+ if (part.type !== "tool-result") {
15737
+ continue;
15738
+ }
15739
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15740
+ if (!isPowerTool(serverName) || toolName !== POWER_TOOL_FILE_READ) {
15741
+ continue;
15742
+ }
15743
+ const callInfo = findAssistantToolCall(messages, part.toolCallId);
15744
+ if (callInfo) {
15745
+ const filePath = extractFilePath(callInfo.input);
15746
+ if (filePath) {
15747
+ protectedReadFilePaths.add(filePath);
15748
+ }
15749
+ }
15750
+ }
15751
+ }
15752
+ const readFileGroups = /* @__PURE__ */ new Map();
15753
+ for (let i = protectedStart - 1; i >= 0; i--) {
15754
+ if (i >= messages.length) {
15755
+ continue;
15756
+ }
15757
+ const msg = messages[i];
15758
+ if (msg.role !== "tool") {
15759
+ continue;
15760
+ }
15761
+ for (const part of msg.content) {
15762
+ if (part.type !== "tool-result") {
15763
+ continue;
15764
+ }
15765
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15766
+ if (!isPowerTool(serverName) || toolName !== POWER_TOOL_FILE_READ) {
15767
+ continue;
15768
+ }
15769
+ const callInfo = findAssistantToolCall(messages, part.toolCallId);
15770
+ if (!callInfo) {
15771
+ continue;
15772
+ }
15773
+ const filePath = extractFilePath(callInfo.input);
15774
+ if (!filePath) {
15775
+ continue;
15776
+ }
15777
+ if (editedFilePaths.has(filePath) || protectedReadFilePaths.has(filePath)) {
15778
+ removeToolCallFromAssistant(messages, part.toolCallId);
15779
+ removeToolResult(messages, part.toolCallId);
15780
+ continue;
15781
+ }
15782
+ if (!readFileGroups.has(filePath)) {
15783
+ readFileGroups.set(filePath, []);
15784
+ }
15785
+ readFileGroups.get(filePath).push({
15786
+ messageIndex: i,
15787
+ toolCallId: part.toolCallId
15788
+ });
15789
+ }
15790
+ }
15791
+ for (const [, reads] of readFileGroups) {
15792
+ if (reads.length <= 1) {
15793
+ continue;
15794
+ }
15795
+ const toRemove = reads.slice(0, -1);
15796
+ for (const read of toRemove) {
15797
+ removeToolCallFromAssistant(messages, read.toolCallId);
15798
+ removeToolResult(messages, read.toolCallId);
15799
+ }
15800
+ }
15801
+ return messages;
15802
+ };
15803
+ const compactFileReads = (messages, protectedMessageCount = 10) => {
15804
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15805
+ for (let i = 0; i < protectedStart; i++) {
15806
+ if (i >= messages.length) {
15807
+ break;
15808
+ }
15809
+ const msg = messages[i];
15810
+ if (msg.role !== "tool") {
15811
+ continue;
15812
+ }
15813
+ for (let j = 0; j < msg.content.length; j++) {
15814
+ const part = msg.content[j];
15815
+ if (part.type !== "tool-result") {
15816
+ continue;
15817
+ }
15818
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15819
+ if (!isPowerTool(serverName) || toolName !== POWER_TOOL_FILE_READ) {
15820
+ continue;
15821
+ }
15822
+ if (part.output.type !== "text") {
15823
+ continue;
15824
+ }
15825
+ const lines = part.output.value.split("\n");
15826
+ if (lines.length > 50) {
15827
+ part.output = {
15828
+ type: "text",
15829
+ value: lines.slice(0, 50).join("\n") + "\n<truncated due to compaction, read the file again if full content is needed>"
15830
+ };
15831
+ }
15832
+ }
15833
+ }
15834
+ return messages;
15835
+ };
15836
+ const removeObsoleteSearches = (messages, protectedMessageCount = 10) => {
15837
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15838
+ const fileModificationPositions = [];
15839
+ for (let i = 0; i < messages.length; i++) {
15840
+ const msg = messages[i];
15841
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
15842
+ for (const part of msg.content) {
15843
+ if (part.type === "text" && part.text.includes("<file-edited")) {
15844
+ fileModificationPositions.push(i);
15845
+ break;
15846
+ }
15847
+ }
15848
+ continue;
15849
+ }
15850
+ if (msg.role !== "tool") {
15851
+ continue;
15852
+ }
15853
+ for (const part of msg.content) {
15854
+ if (part.type !== "tool-result") {
15855
+ continue;
15856
+ }
15857
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15858
+ if (!isPowerTool(serverName)) {
15859
+ continue;
15860
+ }
15861
+ if (toolName === POWER_TOOL_FILE_EDIT || toolName === POWER_TOOL_FILE_WRITE) {
15862
+ fileModificationPositions.push(i);
15863
+ }
15864
+ }
15865
+ }
15866
+ const hasFileModifications = fileModificationPositions.length > 0;
15867
+ if (!hasFileModifications) {
15868
+ return messages;
15869
+ }
15870
+ for (let i = protectedStart - 1; i >= 0; i--) {
15871
+ if (i >= messages.length) {
15872
+ continue;
15873
+ }
15874
+ const msg = messages[i];
15875
+ if (msg.role !== "tool") {
15876
+ continue;
15877
+ }
15878
+ for (const part of msg.content) {
15879
+ if (part.type !== "tool-result") {
15880
+ continue;
15881
+ }
15882
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15883
+ if (!isPowerTool(serverName)) {
15884
+ continue;
15885
+ }
15886
+ if (toolName !== POWER_TOOL_GLOB && toolName !== POWER_TOOL_GREP) {
15887
+ continue;
15888
+ }
15889
+ const hasLaterModification = fileModificationPositions.some((pos) => pos > i);
15890
+ if (hasLaterModification) {
15891
+ removeToolCallFromAssistant(messages, part.toolCallId);
15892
+ removeToolResult(messages, part.toolCallId);
15893
+ }
15894
+ }
15895
+ }
15896
+ return messages;
15897
+ };
15898
+ const compactSemanticSearches = (messages, protectedMessageCount = 10) => {
15899
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15900
+ const searchIndices = [];
15901
+ for (let i = 0; i < protectedStart; i++) {
15902
+ if (i >= messages.length) {
15903
+ break;
15904
+ }
15905
+ const msg = messages[i];
15906
+ if (msg.role !== "tool") {
15907
+ continue;
15908
+ }
15909
+ for (let j = 0; j < msg.content.length; j++) {
15910
+ const part = msg.content[j];
15911
+ if (part.type !== "tool-result") {
15912
+ continue;
15913
+ }
15914
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15915
+ if (!isPowerTool(serverName) || toolName !== POWER_TOOL_SEMANTIC_SEARCH) {
15916
+ continue;
15917
+ }
15918
+ searchIndices.push({ messageIndex: i, partIndex: j, toolCallId: part.toolCallId });
15919
+ }
15920
+ }
15921
+ if (searchIndices.length <= 1) {
15922
+ return messages;
15923
+ }
15924
+ const toRemove = searchIndices.slice(0, -1);
15925
+ const toKeep = searchIndices[searchIndices.length - 1];
15926
+ for (const search2 of toRemove) {
15927
+ removeToolCallFromAssistant(messages, search2.toolCallId);
15928
+ removeToolResult(messages, search2.toolCallId);
15929
+ }
15930
+ const keptToolIdx = messages.findIndex((m) => m.role === "tool" && m.content.some((p) => p.type === "tool-result" && p.toolCallId === toKeep.toolCallId));
15931
+ if (keptToolIdx !== -1) {
15932
+ const keptMsg = messages[keptToolIdx];
15933
+ const keptPartIdx = keptMsg.content.findIndex((p) => p.type === "tool-result" && p.toolCallId === toKeep.toolCallId);
15934
+ const keptPart = keptMsg.content[keptPartIdx];
15935
+ if (keptPart && keptPart.output.type === "text") {
15936
+ const lines = keptPart.output.value.split("\n");
15937
+ if (lines.length > 50) {
15938
+ keptPart.output = {
15939
+ type: "text",
15940
+ value: lines.slice(0, 50).join("\n") + "\n<truncated due to compaction, run again if full output is needed>"
15941
+ };
15942
+ }
15943
+ }
15944
+ }
15945
+ return messages;
15946
+ };
15947
+ const deduplicateBash = (messages, protectedMessageCount = 10) => {
15948
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
15949
+ const bashCommands = /* @__PURE__ */ new Map();
15950
+ for (let i = 0; i < protectedStart; i++) {
15951
+ const msg = messages[i];
15952
+ if (msg.role !== "tool") {
15953
+ continue;
15954
+ }
15955
+ for (const part of msg.content) {
15956
+ if (part.type !== "tool-result") {
15957
+ continue;
15958
+ }
15959
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
15960
+ if (!isPowerTool(serverName) || toolName !== POWER_TOOL_BASH) {
15961
+ continue;
15962
+ }
15963
+ const callInfo = findAssistantToolCall(messages, part.toolCallId);
15964
+ if (!callInfo) {
15965
+ continue;
15966
+ }
15967
+ const command = typeof callInfo.input?.command === "string" ? callInfo.input.command.trim() : null;
15968
+ if (!command) {
15969
+ continue;
15970
+ }
15971
+ if (!bashCommands.has(command)) {
15972
+ bashCommands.set(command, []);
15973
+ }
15974
+ bashCommands.get(command).push({
15975
+ messageIndex: i,
15976
+ toolCallId: part.toolCallId
15977
+ });
15978
+ }
15979
+ }
15980
+ for (const [, occurrences] of bashCommands) {
15981
+ if (occurrences.length <= 1) {
15982
+ continue;
15983
+ }
15984
+ const toRemove = occurrences.slice(0, -1);
15985
+ for (const occ of toRemove) {
15986
+ removeToolCallFromAssistant(messages, occ.toolCallId);
15987
+ removeToolResult(messages, occ.toolCallId);
15988
+ }
15989
+ }
15990
+ for (let i = 0; i < protectedStart; i++) {
15991
+ if (i >= messages.length) {
15992
+ break;
15993
+ }
15994
+ const msg = messages[i];
15995
+ if (msg.role !== "tool") {
15996
+ continue;
15997
+ }
15998
+ for (let j = 0; j < msg.content.length; j++) {
15999
+ const part = msg.content[j];
16000
+ if (part.type !== "tool-result") {
16001
+ continue;
16002
+ }
16003
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
16004
+ if (!isPowerTool(serverName) || toolName !== POWER_TOOL_BASH) {
16005
+ continue;
16006
+ }
16007
+ if (part.output.type !== "text") {
16008
+ continue;
16009
+ }
16010
+ try {
16011
+ const parsed = JSON.parse(part.output.value);
16012
+ if (typeof parsed === "object" && parsed !== null) {
16013
+ const stdout = typeof parsed.stdout === "string" ? parsed.stdout : "";
16014
+ const stderr = typeof parsed.stderr === "string" ? parsed.stderr : "";
16015
+ const redactionMessage = "<output redacted due to compaction, run again if output is needed>";
16016
+ let modified = false;
16017
+ if (stdout.length > 30) {
16018
+ parsed.stdout = redactionMessage;
16019
+ modified = true;
16020
+ }
16021
+ if (stderr.length > 30) {
16022
+ parsed.stderr = redactionMessage;
16023
+ modified = true;
16024
+ }
16025
+ if (modified) {
16026
+ part.output = {
16027
+ type: "text",
16028
+ value: JSON.stringify(parsed)
16029
+ };
16030
+ }
16031
+ }
16032
+ } catch {
16033
+ }
16034
+ }
16035
+ }
16036
+ return messages;
16037
+ };
16038
+ const redactFetchOutputs = (messages, protectedMessageCount = 10) => {
16039
+ const protectedStart = getProtectedStartIndex(messages, protectedMessageCount);
16040
+ for (let i = 0; i < protectedStart; i++) {
16041
+ const msg = messages[i];
16042
+ if (msg.role !== "tool") {
16043
+ continue;
16044
+ }
16045
+ for (let j = 0; j < msg.content.length; j++) {
16046
+ const part = msg.content[j];
16047
+ if (part.type !== "tool-result") {
16048
+ continue;
16049
+ }
16050
+ const [serverName, toolName] = extractServerNameToolName(part.toolName);
16051
+ if (!isPowerTool(serverName) || toolName !== POWER_TOOL_FETCH) {
16052
+ continue;
16053
+ }
16054
+ const outputText = getToolOutputText(part.output);
16055
+ if (outputText.length > 0) {
16056
+ part.output = {
16057
+ type: "text",
16058
+ value: "<content redacted due to compaction, fetch again if content is needed>"
16059
+ };
16060
+ }
16061
+ }
14612
16062
  }
14613
- }
16063
+ return messages;
16064
+ };
14614
16065
  const INTERNAL_TASK_ID = "internal";
14615
16066
  const RESPONSE_CHUNK_FLUSH_INTERVAL_MS = 10;
14616
16067
  const EMPTY_TASK_DATA = {
@@ -14622,7 +16073,6 @@ const EMPTY_TASK_DATA = {
14622
16073
  agentTotalCost: 0,
14623
16074
  mainModel: "",
14624
16075
  currentMode: "agent",
14625
- contextCompactingThreshold: 0,
14626
16076
  weakModelLocked: false,
14627
16077
  parentId: null,
14628
16078
  lastAgentProviderMetadata: void 0
@@ -15117,7 +16567,7 @@ class Task {
15117
16567
  isPromptRunning() {
15118
16568
  return !!this.currentPromptContext || this.agent.isRunning() || this.isCompacting;
15119
16569
  }
15120
- async runPrompt(prompt, mode = this.task.currentMode || "agent", addToInputHistory = true, userMessageId = uuid.v4(), sendNotification = true) {
16570
+ async runPrompt(prompt, mode = this.task.currentMode || "agent", addToInputHistory = true, userMessageId = uuid.v4(), sendNotification = true, images) {
15121
16571
  if (this.currentQuestion) {
15122
16572
  if (await this.answerQuestion("n", prompt)) {
15123
16573
  logger.debug("Processed by the answerQuestion function.");
@@ -15155,7 +16605,7 @@ class Task {
15155
16605
  if (addToInputHistory) {
15156
16606
  await this.project.addToInputHistory(prompt);
15157
16607
  }
15158
- this.addUserMessage(userMessageId, prompt);
16608
+ this.addUserMessage(userMessageId, prompt, promptContext, images);
15159
16609
  this.addLogMessage("loading");
15160
16610
  this.telemetryManager.captureRunPrompt(mode);
15161
16611
  let responses = [];
@@ -15165,7 +16615,7 @@ class Task {
15165
16615
  if (!profile) {
15166
16616
  throw new Error("No active Agent profile found");
15167
16617
  }
15168
- responses = await this.runPromptInAgent(profile, mode, prompt, promptContext, void 0, void 0, void 0, true, sendNotification);
16618
+ responses = await this.runPromptInAgent(profile, mode, prompt, promptContext, void 0, void 0, void 0, true, sendNotification, images);
15169
16619
  } else {
15170
16620
  responses = await this.runPromptInAider(mode, prompt, promptContext, sendNotification);
15171
16621
  }
@@ -15187,7 +16637,8 @@ class Task {
15187
16637
  id: promptContext.id,
15188
16638
  role: MessageRole.User,
15189
16639
  content: prompt,
15190
- promptContext
16640
+ promptContext,
16641
+ timestamp: Date.now()
15191
16642
  });
15192
16643
  this.addUserMessage(promptContext.id, prompt);
15193
16644
  await this.saveTask({
@@ -15220,7 +16671,8 @@ class Task {
15220
16671
  id: promptContext.id,
15221
16672
  role: MessageRole.User,
15222
16673
  content: prompt,
15223
- promptContext
16674
+ promptContext,
16675
+ timestamp: Date.now()
15224
16676
  });
15225
16677
  let messages = this.contextManager.toConnectorMessages();
15226
16678
  let files = this.contextManager.getContextFiles();
@@ -15239,8 +16691,9 @@ class Task {
15239
16691
  promptContext = extensionResult.promptContext;
15240
16692
  messages = extensionResult.messages;
15241
16693
  files = extensionResult.files;
16694
+ const effectiveAutonomyMode = extensionResult.autonomyMode ?? this.task.autonomyMode ?? DEFAULT_AUTONOMY_MODE;
15242
16695
  let responses = await this.sendPromptToAider(prompt, promptContext, mode, messages, files, {
15243
- autoApprove: extensionResult.autoApprove ?? this.task.autoApprove,
16696
+ autoApprove: effectiveAutonomyMode === AutonomyMode.Autonomous,
15244
16697
  denyCommands: extensionResult.denyCommands
15245
16698
  });
15246
16699
  logger.debug("Responses:", { responses });
@@ -15277,7 +16730,7 @@ class Task {
15277
16730
  }
15278
16731
  return responses;
15279
16732
  }
15280
- async runPromptInAgent(profile, mode, prompt, promptContext = { id: uuid.v4() }, contextMessages, contextFiles, systemPrompt, waitForCurrentAgentToFinish = true, sendNotification = true) {
16733
+ async runPromptInAgent(profile, mode, prompt, promptContext = { id: uuid.v4() }, contextMessages, contextFiles, systemPrompt, waitForCurrentAgentToFinish = true, sendNotification = true, images) {
15281
16734
  if (waitForCurrentAgentToFinish) {
15282
16735
  await this.waitForCurrentAgentToFinish();
15283
16736
  }
@@ -15288,7 +16741,19 @@ class Task {
15288
16741
  provider: this.task.provider || profile.provider,
15289
16742
  model: this.task.model || profile.model
15290
16743
  });
15291
- const agentMessages = await this.agent.runAgent(this, profile, prompt, mode, promptContext, contextMessages, contextFiles, systemPrompt);
16744
+ const agentMessages = await this.agent.runAgent(
16745
+ this,
16746
+ profile,
16747
+ prompt,
16748
+ mode,
16749
+ promptContext,
16750
+ contextMessages,
16751
+ contextFiles,
16752
+ systemPrompt,
16753
+ true,
16754
+ void 0,
16755
+ images
16756
+ );
15292
16757
  if (agentMessages.length > 0) {
15293
16758
  this.contextManager.toConnectorMessages(agentMessages).forEach((message) => {
15294
16759
  this.sendAddMessage(message.role, message.content, false);
@@ -15613,7 +17078,8 @@ ${contentText}</agent-response>`;
15613
17078
  diff: message.diff,
15614
17079
  usageReport,
15615
17080
  sequenceNumber: message.sequenceNumber,
15616
- promptContext: message.promptContext
17081
+ promptContext: message.promptContext,
17082
+ timestamp: Date.now()
15617
17083
  };
15618
17084
  const extensionResult = await this.extensionManager.dispatchEvent("onResponseCompleted", { response: data }, this.project, this);
15619
17085
  data = extensionResult.response;
@@ -15680,14 +17146,20 @@ ${contentText}</agent-response>`;
15680
17146
  if (!determinedAnswer) {
15681
17147
  determinedAnswer = normalizedAnswer === "a" || normalizedAnswer === "y" ? "y" : "n";
15682
17148
  }
15683
- if ((normalizedAnswer === "d" || normalizedAnswer === "a") && (determinedAnswer == "y" || determinedAnswer == "n")) {
15684
- logger.debug('Storing answer for question due to "d" or "a" input:', {
17149
+ if (normalizedAnswer === "a") {
17150
+ logger.debug('Storing answer for question due to "a" (Always) input:', {
17151
+ baseDir: this.project.baseDir,
17152
+ questionKey: this.getQuestionKey(this.currentQuestion),
17153
+ rawInput: answer
17154
+ });
17155
+ this.storedQuestionAnswers.set(this.getQuestionKey(this.currentQuestion), "y");
17156
+ } else if (normalizedAnswer === "d") {
17157
+ logger.debug(`Storing answer for question due to "d" (Don't ask again) input:`, {
15685
17158
  baseDir: this.project.baseDir,
15686
17159
  questionKey: this.getQuestionKey(this.currentQuestion),
15687
- rawInput: answer,
15688
- determinedAndStoredAnswer: determinedAnswer
17160
+ rawInput: answer
15689
17161
  });
15690
- this.storedQuestionAnswers.set(this.getQuestionKey(this.currentQuestion), determinedAnswer);
17162
+ this.storedQuestionAnswers.set(this.getQuestionKey(this.currentQuestion), "n");
15691
17163
  }
15692
17164
  const questionToAnswer = this.currentQuestion;
15693
17165
  if (!this.currentQuestion.internal) {
@@ -15850,11 +17322,21 @@ ${contentText}</agent-response>`;
15850
17322
  void this.updateContextInfo(true, true);
15851
17323
  }
15852
17324
  async askQuestion(questionData, awaitAnswer = true) {
15853
- const extensionResult = await this.extensionManager.dispatchEvent("onQuestionAsked", { question: questionData }, this.project, this);
17325
+ let storedAnswer = this.storedQuestionAnswers.get(this.getQuestionKey(questionData));
17326
+ const extensionResult = await this.extensionManager.dispatchEvent(
17327
+ "onQuestionAsked",
17328
+ {
17329
+ question: questionData,
17330
+ storedAnswer
17331
+ },
17332
+ this.project,
17333
+ this
17334
+ );
15854
17335
  if (extensionResult.answer) {
15855
17336
  logger.info("Question answered by extension", {
15856
17337
  question: questionData.text,
15857
- answer: extensionResult.answer
17338
+ answer: extensionResult.answer,
17339
+ storedAnswer
15858
17340
  });
15859
17341
  return [extensionResult.answer, void 0];
15860
17342
  }
@@ -15864,7 +17346,7 @@ ${contentText}</agent-response>`;
15864
17346
  this.currentQuestionResolves.push(resolve);
15865
17347
  });
15866
17348
  }
15867
- const storedAnswer = this.storedQuestionAnswers.get(this.getQuestionKey(questionData));
17349
+ storedAnswer = this.storedQuestionAnswers.get(this.getQuestionKey(questionData));
15868
17350
  if (questionData.isGroupQuestion && !questionData.answers) {
15869
17351
  questionData.answers = [
15870
17352
  { text: "(Y)es", shortkey: "y" },
@@ -16205,7 +17687,8 @@ ${contentText}</agent-response>`;
16205
17687
  message,
16206
17688
  finished,
16207
17689
  promptContext,
16208
- actionIds
17690
+ actionIds,
17691
+ timestamp: Date.now()
16209
17692
  };
16210
17693
  this.eventManager.sendLog(data);
16211
17694
  }
@@ -16325,7 +17808,8 @@ ${contentText}</agent-response>`;
16325
17808
  sendUserMessage(data) {
16326
17809
  logger.debug("Sending user message:", {
16327
17810
  baseDir: this.project.baseDir,
16328
- content: data.content.substring(0, 100)
17811
+ content: data.content?.substring(0, 100),
17812
+ hasImages: (data.images?.length ?? 0) > 0
16329
17813
  });
16330
17814
  this.eventManager.sendUserMessage(data);
16331
17815
  }
@@ -16342,18 +17826,20 @@ ${contentText}</agent-response>`;
16342
17826
  });
16343
17827
  this.eventManager.sendTool(data);
16344
17828
  }
16345
- async clearContext(addToHistory = false, updateContextInfo = true) {
17829
+ async clearContext(addToHistory = false, updateContextInfo = true, updateTaskState = true, createSnapshot = true) {
16346
17830
  logger.info("Clearing context:", {
16347
17831
  baseDir: this.project.baseDir,
16348
17832
  addToHistory,
16349
17833
  updateContextInfo
16350
17834
  });
16351
- await this.updateTask({
16352
- state: DefaultTaskState.Todo,
16353
- metadata: void 0,
16354
- lastAgentProviderMetadata: null
16355
- });
16356
- this.contextManager.clearMessages();
17835
+ if (updateTaskState) {
17836
+ await this.updateTask({
17837
+ state: DefaultTaskState.Todo,
17838
+ metadata: void 0,
17839
+ lastAgentProviderMetadata: null
17840
+ });
17841
+ }
17842
+ this.contextManager.clearMessages(true, createSnapshot);
16357
17843
  await this.runCommand("clear", addToHistory);
16358
17844
  this.eventManager.sendClearTask(this.project.baseDir, this.taskId, true, false);
16359
17845
  if (updateContextInfo) {
@@ -16369,6 +17855,29 @@ ${contentText}</agent-response>`;
16369
17855
  this.contextManager.clearMessages();
16370
17856
  await this.contextManager.save();
16371
17857
  }
17858
+ sendContextInfoUpdated() {
17859
+ this.eventManager.sendContextInfoUpdated({
17860
+ baseDir: this.project.baseDir,
17861
+ taskId: this.taskId,
17862
+ canUndoContextChange: this.contextManager.hasUndoSnapshot()
17863
+ });
17864
+ }
17865
+ async undoContextChange() {
17866
+ const snapshot = this.contextManager.undoContextChange();
17867
+ if (!snapshot) {
17868
+ logger.debug("No undo snapshot available", { taskId: this.taskId });
17869
+ return false;
17870
+ }
17871
+ logger.info("Undoing context change", {
17872
+ baseDir: this.project.baseDir,
17873
+ taskId: this.taskId,
17874
+ messagesCount: snapshot.length
17875
+ });
17876
+ await this.contextManager.loadMessages(snapshot);
17877
+ await this.updateContextInfo();
17878
+ this.sendContextInfoUpdated();
17879
+ return true;
17880
+ }
16372
17881
  /**
16373
17882
  * Load context messages into the task context and send them to the UI.
16374
17883
  * This is used for loading pre-authored context (e.g., from extensions).
@@ -16382,6 +17891,12 @@ ${contentText}</agent-response>`;
16382
17891
  await this.contextManager.loadMessages(messages);
16383
17892
  }
16384
17893
  async interruptResponse(interruptId) {
17894
+ const extensionResult = await this.extensionManager.dispatchEvent("onInterrupted", { interruptId }, this.project, this);
17895
+ if (extensionResult.blocked) {
17896
+ logger.debug("Interrupt blocked by extension", { interruptId });
17897
+ return;
17898
+ }
17899
+ interruptId = extensionResult.interruptId;
16385
17900
  if (interruptId) {
16386
17901
  const subagentAbortController = this.subagentAbortControllers[interruptId];
16387
17902
  if (subagentAbortController) {
@@ -16525,7 +18040,8 @@ ${contentText}</agent-response>`;
16525
18040
  response,
16526
18041
  usageReport,
16527
18042
  promptContext,
16528
- finished
18043
+ finished,
18044
+ timestamp: Date.now()
16529
18045
  };
16530
18046
  if (response && usageReport && saveToDb) {
16531
18047
  this.dataManager.saveMessage(id, "tool", this.project.baseDir, usageReport.model, usageReport, {
@@ -16554,7 +18070,7 @@ ${contentText}</agent-response>`;
16554
18070
  this.eventManager.sendTaskUpdated(this.task);
16555
18071
  }
16556
18072
  }
16557
- addUserMessage(id, content, promptContext) {
18073
+ addUserMessage(id, content, promptContext, images) {
16558
18074
  logger.info("Adding user message:", {
16559
18075
  baseDir: this.project.baseDir,
16560
18076
  content: content.substring(0, 100)
@@ -16565,7 +18081,9 @@ ${contentText}</agent-response>`;
16565
18081
  baseDir: this.project.baseDir,
16566
18082
  taskId: this.taskId,
16567
18083
  content,
16568
- promptContext
18084
+ images,
18085
+ promptContext,
18086
+ timestamp: Date.now()
16569
18087
  };
16570
18088
  this.eventManager.sendUserMessage(data);
16571
18089
  }
@@ -16597,12 +18115,13 @@ ${contentText}</agent-response>`;
16597
18115
  this.eventManager.sendTaskMessageRemoved(this.project.baseDir, this.taskId, messageIds);
16598
18116
  }
16599
18117
  }
16600
- async redoUserPrompt(messageId, mode, updatedPrompt) {
18118
+ async redoUserPrompt(messageId, mode, updatedPrompt, updatedImages) {
16601
18119
  logger.info("Redoing user prompt:", {
16602
18120
  baseDir: this.project.baseDir,
16603
18121
  messageId,
16604
18122
  mode,
16605
- hasUpdatedPrompt: !!updatedPrompt
18123
+ hasUpdatedPrompt: !!updatedPrompt,
18124
+ hasUpdatedImages: !!updatedImages
16606
18125
  });
16607
18126
  const removedMessages = this.contextManager.removeMessagesUpToUserMessage(messageId);
16608
18127
  const originalUserMessage = removedMessages[0];
@@ -16610,14 +18129,16 @@ ${contentText}</agent-response>`;
16610
18129
  logger.warn("Could not find the specified user message to redo.", { messageId });
16611
18130
  return;
16612
18131
  }
16613
- const promptToRun = updatedPrompt ?? originalUserMessage.content;
18132
+ const originalText = extractTextContent(originalUserMessage.content);
18133
+ const promptToRun = updatedPrompt ?? originalText;
18134
+ const imagesToRun = updatedImages ?? (updatedPrompt !== void 0 ? void 0 : extractImagesFromContent(originalUserMessage.content));
16614
18135
  if (promptToRun) {
16615
18136
  logger.info("Found message content to run, reloading and re-running prompt.", {
16616
18137
  remainingMessagesCount: (await this.contextManager.getContextMessages()).length
16617
18138
  });
16618
18139
  this.sendTaskMessageRemoved(removedMessages.slice(1).map((msg) => msg.id));
16619
18140
  await this.updateContextInfo();
16620
- void this.runPrompt(promptToRun, mode, false, originalUserMessage.id);
18141
+ void this.runPrompt(promptToRun, mode, false, originalUserMessage.id, true, imagesToRun);
16621
18142
  } else {
16622
18143
  logger.warn("Could not find a previous user message to redo or an updated prompt to run.");
16623
18144
  }
@@ -16713,6 +18234,22 @@ ${contentText}</agent-response>`;
16713
18234
  }
16714
18235
  return skillMessages;
16715
18236
  }
18237
+ async smartCompactConversation(contextMessages, infoMessage = "Conversation smart-compacted.") {
18238
+ if (!contextMessages) {
18239
+ contextMessages = await this.contextManager.getContextMessages();
18240
+ }
18241
+ if (contextMessages.length === 0) {
18242
+ this.addLogMessage("warning", "No conversation to compact.");
18243
+ return [];
18244
+ }
18245
+ await this.contextManager.backupContext();
18246
+ const compactedMessages = await smartCompactMessages(contextMessages);
18247
+ this.contextManager.setContextMessages(compactedMessages);
18248
+ await this.contextManager.loadMessages(compactedMessages, false);
18249
+ await this.updateContextInfo();
18250
+ this.addLogMessage("info", infoMessage, false, void 0, ["undoContextChange"]);
18251
+ return compactedMessages;
18252
+ }
16716
18253
  async compactConversation(mode, customInstructions, profile = null, contextMessages, promptContext, abortSignal, waitForAgentCompletion = true, loadingMessage = "Compacting conversation...") {
16717
18254
  if (!profile) {
16718
18255
  profile = await this.getTaskAgentProfile();
@@ -16801,7 +18338,7 @@ ${contentText}</agent-response>`;
16801
18338
  await this.contextManager.loadMessages(await this.contextManager.getContextMessages());
16802
18339
  }
16803
18340
  await this.updateContextInfo();
16804
- this.addLogMessage("info", "Conversation compacted.");
18341
+ this.addLogMessage("info", "Conversation compacted.", false, void 0, ["undoContextChange"]);
16805
18342
  } catch (error) {
16806
18343
  logger.error("Failed to compact conversation", {
16807
18344
  baseDir: this.project.baseDir,
@@ -16869,7 +18406,7 @@ ${contentText}</agent-response>`;
16869
18406
  const newTaskData = await this.project.createNewTask({
16870
18407
  parentId: this.task.parentId || this.taskId,
16871
18408
  sendEvent: false,
16872
- autoApprove: execute ? this.task.autoApprove : void 0,
18409
+ autonomyMode: execute ? this.task.autonomyMode : void 0,
16873
18410
  activate: true
16874
18411
  });
16875
18412
  const newTask = this.project.getTask(newTaskData.id);
@@ -16899,6 +18436,7 @@ ${contentText}</agent-response>`;
16899
18436
  }
16900
18437
  async updateContextInfo(checkContextFilesIncluded = false, checkRepoMapIncluded = false) {
16901
18438
  void this.debouncedUpdateContextInfo(checkContextFilesIncluded, checkRepoMapIncluded);
18439
+ void this.sendSkillsUpdated();
16902
18440
  }
16903
18441
  debouncedUpdateContextInfo = debounce(async (checkContextFilesIncluded = false, checkRepoMapIncluded = false) => {
16904
18442
  void this.sendRequestContextInfo();
@@ -17172,7 +18710,6 @@ ${error.stderr}`,
17172
18710
  id: uuid.v4()
17173
18711
  };
17174
18712
  this.addUserMessage(promptContext.id, prompt);
17175
- this.addLogMessage("loading", "Executing custom command...");
17176
18713
  try {
17177
18714
  if (!AIDER_MODES.includes(mode)) {
17178
18715
  const profile = await this.getTaskAgentProfile();
@@ -17180,11 +18717,27 @@ ${error.stderr}`,
17180
18717
  this.addLogMessage("error", "No active Agent profile found");
17181
18718
  return;
17182
18719
  }
17183
- const systemPrompt = await this.promptsManager.getSystemPrompt(this.store.getSettings(), this, profile, command.autoApprove ?? this.task.autoApprove);
18720
+ if (command.skills?.length) {
18721
+ for (const skillName of command.skills) {
18722
+ try {
18723
+ await this.activateSkill(skillName);
18724
+ } catch (error) {
18725
+ logger.warn(`Failed to activate skill '${skillName}' for command '${commandName}': ${error instanceof Error ? error.message : String(error)}`);
18726
+ }
18727
+ }
18728
+ }
18729
+ const systemPrompt = await this.promptsManager.getSystemPrompt(
18730
+ this.store.getSettings(),
18731
+ this,
18732
+ profile,
18733
+ command.autonomyMode ?? this.task.autonomyMode ?? DEFAULT_AUTONOMY_MODE
18734
+ );
17184
18735
  const messages = command.includeContext === false ? [] : void 0;
17185
18736
  const contextFiles = command.includeContext === false ? [] : void 0;
18737
+ this.addLogMessage("loading", "Executing custom command...");
17186
18738
  await this.runPromptInAgent(profile, mode, prompt, promptContext, messages, contextFiles, systemPrompt);
17187
18739
  } else {
18740
+ this.addLogMessage("loading", "Executing custom command...");
17188
18741
  await this.runPromptInAider(mode, prompt, promptContext);
17189
18742
  }
17190
18743
  } finally {
@@ -17929,7 +19482,7 @@ ${diff}
17929
19482
  const newTaskData = await this.project.createNewTask({
17930
19483
  name: taskName,
17931
19484
  sendEvent: false,
17932
- autoApprove: true,
19485
+ autonomyMode: AutonomyMode.Autonomous,
17933
19486
  activate: true,
17934
19487
  mode,
17935
19488
  parentId: this.task.parentId || this.taskId,
@@ -18069,7 +19622,7 @@ class Project {
18069
19622
  this.promptsManager = promptsManager;
18070
19623
  this.extensionManager = extensionManager;
18071
19624
  this.pythonInstaller = pythonInstaller;
18072
- this.customCommandManager = new CustomCommandManager(this, this.eventManager, this.extensionManager);
19625
+ this.customCommandManager = new CustomCommandManager(this, this.eventManager, this.extensionManager, this.store);
18073
19626
  this.tasksLoadingPromise = this.loadTasks();
18074
19627
  }
18075
19628
  customCommandManager;
@@ -18106,6 +19659,9 @@ class Project {
18106
19659
  if (!parentTask) {
18107
19660
  throw new Error(`Parent task with id ${normalizedParams.parentId} not found`);
18108
19661
  }
19662
+ if (parentTask.task.parentId) {
19663
+ normalizedParams.parentId = parentTask.task.parentId;
19664
+ }
18109
19665
  if (!parentTask.task.createdAt) {
18110
19666
  await parentTask.saveTask();
18111
19667
  }
@@ -18142,11 +19698,7 @@ class Project {
18142
19698
  ...normalizedParams
18143
19699
  };
18144
19700
  }
18145
- const projectSettings = this.getProjectSettings();
18146
- const taskSettings = this.store.getSettings().taskSettings;
18147
19701
  const taskData = {
18148
- contextCompactingThreshold: taskSettings.contextCompactingThreshold,
18149
- autoApprove: projectSettings.autoApproveLocked ? true : initialTaskData.autoApprove,
18150
19702
  ...initialTaskData
18151
19703
  };
18152
19704
  const extResult = await this.extensionManager.dispatchEvent("onTaskCreated", { task: taskData }, this);
@@ -18164,7 +19716,14 @@ class Project {
18164
19716
  }
18165
19717
  return task.task;
18166
19718
  }
18167
- async prepareTask(taskId = uuid.v4(), initialTaskData) {
19719
+ generateShortTaskId() {
19720
+ let id;
19721
+ do {
19722
+ id = uuid.v4().substring(0, 8);
19723
+ } while (this.tasks.has(id));
19724
+ return id;
19725
+ }
19726
+ async prepareTask(taskId = this.generateShortTaskId(), initialTaskData) {
18168
19727
  const task = new Task(
18169
19728
  this,
18170
19729
  taskId,
@@ -18333,6 +19892,9 @@ class Project {
18333
19892
  getAgentProfiles() {
18334
19893
  return this.agentProfileManager.getProjectProfiles(this);
18335
19894
  }
19895
+ resolveAgentProfile(id) {
19896
+ return this.agentProfileManager.resolveAgentProfile(id);
19897
+ }
18336
19898
  /**
18337
19899
  * Checks if any other task (excluding the specified taskId) uses the given worktree path.
18338
19900
  */
@@ -18438,6 +20000,7 @@ class Project {
18438
20000
  this.forEachTask((task) => {
18439
20001
  void task.settingsChanged(oldSettings, newSettings);
18440
20002
  });
20003
+ void this.customCommandManager.settingsChanged(oldSettings, newSettings);
18441
20004
  }
18442
20005
  async projectSettingsChanged(oldSettings, newSettings) {
18443
20006
  this.forEachTask((task) => {
@@ -18588,6 +20151,10 @@ class EventManager {
18588
20151
  this.sendToWindows("clear-task", data);
18589
20152
  this.broadcastToEventConnectors("clear-task", data);
18590
20153
  }
20154
+ sendContextInfoUpdated(data) {
20155
+ this.sendToWindows("context-info-updated", data);
20156
+ this.broadcastToEventConnectors("context-info-updated", data);
20157
+ }
18591
20158
  // File management events
18592
20159
  sendFileAdded(baseDir, taskId, file) {
18593
20160
  const data = {
@@ -18683,7 +20250,8 @@ class EventManager {
18683
20250
  baseDir,
18684
20251
  taskId,
18685
20252
  command,
18686
- output
20253
+ output,
20254
+ timestamp: Date.now()
18687
20255
  };
18688
20256
  this.sendToWindows("command-output", data);
18689
20257
  this.broadcastToEventConnectors("command-output", data);
@@ -20979,7 +22547,8 @@ const createLmStudioLlm = (profile, model, settings, projectDir) => {
20979
22547
  const lmStudioProvider = openaiCompatible.createOpenAICompatible({
20980
22548
  name: "lmstudio",
20981
22549
  baseURL: baseUrl,
20982
- headers: profile.headers
22550
+ headers: profile.headers,
22551
+ includeUsage: true
20983
22552
  });
20984
22553
  return lmStudioProvider(model.id);
20985
22554
  };
@@ -21291,6 +22860,117 @@ const mistralProviderStrategy = {
21291
22860
  getAiderMapping: getMistralAiderMapping,
21292
22861
  getModelInfo: getDefaultModelInfo
21293
22862
  };
22863
+ const NEURALWATT_BASE_URL = "https://api.neuralwatt.com/v1";
22864
+ const loadNeuralwattModels = async (profile, settings) => {
22865
+ if (!isNeuralwattProvider(profile.provider)) {
22866
+ return { models: [], success: false };
22867
+ }
22868
+ const provider = profile.provider;
22869
+ const apiKey = provider.apiKey || "";
22870
+ const apiKeyEnv = getEffectiveEnvironmentVariable("NEURALWATT_API_KEY", settings);
22871
+ const effectiveApiKey = apiKey || apiKeyEnv?.value || "";
22872
+ if (!effectiveApiKey) {
22873
+ logger.debug("Neuralwatt API key is required. Please set it in Providers settings or via NEURALWATT_API_KEY environment variable.");
22874
+ return { models: [], success: false };
22875
+ }
22876
+ try {
22877
+ const response = await fetch(`${NEURALWATT_BASE_URL}/models`, {
22878
+ headers: { Authorization: `Bearer ${effectiveApiKey}` }
22879
+ });
22880
+ if (!response.ok) {
22881
+ const errorMsg = `Neuralwatt models API response failed: ${response.status} ${response.statusText} ${await response.text()}`;
22882
+ logger.error(errorMsg, { status: response.status, statusText: response.statusText });
22883
+ return { models: [], success: false, error: errorMsg };
22884
+ }
22885
+ const data = await response.json();
22886
+ logger.info(`Received response from Neuralwatt models API for profile ${profile.id}`, { data });
22887
+ const models = data.data?.map((model) => {
22888
+ const metadata = model.metadata;
22889
+ const pricing = metadata?.pricing;
22890
+ const limits = metadata?.limits;
22891
+ const capabilities = metadata?.capabilities;
22892
+ return {
22893
+ id: model.id,
22894
+ providerId: profile.id,
22895
+ maxInputTokens: limits?.max_context_length ?? model.max_model_len,
22896
+ maxOutputTokensLimit: limits?.max_output_tokens ?? void 0,
22897
+ inputCostPerToken: pricing ? pricing.input_per_million / 1e6 : void 0,
22898
+ outputCostPerToken: pricing ? pricing.output_per_million / 1e6 : void 0,
22899
+ cacheReadInputTokenCost: pricing?.cached_input_per_million != null ? pricing.cached_input_per_million / 1e6 : void 0,
22900
+ supportsTools: capabilities?.tools
22901
+ };
22902
+ }) || [];
22903
+ logger.info(`Loaded ${models.length} Neuralwatt models for profile ${profile.id}`);
22904
+ return { models, success: true };
22905
+ } catch (error) {
22906
+ const errorMsg = typeof error === "string" ? error : error instanceof Error ? error.message : "Unknown error loading Neuralwatt models";
22907
+ logger.error("Error loading Neuralwatt models:", error);
22908
+ return { models: [], success: false, error: errorMsg };
22909
+ }
22910
+ };
22911
+ const hasNeuralwattEnvVars = (settings) => {
22912
+ return !!getEffectiveEnvironmentVariable("NEURALWATT_API_KEY", settings, void 0)?.value;
22913
+ };
22914
+ const getNeuralwattAiderMapping = (provider, modelId) => {
22915
+ const neuralwattProvider = provider.provider;
22916
+ const envVars = {};
22917
+ if (neuralwattProvider.apiKey) {
22918
+ envVars.OPENAI_API_KEY = neuralwattProvider.apiKey;
22919
+ }
22920
+ envVars.OPENAI_API_BASE = NEURALWATT_BASE_URL;
22921
+ return {
22922
+ modelName: `openai/${modelId}`,
22923
+ environmentVariables: envVars
22924
+ };
22925
+ };
22926
+ const createNeuralwattLlm = (profile, model, settings, projectDir) => {
22927
+ const provider = profile.provider;
22928
+ let apiKey = provider.apiKey;
22929
+ if (!apiKey) {
22930
+ const effectiveVar = getEffectiveEnvironmentVariable("NEURALWATT_API_KEY", settings, projectDir);
22931
+ if (effectiveVar) {
22932
+ apiKey = effectiveVar.value;
22933
+ logger.debug(`Loaded NEURALWATT_API_KEY from ${effectiveVar.source}`);
22934
+ }
22935
+ }
22936
+ if (!apiKey) {
22937
+ throw new Error("Neuralwatt API key is required in Providers settings or Aider environment variables (NEURALWATT_API_KEY)");
22938
+ }
22939
+ const compatibleProvider = openaiCompatible.createOpenAICompatible({
22940
+ name: "neuralwatt",
22941
+ apiKey,
22942
+ baseURL: NEURALWATT_BASE_URL,
22943
+ headers: profile.headers
22944
+ });
22945
+ return compatibleProvider(model.id);
22946
+ };
22947
+ const getNeuralwattUsageReport = (task, provider, model, usage, providerMetadata) => {
22948
+ const totalSentTokens = usage.inputTokens || 0;
22949
+ const receivedTokens = usage.outputTokens || 0;
22950
+ const cacheReadTokens = usage.cachedInputTokens || 0;
22951
+ const sentTokens = totalSentTokens - cacheReadTokens;
22952
+ logger.info("Neuralwatt usage report", {
22953
+ providerMetadata,
22954
+ usage
22955
+ });
22956
+ const messageCost = calculateCost(model, sentTokens, receivedTokens, cacheReadTokens);
22957
+ return {
22958
+ model: `${provider.id}/${model.id}`,
22959
+ sentTokens,
22960
+ receivedTokens,
22961
+ cacheReadTokens,
22962
+ messageCost,
22963
+ agentTotalCost: task.task.agentTotalCost + messageCost
22964
+ };
22965
+ };
22966
+ const neuralwattProviderStrategy = {
22967
+ createLlm: createNeuralwattLlm,
22968
+ getUsageReport: getNeuralwattUsageReport,
22969
+ loadModels: loadNeuralwattModels,
22970
+ hasEnvVars: hasNeuralwattEnvVars,
22971
+ getAiderMapping: getNeuralwattAiderMapping,
22972
+ getModelInfo: getDefaultModelInfo
22973
+ };
21294
22974
  const loadOllamaModels = async (profile, settings) => {
21295
22975
  if (!isOllamaProvider(profile.provider)) {
21296
22976
  return { models: [], success: false };
@@ -21652,12 +23332,18 @@ const loadOpenaiCompatibleModels = async (profile, settings) => {
21652
23332
  return { models: [], success: false, error: errorMsg };
21653
23333
  }
21654
23334
  const data = await response.json();
21655
- const models = data.data?.map((model) => {
21656
- return {
21657
- id: model.id,
21658
- providerId: profile.id
21659
- };
21660
- }) || [];
23335
+ const models = data.data?.map(
23336
+ (model) => {
23337
+ const maxInputTokens = model.max_model_len ?? model.context_length ?? model.num_ctx ?? model.context_window;
23338
+ const maxOutputTokensLimit = model.max_completion_tokens ?? model.max_tokens;
23339
+ return {
23340
+ id: model.id,
23341
+ providerId: profile.id,
23342
+ ...maxInputTokens != null && { maxInputTokens },
23343
+ ...maxOutputTokensLimit != null && { maxOutputTokensLimit }
23344
+ };
23345
+ }
23346
+ ) || [];
21661
23347
  logger.info(`Loaded ${models.length} OpenAI-compatible models for profile ${profile.id}`);
21662
23348
  return { models, success: true };
21663
23349
  } catch (error) {
@@ -21706,11 +23392,14 @@ const createOpenAiCompatibleLlm = (profile, model, settings, projectDir) => {
21706
23392
  if (!baseUrl) {
21707
23393
  throw new Error(`Base URL is required for ${provider.name} provider. Set it in Providers settings or via the OPENAI_API_BASE environment variable.`);
21708
23394
  }
23395
+ const providerOverrides = model.providerOverrides;
23396
+ const trackTokenUsage = providerOverrides?.trackTokenUsage ?? provider.trackTokenUsage;
21709
23397
  const compatibleProvider = openaiCompatible.createOpenAICompatible({
21710
23398
  name: provider.name,
21711
23399
  apiKey,
21712
23400
  baseURL: baseUrl,
21713
- headers: profile.headers
23401
+ headers: profile.headers,
23402
+ includeUsage: trackTokenUsage !== false
21714
23403
  });
21715
23404
  return compatibleProvider(model.id);
21716
23405
  };
@@ -22192,7 +23881,7 @@ const getRequestyUsageReport = (task, provider, model, usage, providerMetadata)
22192
23881
  const cacheWriteTokens = requesty?.usage?.cachingTokens ?? 0;
22193
23882
  const cacheReadTokens = requesty?.usage?.cachedTokens ?? 0;
22194
23883
  const sentTokens = totalSentTokens - cacheReadTokens;
22195
- const messageCost = calculateRequestyCost(model, sentTokens, receivedTokens, cacheWriteTokens, cacheReadTokens);
23884
+ const messageCost = requesty?.usage?.cost ?? calculateRequestyCost(model, sentTokens, receivedTokens, cacheWriteTokens, cacheReadTokens);
22196
23885
  return {
22197
23886
  model: `${provider.id}/${model.id}`,
22198
23887
  sentTokens,
@@ -22645,6 +24334,7 @@ class ModelManager {
22645
24334
  lmstudio: lmStudioProviderStrategy,
22646
24335
  minimax: minimaxProviderStrategy,
22647
24336
  mistral: mistralProviderStrategy,
24337
+ neuralwatt: neuralwattProviderStrategy,
22648
24338
  ollama: ollamaProviderStrategy,
22649
24339
  openai: openaiProviderStrategy,
22650
24340
  "openai-compatible": openaiCompatibleProviderStrategy,
@@ -23102,6 +24792,7 @@ class ModelManager {
23102
24792
  getModelSettings(providerId, modelId, useModelInfoFallback = false) {
23103
24793
  let model;
23104
24794
  const providerModels = this.providerModels[providerId];
24795
+ logger.debug(`getModelSettings providerModels for provider: ${providerId}`, { providerModels });
23105
24796
  if (providerModels) {
23106
24797
  model = providerModels.find((m) => m.id === modelId);
23107
24798
  }
@@ -23115,6 +24806,11 @@ class ModelManager {
23115
24806
  };
23116
24807
  }
23117
24808
  }
24809
+ logger.debug("getModelSettings model", {
24810
+ providerId,
24811
+ modelId,
24812
+ model
24813
+ });
23118
24814
  return model;
23119
24815
  }
23120
24816
  createLlm(provider, model, settings, projectDir, toolSet, systemPrompt, providerMetadata) {
@@ -23680,7 +25376,7 @@ class VersionsManager {
23680
25376
  ...this.versionsInfo
23681
25377
  };
23682
25378
  }
23683
- logger.info("Checking for version updates...");
25379
+ logger.debug("Checking for version updates...");
23684
25380
  const aiderDeskCurrentVersion = app.getVersion();
23685
25381
  const autoUpdater = require("electron-updater").autoUpdater;
23686
25382
  if (this.pythonInstaller) {
@@ -23990,7 +25686,7 @@ class MemoryManager {
23990
25686
  done: this.embeddingProgress.done,
23991
25687
  total: this.embeddingProgress.total,
23992
25688
  finished: true,
23993
- error: "Failed to load @huggingface/transformers. This is usually a packaging issue with native dependencies (e.g. sharp/libvips)."
25689
+ error: "Failed to load @huggingface/transformers. This is usually a packaging issue with native dependencies."
23994
25690
  };
23995
25691
  logger.error("Failed to load transformers module. Memory embedding will be unavailable.", error);
23996
25692
  return;
@@ -24162,7 +25858,7 @@ class MemoryManager {
24162
25858
  done: this.embeddingProgress.done,
24163
25859
  total: this.embeddingProgress.total,
24164
25860
  finished: true,
24165
- error: "Failed to load @huggingface/transformers. This is usually a packaging issue with native dependencies (e.g. sharp/libvips)."
25861
+ error: "Failed to load @huggingface/transformers. This is usually a packaging issue with native dependencies."
24166
25862
  };
24167
25863
  logger.error("Failed to load transformers module. Memory embedding will be unavailable.", error);
24168
25864
  return;
@@ -24818,6 +26514,18 @@ class ExtensionContextImpl {
24818
26514
  return [];
24819
26515
  }
24820
26516
  }
26517
+ getProviders() {
26518
+ if (!this.modelManager) {
26519
+ this.log("ModelManager not available, returning empty providers", "warn");
26520
+ return [];
26521
+ }
26522
+ try {
26523
+ return this.modelManager.getProviders();
26524
+ } catch (error) {
26525
+ this.log(`Failed to get providers: ${error}`, "error");
26526
+ return [];
26527
+ }
26528
+ }
24821
26529
  async getSetting(key) {
24822
26530
  if (!this.store) {
24823
26531
  throw new Error("Store not available");
@@ -24908,6 +26616,9 @@ class ExtensionContextImpl {
24908
26616
  }
24909
26617
  return this.memoryManager;
24910
26618
  }
26619
+ async truncateToolResult(content, maxLines, maxSizeKB, maxTokens, saveToFile, truncationSuffix) {
26620
+ return truncateToolResult(content, maxLines, maxSizeKB, maxTokens, saveToFile, truncationSuffix);
26621
+ }
24911
26622
  }
24912
26623
  const CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
24913
26624
  class ExtensionFetcher {
@@ -25036,6 +26747,41 @@ class ExtensionFetcher {
25036
26747
  const extensions = [];
25037
26748
  try {
25038
26749
  const entries = await fs.readdir(extensionsPath, { withFileTypes: true });
26750
+ const rootIndexTs = path.join(extensionsPath, "index.ts");
26751
+ const rootIndexJs = path.join(extensionsPath, "index.js");
26752
+ const rootPackageJson = path.join(extensionsPath, "package.json");
26753
+ let rootIndexFile = null;
26754
+ if (await this.fileExists(rootIndexTs)) {
26755
+ rootIndexFile = rootIndexTs;
26756
+ } else if (await this.fileExists(rootIndexJs)) {
26757
+ rootIndexFile = rootIndexJs;
26758
+ }
26759
+ const hasRootPackageJson = await this.fileExists(rootPackageJson);
26760
+ if (rootIndexFile && hasRootPackageJson) {
26761
+ const metadata = await this.extractMetadataFromLocalFile(rootIndexFile);
26762
+ if (metadata) {
26763
+ const repoName = this.getRepoName(repoUrl);
26764
+ let readmeContent;
26765
+ const readmePath = path.join(extensionsPath, "README.md");
26766
+ try {
26767
+ const content = await fs.readFile(readmePath, "utf-8");
26768
+ if (content.trim()) {
26769
+ readmeContent = content;
26770
+ }
26771
+ } catch {
26772
+ }
26773
+ extensions.push({
26774
+ ...metadata,
26775
+ id: repoName,
26776
+ type: "folder",
26777
+ folder: repoName,
26778
+ repositoryUrl: repoUrl,
26779
+ hasDependencies: true,
26780
+ readmeContent
26781
+ });
26782
+ return extensions;
26783
+ }
26784
+ }
25039
26785
  for (const entry of entries) {
25040
26786
  if (entry.isDirectory()) {
25041
26787
  const indexPathTs = path.join(extensionsPath, entry.name, "index.ts");
@@ -25089,6 +26835,17 @@ class ExtensionFetcher {
25089
26835
  }
25090
26836
  return extensions;
25091
26837
  }
26838
+ getRepoName(repoUrl) {
26839
+ try {
26840
+ const url = new URL(repoUrl);
26841
+ const pathParts = url.pathname.split("/").filter(Boolean);
26842
+ if (pathParts.length >= 2) {
26843
+ return pathParts[1].replace(/\.git$/, "");
26844
+ }
26845
+ } catch {
26846
+ }
26847
+ return repoUrl.replace(/[^a-zA-Z0-9]/g, "-");
26848
+ }
25092
26849
  async fileExists(filePath) {
25093
26850
  try {
25094
26851
  await fs.access(filePath);
@@ -25267,6 +27024,9 @@ class ExtensionManager {
25267
27024
  logger.debug("[Extensions] Repository list changed, refreshing extension cache");
25268
27025
  void this.fetcher.getAvailableExtensions(newRepositories, true);
25269
27026
  }
27027
+ if (oldSettings.fileWatchMode !== newSettings.fileWatchMode) {
27028
+ void this.restartWatchers();
27029
+ }
25270
27030
  }
25271
27031
  addListener(listener) {
25272
27032
  this.listeners.push(listener);
@@ -25571,7 +27331,7 @@ class ExtensionManager {
25571
27331
  };
25572
27332
  const watcher = chokidar.watch(dir, {
25573
27333
  persistent: true,
25574
- usePolling: true,
27334
+ usePolling: shouldUsePolling(dir, this.store.getSettings().fileWatchMode),
25575
27335
  ignoreInitial: true,
25576
27336
  ignored: (filePath) => {
25577
27337
  const basename = path.basename(filePath);
@@ -25655,6 +27415,25 @@ class ExtensionManager {
25655
27415
  logger.debug(`[Extensions] Stopped watching project extensions: ${projectDir}`);
25656
27416
  }
25657
27417
  }
27418
+ async restartWatchers() {
27419
+ const projectDirs = Array.from(this.projectWatchers.keys());
27420
+ await this.stopGlobalWatcher();
27421
+ for (const dir of projectDirs) {
27422
+ await this.stopProjectWatcher(dir);
27423
+ }
27424
+ await this.startHotReloadWatcher();
27425
+ for (const dir of projectDirs) {
27426
+ const projectExtensionsDir = path.join(dir, AIDER_DESK_EXTENSIONS_DIR);
27427
+ const watcher = await this.setupWatcherForDir(projectExtensionsDir, async () => {
27428
+ logger.debug(`[Extensions] Project extensions changed for ${dir}, reloading...`);
27429
+ await this.unloadExtensionsForDir(projectExtensionsDir);
27430
+ await this.loadExtensionsForDir(projectExtensionsDir);
27431
+ });
27432
+ if (watcher) {
27433
+ this.projectWatchers.set(dir, watcher);
27434
+ }
27435
+ }
27436
+ }
25658
27437
  static TOOL_NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
25659
27438
  validateToolDefinition(tool) {
25660
27439
  const errors = [];
@@ -26009,7 +27788,7 @@ class ExtensionManager {
26009
27788
  }
26010
27789
  collectedSkills.push({
26011
27790
  ...skill,
26012
- location: "extension"
27791
+ location: skill.location || "extension"
26013
27792
  });
26014
27793
  }
26015
27794
  } catch (error) {
@@ -26350,12 +28129,19 @@ class ExtensionManager {
26350
28129
  } else if (extension.type === "folder" && extension.folder) {
26351
28130
  const repoDir = await this.fetcher.ensureRepoCloned(repositoryUrl);
26352
28131
  const extensionsPath = this.fetcher.getExtensionsPath(repositoryUrl, repoDir);
26353
- const sourcePath = path.join(extensionsPath, extension.folder);
26354
28132
  const targetPath = path.join(targetDir, extension.folder);
28133
+ let sourcePath = path.join(extensionsPath, extension.folder);
28134
+ if (!await this.fileExists(sourcePath)) {
28135
+ sourcePath = extensionsPath;
28136
+ }
26355
28137
  if (!await this.fileExists(sourcePath)) {
26356
28138
  throw new Error(`Extension folder not found in repository: ${extension.folder}`);
26357
28139
  }
26358
28140
  await fs.cp(sourcePath, targetPath, { recursive: true });
28141
+ const gitDir = path.join(targetPath, ".git");
28142
+ if (await this.fileExists(gitDir)) {
28143
+ await fs.rm(gitDir, { recursive: true, force: true });
28144
+ }
26359
28145
  if (extension.hasDependencies) {
26360
28146
  logger.debug(`[Extensions] Installing dependencies for ${extension.name}...`);
26361
28147
  await this.installDependencies(targetPath);
@@ -26480,12 +28266,19 @@ class ExtensionManager {
26480
28266
  } else if (extension.type === "folder" && extension.folder) {
26481
28267
  const repoDir = await this.fetcher.ensureRepoCloned(repositoryUrl);
26482
28268
  const extensionsPath = this.fetcher.getExtensionsPath(repositoryUrl, repoDir);
26483
- const sourcePath = path.join(extensionsPath, extension.folder);
26484
28269
  const targetPath = path.join(targetDir, extension.folder);
28270
+ let sourcePath = path.join(extensionsPath, extension.folder);
28271
+ if (!await this.fileExists(sourcePath)) {
28272
+ sourcePath = extensionsPath;
28273
+ }
26485
28274
  if (!await this.fileExists(sourcePath)) {
26486
28275
  throw new Error(`Extension folder not found in repository: ${extension.folder}`);
26487
28276
  }
26488
28277
  await fs.cp(sourcePath, targetPath, { recursive: true });
28278
+ const gitDir = path.join(targetPath, ".git");
28279
+ if (await this.fileExists(gitDir)) {
28280
+ await fs.rm(gitDir, { recursive: true, force: true });
28281
+ }
26489
28282
  const packageJsonPath = path.join(targetPath, "package.json");
26490
28283
  if (await this.fileExists(packageJsonPath)) {
26491
28284
  logger.debug(`[Extensions] Installing dependencies for ${extension.name}...`);
@@ -26535,7 +28328,7 @@ class ExtensionManager {
26535
28328
  if (result && typeof result === "object") {
26536
28329
  const partialEvent = result;
26537
28330
  currentEvent = { ...currentEvent, ...partialEvent };
26538
- if ("blocked" in currentEvent && currentEvent.blocked === true) {
28331
+ if ("blocked" in currentEvent && currentEvent.blocked) {
26539
28332
  logger.debug(`[Extensions] Event '${String(eventName)}' blocked by extension '${metadata.name}'`);
26540
28333
  break;
26541
28334
  }
@@ -26559,7 +28352,7 @@ class ExtensionManager {
26559
28352
  }
26560
28353
  }
26561
28354
  class EventsHandler {
26562
- constructor(projectManager, store, mcpManager, versionsManager, modelManager, telemetryManager, dataManager, terminalManager, cloudflareTunnelManager, eventManager, agentProfileManager, memoryManager, extensionManager, proxyManager, windowManager) {
28355
+ constructor(projectManager, store, mcpManager, versionsManager, modelManager, telemetryManager, dataManager, terminalManager, cloudflareTunnelManager, eventManager, agentProfileManager, memoryManager, extensionManager, proxyManager, promptsManager, windowManager) {
26563
28356
  this.projectManager = projectManager;
26564
28357
  this.store = store;
26565
28358
  this.mcpManager = mcpManager;
@@ -26574,6 +28367,7 @@ class EventsHandler {
26574
28367
  this.memoryManager = memoryManager;
26575
28368
  this.extensionManager = extensionManager;
26576
28369
  this.proxyManager = proxyManager;
28370
+ this.promptsManager = promptsManager;
26577
28371
  this.windowManager = windowManager;
26578
28372
  }
26579
28373
  loadSettings() {
@@ -26587,6 +28381,8 @@ class EventsHandler {
26587
28381
  this.telemetryManager.settingsChanged(oldSettings, newSettings);
26588
28382
  void this.memoryManager.settingsChanged(oldSettings, newSettings);
26589
28383
  this.extensionManager.settingsChanged(oldSettings, newSettings);
28384
+ void this.agentProfileManager.settingsChanged(oldSettings, newSettings);
28385
+ void this.promptsManager.settingsChanged(oldSettings, newSettings);
26590
28386
  this.eventManager.sendSettingsUpdated(newSettings);
26591
28387
  return this.store.getSettings();
26592
28388
  }
@@ -26704,8 +28500,8 @@ class EventsHandler {
26704
28500
  const removedIds = await this.projectManager.getProject(baseDir).getTask(taskId)?.removeMessagesUpTo(messageId) ?? [];
26705
28501
  this.eventManager.sendTaskMessageRemoved(baseDir, taskId, removedIds);
26706
28502
  }
26707
- async redoUserPrompt(baseDir, taskId, messageId, mode, updatedPrompt) {
26708
- void this.projectManager.getProject(baseDir).getTask(taskId)?.redoUserPrompt(messageId, mode, updatedPrompt);
28503
+ async redoUserPrompt(baseDir, taskId, messageId, mode, updatedPrompt, updatedImages) {
28504
+ void this.projectManager.getProject(baseDir).getTask(taskId)?.redoUserPrompt(messageId, mode, updatedPrompt, updatedImages);
26709
28505
  }
26710
28506
  async resumeTask(baseDir, taskId) {
26711
28507
  void this.projectManager.getProject(baseDir).getTask(taskId)?.resumeTask();
@@ -26716,6 +28512,19 @@ class EventsHandler {
26716
28512
  await task.compactConversation(mode, customInstructions);
26717
28513
  }
26718
28514
  }
28515
+ async smartCompactConversation(baseDir, taskId) {
28516
+ const task = this.projectManager.getProject(baseDir).getTask(taskId);
28517
+ if (task) {
28518
+ await task.smartCompactConversation();
28519
+ }
28520
+ }
28521
+ async undoContextChange(baseDir, taskId) {
28522
+ const task = this.projectManager.getProject(baseDir).getTask(taskId);
28523
+ if (task) {
28524
+ return task.undoContextChange();
28525
+ }
28526
+ return false;
28527
+ }
26719
28528
  async handoffConversation(baseDir, taskId, focus) {
26720
28529
  const task = this.projectManager.getProject(baseDir).getTask(taskId);
26721
28530
  if (!task) {
@@ -26800,8 +28609,8 @@ class EventsHandler {
26800
28609
  applyEdits(baseDir, taskId, edits) {
26801
28610
  this.projectManager.getProject(baseDir).getTask(taskId)?.applyEdits(edits);
26802
28611
  }
26803
- async runPrompt(baseDir, taskId, prompt, mode) {
26804
- return this.projectManager.getProject(baseDir).getTask(taskId)?.runPrompt(prompt, mode) || [];
28612
+ async runPrompt(baseDir, taskId, prompt, mode, images) {
28613
+ return this.projectManager.getProject(baseDir).getTask(taskId)?.runPrompt(prompt, mode, true, void 0, true, images) || [];
26805
28614
  }
26806
28615
  async savePrompt(baseDir, taskId, prompt) {
26807
28616
  return this.projectManager.getProject(baseDir).getTask(taskId)?.savePromptOnly(prompt);
@@ -27554,8 +29363,9 @@ const registerAllHelpers = () => {
27554
29363
  registerFormattingHelpers();
27555
29364
  };
27556
29365
  class PromptsManager {
27557
- constructor(extensionManager, defaultTemplatesDir = AIDER_DESK_DEFAULT_PROMPTS_DIR, globalPromptsDir = AIDER_DESK_GLOBAL_PROMPTS_DIR) {
29366
+ constructor(extensionManager, store, defaultTemplatesDir = AIDER_DESK_DEFAULT_PROMPTS_DIR, globalPromptsDir = AIDER_DESK_GLOBAL_PROMPTS_DIR) {
27558
29367
  this.extensionManager = extensionManager;
29368
+ this.store = store;
27559
29369
  this.defaultTemplatesDir = defaultTemplatesDir;
27560
29370
  this.globalPromptsDir = globalPromptsDir;
27561
29371
  registerAllHelpers();
@@ -27649,7 +29459,7 @@ class PromptsManager {
27649
29459
  }
27650
29460
  const watcher = chokidar.watch(this.globalPromptsDir, {
27651
29461
  persistent: true,
27652
- usePolling: true,
29462
+ usePolling: shouldUsePolling(this.globalPromptsDir, this.store.getSettings().fileWatchMode),
27653
29463
  ignoreInitial: true
27654
29464
  });
27655
29465
  const debouncedReload = debounce(async () => {
@@ -27674,7 +29484,7 @@ class PromptsManager {
27674
29484
  await this.compileProjectTemplates(projectDir);
27675
29485
  const watcher = chokidar.watch(projectPromptsDir, {
27676
29486
  persistent: true,
27677
- usePolling: true,
29487
+ usePolling: shouldUsePolling(projectPromptsDir, this.store.getSettings().fileWatchMode),
27678
29488
  ignoreInitial: true
27679
29489
  });
27680
29490
  const debouncedReload = debounce(async () => {
@@ -27703,6 +29513,20 @@ class PromptsManager {
27703
29513
  this.projectTemplatesCache.clear();
27704
29514
  logger.info("PromptsManager disposed");
27705
29515
  }
29516
+ async settingsChanged(oldSettings, newSettings) {
29517
+ if (oldSettings.fileWatchMode === newSettings.fileWatchMode) {
29518
+ return;
29519
+ }
29520
+ const watchedProjects = Array.from(this.watchers.keys()).filter((k) => k !== "global");
29521
+ for (const watcher of this.watchers.values()) {
29522
+ await watcher.close();
29523
+ }
29524
+ this.watchers.clear();
29525
+ await this.setupGlobalWatcher();
29526
+ for (const projectDir of watchedProjects) {
29527
+ await this.watchProject(projectDir);
29528
+ }
29529
+ }
27706
29530
  async render(name, data, projectDir, task) {
27707
29531
  const projectTemplates = this.projectTemplatesCache.get(projectDir);
27708
29532
  const projectTemplate = projectTemplates?.get(name);
@@ -27728,7 +29552,7 @@ class PromptsManager {
27728
29552
  }
27729
29553
  return prompt;
27730
29554
  }
27731
- calculateToolPermissions = (settings, agentProfile, autoApprove) => {
29555
+ calculateToolPermissions = (settings, agentProfile, autonomyMode) => {
27732
29556
  const { usePowerTools = false, useMemoryTools = false, useSkillsTools = false } = agentProfile;
27733
29557
  const memoryEnabled = settings.memory.enabled && useMemoryTools;
27734
29558
  const isAllowed = (tool) => agentProfile.toolApprovals[tool] !== ToolApprovalState.Never;
@@ -27757,11 +29581,12 @@ class PromptsManager {
27757
29581
  skills: {
27758
29582
  allowed: useSkillsTools && isAllowed(`${SKILLS_TOOL_GROUP_NAME}${TOOL_GROUP_NAME_SEPARATOR}${SKILLS_TOOL_ACTIVATE_SKILL}`)
27759
29583
  },
27760
- autoApprove
29584
+ autonomyMode
27761
29585
  };
27762
29586
  };
27763
- getSystemPrompt = async (settings, task, agentProfile, autoApprove = task.task.autoApprove ?? false, additionalInstructions) => {
27764
- const toolPermissions = this.calculateToolPermissions(settings, agentProfile, autoApprove);
29587
+ getSystemPrompt = async (settings, task, agentProfile, autonomyMode, additionalInstructions) => {
29588
+ const effectiveAutonomyMode = autonomyMode ?? task.task.autonomyMode ?? DEFAULT_AUTONOMY_MODE;
29589
+ const toolPermissions = this.calculateToolPermissions(settings, agentProfile, effectiveAutonomyMode);
27765
29590
  toolPermissions.powerTools.anyEnabled = Object.values(toolPermissions.powerTools).some((v) => v);
27766
29591
  const rulesFiles = await this.getRulesContent(task, agentProfile);
27767
29592
  const customInstructions = [agentProfile.customInstructions, additionalInstructions].filter(Boolean).join("\n\n").trim();
@@ -28170,12 +29995,12 @@ const initManagers = async (store, windowManager) => {
28170
29995
  extensionManager.init().catch((error) => {
28171
29996
  logger.error("[Extensions] Extension system initialization failed, continuing without extensions:", error);
28172
29997
  });
28173
- const promptsManager = new PromptsManager(extensionManager);
29998
+ const promptsManager = new PromptsManager(extensionManager, store);
28174
29999
  promptsManager.init().catch((error) => {
28175
30000
  logger.error("[Prompts] Prompts system initialization failed:", error);
28176
30001
  });
28177
30002
  const worktreeManager = new WorktreeManager();
28178
- const agentProfileManager = new AgentProfileManager(eventManager, extensionManager);
30003
+ const agentProfileManager = new AgentProfileManager(eventManager, extensionManager, store);
28179
30004
  agentProfileManager.init().catch((error) => {
28180
30005
  logger.error("[AgentProfile] Agent profile system initialization failed:", error);
28181
30006
  });
@@ -28212,6 +30037,7 @@ const initManagers = async (store, windowManager) => {
28212
30037
  memoryManager,
28213
30038
  extensionManager,
28214
30039
  proxyManager,
30040
+ promptsManager,
28215
30041
  windowManager
28216
30042
  );
28217
30043
  const serverController = new ServerController(httpServer, projectManager, eventsHandler, store, pythonInstaller);
@@ -28760,6 +30586,16 @@ const migrateSettingsV18toV19 = (settings) => {
28760
30586
  }
28761
30587
  };
28762
30588
  };
30589
+ const migrateSettingsV19toV20 = (settings) => {
30590
+ const oldThreshold = settings.taskSettings?.contextCompactingThreshold;
30591
+ return {
30592
+ ...settings,
30593
+ taskSettings: {
30594
+ ...settings.taskSettings,
30595
+ contextCompactingThreshold: typeof oldThreshold === "number" ? { percentage: oldThreshold, tokens: 1e5 } : oldThreshold || { percentage: 90, tokens: 1e5 }
30596
+ }
30597
+ };
30598
+ };
28763
30599
  const DEFAULT_SETTINGS = {
28764
30600
  language: "en",
28765
30601
  startupMode: ProjectStartMode.Empty,
@@ -28820,7 +30656,7 @@ const DEFAULT_SETTINGS = {
28820
30656
  autoGenerateTaskName: true,
28821
30657
  showTaskStateActions: true,
28822
30658
  worktreeSymlinkFolders: ["node_modules", "vendor", "__pycache__", ".venv", "venv"],
28823
- contextCompactingThreshold: 0,
30659
+ contextCompactingThreshold: { percentage: 90, tokens: 1e5 },
28824
30660
  contextCompactionType: ContextCompactionType.Compact,
28825
30661
  defaultWorkingMode: "local",
28826
30662
  worktreeBranchPrefix: "aider-desk/task/",
@@ -28833,12 +30669,13 @@ const DEFAULT_SETTINGS = {
28833
30669
  proxy: {
28834
30670
  enabled: false,
28835
30671
  url: ""
28836
- }
30672
+ },
30673
+ fileWatchMode: FileWatchMode.Auto
28837
30674
  };
28838
30675
  const compareBaseDirs = (baseDir1, baseDir2) => {
28839
30676
  return normalizeBaseDir(baseDir1) === normalizeBaseDir(baseDir2);
28840
30677
  };
28841
- const CURRENT_SETTINGS_VERSION = 19;
30678
+ const CURRENT_SETTINGS_VERSION = 20;
28842
30679
  class Store {
28843
30680
  // @ts-expect-error expected to be initialized
28844
30681
  store;
@@ -28989,6 +30826,10 @@ class Store {
28989
30826
  settings = migrateSettingsV18toV19(settings);
28990
30827
  settingsVersion = 19;
28991
30828
  }
30829
+ if (settingsVersion === 19) {
30830
+ settings = migrateSettingsV19toV20(settings);
30831
+ settingsVersion = 20;
30832
+ }
28992
30833
  this.store.set("settings", settings);
28993
30834
  this.store.set("openProjects", openProjects || []);
28994
30835
  this.store.set("providers", providers || []);
@@ -29182,4 +31023,5 @@ if (!process.env.VITEST) {
29182
31023
  }
29183
31024
  exports.addProjectsFromEnv = addProjectsFromEnv;
29184
31025
  exports.initializeLangfuseExporter = initializeLangfuseExporter;
31026
+ exports.initializePostHogExporter = initializePostHogExporter;
29185
31027
  exports.logger = logger;