ur-agent 1.65.9 → 1.65.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -56962,13 +56962,19 @@ function normalizeQuestionOption(value) {
56962
56962
  const option = objectValue(value);
56963
56963
  if (!option)
56964
56964
  return null;
56965
- const label = typeof option.label === "string" && option.label.trim() ? option.label.trim() : typeof option.value === "string" && option.value.trim() ? option.value.trim() : "";
56966
- const description = typeof option.description === "string" && option.description.trim() ? option.description.trim() : label;
56967
- if (!label || !description)
56965
+ if (!sameKeys(option, ["label"], ["description", "preview"]))
56968
56966
  return null;
56967
+ if (typeof option.label !== "string" || !option.label.trim())
56968
+ return null;
56969
+ if (option.description !== undefined && (typeof option.description !== "string" || !option.description.trim())) {
56970
+ return null;
56971
+ }
56972
+ if (option.preview !== undefined && typeof option.preview !== "string") {
56973
+ return null;
56974
+ }
56969
56975
  return {
56970
- label,
56971
- description,
56976
+ label: option.label.trim(),
56977
+ ...typeof option.description === "string" ? { description: option.description.trim() } : {},
56972
56978
  ...typeof option.preview === "string" ? { preview: option.preview } : {}
56973
56979
  };
56974
56980
  }
@@ -56988,10 +56994,14 @@ function normalizeQuestion(value, index2) {
56988
56994
  ]);
56989
56995
  if (!questionText || !Array.isArray(question.options))
56990
56996
  return null;
56991
- const options = question.options.map(normalizeQuestionOption).filter((option) => option !== null);
56992
- if (options.length < 2 || options.length > 4)
56997
+ const normalizedOptions = question.options.map(normalizeQuestionOption);
56998
+ if (!normalizedOptions.every((option) => option !== null)) {
56993
56999
  return null;
56994
- const header = typeof question.header === "string" && question.header.trim() ? question.header.trim().slice(0, 12) : headerFromQuestion(questionText, index2);
57000
+ }
57001
+ const options = normalizedOptions;
57002
+ if (options.length < 2 || options.length > 8)
57003
+ return null;
57004
+ const header = typeof question.header === "string" && question.header.trim() ? question.header.trim() : headerFromQuestion(questionText, index2);
56995
57005
  return {
56996
57006
  question: questionText,
56997
57007
  header,
@@ -57295,135 +57305,7 @@ function synthesizeKimiToolCalls(message) {
57295
57305
  m.content = [...kept, ...synthesized];
57296
57306
  m.stop_reason = "tool_use";
57297
57307
  }
57298
- function clarifyHeader(question) {
57299
- const word = question.replace(/[^A-Za-z0-9]+/g, " ").split(/\s+/).find((part) => part && !CLARIFY_HEADER_STOP_WORDS.has(part.toLowerCase()));
57300
- return (word ?? "Options").slice(0, 12);
57301
- }
57302
- function cleanOption(raw) {
57303
- let opt = raw.trim();
57304
- opt = opt.replace(/^[\s"'`*_\-\u2013\u2014]+/, "").replace(/[\s"'`*_.?!,;:]+$/g, "");
57305
- for (let i2 = 0;i2 < 3; i2++) {
57306
- const before = opt;
57307
- opt = opt.replace(OPTION_LEADIN_RE, "").replace(OPTION_QUESTION_LEADIN_RE, "");
57308
- if (opt === before)
57309
- break;
57310
- }
57311
- opt = opt.replace(OPTION_TRAILING_QUALIFIER_RE, "");
57312
- opt = opt.replace(/\b(?:instead|please|etc\.?)$/i, "");
57313
- return opt.replace(/[\s,;:]+$/g, "").trim();
57314
- }
57315
- function splitEnumeration(s) {
57316
- if (!/\bor\b/i.test(s))
57317
- return null;
57318
- const parts = s.split(/\s*,?\s+or\s+|\s*,\s*/gi).map((p) => p.trim()).filter(Boolean);
57319
- return parts.length >= 2 ? parts : null;
57320
- }
57321
- function extractClarifyOptions(text) {
57322
- const trimmed = text.trim();
57323
- if (!trimmed)
57324
- return [];
57325
- const clauses = trimmed.split(/(?<=[?.!;])\s+/).map((c3) => c3.trim()).filter(Boolean);
57326
- const options = [];
57327
- for (const clause of clauses) {
57328
- const stripped = clause.replace(OPTION_LEADIN_RE, "");
57329
- const candidates = splitEnumeration(stripped) ?? [stripped];
57330
- for (const candidate of candidates) {
57331
- const cleaned = cleanOption(candidate);
57332
- if (!cleaned || cleaned.length > 120)
57333
- continue;
57334
- if (OPTION_CATCHALL_RE.test(cleaned))
57335
- continue;
57336
- options.push(cleaned);
57337
- }
57338
- }
57339
- const seen = new Set;
57340
- const unique = [];
57341
- for (const opt of options) {
57342
- const key = opt.toLowerCase();
57343
- if (seen.has(key))
57344
- continue;
57345
- seen.add(key);
57346
- unique.push(opt);
57347
- if (unique.length === 4)
57348
- break;
57349
- }
57350
- return unique;
57351
- }
57352
- function buildClarifyQuestion(segment) {
57353
- const s = segment.replace(/^\s*(?:\d+[.)]|[-*\u2022])\s+/, "").replace(/\*\*/g, "").trim();
57354
- const qEnd = s.indexOf("?");
57355
- if (qEnd === -1)
57356
- return null;
57357
- const question = s.slice(0, qEnd + 1).trim();
57358
- const remainder = s.slice(qEnd + 1).trim();
57359
- let options = extractClarifyOptions(remainder);
57360
- if (options.length < 2) {
57361
- const inner = question.replace(/\?+$/, "");
57362
- const lastClause = (inner.split(/(?<=[.!;])\s+/).pop() ?? inner).trim();
57363
- if (/\bor\b/i.test(lastClause)) {
57364
- const inline = extractClarifyOptions(lastClause + ".");
57365
- if (inline.length >= 2)
57366
- options = inline;
57367
- }
57368
- }
57369
- if (options.length < 2)
57370
- return null;
57371
- return {
57372
- question,
57373
- header: clarifyHeader(question),
57374
- options: options.map((label) => ({ label, description: label }))
57375
- };
57376
- }
57377
- function parseClarifyingQuestions(text, options = {}) {
57378
- if (!hasTool(options.availableToolNames, "AskUserQuestion"))
57379
- return null;
57380
- const trimmed = text.trim();
57381
- if (!trimmed || trimmed.length > CLARIFY_MAX_LEN)
57382
- return null;
57383
- if (trimmed.includes("```") || trimmed.includes("<|"))
57384
- return null;
57385
- const lines = trimmed.split(`
57386
- `).map((l) => l.trim()).filter(Boolean);
57387
- if (lines.length === 0 || !lines[lines.length - 1].endsWith("?"))
57388
- return null;
57389
- const isListItem = (l) => /^(?:\d+[.)]|[-*\u2022])\s+/.test(l);
57390
- let segments;
57391
- if (lines.some(isListItem)) {
57392
- segments = [];
57393
- for (const line of lines) {
57394
- if (isListItem(line) || segments.length === 0)
57395
- segments.push(line);
57396
- else
57397
- segments[segments.length - 1] += " " + line;
57398
- }
57399
- } else {
57400
- segments = trimmed.split(/\n{2,}/).map((s) => s.replace(/\n/g, " ").trim()).filter((s) => s.includes("?"));
57401
- if (segments.length === 0)
57402
- segments = [trimmed.replace(/\n/g, " ")];
57403
- }
57404
- const questions = [];
57405
- const seenQuestions = new Set;
57406
- for (const segment of segments) {
57407
- if (questions.length === 4)
57408
- break;
57409
- const built = buildClarifyQuestion(segment);
57410
- if (!built)
57411
- continue;
57412
- const key = built.question.toLowerCase();
57413
- if (seenQuestions.has(key))
57414
- continue;
57415
- seenQuestions.add(key);
57416
- questions.push(built);
57417
- }
57418
- if (questions.length === 0)
57419
- return null;
57420
- return {
57421
- id: `clarify_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
57422
- name: "AskUserQuestion",
57423
- input: { questions }
57424
- };
57425
- }
57426
- var SECTION_RE, CALL_RE, STRAY_RE, KimiToolCallParseError, CLARIFY_MAX_LEN = 2000, OPTION_LEADIN_RE, OPTION_QUESTION_LEADIN_RE, OPTION_CATCHALL_RE, OPTION_TRAILING_QUALIFIER_RE, CLARIFY_HEADER_STOP_WORDS;
57308
+ var SECTION_RE, CALL_RE, STRAY_RE, KimiToolCallParseError;
57427
57309
  var init_kimiToolCalls = __esm(() => {
57428
57310
  init_json();
57429
57311
  SECTION_RE = /<\|tool_calls_section_begin\|>([\s\S]*?)<\|tool_calls_section_end\|>/g;
@@ -57436,41 +57318,6 @@ var init_kimiToolCalls = __esm(() => {
57436
57318
  Object.setPrototypeOf(this, new.target.prototype);
57437
57319
  }
57438
57320
  };
57439
- OPTION_LEADIN_RE = /^(?:and\s+)?(?:or|also|just|maybe|perhaps|either|alternatively|optionally|well)\b[\s,:]*/i;
57440
- OPTION_QUESTION_LEADIN_RE = /^(?:(?:do|would|should|could|can|will)\s+(?:you|we|i)\s+(?:want|prefer|like|need|use|go\s+with|have)?|you\s+(?:could|can|might|may)|i\s+(?:could|can|would|might)|we\s+(?:could|can|might)|want|prefer|pick|choose|use|go\s+with|how\s+about|what\s+about)\b[\s,:]*/i;
57441
- OPTION_CATCHALL_RE = /^(?:(?:or\s+)?(?:something|anything|someone)\s+else|other(?:\s+option)?|none(?:\s+of\s+(?:the\s+)?(?:above|these))?|no|nope|not\s+sure|any(?:thing)?|else|you\s+(?:choose|decide|pick)|your\s+(?:call|choice))$/i;
57442
- OPTION_TRAILING_QUALIFIER_RE = /\s+(?:is|are|would\s+be|seems?|sounds?|looks?)\s+(?:the\s+)?(?:simplest|easiest|best|recommended|fastest|cleanest|most\s+\w+)(?:\s+(?:option|choice|approach))?$/i;
57443
- CLARIFY_HEADER_STOP_WORDS = new Set([
57444
- "a",
57445
- "about",
57446
- "also",
57447
- "an",
57448
- "and",
57449
- "are",
57450
- "be",
57451
- "can",
57452
- "could",
57453
- "do",
57454
- "does",
57455
- "for",
57456
- "i",
57457
- "is",
57458
- "or",
57459
- "should",
57460
- "support",
57461
- "that",
57462
- "the",
57463
- "this",
57464
- "to",
57465
- "want",
57466
- "we",
57467
- "what",
57468
- "which",
57469
- "with",
57470
- "without",
57471
- "would",
57472
- "you"
57473
- ]);
57474
57321
  });
57475
57322
 
57476
57323
  // src/services/api/ollama.ts
@@ -58115,11 +57962,6 @@ async function* streamURHQEvents(response, params, controller, requestId, textTo
58115
57962
  if (textToolFallbackAllowed) {
58116
57963
  const kimiParsed = parseKimiToolCalls(text);
58117
57964
  textToolCalls.push(...kimiParsed.toolCalls);
58118
- if (toolCalls.length === 0 && textToolCalls.length === 0) {
58119
- const clarify = parseClarifyingQuestions(text, { availableToolNames });
58120
- if (clarify)
58121
- textToolCalls.push(clarify);
58122
- }
58123
57965
  }
58124
57966
  const normalizedToolUses = normalizeOllamaToolUses(toolCalls, textToolCalls, availableToolNames, "Ollama stream");
58125
57967
  for (const call of normalizedToolUses) {
@@ -58260,9 +58102,6 @@ function ollamaResponseToURHQMessage(response, params, textToolFallbackAllowed)
58260
58102
  }) : { text: rawText, toolCalls: [] };
58261
58103
  const text = parsedText.text;
58262
58104
  const textToolCalls = [...parsedText.toolCalls];
58263
- const clarifyCall = textToolFallbackAllowed && structured.length === 0 && textToolCalls.length === 0 ? parseClarifyingQuestions(text, { availableToolNames }) : null;
58264
- if (clarifyCall)
58265
- textToolCalls.push(clarifyCall);
58266
58105
  const normalizedToolUses = normalizeOllamaToolUses(structured, textToolCalls, availableToolNames, "Ollama response");
58267
58106
  if (thinking) {
58268
58107
  content.push({
@@ -75597,7 +75436,7 @@ var init_auth = __esm(() => {
75597
75436
 
75598
75437
  // src/utils/userAgent.ts
75599
75438
  function getURCodeUserAgent() {
75600
- return `ur/${"1.65.9"}`;
75439
+ return `ur/${"1.65.11"}`;
75601
75440
  }
75602
75441
 
75603
75442
  // src/utils/workloadContext.ts
@@ -75619,7 +75458,7 @@ function getUserAgent() {
75619
75458
  const clientApp = process.env.UR_AGENT_SDK_CLIENT_APP ? `, client-app/${process.env.UR_AGENT_SDK_CLIENT_APP}` : "";
75620
75459
  const workload = getWorkload();
75621
75460
  const workloadSuffix = workload ? `, workload/${workload}` : "";
75622
- return `ur-cli/${"1.65.9"} (${process.env.USER_TYPE}, ${process.env.UR_CODE_ENTRYPOINT ?? "cli"}${agentSdkVersion}${clientApp}${workloadSuffix})`;
75461
+ return `ur-cli/${"1.65.11"} (${process.env.USER_TYPE}, ${process.env.UR_CODE_ENTRYPOINT ?? "cli"}${agentSdkVersion}${clientApp}${workloadSuffix})`;
75623
75462
  }
75624
75463
  function getMCPUserAgent() {
75625
75464
  const parts = [];
@@ -75633,7 +75472,7 @@ function getMCPUserAgent() {
75633
75472
  parts.push(`client-app/${process.env.UR_AGENT_SDK_CLIENT_APP}`);
75634
75473
  }
75635
75474
  const suffix = parts.length > 0 ? ` (${parts.join(", ")})` : "";
75636
- return `ur/${"1.65.9"}${suffix}`;
75475
+ return `ur/${"1.65.11"}${suffix}`;
75637
75476
  }
75638
75477
  function getWebFetchUserAgent() {
75639
75478
  return `UR-User (${getURCodeUserAgent()})`;
@@ -75771,7 +75610,7 @@ var init_user = __esm(() => {
75771
75610
  deviceId,
75772
75611
  sessionId: getSessionId(),
75773
75612
  email: getEmail(),
75774
- appVersion: "1.65.9",
75613
+ appVersion: "1.65.11",
75775
75614
  platform: getHostPlatformForAnalytics(),
75776
75615
  organizationUuid,
75777
75616
  accountUuid,
@@ -83971,7 +83810,7 @@ var init_metadata = __esm(() => {
83971
83810
  COMPOUND_OPERATOR_REGEX = /\s*(?:&&|\|\||[;|])\s*/;
83972
83811
  WHITESPACE_REGEX = /\s+/;
83973
83812
  getVersionBase = memoize_default(() => {
83974
- const match = "1.65.9".match(/^\d+\.\d+\.\d+(?:-[a-z]+)?/);
83813
+ const match = "1.65.11".match(/^\d+\.\d+\.\d+(?:-[a-z]+)?/);
83975
83814
  return match ? match[0] : undefined;
83976
83815
  });
83977
83816
  buildEnvContext = memoize_default(async () => {
@@ -84011,7 +83850,7 @@ var init_metadata = __esm(() => {
84011
83850
  isGithubAction: isEnvTruthy(process.env.GITHUB_ACTIONS),
84012
83851
  isURCodeAction: isEnvTruthy(process.env.UR_CODE_ACTION),
84013
83852
  isURAiAuth: isURAISubscriber(),
84014
- version: "1.65.9",
83853
+ version: "1.65.11",
84015
83854
  versionBase: getVersionBase(),
84016
83855
  buildTime: "",
84017
83856
  deploymentEnvironment: env2.detectDeploymentEnvironment(),
@@ -84681,7 +84520,7 @@ function initialize1PEventLogging() {
84681
84520
  const platform2 = getPlatform();
84682
84521
  const attributes = {
84683
84522
  [import_semantic_conventions4.ATTR_SERVICE_NAME]: "ur",
84684
- [import_semantic_conventions4.ATTR_SERVICE_VERSION]: "1.65.9"
84523
+ [import_semantic_conventions4.ATTR_SERVICE_VERSION]: "1.65.11"
84685
84524
  };
84686
84525
  if (platform2 === "wsl") {
84687
84526
  const wslVersion = getWslVersion();
@@ -84709,7 +84548,7 @@ function initialize1PEventLogging() {
84709
84548
  })
84710
84549
  ]
84711
84550
  });
84712
- firstPartyEventLogger = firstPartyEventLoggerProvider.getLogger("com.urhq.ur.events", "1.65.9");
84551
+ firstPartyEventLogger = firstPartyEventLoggerProvider.getLogger("com.urhq.ur.events", "1.65.11");
84713
84552
  }
84714
84553
  async function reinitialize1PEventLoggingIfConfigChanged() {
84715
84554
  if (!is1PEventLoggingEnabled() || !firstPartyEventLoggerProvider) {
@@ -86756,12 +86595,13 @@ function extractMcpToolDisplayName(userFacingName) {
86756
86595
  var init_mcpStringUtils = () => {};
86757
86596
 
86758
86597
  // src/tools/AgentTool/constants.ts
86759
- var AGENT_TOOL_NAME = "Agent", LEGACY_AGENT_TOOL_NAME = "Task", VERIFICATION_AGENT_TYPE = "verification", ONE_SHOT_BUILTIN_AGENT_TYPES;
86598
+ var AGENT_TOOL_NAME = "Agent", LEGACY_AGENT_TOOL_NAME = "Task", VERIFICATION_AGENT_TYPE = "verification", READ_ONLY_PLAN_AGENT_TYPES, ONE_SHOT_BUILTIN_AGENT_TYPES;
86760
86599
  var init_constants2 = __esm(() => {
86761
- ONE_SHOT_BUILTIN_AGENT_TYPES = new Set([
86600
+ READ_ONLY_PLAN_AGENT_TYPES = new Set([
86762
86601
  "Explore",
86763
86602
  "Plan"
86764
86603
  ]);
86604
+ ONE_SHOT_BUILTIN_AGENT_TYPES = READ_ONLY_PLAN_AGENT_TYPES;
86765
86605
  });
86766
86606
 
86767
86607
  // src/tools/TaskOutputTool/constants.ts
@@ -94596,7 +94436,7 @@ function formatAgentTrendReport(report = buildAgentTrendReport()) {
94596
94436
  function formatA2AAgentCard(options = {}, pretty = true) {
94597
94437
  return JSON.stringify(buildA2AAgentCard(options), null, pretty ? 2 : 0);
94598
94438
  }
94599
- var urVersion = "1.65.9", researchSnapshotDate = "2026-07-15", coverage, priorityRoadmap;
94439
+ var urVersion = "1.65.11", researchSnapshotDate = "2026-07-15", coverage, priorityRoadmap;
94600
94440
  var init_trends = __esm(() => {
94601
94441
  init_a2aCardSignature();
94602
94442
  coverage = [
@@ -97399,7 +97239,7 @@ function getAttributionHeader(fingerprint) {
97399
97239
  if (!isAttributionHeaderEnabled()) {
97400
97240
  return "";
97401
97241
  }
97402
- const version2 = `${"1.65.9"}.${fingerprint}`;
97242
+ const version2 = `${"1.65.11"}.${fingerprint}`;
97403
97243
  const entrypoint = process.env.UR_CODE_ENTRYPOINT ?? "unknown";
97404
97244
  const cch = "";
97405
97245
  const workload = getWorkload();
@@ -97920,6 +97760,10 @@ function getWriteToolDescription() {
97920
97760
 
97921
97761
  Usage:
97922
97762
  - This tool will overwrite the existing file if there is one at the provided path.${getPreReadInstruction()}
97763
+ - Every call must include both required fields in the same structured invocation: \`file_path\` and the complete literal file text in \`content\`.
97764
+ - Put the actual file text inside \`content\`; surrounding assistant prose is never copied into the file. Never call Write with only a path, and never invent or recover missing content from prose.
97765
+ - An empty \`content\` string creates an empty file. Use it only when an empty file is genuinely intended.
97766
+ - A file is not created or updated until this tool returns a success result. If validation fails, correct the arguments and retry; do not claim the write succeeded.
97923
97767
  - Prefer the Edit tool for modifying existing files \u2014 it only sends the diff. Only use this tool to create new files or for complete rewrites.
97924
97768
  - NEVER create documentation files (*.md) or README files unless explicitly requested by the User.
97925
97769
  - Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.`;
@@ -144901,6 +144745,8 @@ function getExploreSystemPrompt() {
144901
144745
  const embedded = hasEmbeddedSearchTools();
144902
144746
  const globGuidance = embedded ? `- Use \`find\` via ${BASH_TOOL_NAME} for broad file pattern matching` : `- Use ${GLOB_TOOL_NAME} for broad file pattern matching`;
144903
144747
  const grepGuidance = embedded ? `- Use \`grep\` via ${BASH_TOOL_NAME} for searching file contents with regex` : `- Use ${GREP_TOOL_NAME} for searching file contents with regex`;
144748
+ const shellGuidance = embedded ? `- Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find, grep, cat, head, tail)
144749
+ - NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, package installation, redirects, or any file creation/modification` : "";
144904
144750
  return `You are a file search specialist for Ur. You excel at thoroughly navigating and exploring codebases.
144905
144751
 
144906
144752
  === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
@@ -144924,8 +144770,7 @@ Guidelines:
144924
144770
  ${globGuidance}
144925
144771
  ${grepGuidance}
144926
144772
  - Use ${FILE_READ_TOOL_NAME} when you know the specific file path you need to read
144927
- - Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find${embedded ? ", grep" : ""}, cat, head, tail)
144928
- - NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
144773
+ ${shellGuidance}
144929
144774
  - Adapt your search approach based on the thoroughness level specified by the caller
144930
144775
  - Communicate your final report directly as a regular message - do NOT attempt to create files
144931
144776
 
@@ -144945,6 +144790,7 @@ var init_exploreAgent = __esm(() => {
144945
144790
  EXPLORE_AGENT = {
144946
144791
  agentType: "Explore",
144947
144792
  whenToUse: EXPLORE_WHEN_TO_USE,
144793
+ tools: hasEmbeddedSearchTools() ? [BASH_TOOL_NAME, FILE_READ_TOOL_NAME] : [GLOB_TOOL_NAME, GREP_TOOL_NAME, FILE_READ_TOOL_NAME],
144948
144794
  disallowedTools: [
144949
144795
  AGENT_TOOL_NAME,
144950
144796
  EXIT_PLAN_MODE_TOOL_NAME,
@@ -144955,6 +144801,7 @@ var init_exploreAgent = __esm(() => {
144955
144801
  source: "built-in",
144956
144802
  baseDir: "built-in",
144957
144803
  model: process.env.USER_TYPE === "ant" ? "inherit" : "modelH",
144804
+ permissionMode: "dontAsk",
144958
144805
  omitAgentMd: true,
144959
144806
  getSystemPrompt: () => getExploreSystemPrompt()
144960
144807
  };
@@ -144994,9 +144841,58 @@ var init_generalPurposeAgent = __esm(() => {
144994
144841
  };
144995
144842
  });
144996
144843
 
144844
+ // src/tools/TaskCreateTool/constants.ts
144845
+ var TASK_CREATE_TOOL_NAME = "TaskCreate";
144846
+
144847
+ // src/tools/TaskUpdateTool/constants.ts
144848
+ var TASK_UPDATE_TOOL_NAME = "TaskUpdate";
144849
+
144850
+ // src/tools/TodoWriteTool/constants.ts
144851
+ var TODO_WRITE_TOOL_NAME = "TodoWrite";
144852
+
144853
+ // src/constants/planImplementationContract.ts
144854
+ function getApprovedPlanCapabilities(toolUseContext) {
144855
+ const tools = toolUseContext.options.tools;
144856
+ const hasTaskV2 = tools.some((tool) => toolMatchesName(tool, TASK_CREATE_TOOL_NAME)) && tools.some((tool) => toolMatchesName(tool, TASK_UPDATE_TOOL_NAME));
144857
+ const hasTodoWrite = tools.some((tool) => toolMatchesName(tool, TODO_WRITE_TOOL_NAME));
144858
+ const hasAgent = tools.some((tool) => toolMatchesName(tool, AGENT_TOOL_NAME));
144859
+ const activeAgents = toolUseContext.options.agentDefinitions?.activeAgents ?? [];
144860
+ const implementationAgentType = hasAgent ? ["worker", "general-purpose"].find((agentType) => activeAgents.some((agent) => agent.agentType === agentType && agent.source === "built-in")) : undefined;
144861
+ return {
144862
+ taskTool: hasTaskV2 ? "task-v2" : hasTodoWrite ? "todo-write" : "none",
144863
+ ...implementationAgentType ? { implementationAgentType } : {}
144864
+ };
144865
+ }
144866
+ function getApprovedPlanImplementationInstruction(capabilities) {
144867
+ const taskTracking = capabilities.taskTool === "task-v2" ? [
144868
+ `Use one ${TASK_CREATE_TOOL_NAME} call per cohesive, independently verifiable outcome; one umbrella task does not satisfy this requirement. Keep genuinely atomic work whole instead of manufacturing file- or tool-call-level tasks.`,
144869
+ `Emit independent ${TASK_CREATE_TOOL_NAME} calls together (up to 8 per turn), then use ${TASK_UPDATE_TOOL_NAME} to add dependencies once task IDs are known. Leave unrelated tasks unblocked.`
144870
+ ] : capabilities.taskTool === "todo-write" ? [
144871
+ `Use ${TODO_WRITE_TOOL_NAME} to record the complete list with one item per cohesive, independently verifiable outcome; one umbrella item does not satisfy this requirement. Keep genuinely atomic work whole instead of manufacturing file- or tool-call-level items.`,
144872
+ "Order dependent items after their prerequisites, keep every real outcome visible, and update each status from pending to in_progress to completed only as evidence is obtained."
144873
+ ] : [
144874
+ "Use the plan\u2019s numbered Implementation Tasks as the execution checklist, with one cohesive, independently verifiable outcome per item. Keep genuinely atomic work whole; do not invent unavailable task tools."
144875
+ ];
144876
+ const delegation = capabilities.implementationAgentType ? `After the graph is complete, launch up to 8 ready tasks with no conflicting shared mutation per parallel wave through ${AGENT_TOOL_NAME} using subagent_type=${capabilities.implementationAgentType}; continue with later waves as slots free. Give each worker its ${capabilities.taskTool === "task-v2" ? "task ID" : "numbered outcome"}, bounded scope, dependency outputs, completion check, and required verification. Keep dependent tasks and conflicting shared writes sequential; independently verify worker results before completing their tasks.` : "After the graph is complete, execute ready tasks in dependency order and independently verify each result before completing its task.";
144877
+ return [
144878
+ "Before changing the workspace, translate the approved plan into a complete task graph.",
144879
+ ...taskTracking,
144880
+ delegation
144881
+ ].join(`
144882
+ `);
144883
+ }
144884
+ var PLAN_TASK_GRAPH_REQUIREMENT = "Add an **Implementation Tasks** section with one numbered task per cohesive, independently verifiable outcome. Do not collapse separate deliverables into one umbrella task or split atomic work into file/tool-call micro-tasks. For each task, state its completion check, real dependencies, and whether it can run in a parallel worker wave.";
144885
+ var init_planImplementationContract = __esm(() => {
144886
+ init_Tool();
144887
+ init_constants2();
144888
+ });
144889
+
144997
144890
  // src/tools/AgentTool/built-in/planAgent.ts
144998
144891
  function getPlanV2SystemPrompt() {
144999
- const searchToolsHint = hasEmbeddedSearchTools() ? `\`find\`, \`grep\`, and ${FILE_READ_TOOL_NAME}` : `${GLOB_TOOL_NAME}, ${GREP_TOOL_NAME}, and ${FILE_READ_TOOL_NAME}`;
144892
+ const embedded = hasEmbeddedSearchTools();
144893
+ const searchToolsHint = embedded ? `\`find\`, \`grep\`, and ${FILE_READ_TOOL_NAME}` : `${GLOB_TOOL_NAME}, ${GREP_TOOL_NAME}, and ${FILE_READ_TOOL_NAME}`;
144894
+ const shellGuidance = embedded ? ` - Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find, grep, cat, head, tail)
144895
+ - NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, package installation, redirects, or any file creation/modification` : "";
145000
144896
  return `You are a software architect and planning specialist for Ur. Your role is to explore the codebase and design implementation plans.
145001
144897
 
145002
144898
  === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
@@ -145023,8 +144919,7 @@ You will be provided with a set of requirements and optionally a perspective on
145023
144919
  - Understand the current architecture
145024
144920
  - Identify similar features as reference
145025
144921
  - Trace through relevant code paths
145026
- - Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find${hasEmbeddedSearchTools() ? ", grep" : ""}, cat, head, tail)
145027
- - NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
144922
+ ${shellGuidance}
145028
144923
 
145029
144924
  3. **Design Solution**:
145030
144925
  - Create implementation approach based on your assigned perspective
@@ -145038,6 +144933,9 @@ You will be provided with a set of requirements and optionally a perspective on
145038
144933
 
145039
144934
  ## Required Output
145040
144935
 
144936
+ Include an **Implementation Tasks** section before the critical-files list.
144937
+ ${PLAN_TASK_GRAPH_REQUIREMENT}
144938
+
145041
144939
  End your response with:
145042
144940
 
145043
144941
  ### Critical Files for Implementation
@@ -145050,6 +144948,7 @@ REMEMBER: You can ONLY explore and plan. You CANNOT and MUST NOT write, edit, or
145050
144948
  }
145051
144949
  var PLAN_AGENT;
145052
144950
  var init_planAgent = __esm(() => {
144951
+ init_planImplementationContract();
145053
144952
  init_prompt2();
145054
144953
  init_prompt3();
145055
144954
  init_prompt();
@@ -145068,6 +144967,7 @@ var init_planAgent = __esm(() => {
145068
144967
  ],
145069
144968
  source: "built-in",
145070
144969
  tools: EXPLORE_AGENT.tools,
144970
+ permissionMode: "dontAsk",
145071
144971
  baseDir: "built-in",
145072
144972
  model: "inherit",
145073
144973
  omitAgentMd: true,
@@ -145368,7 +145268,7 @@ Use the literal string \`VERDICT: \` followed by exactly one of \`PASS\`, \`FAIL
145368
145268
  // src/tools/AgentTool/builtInAgents.ts
145369
145269
  function areExplorePlanAgentsEnabled() {
145370
145270
  if (false) {}
145371
- return false;
145271
+ return !(isEnvTruthy(process.env.UR_AGENT_SDK_DISABLE_BUILTIN_AGENTS) && getIsNonInteractiveSession());
145372
145272
  }
145373
145273
  function getBuiltInAgents() {
145374
145274
  if (isEnvTruthy(process.env.UR_AGENT_SDK_DISABLE_BUILTIN_AGENTS) && getIsNonInteractiveSession()) {
@@ -145394,7 +145294,6 @@ function getBuiltInAgents() {
145394
145294
  }
145395
145295
  var init_builtInAgents = __esm(() => {
145396
145296
  init_state();
145397
- init_growthbook();
145398
145297
  init_envUtils();
145399
145298
  init_urCodeGuideAgent();
145400
145299
  init_exploreAgent();
@@ -155213,7 +155112,7 @@ var init_projectSafety = __esm(() => {
155213
155112
  function getInstruments() {
155214
155113
  if (instruments)
155215
155114
  return instruments;
155216
- const meter = import_api10.metrics.getMeter("ur-agent.gen_ai", "1.65.9");
155115
+ const meter = import_api10.metrics.getMeter("ur-agent.gen_ai", "1.65.11");
155217
155116
  instruments = {
155218
155117
  operationDuration: meter.createHistogram("gen_ai.client.operation.duration", {
155219
155118
  description: "GenAI operation duration.",
@@ -155311,7 +155210,7 @@ function genAiAgentAttributes() {
155311
155210
  "gen_ai.operation.name": GEN_AI_OPERATION_INVOKE_AGENT,
155312
155211
  "gen_ai.provider.name": "ur",
155313
155212
  "gen_ai.agent.name": "UR-Nexus",
155314
- "gen_ai.agent.version": "1.65.9"
155213
+ "gen_ai.agent.version": "1.65.11"
155315
155214
  };
155316
155215
  }
155317
155216
  function genAiWorkflowAttributes(workflowName) {
@@ -155327,7 +155226,7 @@ function genAiWorkflowAttributes(workflowName) {
155327
155226
  function startGenAiWorkflowSpan(workflowName) {
155328
155227
  const attributes = genAiWorkflowAttributes(workflowName);
155329
155228
  const name = typeof attributes["gen_ai.workflow.name"] === "string" ? `invoke_workflow ${attributes["gen_ai.workflow.name"]}` : GEN_AI_OPERATION_INVOKE_WORKFLOW;
155330
- return import_api10.trace.getTracer("ur-agent.gen_ai", "1.65.9").startSpan(name, { kind: import_api10.SpanKind.INTERNAL, attributes });
155229
+ return import_api10.trace.getTracer("ur-agent.gen_ai", "1.65.11").startSpan(name, { kind: import_api10.SpanKind.INTERNAL, attributes });
155331
155230
  }
155332
155231
  function endGenAiWorkflowSpan(span, options2 = {}) {
155333
155232
  try {
@@ -155365,7 +155264,7 @@ function startGenAiMemorySpan(operation, options2 = {}) {
155365
155264
  if (options2.recordCount !== undefined && Number.isInteger(options2.recordCount) && options2.recordCount >= 0) {
155366
155265
  attributes["gen_ai.memory.record.count"] = options2.recordCount;
155367
155266
  }
155368
- return import_api10.trace.getTracer("ur-agent.gen_ai", "1.65.9").startSpan(operation, { kind: import_api10.SpanKind.INTERNAL, attributes });
155267
+ return import_api10.trace.getTracer("ur-agent.gen_ai", "1.65.11").startSpan(operation, { kind: import_api10.SpanKind.INTERNAL, attributes });
155369
155268
  }
155370
155269
  function endGenAiMemorySpan(span, options2 = {}) {
155371
155270
  try {
@@ -248848,7 +248747,7 @@ function getTelemetryAttributes() {
248848
248747
  attributes["session.id"] = sessionId;
248849
248748
  }
248850
248749
  if (shouldIncludeAttribute("OTEL_METRICS_INCLUDE_VERSION")) {
248851
- attributes["app.version"] = "1.65.9";
248750
+ attributes["app.version"] = "1.65.11";
248852
248751
  }
248853
248752
  const oauthAccount = getOauthAccountInfo();
248854
248753
  if (oauthAccount) {
@@ -265059,11 +264958,11 @@ Preview content is rendered as markdown in a monospace box. Multi-line text with
265059
264958
  html: `
265060
264959
  Preview feature:
265061
264960
  Use the optional \`preview\` field on options when presenting concrete artifacts that users need to visually compare:
265062
- - HTML mockups of UI layouts or components
265063
- - Formatted code snippets showing different implementations
265064
- - Visual comparisons or diagrams
264961
+ - Plain-text or ASCII mockups of UI layouts or components
264962
+ - Inert code snippets showing different implementations
264963
+ - Textual visual comparisons or diagrams
265065
264964
 
265066
- Preview content must be a self-contained HTML fragment (no <html>/<body> wrapper, no <script> or <style> tags \u2014 use inline style attributes instead). Do not use previews for simple preference questions where labels and descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).
264965
+ Preview content is untrusted text: raw HTML is not accepted or executed. It is escaped and rendered as inert preformatted text. Do not include HTML tags, attributes, URLs, scripts, styles, event handlers, or other executable markup. Do not use previews for simple preference questions where labels and descriptions suffice. Note: previews are only supported for single-select questions (not multiSelect).
265067
264966
  `
265068
264967
  };
265069
264968
  ASK_USER_QUESTION_TOOL_PROMPT = `Use this tool when you need to ask the user questions during execution. This allows you to:
@@ -265074,10 +264973,20 @@ Preview content must be a self-contained HTML fragment (no <html>/<body> wrapper
265074
264973
 
265075
264974
  Strongly prefer this tool over asking a question in plain assistant text. Any time your reply would end with a question that offers the user options or asks them to choose a direction (e.g. "Would you like A or B?", "Which approach should I take?", "Want me to do X or Y?"), call this tool with those options instead so the user gets a selectable arrow-key menu. Only ask in plain text when the answer is genuinely open-ended and cannot be expressed as a small set of choices.
265076
264975
 
264976
+ Strict input hierarchy:
264977
+ - Invoke the tool with exactly one top-level \`questions\` array containing 1-4 complete question objects.
264978
+ - Every question object contains \`question\`, a concise \`header\` (maximum 12 characters), and an \`options\` array with 2-8 option objects. Use \`multiSelect: true\` only when more than one choice may apply.
264979
+ - Every option object contains a \`label\`. Add \`description\` only when it contributes a real consequence, trade-off, or limitation; \`preview\` is optional.
264980
+ - Keep each question and its own options nested together. Never put option rows directly in the top-level \`questions\` array, and never send incomplete header/prompt-only entries.
264981
+
264982
+ Canonical valid tool arguments (invoke the structured tool; do not print this object as prose):
264983
+ {"questions":[{"question":"Which database should we use?","header":"Database","options":[{"label":"PostgreSQL (Recommended)","description":"Strong consistency and concurrency; requires a running server and migrations."},{"label":"SQLite","description":"Zero setup and a single file; unsuitable for multiple concurrent writers."}],"multiSelect":false}]}
264984
+
265077
264985
  Usage notes:
265078
264986
  - Users will always be able to select "Other" to provide custom text input, so it is safe to offer choices even when you are unsure you have listed every option
265079
264987
  - Use multiSelect: true to allow multiple answers to be selected for a question
265080
264988
  - If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
264989
+ - Do not over-question. Ask only decisions that materially affect the result and cannot be inferred safely. If more than four decisions are truly needed, ask the most blocking 1-4 first and ask the remainder in a later round.
265081
264990
 
265082
264991
  Writing the three fields \u2014 they must each carry DIFFERENT information:
265083
264992
  - \`header\` names the dimension being decided ("Database", "Auth method"). It is not a shortened copy of the question.
@@ -265101,24 +265010,15 @@ Plan mode note: In plan mode, use this tool to clarify requirements or choose be
265101
265010
  `;
265102
265011
  });
265103
265012
 
265104
- // src/tools/TodoWriteTool/constants.ts
265105
- var TODO_WRITE_TOOL_NAME = "TodoWrite";
265106
-
265107
265013
  // src/tools/SkillTool/constants.ts
265108
265014
  var SKILL_TOOL_NAME = "Skill";
265109
265015
 
265110
- // src/tools/TaskCreateTool/constants.ts
265111
- var TASK_CREATE_TOOL_NAME = "TaskCreate";
265112
-
265113
265016
  // src/tools/TaskGetTool/constants.ts
265114
265017
  var TASK_GET_TOOL_NAME = "TaskGet";
265115
265018
 
265116
265019
  // src/tools/TaskListTool/constants.ts
265117
265020
  var TASK_LIST_TOOL_NAME = "TaskList";
265118
265021
 
265119
- // src/tools/TaskUpdateTool/constants.ts
265120
- var TASK_UPDATE_TOOL_NAME = "TaskUpdate";
265121
-
265122
265022
  // src/tools/EnterWorktreeTool/constants.ts
265123
265023
  var ENTER_WORKTREE_TOOL_NAME = "EnterWorktree";
265124
265024
 
@@ -295391,7 +295291,7 @@ function getInstallationEnv() {
295391
295291
  return;
295392
295292
  }
295393
295293
  function getURCodeVersion() {
295394
- return "1.65.9";
295294
+ return "1.65.11";
295395
295295
  }
295396
295296
  async function getInstalledVSCodeExtensionVersion(command) {
295397
295297
  const { stdout } = await execFileNoThrow(command, ["--list-extensions", "--show-versions"], {
@@ -302722,7 +302622,7 @@ async function setupSdkMcpClients(sdkMcpConfigs, sendMcpMessage) {
302722
302622
  const client2 = new Client({
302723
302623
  name: "ur",
302724
302624
  title: "UR",
302725
- version: "1.65.9",
302625
+ version: "1.65.11",
302726
302626
  description: "UR-Nexus autonomous engineering workflow engine",
302727
302627
  websiteUrl: PRODUCT_URL
302728
302628
  }, {
@@ -303082,7 +302982,7 @@ var init_client5 = __esm(() => {
303082
302982
  const client2 = new Client({
303083
302983
  name: "ur",
303084
302984
  title: "UR",
303085
- version: "1.65.9",
302985
+ version: "1.65.11",
303086
302986
  description: "UR-Nexus autonomous engineering workflow engine",
303087
302987
  websiteUrl: PRODUCT_URL
303088
302988
  }, {
@@ -315621,7 +315521,7 @@ async function createRuntime() {
315621
315521
  bootstrapTelemetry();
315622
315522
  const resource = defaultResource().merge(detectResources({ detectors: [envDetector] })).merge(resourceFromAttributes({
315623
315523
  [import_semantic_conventions7.ATTR_SERVICE_NAME]: "ur-agent",
315624
- [import_semantic_conventions7.ATTR_SERVICE_VERSION]: "1.65.9"
315524
+ [import_semantic_conventions7.ATTR_SERVICE_VERSION]: "1.65.11"
315625
315525
  }));
315626
315526
  const tracerProvider = exporters.traces.length > 0 ? new BasicTracerProvider({
315627
315527
  resource,
@@ -315654,11 +315554,11 @@ async function createRuntime() {
315654
315554
  setMeterProvider(meterProvider);
315655
315555
  setLoggerProvider(loggerProvider);
315656
315556
  if (meterProvider) {
315657
- const meter = meterProvider.getMeter("ur-agent", "1.65.9");
315557
+ const meter = meterProvider.getMeter("ur-agent", "1.65.11");
315658
315558
  setMeter(meter, (name, options2) => meter.createCounter(name, options2));
315659
315559
  }
315660
315560
  if (loggerProvider) {
315661
- setEventLogger(loggerProvider.getLogger("ur-agent.events", "1.65.9"));
315561
+ setEventLogger(loggerProvider.getLogger("ur-agent.events", "1.65.11"));
315662
315562
  }
315663
315563
  if (!cleanupRegistered2) {
315664
315564
  cleanupRegistered2 = true;
@@ -316320,9 +316220,9 @@ async function assertMinVersion() {
316320
316220
  if (false) {}
316321
316221
  try {
316322
316222
  const versionConfig = await getDynamicConfig_BLOCKS_ON_INIT("tengu_version_config", { minVersion: "0.0.0" });
316323
- if (versionConfig.minVersion && lt("1.65.9", versionConfig.minVersion)) {
316223
+ if (versionConfig.minVersion && lt("1.65.11", versionConfig.minVersion)) {
316324
316224
  console.error(`
316325
- It looks like your version of UR (${"1.65.9"}) needs an update.
316225
+ It looks like your version of UR (${"1.65.11"}) needs an update.
316326
316226
  A newer version (${versionConfig.minVersion} or higher) is required to continue.
316327
316227
 
316328
316228
  To update, please run:
@@ -316538,7 +316438,7 @@ async function installGlobalPackage(specificVersion) {
316538
316438
  logError2(new AutoUpdaterError("Another process is currently installing an update"));
316539
316439
  logEvent("tengu_auto_updater_lock_contention", {
316540
316440
  pid: process.pid,
316541
- currentVersion: "1.65.9"
316441
+ currentVersion: "1.65.11"
316542
316442
  });
316543
316443
  return "in_progress";
316544
316444
  }
@@ -316547,7 +316447,7 @@ async function installGlobalPackage(specificVersion) {
316547
316447
  if (!env2.isRunningWithBun() && env2.isNpmFromWindowsPath()) {
316548
316448
  logError2(new Error("Windows NPM detected in WSL environment"));
316549
316449
  logEvent("tengu_auto_updater_windows_npm_in_wsl", {
316550
- currentVersion: "1.65.9"
316450
+ currentVersion: "1.65.11"
316551
316451
  });
316552
316452
  console.error(`
316553
316453
  Error: Windows NPM detected in WSL
@@ -317082,7 +316982,7 @@ function detectLinuxGlobPatternWarnings() {
317082
316982
  }
317083
316983
  async function getDoctorDiagnostic() {
317084
316984
  const installationType = await getCurrentInstallationType();
317085
- const version2 = typeof MACRO !== "undefined" ? "1.65.9" : "unknown";
316985
+ const version2 = typeof MACRO !== "undefined" ? "1.65.11" : "unknown";
317086
316986
  const installationPath = await getInstallationPath();
317087
316987
  const invokedBinary = getInvokedBinary();
317088
316988
  const multipleInstallations = await detectMultipleInstallations();
@@ -318017,8 +317917,8 @@ async function updateLatest(channelOrVersion, forceReinstall = false) {
318017
317917
  const maxVersion = await getMaxVersion();
318018
317918
  if (maxVersion && gt(version2, maxVersion)) {
318019
317919
  logForDebugging(`Native installer: maxVersion ${maxVersion} is set, capping update from ${version2} to ${maxVersion}`);
318020
- if (gte("1.65.9", maxVersion)) {
318021
- logForDebugging(`Native installer: current version ${"1.65.9"} is already at or above maxVersion ${maxVersion}, skipping update`);
317920
+ if (gte("1.65.11", maxVersion)) {
317921
+ logForDebugging(`Native installer: current version ${"1.65.11"} is already at or above maxVersion ${maxVersion}, skipping update`);
318022
317922
  logEvent("tengu_native_update_skipped_max_version", {
318023
317923
  latency_ms: Date.now() - startTime,
318024
317924
  max_version: maxVersion,
@@ -318029,7 +317929,7 @@ async function updateLatest(channelOrVersion, forceReinstall = false) {
318029
317929
  version2 = maxVersion;
318030
317930
  }
318031
317931
  }
318032
- if (!forceReinstall && version2 === "1.65.9" && await versionIsAvailable(version2) && await isPossibleURBinary(executablePath)) {
317932
+ if (!forceReinstall && version2 === "1.65.11" && await versionIsAvailable(version2) && await isPossibleURBinary(executablePath)) {
318033
317933
  logForDebugging(`Found ${version2} at ${executablePath}, skipping install`);
318034
317934
  logEvent("tengu_native_update_complete", {
318035
317935
  latency_ms: Date.now() - startTime,
@@ -339523,19 +339423,23 @@ that can be completed clearly in fewer than three small steps.
339523
339423
 
339524
339424
  ## Lifecycle
339525
339425
 
339526
- 1. Put todos in dependency order. Each item must be specific, actionable, and
339527
- independently checkable.
339528
- 2. Provide both forms for every item:
339426
+ 1. Decompose non-trivial work into one item per cohesive outcome with its own
339427
+ observable done check. Split separately completable deliverables out of
339428
+ omnibus items, but keep genuinely atomic work as one item; never create
339429
+ items merely for individual files, tool calls, or tiny mechanical steps.
339430
+ 2. Put todos in dependency order. Keep unrelated outcomes independent rather
339431
+ than inventing dependencies.
339432
+ 3. Provide both forms for every item:
339529
339433
  - \`content\`: imperative outcome, such as "Run tests".
339530
339434
  - \`activeForm\`: present-continuous status, such as "Running tests".
339531
- 3. Mark the next unblocked item \`in_progress\` when work starts. Keep only one
339435
+ 4. Mark the next unblocked item \`in_progress\` when work starts. Keep only one
339532
339436
  item \`in_progress\` in this agent's list.
339533
- 4. Update the list immediately when requirements or discovered work change.
339534
- 5. Mark an item \`completed\` only after its implementation and relevant
339437
+ 5. Update the list immediately when requirements or discovered work change.
339438
+ 6. Mark an item \`completed\` only after its implementation and relevant
339535
339439
  verification have succeeded. Do not batch completion updates.
339536
- 6. If work is partial, blocked, or failing, leave the item open and record the
339440
+ 7. If work is partial, blocked, or failing, leave the item open and record the
339537
339441
  concrete follow-up or blocker in the list.
339538
- 7. Remove an item only when it is genuinely obsolete or was created by mistake.
339442
+ 8. Remove an item only when it is genuinely obsolete or was created by mistake.
339539
339443
 
339540
339444
  Never mark an item completed when tests still fail, an error is unresolved, a
339541
339445
  required dependency is missing, or only part of the outcome was implemented.
@@ -361089,14 +360993,15 @@ function getEditToolDescription() {
361089
360993
  }
361090
360994
  function getDefaultEditDescription() {
361091
360995
  const prefixFormat = isCompactLinePrefixEnabled() ? "line number + tab" : "spaces + line number + arrow";
361092
- const minimalUniquenessHint = process.env.USER_TYPE === "ant" ? `
361093
- - Use the smallest old_string that's clearly unique \u2014 usually 2-4 adjacent lines is sufficient. Avoid including 10+ lines of context when less uniquely identifies the target.` : "";
360996
+ const minimalUniquenessHint = `
360997
+ - Use the smallest old_string that's clearly unique \u2014 usually 2-4 adjacent lines is sufficient. Avoid including 10+ lines of context when less uniquely identifies the target. Split changes across distant HTML/CSS/JavaScript sections into separate edits instead of replacing one large cross-section block.`;
361094
360998
  return `Performs exact string replacements in files.
361095
360999
 
361096
361000
  Usage:${getPreReadInstruction2()}
361097
361001
  - When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: ${prefixFormat}. Everything after that is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.
361098
361002
  - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
361099
361003
  - Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
361004
+ - \`old_string\` must be copied from a recent Read of the target file as one exact, contiguous block. Never reconstruct it from memory, from an earlier full-file Write, or from what you expected the file to contain. If it is not found, re-read the target region and retry with a corrected smaller block; never retry the unchanged call.
361100
361005
  - The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.${minimalUniquenessHint}
361101
361006
  - Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.`;
361102
361007
  }
@@ -361140,6 +361045,7 @@ var init_types11 = __esm(() => {
361140
361045
  structuredPatch: exports_external.array(hunkSchema()).describe("Diff patch showing the changes"),
361141
361046
  userModified: exports_external.boolean().describe("Whether the user modified the proposed changes"),
361142
361047
  replaceAll: exports_external.boolean().describe("Whether all occurrences were replaced"),
361048
+ alreadyApplied: exports_external.boolean().optional().describe("Whether the requested deletion-only replacement was already present and no write was needed"),
361143
361049
  gitDiff: gitDiffSchema().optional()
361144
361050
  }));
361145
361051
  });
@@ -363476,6 +363382,58 @@ function findActualStringWhitespaceTolerant(fileContent, searchString) {
363476
363382
  }
363477
363383
  return null;
363478
363384
  }
363385
+ function findSearchAnchor(fileContent, searchString) {
363386
+ const fileLines = fileContent.split(`
363387
+ `);
363388
+ const occurrencesByLine = new Map;
363389
+ for (let index2 = 0;index2 < fileLines.length; index2++) {
363390
+ const normalized = normalizeLineForMatch(fileLines[index2]);
363391
+ if (normalized.trim().length === 0)
363392
+ continue;
363393
+ const occurrence = occurrencesByLine.get(normalized);
363394
+ if (occurrence) {
363395
+ occurrence.count++;
363396
+ } else {
363397
+ occurrencesByLine.set(normalized, { firstIndex: index2, count: 1 });
363398
+ }
363399
+ }
363400
+ let repeatedMatch = null;
363401
+ for (const searchLine of searchString.split(`
363402
+ `)) {
363403
+ const normalized = normalizeLineForMatch(searchLine);
363404
+ if (normalized.trim().length === 0)
363405
+ continue;
363406
+ const occurrence = occurrencesByLine.get(normalized);
363407
+ if (!occurrence)
363408
+ continue;
363409
+ if (occurrence.count === 1) {
363410
+ return { fileLine: occurrence.firstIndex + 1, unique: true };
363411
+ }
363412
+ repeatedMatch ??= {
363413
+ fileLine: occurrence.firstIndex + 1,
363414
+ unique: false
363415
+ };
363416
+ }
363417
+ return repeatedMatch;
363418
+ }
363419
+ function formatStringNotFoundMessage(fileContent, searchString) {
363420
+ const lineCount = searchString.split(`
363421
+ `).length;
363422
+ const anchor = findSearchAnchor(fileContent, searchString);
363423
+ const location = anchor ? `A line from old_string ${anchor.unique ? "uniquely " : ""}matches the current file at line ${anchor.fileLine}, but the complete ${lineCount}-line block is not contiguous there.` : "No complete non-empty line from old_string matches the current file.";
363424
+ const recovery = anchor ? `Re-read the target around line ${anchor.fileLine}, then retry with the smallest unique contiguous old_string copied from the current Read output (usually 2-4 lines).` : "Search for a short distinctive fragment, re-read the current target region, then retry with the smallest unique contiguous old_string (usually 2-4 lines).";
363425
+ const preview = searchString.length > STRING_NOT_FOUND_PREVIEW_CHARS ? `${searchString.slice(0, STRING_NOT_FOUND_PREVIEW_CHARS)}
363426
+ \u2026 [old_string preview truncated; ${searchString.length} characters total]` : searchString;
363427
+ return [
363428
+ "String to replace not found in file.",
363429
+ location,
363430
+ recovery,
363431
+ "Do not include Read line-number prefixes. Split large cross-section replacements into smaller edits, and do not retry this call unchanged.",
363432
+ `old_string preview:
363433
+ ${preview}`
363434
+ ].join(`
363435
+ `);
363436
+ }
363479
363437
  function findActualString(fileContent, searchString) {
363480
363438
  if (fileContent.includes(searchString)) {
363481
363439
  return searchString;
@@ -363488,6 +363446,15 @@ function findActualString(fileContent, searchString) {
363488
363446
  }
363489
363447
  return findActualStringWhitespaceTolerant(fileContent, searchString);
363490
363448
  }
363449
+ function isDeletionOnlyEditAlreadyApplied(fileContent, oldString, newString, replaceAll) {
363450
+ if (replaceAll || oldString.length === 0 || newString.length === 0 || oldString === newString || !oldString.includes(newString) || findActualString(fileContent, oldString) !== null) {
363451
+ return false;
363452
+ }
363453
+ const actualNewString = findActualString(fileContent, newString);
363454
+ if (actualNewString === null)
363455
+ return false;
363456
+ return fileContent.split(actualNewString).length - 1 === 1;
363457
+ }
363491
363458
  function preserveQuoteStyle(oldString, actualOldString, newString) {
363492
363459
  if (oldString === actualOldString) {
363493
363460
  return newString;
@@ -363787,7 +363754,7 @@ function areFileEditsInputsEquivalent(input1, input2) {
363787
363754
  }
363788
363755
  return areFileEditsEquivalent(input1.edits, input2.edits, fileContent);
363789
363756
  }
363790
- var LEFT_SINGLE_CURLY_QUOTE = "\u2018", RIGHT_SINGLE_CURLY_QUOTE = "\u2019", LEFT_DOUBLE_CURLY_QUOTE = "\u201C", RIGHT_DOUBLE_CURLY_QUOTE = "\u201D", DIFF_SNIPPET_MAX_BYTES = 8192, DESANITIZATIONS;
363757
+ var LEFT_SINGLE_CURLY_QUOTE = "\u2018", RIGHT_SINGLE_CURLY_QUOTE = "\u2019", LEFT_DOUBLE_CURLY_QUOTE = "\u201C", RIGHT_DOUBLE_CURLY_QUOTE = "\u201D", STRING_NOT_FOUND_PREVIEW_CHARS = 600, DIFF_SNIPPET_MAX_BYTES = 8192, DESANITIZATIONS;
363791
363758
  var init_utils10 = __esm(() => {
363792
363759
  init_libesm();
363793
363760
  init_log2();
@@ -363863,11 +363830,20 @@ function renderToolUseMessage9({
363863
363830
  function renderToolResultMessage8({
363864
363831
  filePath,
363865
363832
  structuredPatch: structuredPatch2,
363866
- originalFile
363833
+ originalFile,
363834
+ alreadyApplied
363867
363835
  }, _progressMessagesForMessage, {
363868
363836
  style,
363869
363837
  verbose
363870
363838
  }) {
363839
+ if (alreadyApplied) {
363840
+ return /* @__PURE__ */ jsx_dev_runtime132.jsxDEV(MessageResponse, {
363841
+ children: /* @__PURE__ */ jsx_dev_runtime132.jsxDEV(ThemedText, {
363842
+ dimColor: true,
363843
+ children: "Already up to date"
363844
+ }, undefined, false, undefined, this)
363845
+ }, undefined, false, undefined, this);
363846
+ }
363871
363847
  const isPlanFile = filePath.startsWith(getPlansDirectory());
363872
363848
  return /* @__PURE__ */ jsx_dev_runtime132.jsxDEV(FileEditToolUpdatedMessage, {
363873
363849
  filePath,
@@ -364343,11 +364319,13 @@ var init_FileEditTool = __esm(() => {
364343
364319
  const file2 = fileContent;
364344
364320
  const actualOldString = findActualString(file2, old_string);
364345
364321
  if (!actualOldString) {
364322
+ if (isDeletionOnlyEditAlreadyApplied(file2, old_string, new_string, replace_all)) {
364323
+ return { result: true };
364324
+ }
364346
364325
  return {
364347
364326
  result: false,
364348
364327
  behavior: "ask",
364349
- message: `String to replace not found in file.
364350
- String: ${old_string}`,
364328
+ message: formatStringNotFoundMessage(file2, old_string),
364351
364329
  meta: {
364352
364330
  isFilePathAbsolute: String(isAbsolute24(file_path))
364353
364331
  },
@@ -364407,6 +364385,27 @@ String: ${old_string}`,
364407
364385
  const { file_path, old_string, new_string, replace_all = false } = input;
364408
364386
  const fs4 = getFsImplementation();
364409
364387
  const absoluteFilePath = expandPath(file_path);
364388
+ const initialState = readFileForEdit(absoluteFilePath);
364389
+ if (initialState.fileExists) {
364390
+ const lastRead = readFileState.get(absoluteFilePath);
364391
+ if (!lastRead || getFileModificationTime(absoluteFilePath) > lastRead.timestamp || !fileStateMatchesContent(initialState.content, lastRead)) {
364392
+ throw new Error(FILE_UNEXPECTEDLY_MODIFIED_ERROR);
364393
+ }
364394
+ if (isDeletionOnlyEditAlreadyApplied(initialState.content, old_string, new_string, replace_all)) {
364395
+ return {
364396
+ data: {
364397
+ filePath: file_path,
364398
+ oldString: old_string,
364399
+ newString: new_string,
364400
+ originalFile: initialState.content,
364401
+ structuredPatch: [],
364402
+ userModified: userModified ?? false,
364403
+ replaceAll: replace_all,
364404
+ alreadyApplied: true
364405
+ }
364406
+ };
364407
+ }
364408
+ }
364410
364409
  const cwd2 = getCwd();
364411
364410
  if (!isEnvTruthy(process.env.UR_CODE_SIMPLE)) {
364412
364411
  const newSkillDirs = await discoverSkillDirsForPaths([absoluteFilePath], cwd2);
@@ -364524,7 +364523,14 @@ String: ${old_string}`,
364524
364523
  };
364525
364524
  },
364526
364525
  mapToolResultToToolResultBlockParam(data, toolUseID) {
364527
- const { filePath, userModified, replaceAll } = data;
364526
+ const { filePath, userModified, replaceAll, alreadyApplied } = data;
364527
+ if (alreadyApplied) {
364528
+ return {
364529
+ tool_use_id: toolUseID,
364530
+ type: "tool_result",
364531
+ content: `The file ${filePath} already contains the requested replacement. No change was needed.`
364532
+ };
364533
+ }
364528
364534
  const modifiedNote = userModified ? ". The user modified your proposed changes before accepting them. " : "";
364529
364535
  if (replaceAll) {
364530
364536
  return {
@@ -371511,9 +371517,9 @@ var permissionSetupModule = null, allowedPromptSchema, allowedPromptsSchema, inp
371511
371517
  var init_ExitPlanModeV2Tool = __esm(() => {
371512
371518
  init_v4();
371513
371519
  init_state();
371520
+ init_planImplementationContract();
371514
371521
  init_analytics();
371515
371522
  init_Tool();
371516
- init_agentSwarmsEnabled();
371517
371523
  init_debug();
371518
371524
  init_inProcessTeammateHelpers();
371519
371525
  init_log2();
@@ -371521,7 +371527,6 @@ var init_ExitPlanModeV2Tool = __esm(() => {
371521
371527
  init_slowOperations();
371522
371528
  init_teammate();
371523
371529
  init_teammateMailbox();
371524
- init_constants2();
371525
371530
  init_prompt14();
371526
371531
  init_UI18();
371527
371532
  allowedPromptSchema = lazySchema(() => exports_external.object({
@@ -371541,7 +371546,8 @@ var init_ExitPlanModeV2Tool = __esm(() => {
371541
371546
  plan: exports_external.string().nullable().describe("The plan that was presented to the user"),
371542
371547
  isAgent: exports_external.boolean(),
371543
371548
  filePath: exports_external.string().optional().describe("The file path where the plan was saved"),
371544
- hasTaskTool: exports_external.boolean().optional().describe("Whether the Agent tool is available in the current context"),
371549
+ implementationTaskTool: exports_external.enum(["task-v2", "todo-write", "none"]).optional().describe("Task tracking surface available after approval"),
371550
+ implementationAgentType: exports_external.string().optional().describe("Executable built-in implementation worker type, when present"),
371545
371551
  planWasEdited: exports_external.boolean().optional().describe("True when the user edited the plan (CCR web UI or Ctrl+G); determines whether the plan is echoed back in tool_result"),
371546
371552
  awaitingLeaderApproval: exports_external.boolean().optional().describe("When true, the teammate has sent a plan approval request to the team leader"),
371547
371553
  requestId: exports_external.string().optional().describe("Unique identifier for the plan approval request")
@@ -371696,13 +371702,14 @@ var init_ExitPlanModeV2Tool = __esm(() => {
371696
371702
  }
371697
371703
  };
371698
371704
  });
371699
- const hasTaskTool = isAgentSwarmsEnabled() && context5.options.tools.some((t) => toolMatchesName(t, AGENT_TOOL_NAME));
371705
+ const implementationCapabilities = getApprovedPlanCapabilities(context5);
371700
371706
  return {
371701
371707
  data: {
371702
371708
  plan,
371703
371709
  isAgent,
371704
371710
  filePath,
371705
- hasTaskTool: hasTaskTool || undefined,
371711
+ implementationTaskTool: implementationCapabilities.taskTool,
371712
+ implementationAgentType: implementationCapabilities.implementationAgentType,
371706
371713
  planWasEdited: inputPlan !== undefined || undefined
371707
371714
  }
371708
371715
  };
@@ -371711,7 +371718,8 @@ var init_ExitPlanModeV2Tool = __esm(() => {
371711
371718
  isAgent,
371712
371719
  plan,
371713
371720
  filePath,
371714
- hasTaskTool,
371721
+ implementationTaskTool,
371722
+ implementationAgentType,
371715
371723
  planWasEdited,
371716
371724
  awaitingLeaderApproval,
371717
371725
  requestId
@@ -371749,16 +371757,19 @@ Request ID: ${requestId}`,
371749
371757
  tool_use_id: toolUseID
371750
371758
  };
371751
371759
  }
371752
- const teamHint = hasTaskTool ? `
371753
-
371754
- If this plan can be broken down into multiple independent tasks, consider using the ${TEAM_CREATE_TOOL_NAME} tool to create a team and parallelize the work.` : "";
371760
+ const implementationInstruction = getApprovedPlanImplementationInstruction({
371761
+ taskTool: implementationTaskTool ?? "none",
371762
+ ...implementationAgentType ? { implementationAgentType } : {}
371763
+ });
371755
371764
  const planLabel = planWasEdited ? "Approved Plan (edited by user)" : "Approved Plan";
371756
371765
  return {
371757
371766
  type: "tool_result",
371758
- content: `User has approved your plan. You can now start coding. Start with updating your todo list if applicable
371767
+ content: `User has approved your plan. You can now start implementation.
371759
371768
 
371760
371769
  Your plan has been saved to: ${filePath}
371761
- You can refer back to it if needed during implementation.${teamHint}
371770
+ You can refer back to it if needed during implementation.
371771
+
371772
+ ${implementationInstruction}
371762
371773
 
371763
371774
  ## ${planLabel}:
371764
371775
  ${plan}`,
@@ -373179,6 +373190,21 @@ function TungstenLiveMonitor() {
373179
373190
  }
373180
373191
  var TungstenTool = null;
373181
373192
 
373193
+ // src/utils/zodToJsonSchema.ts
373194
+ function zodToJsonSchema3(schema) {
373195
+ const hit = cache3.get(schema);
373196
+ if (hit)
373197
+ return hit;
373198
+ const result = toJSONSchema(schema);
373199
+ cache3.set(schema, result);
373200
+ return result;
373201
+ }
373202
+ var cache3;
373203
+ var init_zodToJsonSchema2 = __esm(() => {
373204
+ init_v4();
373205
+ cache3 = new WeakMap;
373206
+ });
373207
+
373182
373208
  // src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx
373183
373209
  function objectValue3(value) {
373184
373210
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -373198,81 +373224,108 @@ function stringField2(input, names) {
373198
373224
  }
373199
373225
  function normalizeQuestionOptionInput(value) {
373200
373226
  if (typeof value === "string") {
373201
- const label2 = value.trim();
373202
- return label2 ? {
373203
- label: label2,
373204
- description: label2
373227
+ const label = value.trim();
373228
+ return label ? {
373229
+ label
373205
373230
  } : value;
373206
373231
  }
373207
373232
  const option = objectValue3(value);
373208
373233
  if (!option)
373209
373234
  return value;
373210
- const label = typeof option.label === "string" && option.label.trim() ? option.label.trim() : typeof option.value === "string" && option.value.trim() ? option.value.trim() : typeof option.description === "string" && option.description.trim() ? option.description.trim() : "";
373211
- const description = typeof option.description === "string" && option.description.trim() ? option.description.trim() : label;
373212
- if (!label || !description)
373213
- return value;
373214
- return {
373215
- label,
373216
- description,
373217
- ...typeof option.preview === "string" ? {
373218
- preview: option.preview
373219
- } : {}
373220
- };
373235
+ const normalized = { ...option };
373236
+ if (typeof option.label === "string")
373237
+ normalized.label = option.label.trim();
373238
+ if (typeof option.description === "string")
373239
+ normalized.description = option.description.trim();
373240
+ if (typeof option.preview === "string")
373241
+ normalized.preview = normalizePreviewInput(option.preview);
373242
+ return normalized;
373243
+ }
373244
+ function normalizePreviewInput(preview) {
373245
+ if (getQuestionPreviewFormat() !== "html")
373246
+ return preview;
373247
+ const alreadySafe = preview.match(/^<pre data-ur-preview="text">([\s\S]*)<\/pre>$/);
373248
+ if (alreadySafe && !alreadySafe[1]?.includes("<"))
373249
+ return preview;
373250
+ const escaped = preview.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
373251
+ return `<pre data-ur-preview="text">${escaped}</pre>`;
373221
373252
  }
373222
373253
  function normalizeQuestionInput(value, index2) {
373223
373254
  const question = objectValue3(value);
373224
- if (!question || !Array.isArray(question.options))
373225
- return value;
373226
- const questionText = stringField2(question, ["question", "questionText", "question_text", "prompt", "text", "title", "message", "body"]);
373227
- if (!questionText)
373255
+ if (!question)
373228
373256
  return value;
373229
- return {
373230
- question: questionText,
373231
- header: typeof question.header === "string" && question.header.trim() ? question.header.trim().slice(0, ASK_USER_QUESTION_TOOL_CHIP_WIDTH) : headerFromQuestion2(questionText, index2),
373232
- options: question.options.map(normalizeQuestionOptionInput),
373233
- ...typeof question.multiSelect === "boolean" ? {
373234
- multiSelect: question.multiSelect
373235
- } : {}
373236
- };
373257
+ const normalized = { ...question };
373258
+ const questionText = stringField2(question, [...QUESTION_TEXT_ALIASES]);
373259
+ if (questionText)
373260
+ normalized.question = questionText;
373261
+ for (const alias of QUESTION_TEXT_ALIASES) {
373262
+ if (alias !== "question")
373263
+ delete normalized[alias];
373264
+ }
373265
+ let options2 = question.options;
373266
+ if (options2 === undefined && question.choices !== undefined) {
373267
+ options2 = question.choices;
373268
+ delete normalized.choices;
373269
+ }
373270
+ if (typeof options2 === "string") {
373271
+ const parsed = parseToolInputJsonLenient(options2);
373272
+ if (Array.isArray(parsed))
373273
+ options2 = parsed;
373274
+ }
373275
+ if (Array.isArray(options2)) {
373276
+ normalized.options = options2.map(normalizeQuestionOptionInput);
373277
+ }
373278
+ if (typeof question.header === "string" && question.header.trim()) {
373279
+ normalized.header = question.header.trim();
373280
+ } else if (questionText) {
373281
+ normalized.header = headerFromQuestion2(questionText, index2);
373282
+ }
373283
+ return normalized;
373237
373284
  }
373238
373285
  function normalizeAskUserQuestionInput2(value) {
373239
373286
  const input = objectValue3(value);
373240
373287
  if (!input)
373241
373288
  return value;
373242
- const commonFields = {
373243
- ...objectValue3(input.answers) ? {
373244
- answers: input.answers
373245
- } : {},
373246
- ...objectValue3(input.annotations) ? {
373247
- annotations: input.annotations
373248
- } : {},
373249
- ...objectValue3(input.metadata) ? {
373250
- metadata: input.metadata
373251
- } : {}
373252
- };
373253
- if (typeof input.questions === "string") {
373254
- const parsed = parseToolInputJsonLenient(input.questions);
373289
+ const normalized = { ...input };
373290
+ let questions = input.questions;
373291
+ if (typeof questions === "string") {
373292
+ const parsed = parseToolInputJsonLenient(questions);
373255
373293
  if (Array.isArray(parsed))
373256
- input.questions = parsed;
373294
+ questions = parsed;
373257
373295
  }
373258
- if (typeof input.options === "string") {
373259
- const parsed = parseToolInputJsonLenient(input.options);
373260
- if (Array.isArray(parsed))
373261
- input.options = parsed;
373296
+ if (Array.isArray(questions)) {
373297
+ normalized.questions = questions.map(normalizeQuestionInput);
373298
+ return normalized;
373262
373299
  }
373263
- if (Array.isArray(input.questions)) {
373264
- return {
373265
- questions: input.questions.map(normalizeQuestionInput),
373266
- ...commonFields
373267
- };
373300
+ let options2 = input.options;
373301
+ if (typeof options2 === "string") {
373302
+ const parsed = parseToolInputJsonLenient(options2);
373303
+ if (Array.isArray(parsed))
373304
+ options2 = parsed;
373268
373305
  }
373269
- if (typeof input.question === "string" && Array.isArray(input.options)) {
373306
+ if (stringField2(input, [...QUESTION_TEXT_ALIASES]) && Array.isArray(options2)) {
373307
+ const singleQuestion = normalizeQuestionInput({
373308
+ question: stringField2(input, [...QUESTION_TEXT_ALIASES]),
373309
+ ...input.header !== undefined ? {
373310
+ header: input.header
373311
+ } : {},
373312
+ options: options2,
373313
+ ...input.multiSelect !== undefined ? {
373314
+ multiSelect: input.multiSelect
373315
+ } : {}
373316
+ }, 0);
373317
+ for (const key of [...QUESTION_TEXT_ALIASES, "header", "options", "choices", "multiSelect"]) {
373318
+ delete normalized[key];
373319
+ }
373270
373320
  return {
373271
- questions: [normalizeQuestionInput(input, 0)],
373272
- ...commonFields
373321
+ ...normalized,
373322
+ questions: [singleQuestion]
373273
373323
  };
373274
373324
  }
373275
- return value;
373325
+ return normalized;
373326
+ }
373327
+ function boundedText(max2, field) {
373328
+ return exports_external.string().trim().min(1, `${field} cannot be empty`).max(max2, `${field} must be at most ${max2} characters`).refine((value) => !CONTROL_OR_ANSI_RE.test(value), `${field} must not contain control or ANSI escape characters`);
373276
373329
  }
373277
373330
  function AskUserQuestionResultMessage(t0) {
373278
373331
  const $2 = import_compiler_runtime114.c(3);
@@ -373337,18 +373390,15 @@ function _temp51(t0) {
373337
373390
  function validateHtmlPreview(preview) {
373338
373391
  if (preview === undefined)
373339
373392
  return null;
373340
- if (/<\s*(html|body|!doctype)\b/i.test(preview)) {
373341
- return "preview must be an HTML fragment, not a full document (no <html>, <body>, or <!DOCTYPE>)";
373342
- }
373343
- if (/<\s*(script|style)\b/i.test(preview)) {
373344
- return "preview must not contain <script> or <style> tags. Use inline styles via the style attribute if needed.";
373345
- }
373346
- if (!/<[a-z][^>]*>/i.test(preview)) {
373347
- return 'preview must contain HTML (previewFormat is set to "html"). Wrap content in a tag like <div> or <pre>.';
373393
+ if (getQuestionPreviewFormat() !== "html")
373394
+ return null;
373395
+ const safeTextWrapper = preview.match(/^<pre data-ur-preview="text">([\s\S]*)<\/pre>$/);
373396
+ if (!safeTextWrapper || safeTextWrapper[1]?.includes("<")) {
373397
+ return "HTML previews must use UR\u2019s escaped text wrapper; raw model-provided HTML is not rendered";
373348
373398
  }
373349
373399
  return null;
373350
373400
  }
373351
- var import_compiler_runtime114, jsx_dev_runtime145, questionOptionSchema, questionSchema, annotationsSchema, UNIQUENESS_REFINE, commonFields, inputSchema32, outputSchema27, AskUserQuestionTool;
373401
+ var import_compiler_runtime114, jsx_dev_runtime145, MAX_QUESTIONS = 4, MAX_OPTIONS = 8, MAX_QUESTION_CHARS = 500, MAX_LABEL_CHARS = 80, MAX_DESCRIPTION_CHARS = 500, MAX_PREVIEW_CHARS, MAX_PREVIEW_LINES = 200, MAX_ANSWER_CHARS = 2000, MAX_TOTAL_INPUT_CHARS, RESERVED_RECORD_KEYS, QUESTION_TEXT_ALIASES, CONTROL_OR_ANSI_RE, UNIQUENESS_REFINE, questionOptionSchema, questionSchema, annotationsSchema, responseFields, metadataSchema, requestObjectSchema, inputSchema32, modelInputJSONSchema, outputSchema27, AskUserQuestionTool;
373352
373402
  var init_AskUserQuestionTool = __esm(() => {
373353
373403
  init_state();
373354
373404
  init_MessageResponse();
@@ -373358,59 +373408,82 @@ var init_AskUserQuestionTool = __esm(() => {
373358
373408
  init_v4();
373359
373409
  init_ink2();
373360
373410
  init_Tool();
373411
+ init_zodToJsonSchema2();
373361
373412
  init_prompt9();
373362
373413
  import_compiler_runtime114 = __toESM(require_compiler_runtime(), 1);
373363
373414
  jsx_dev_runtime145 = __toESM(require_jsx_dev_runtime(), 1);
373364
- questionOptionSchema = lazySchema(() => exports_external.object({
373365
- label: exports_external.string().describe('The choice itself, 1-5 words. Name the option, do not restate the question: for "Which database?" use "PostgreSQL", not "Use PostgreSQL for the database".'),
373366
- description: exports_external.string().describe('What actually happens if this is chosen, and the cost of choosing it \u2014 the information the user needs that the label does not already give them. Must NOT restate the label in a full sentence. Bad: label "PostgreSQL" / description "Use PostgreSQL." Good: label "PostgreSQL" / description "Relational, strong consistency; needs a running server and a migration step." Include the trade-off, limitation, or consequence that makes this choice different from the others.'),
373367
- preview: exports_external.string().optional().describe("Optional preview content rendered when this option is focused. Use for mockups, code snippets, or visual comparisons that help users compare options. See the tool description for the expected content format.")
373368
- }));
373369
- questionSchema = lazySchema(() => exports_external.object({
373370
- question: exports_external.string().describe('The complete question to ask the user. Should be clear, specific, and end with a question mark. Example: "Which library should we use for date formatting?" If multiSelect is true, phrase it accordingly, e.g. "Which features do you want to enable?"'),
373371
- header: exports_external.string().describe(`The category being decided, as a chip/tag (max ${ASK_USER_QUESTION_TOOL_CHIP_WIDTH} chars). Name the dimension, not the question: for "Which database should we use?" the header is "Database", not "Which DB". Examples: "Auth method", "Library", "Approach".`),
373372
- options: exports_external.array(questionOptionSchema()).min(2).max(8).describe(`REQUIRED: 2-8 concrete choices. A question with no options is not askable here \u2014 if you cannot name at least two specific answers, the question is open-ended, so ask it in plain assistant text instead of calling this tool. Do not call this tool with a prose question and omit options. Keep options concise and distinct; there should be no 'Other' option, that will be provided automatically.`),
373373
- multiSelect: exports_external.boolean().default(false).describe("Set to true to allow the user to select multiple options instead of just one. Use when choices are not mutually exclusive.")
373374
- }));
373375
- annotationsSchema = lazySchema(() => {
373376
- const annotationSchema = exports_external.object({
373377
- preview: exports_external.string().optional().describe("The preview content of the selected option, if the question used previews."),
373378
- notes: exports_external.string().optional().describe("Free-text notes the user added to their selection.")
373379
- });
373380
- return exports_external.record(exports_external.string(), annotationSchema).optional().describe("Optional per-question annotations from the user (e.g., notes on preview selections). Keyed by question text.");
373381
- });
373415
+ MAX_PREVIEW_CHARS = 16 * 1024;
373416
+ MAX_TOTAL_INPUT_CHARS = 64 * 1024;
373417
+ RESERVED_RECORD_KEYS = new Set(["__proto__", "constructor", "prototype", "toString", "valueOf"]);
373418
+ QUESTION_TEXT_ALIASES = ["question", "questionText", "question_text", "prompt", "text"];
373419
+ CONTROL_OR_ANSI_RE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]|\u001B\[/;
373382
373420
  UNIQUENESS_REFINE = {
373383
373421
  check: (data) => {
373384
- const questions = data.questions.map((q) => q.question);
373422
+ const questions = data.questions.map((q) => q.question.toLocaleLowerCase());
373385
373423
  if (questions.length !== new Set(questions).size) {
373386
373424
  return false;
373387
373425
  }
373388
373426
  for (const question of data.questions) {
373389
- const labels = question.options.map((opt) => opt.label);
373427
+ const labels = question.options.map((opt) => opt.label.toLocaleLowerCase());
373390
373428
  if (labels.length !== new Set(labels).size) {
373391
373429
  return false;
373392
373430
  }
373393
373431
  }
373394
373432
  return true;
373395
373433
  },
373396
- message: "Question texts must be unique, option labels must be unique within each question"
373434
+ message: "Question texts must be unique, and option labels must be unique within each question (ignoring case)"
373397
373435
  };
373398
- commonFields = lazySchema(() => ({
373399
- answers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("User answers collected by the permission component"),
373400
- annotations: annotationsSchema(),
373401
- metadata: exports_external.object({
373402
- source: exports_external.string().optional().describe('Optional identifier for the source of this question (e.g., "remember" for /remember command). Used for analytics tracking.')
373403
- }).optional().describe("Optional metadata for tracking and analytics purposes. Not displayed to user.")
373436
+ questionOptionSchema = lazySchema(() => exports_external.strictObject({
373437
+ label: boundedText(MAX_LABEL_CHARS, "Option label").refine((label) => {
373438
+ const normalized = label.trim().toLocaleLowerCase();
373439
+ return normalized !== "other" && normalized !== "__other__";
373440
+ }, "Do not provide an Other option; the UI supplies it automatically.").describe("The concise name of this choice, usually 1-5 words. It must be distinct from every other label in this question."),
373441
+ description: boundedText(MAX_DESCRIPTION_CHARS, "Option description").optional().describe("Optional consequence, trade-off, or limitation that adds information beyond the label. Omit it when there is nothing useful to add; never duplicate the label merely to fill this field."),
373442
+ preview: exports_external.string().max(MAX_PREVIEW_CHARS, `Option preview must be at most ${MAX_PREVIEW_CHARS} characters`).refine((value) => value.split(/\r?\n/).length <= MAX_PREVIEW_LINES, `Option preview must be at most ${MAX_PREVIEW_LINES} lines`).optional().describe("Optional bounded preview content rendered when this option is focused.")
373443
+ }));
373444
+ questionSchema = lazySchema(() => exports_external.strictObject({
373445
+ question: boundedText(MAX_QUESTION_CHARS, "Question").refine((question) => !RESERVED_RECORD_KEYS.has(question), "Question text uses a reserved record key; rephrase it.").describe("The complete, specific decision question shown to the user. Ask one decision per object."),
373446
+ header: boundedText(ASK_USER_QUESTION_TOOL_CHIP_WIDTH, "Question header").describe(`A short category chip naming the decision dimension, not a shortened question (max ${ASK_USER_QUESTION_TOOL_CHIP_WIDTH} characters; for \u201CWhich database?\u201D use \u201CDatabase\u201D).`),
373447
+ options: exports_external.array(questionOptionSchema()).min(2).max(MAX_OPTIONS).describe(`REQUIRED: 2-${MAX_OPTIONS} concrete choices nested inside this question object. Do not put option rows directly in the top-level questions array.`),
373448
+ multiSelect: exports_external.boolean().optional().describe("Set to true only when choices are not mutually exclusive. Omit it for ordinary single-select questions.")
373449
+ }).refine((question) => !(question.multiSelect && question.options.some((option) => option.preview !== undefined)), {
373450
+ message: "Preview choices are single-select only; remove previews or set multiSelect to false."
373451
+ }));
373452
+ annotationsSchema = lazySchema(() => {
373453
+ const annotationSchema = exports_external.strictObject({
373454
+ preview: exports_external.string().max(MAX_PREVIEW_CHARS).optional(),
373455
+ notes: exports_external.string().trim().max(MAX_ANSWER_CHARS).optional()
373456
+ });
373457
+ return exports_external.record(exports_external.string(), annotationSchema).optional();
373458
+ });
373459
+ responseFields = lazySchema(() => ({
373460
+ answers: exports_external.record(exports_external.string(), exports_external.string().trim().min(1).max(MAX_ANSWER_CHARS)).optional(),
373461
+ annotations: annotationsSchema()
373462
+ }));
373463
+ metadataSchema = lazySchema(() => exports_external.strictObject({
373464
+ source: exports_external.string().trim().min(1).max(100).optional()
373465
+ }).optional());
373466
+ requestObjectSchema = lazySchema(() => exports_external.strictObject({
373467
+ questions: exports_external.array(questionSchema()).min(1).max(MAX_QUESTIONS).describe(`Questions to ask the user (1-${MAX_QUESTIONS}). Ask only decisions that materially affect the result and cannot be inferred.`),
373468
+ metadata: metadataSchema()
373469
+ }).refine(UNIQUENESS_REFINE.check, {
373470
+ message: UNIQUENESS_REFINE.message
373471
+ }).refine((input) => JSON.stringify(input).length <= MAX_TOTAL_INPUT_CHARS, {
373472
+ message: `AskUserQuestion input must be at most ${MAX_TOTAL_INPUT_CHARS} characters`
373404
373473
  }));
373405
373474
  inputSchema32 = lazySchema(() => exports_external.preprocess(normalizeAskUserQuestionInput2, exports_external.strictObject({
373406
- questions: exports_external.array(questionSchema()).min(1).max(4).describe("Questions to ask the user (1-4 questions)"),
373407
- ...commonFields()
373475
+ questions: exports_external.array(questionSchema()).min(1).max(MAX_QUESTIONS),
373476
+ metadata: metadataSchema(),
373477
+ ...responseFields()
373408
373478
  }).refine(UNIQUENESS_REFINE.check, {
373409
373479
  message: UNIQUENESS_REFINE.message
373480
+ }).refine((input) => JSON.stringify(input).length <= MAX_TOTAL_INPUT_CHARS, {
373481
+ message: `AskUserQuestion input must be at most ${MAX_TOTAL_INPUT_CHARS} characters`
373410
373482
  })));
373411
- outputSchema27 = lazySchema(() => exports_external.object({
373483
+ modelInputJSONSchema = zodToJsonSchema3(requestObjectSchema());
373484
+ outputSchema27 = lazySchema(() => exports_external.strictObject({
373412
373485
  questions: exports_external.array(questionSchema()).describe("The questions that were asked"),
373413
- answers: exports_external.record(exports_external.string(), exports_external.string()).describe("The answers provided by the user (question text -> answer string; multi-select answers are comma-separated)"),
373486
+ answers: exports_external.record(exports_external.string(), exports_external.string().trim().min(1).max(MAX_ANSWER_CHARS)).describe("The answers provided by the user (question text -> answer string; multi-select answers are comma-separated)"),
373414
373487
  annotations: annotationsSchema()
373415
373488
  }));
373416
373489
  AskUserQuestionTool = buildTool({
@@ -373431,6 +373504,7 @@ var init_AskUserQuestionTool = __esm(() => {
373431
373504
  get inputSchema() {
373432
373505
  return inputSchema32();
373433
373506
  },
373507
+ inputJSONSchema: modelInputJSONSchema,
373434
373508
  get outputSchema() {
373435
373509
  return outputSchema27();
373436
373510
  },
@@ -373453,14 +373527,10 @@ var init_AskUserQuestionTool = __esm(() => {
373453
373527
  requiresUserInteraction() {
373454
373528
  return true;
373455
373529
  },
373456
- async validateInput({
373457
- questions
373458
- }) {
373459
- if (getQuestionPreviewFormat() !== "html") {
373460
- return {
373461
- result: true
373462
- };
373463
- }
373530
+ async validateInput(input, context5) {
373531
+ const {
373532
+ questions
373533
+ } = input;
373464
373534
  for (const q of questions) {
373465
373535
  for (const opt of q.options) {
373466
373536
  const err2 = validateHtmlPreview(opt.preview);
@@ -373473,6 +373543,45 @@ var init_AskUserQuestionTool = __esm(() => {
373473
373543
  }
373474
373544
  }
373475
373545
  }
373546
+ if (context5.validationPhase !== "post-permission") {
373547
+ if (Object.prototype.hasOwnProperty.call(input, "answers") || Object.prototype.hasOwnProperty.call(input, "annotations")) {
373548
+ return {
373549
+ result: false,
373550
+ message: "answers and annotations are response fields supplied only after trusted user interaction; omit them from the tool request",
373551
+ errorCode: 1
373552
+ };
373553
+ }
373554
+ return {
373555
+ result: true
373556
+ };
373557
+ }
373558
+ if (!Object.prototype.hasOwnProperty.call(input, "answers") || !input.answers) {
373559
+ return {
373560
+ result: false,
373561
+ message: "No verified user answers were collected. AskUserQuestion cannot complete from an unchanged permission approval.",
373562
+ errorCode: 1
373563
+ };
373564
+ }
373565
+ const expectedQuestions = new Set(questions.map((question) => question.question));
373566
+ const answerKeys = Object.keys(input.answers);
373567
+ const missingAnswers = questions.filter((question) => !Object.prototype.hasOwnProperty.call(input.answers, question.question)).map((question) => question.question);
373568
+ const unexpectedAnswers = answerKeys.filter((key) => !expectedQuestions.has(key));
373569
+ if (missingAnswers.length > 0 || unexpectedAnswers.length > 0) {
373570
+ const details = [...missingAnswers.length > 0 ? [`missing: ${missingAnswers.join(", ")}`] : [], ...unexpectedAnswers.length > 0 ? [`unexpected: ${unexpectedAnswers.join(", ")}`] : []].join("; ");
373571
+ return {
373572
+ result: false,
373573
+ message: `Verified answers must contain exactly one entry for every question (${details}).`,
373574
+ errorCode: 1
373575
+ };
373576
+ }
373577
+ const unexpectedAnnotations = Object.keys(input.annotations ?? {}).filter((key) => !expectedQuestions.has(key));
373578
+ if (unexpectedAnnotations.length > 0) {
373579
+ return {
373580
+ result: false,
373581
+ message: `User annotations contain unknown question keys: ${unexpectedAnnotations.join(", ")}`,
373582
+ errorCode: 1
373583
+ };
373584
+ }
373476
373585
  return {
373477
373586
  result: true
373478
373587
  };
@@ -373520,9 +373629,12 @@ var init_AskUserQuestionTool = __esm(() => {
373520
373629
  },
373521
373630
  async call({
373522
373631
  questions,
373523
- answers = {},
373632
+ answers,
373524
373633
  annotations
373525
373634
  }, _context) {
373635
+ if (!answers) {
373636
+ throw new Error("AskUserQuestion reached execution without verified user answers");
373637
+ }
373526
373638
  return {
373527
373639
  data: {
373528
373640
  questions,
@@ -375224,10 +375336,10 @@ function DANGEROUS_uncachedSystemPromptSection(name, compute, _reason) {
375224
375336
  return { name, compute, cacheBreak: true };
375225
375337
  }
375226
375338
  async function resolveSystemPromptSections(sections) {
375227
- const cache3 = getSystemPromptSectionCache();
375339
+ const cache4 = getSystemPromptSectionCache();
375228
375340
  return Promise.all(sections.map(async (s) => {
375229
- if (!s.cacheBreak && cache3.has(s.name)) {
375230
- return cache3.get(s.name) ?? null;
375341
+ if (!s.cacheBreak && cache4.has(s.name)) {
375342
+ return cache4.get(s.name) ?? null;
375231
375343
  }
375232
375344
  const value = await s.compute();
375233
375345
  setSystemPromptSectionCacheEntry(s.name, value);
@@ -377510,6 +377622,23 @@ var init_ConfigTool = __esm(() => {
377510
377622
  });
377511
377623
  });
377512
377624
 
377625
+ // src/tools/taskIdInput.ts
377626
+ function taskIdInputSchema(description) {
377627
+ return exports_external.union([
377628
+ exports_external.string().min(1),
377629
+ exports_external.number().int().positive().max(Number.MAX_SAFE_INTEGER)
377630
+ ]).describe(description);
377631
+ }
377632
+ function normalizeTaskIdInput(taskId) {
377633
+ return String(taskId);
377634
+ }
377635
+ function normalizeTaskIdInputs(taskIds) {
377636
+ return taskIds?.map(normalizeTaskIdInput);
377637
+ }
377638
+ var init_taskIdInput = __esm(() => {
377639
+ init_v4();
377640
+ });
377641
+
377513
377642
  // src/tools/TaskCreateTool/prompt.ts
377514
377643
  function getPrompt4() {
377515
377644
  const teammateContext = isAgentSwarmsEnabled() ? " and potentially assigned to teammates" : "";
@@ -377543,6 +377672,23 @@ NOTE that you should not use this tool if there is only one trivial task to do.
377543
377672
 
377544
377673
  EXCEPTION: none of the "skip" rules apply when the user explicitly asks for an item to be added to the task list ("add to your tasks \u2026"). An explicit request always wins \u2014 create the task.
377545
377674
 
377675
+ ## Decomposition Quality
377676
+
377677
+ For non-trivial work, create the complete task graph before implementation:
377678
+
377679
+ - One task represents one cohesive outcome with an observable done check.
377680
+ Split an omnibus task when it contains separately completable deliverables.
377681
+ - Keep a genuinely atomic outcome as one task. Do not manufacture tasks for
377682
+ individual files, tool calls, or tiny mechanical steps.
377683
+ - Express real ordering constraints with \`blocks\` / \`blockedBy\`. Leave
377684
+ unrelated tasks unblocked so agents can claim them in parallel.
377685
+ - If delegation is available, launch mutually independent tasks concurrently
377686
+ only when they have no conflicting shared mutations. Keep dependent or
377687
+ conflicting work sequential.
377688
+ - Emit one \`TaskCreate\` call per outcome. Batch independent creates in the
377689
+ same assistant turn (up to 8), then use \`TaskUpdate\` to add dependency
377690
+ edges once IDs are known and before starting blocked work.
377691
+
377546
377692
  ## Task Fields
377547
377693
 
377548
377694
  - **subject**: A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")
@@ -377574,17 +377720,21 @@ var init_TaskCreateTool = __esm(() => {
377574
377720
  init_hooks5();
377575
377721
  init_tasks();
377576
377722
  init_teammate();
377723
+ init_taskIdInput();
377577
377724
  init_prompt18();
377578
- inputSchema38 = lazySchema(() => exports_external.strictObject({
377579
- subject: exports_external.string().describe("A brief title for the task"),
377580
- description: exports_external.string().describe("What needs to be done"),
377581
- activeForm: exports_external.string().optional().describe('Present continuous form shown in spinner when in_progress (e.g., "Running tests")'),
377582
- metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Arbitrary metadata to attach to the task"),
377583
- blocks: exports_external.array(exports_external.string()).optional().describe("Task IDs that this task blocks at creation time"),
377584
- blockedBy: exports_external.array(exports_external.string()).optional().describe("Task IDs that block this task at creation time"),
377585
- addBlocks: exports_external.array(exports_external.string()).optional().describe("Alias for blocks, accepted for compatibility with TaskUpdate"),
377586
- addBlockedBy: exports_external.array(exports_external.string()).optional().describe("Alias for blockedBy, accepted for compatibility with TaskUpdate")
377587
- }));
377725
+ inputSchema38 = lazySchema(() => {
377726
+ const TaskIdSchema = taskIdInputSchema("A task dependency ID. Positive integer JSON values are accepted and normalized to strings.");
377727
+ return exports_external.strictObject({
377728
+ subject: exports_external.string().describe("A brief title for the task"),
377729
+ description: exports_external.string().describe("What needs to be done"),
377730
+ activeForm: exports_external.string().optional().describe('Present continuous form shown in spinner when in_progress (e.g., "Running tests")'),
377731
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Arbitrary metadata to attach to the task"),
377732
+ blocks: exports_external.array(TaskIdSchema).optional().describe("Task IDs that this task blocks at creation time"),
377733
+ blockedBy: exports_external.array(TaskIdSchema).optional().describe("Task IDs that block this task at creation time"),
377734
+ addBlocks: exports_external.array(TaskIdSchema).optional().describe("Alias for blocks, accepted for compatibility with TaskUpdate"),
377735
+ addBlockedBy: exports_external.array(TaskIdSchema).optional().describe("Alias for blockedBy, accepted for compatibility with TaskUpdate")
377736
+ });
377737
+ });
377588
377738
  outputSchema33 = lazySchema(() => exports_external.object({
377589
377739
  task: exports_external.object({
377590
377740
  id: exports_external.string(),
@@ -377633,9 +377783,14 @@ var init_TaskCreateTool = __esm(() => {
377633
377783
  addBlocks,
377634
377784
  addBlockedBy
377635
377785
  }, context5) {
377636
- const initialBlocks = [...new Set([...blocks ?? [], ...addBlocks ?? []])];
377786
+ const initialBlocks = [
377787
+ ...new Set(normalizeTaskIdInputs([...blocks ?? [], ...addBlocks ?? []]) ?? [])
377788
+ ];
377637
377789
  const initialBlockedBy = [
377638
- ...new Set([...blockedBy ?? [], ...addBlockedBy ?? []])
377790
+ ...new Set(normalizeTaskIdInputs([
377791
+ ...blockedBy ?? [],
377792
+ ...addBlockedBy ?? []
377793
+ ]) ?? [])
377639
377794
  ];
377640
377795
  const taskListId = getTaskListId();
377641
377796
  const taskId = await createTask(taskListId, {
@@ -377701,7 +377856,9 @@ var init_TaskCreateTool = __esm(() => {
377701
377856
  return {
377702
377857
  tool_use_id: toolUseID,
377703
377858
  type: "tool_result",
377704
- content: `Task #${task.id} created successfully: ${task.subject}`
377859
+ content: `Task #${task.id} created successfully: ${task.subject}
377860
+
377861
+ ` + `If this is one outcome within non-trivial work, create the remaining ` + `outcome tasks before implementation. Keep one task only when the ` + `work is genuinely atomic.`
377705
377862
  };
377706
377863
  }
377707
377864
  });
@@ -377737,8 +377894,9 @@ var init_TaskGetTool = __esm(() => {
377737
377894
  init_v4();
377738
377895
  init_Tool();
377739
377896
  init_tasks();
377897
+ init_taskIdInput();
377740
377898
  inputSchema39 = lazySchema(() => exports_external.strictObject({
377741
- taskId: exports_external.string().describe("The ID of the task to retrieve")
377899
+ taskId: taskIdInputSchema("The ID of the task to retrieve. Positive integer JSON values are accepted and normalized to strings.")
377742
377900
  }));
377743
377901
  outputSchema34 = lazySchema(() => exports_external.object({
377744
377902
  task: exports_external.object({
@@ -377780,12 +377938,13 @@ var init_TaskGetTool = __esm(() => {
377780
377938
  return true;
377781
377939
  },
377782
377940
  toAutoClassifierInput(input) {
377783
- return input.taskId;
377941
+ return String(input.taskId);
377784
377942
  },
377785
377943
  renderToolUseMessage() {
377786
377944
  return null;
377787
377945
  },
377788
- async call({ taskId }) {
377946
+ async call({ taskId: rawTaskId }) {
377947
+ const taskId = normalizeTaskIdInput(rawTaskId);
377789
377948
  const taskListId = getTaskListId();
377790
377949
  const task = await getTask(taskListId, taskId);
377791
377950
  if (!task) {
@@ -377912,16 +378071,18 @@ var init_TaskUpdateTool = __esm(() => {
377912
378071
  init_teammate();
377913
378072
  init_teammateMailbox();
377914
378073
  init_constants2();
378074
+ init_taskIdInput();
377915
378075
  inputSchema40 = lazySchema(() => {
377916
378076
  const TaskUpdateStatusSchema = TaskStatusSchema2().or(exports_external.literal("deleted"));
378077
+ const TaskIdSchema = taskIdInputSchema("The ID of the task to update. Positive integer JSON values are accepted and normalized to strings.");
377917
378078
  return exports_external.strictObject({
377918
- taskId: exports_external.string().describe("The ID of the task to update"),
378079
+ taskId: TaskIdSchema,
377919
378080
  subject: exports_external.string().optional().describe("New subject for the task"),
377920
378081
  description: exports_external.string().optional().describe("New description for the task"),
377921
378082
  activeForm: exports_external.string().optional().describe('Present continuous form shown in spinner when in_progress (e.g., "Running tests")'),
377922
378083
  status: TaskUpdateStatusSchema.optional().describe("New status for the task"),
377923
- addBlocks: exports_external.array(exports_external.string()).optional().describe("Task IDs that this task blocks"),
377924
- addBlockedBy: exports_external.array(exports_external.string()).optional().describe("Task IDs that block this task"),
378084
+ addBlocks: exports_external.array(TaskIdSchema).optional().describe("Task IDs that this task blocks. Positive integer JSON values are accepted and normalized to strings."),
378085
+ addBlockedBy: exports_external.array(TaskIdSchema).optional().describe("Task IDs that block this task. Positive integer JSON values are accepted and normalized to strings."),
377925
378086
  owner: exports_external.string().optional().describe("New owner for the task"),
377926
378087
  metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Metadata keys to merge into the task. Set a key to null to delete it.")
377927
378088
  });
@@ -377964,7 +378125,7 @@ var init_TaskUpdateTool = __esm(() => {
377964
378125
  return false;
377965
378126
  },
377966
378127
  toAutoClassifierInput(input) {
377967
- const parts = [input.taskId];
378128
+ const parts = [String(input.taskId)];
377968
378129
  if (input.status)
377969
378130
  parts.push(input.status);
377970
378131
  if (input.subject)
@@ -377975,7 +378136,7 @@ var init_TaskUpdateTool = __esm(() => {
377975
378136
  return null;
377976
378137
  },
377977
378138
  async call({
377978
- taskId,
378139
+ taskId: rawTaskId,
377979
378140
  subject,
377980
378141
  description,
377981
378142
  activeForm,
@@ -377985,6 +378146,9 @@ var init_TaskUpdateTool = __esm(() => {
377985
378146
  addBlockedBy,
377986
378147
  metadata
377987
378148
  }, context5) {
378149
+ const taskId = normalizeTaskIdInput(rawTaskId);
378150
+ const normalizedAddBlocks = normalizeTaskIdInputs(addBlocks);
378151
+ const normalizedAddBlockedBy = normalizeTaskIdInputs(addBlockedBy);
377988
378152
  const taskListId = getTaskListId();
377989
378153
  context5.setAppState((prev) => {
377990
378154
  if (prev.expandedView === "tasks")
@@ -378003,12 +378167,12 @@ var init_TaskUpdateTool = __esm(() => {
378003
378167
  };
378004
378168
  }
378005
378169
  const requestedDependencies = [
378006
- ...(addBlocks ?? []).map((targetId) => ({
378170
+ ...(normalizedAddBlocks ?? []).map((targetId) => ({
378007
378171
  fromTaskId: taskId,
378008
378172
  toTaskId: targetId,
378009
378173
  field: "addBlocks"
378010
378174
  })),
378011
- ...(addBlockedBy ?? []).map((blockerId) => ({
378175
+ ...(normalizedAddBlockedBy ?? []).map((blockerId) => ({
378012
378176
  fromTaskId: blockerId,
378013
378177
  toTaskId: taskId,
378014
378178
  field: "addBlockedBy"
@@ -378082,7 +378246,7 @@ var init_TaskUpdateTool = __esm(() => {
378082
378246
  const tasksById = new Map((await listTasks(taskListId)).map((task) => [task.id, task]));
378083
378247
  const effectiveBlockers = new Set([
378084
378248
  ...existingTask.blockedBy,
378085
- ...addBlockedBy ?? []
378249
+ ...normalizedAddBlockedBy ?? []
378086
378250
  ]);
378087
378251
  const unresolvedBlockers = [...effectiveBlockers].filter((blockerId) => {
378088
378252
  const blocker = tasksById.get(blockerId);
@@ -378121,8 +378285,8 @@ var init_TaskUpdateTool = __esm(() => {
378121
378285
  updatedFields.push("status");
378122
378286
  }
378123
378287
  }
378124
- const newBlocks = (addBlocks ?? []).filter((id) => !existingTask.blocks.includes(id));
378125
- const newBlockedBy = (addBlockedBy ?? []).filter((id) => !existingTask.blockedBy.includes(id));
378288
+ const newBlocks = (normalizedAddBlocks ?? []).filter((id) => !existingTask.blocks.includes(id));
378289
+ const newBlockedBy = (normalizedAddBlockedBy ?? []).filter((id) => !existingTask.blockedBy.includes(id));
378126
378290
  if (newBlocks.length > 0)
378127
378291
  updatedFields.push("blocks");
378128
378292
  if (newBlockedBy.length > 0)
@@ -378529,7 +378693,14 @@ var init_TeamCreateTool = __esm(() => {
378529
378693
  toAutoClassifierInput(input) {
378530
378694
  return input.team_name;
378531
378695
  },
378532
- async validateInput(input, _context) {
378696
+ async validateInput(input, context5) {
378697
+ if (context5.getAppState().toolPermissionContext.mode === "plan") {
378698
+ return {
378699
+ result: false,
378700
+ message: "TeamCreate is unavailable in plan mode because it changes team and task state. Finish and approve the plan before creating a team; use read-only Explore or Plan agents for planning research.",
378701
+ errorCode: 9
378702
+ };
378703
+ }
378533
378704
  if (!input.team_name || input.team_name.trim().length === 0) {
378534
378705
  return {
378535
378706
  result: false,
@@ -378560,6 +378731,9 @@ var init_TeamCreateTool = __esm(() => {
378560
378731
  async call(input, context5) {
378561
378732
  const { setAppState, getAppState } = context5;
378562
378733
  const { team_name, description: _description, agent_type } = input;
378734
+ if (getAppState().toolPermissionContext.mode === "plan") {
378735
+ throw new Error("TeamCreate is unavailable in plan mode because it changes team and task state.");
378736
+ }
378563
378737
  const appState = getAppState();
378564
378738
  const existingTeam = appState.teamContext?.teamName;
378565
378739
  if (existingTeam) {
@@ -378704,6 +378878,16 @@ var init_TeamDeleteTool = __esm(() => {
378704
378878
  async prompt() {
378705
378879
  return getPrompt7();
378706
378880
  },
378881
+ async validateInput(_input, context5) {
378882
+ if (context5.getAppState().toolPermissionContext.mode === "plan") {
378883
+ return {
378884
+ result: false,
378885
+ message: "TeamDelete is unavailable in plan mode because it changes team and task state. Finish or exit plan mode before deleting a team.",
378886
+ errorCode: 9
378887
+ };
378888
+ }
378889
+ return { result: true };
378890
+ },
378707
378891
  mapToolResultToToolResultBlockParam(data, toolUseID) {
378708
378892
  return {
378709
378893
  tool_use_id: toolUseID,
@@ -378719,6 +378903,9 @@ var init_TeamDeleteTool = __esm(() => {
378719
378903
  async call(_input, context5) {
378720
378904
  const { setAppState, getAppState } = context5;
378721
378905
  const appState = getAppState();
378906
+ if (appState.toolPermissionContext.mode === "plan") {
378907
+ throw new Error("TeamDelete is unavailable in plan mode because it changes team and task state.");
378908
+ }
378722
378909
  const teamName = appState.teamContext?.teamName;
378723
378910
  if (teamName) {
378724
378911
  const teamFile = readTeamFile(teamName);
@@ -381485,7 +381672,9 @@ The ${AGENT_TOOL_NAME} tool launches specialized agents (subprocesses) that auto
381485
381672
 
381486
381673
  ${agentListSection}
381487
381674
 
381488
- ${forkEnabled ? `When using the ${AGENT_TOOL_NAME} tool, specify a subagent_type to use a specialized agent, or omit it to fork yourself \u2014 a fork inherits your full conversation context.` : `When using the ${AGENT_TOOL_NAME} tool, specify a subagent_type parameter to select which agent type to use. If omitted, the general-purpose agent is used.`}`;
381675
+ ${forkEnabled ? `When using the ${AGENT_TOOL_NAME} tool, specify a subagent_type to use a specialized agent, or omit it to fork yourself \u2014 a fork inherits your full conversation context.` : `When using the ${AGENT_TOOL_NAME} tool, specify a subagent_type parameter to select which agent type to use. If omitted, the general-purpose agent is used.`}
381676
+
381677
+ For non-trivial delegation, define one cohesive task with its own observable done check per outcome before launching workers. Launch mutually independent tasks together only when they have no conflicting shared mutations; keep dependent or conflicting work sequential. Keep genuinely atomic work as one task instead of manufacturing extra agents.`;
381489
381678
  if (isCoordinator) {
381490
381679
  return shared;
381491
381680
  }
@@ -387616,7 +387805,7 @@ function isAnyTracingEnabled() {
387616
387805
  return isTelemetryEnabled() || isEnhancedTelemetryEnabled() || isBetaTracingEnabled();
387617
387806
  }
387618
387807
  function getTracer() {
387619
- return import_api39.trace.getTracer("ur-agent.gen_ai", "1.65.9");
387808
+ return import_api39.trace.getTracer("ur-agent.gen_ai", "1.65.11");
387620
387809
  }
387621
387810
  function createSpanAttributes(spanType, customAttributes = {}) {
387622
387811
  const baseAttributes = getTelemetryAttributes();
@@ -388235,9 +388424,129 @@ function formatValidationPath(path14) {
388235
388424
  return index2 === 0 ? segmentStr : `${String(acc)}.${segmentStr}`;
388236
388425
  }, "");
388237
388426
  }
388427
+ function formatList(values2) {
388428
+ const quoted = values2.map((value) => `\`${value}\``);
388429
+ if (quoted.length <= 1)
388430
+ return quoted[0] ?? "";
388431
+ if (quoted.length === 2)
388432
+ return `${quoted[0]} and ${quoted[1]}`;
388433
+ return `${quoted.slice(0, -1).join(", ")}, and ${quoted.at(-1)}`;
388434
+ }
388435
+ function formatIndexSet(indexes) {
388436
+ const sorted = [...new Set(indexes)].sort((a2, b) => a2 - b);
388437
+ if (sorted.length === 0)
388438
+ return "";
388439
+ const contiguous = sorted.every((value, index2) => index2 === 0 || value === sorted[index2 - 1] + 1);
388440
+ if (contiguous && sorted.length > 1) {
388441
+ return `${sorted[0]}..${sorted.at(-1)}`;
388442
+ }
388443
+ return sorted.join(",");
388444
+ }
388445
+ function formatMissingParameterErrors(error40) {
388446
+ const missing = error40.issues.map((issue2, order) => ({ issue: issue2, order })).filter(({ issue: issue2 }) => issue2.code === "invalid_type" && issue2.message.includes("received undefined"));
388447
+ const byPathShape = new Map;
388448
+ for (const { issue: issue2, order } of missing) {
388449
+ const numericAt = issue2.path.findIndex((segment2) => typeof segment2 === "number");
388450
+ if (numericAt === -1 || numericAt === issue2.path.length - 1)
388451
+ continue;
388452
+ const prefix = issue2.path.slice(0, numericAt);
388453
+ const suffix = issue2.path.slice(numericAt + 1);
388454
+ const index2 = issue2.path[numericAt];
388455
+ if (typeof index2 !== "number")
388456
+ continue;
388457
+ const key = JSON.stringify([prefix, suffix]);
388458
+ const group = byPathShape.get(key) ?? {
388459
+ prefix,
388460
+ suffix,
388461
+ indexes: [],
388462
+ issueOrders: [],
388463
+ firstOrder: order
388464
+ };
388465
+ group.indexes.push(index2);
388466
+ group.issueOrders.push(order);
388467
+ byPathShape.set(key, group);
388468
+ }
388469
+ const combined = new Map;
388470
+ for (const group of byPathShape.values()) {
388471
+ const indexes = [...new Set(group.indexes)].sort((a2, b) => a2 - b);
388472
+ if (indexes.length < 2)
388473
+ continue;
388474
+ const key = JSON.stringify([group.prefix, indexes]);
388475
+ const entry = combined.get(key) ?? {
388476
+ prefix: group.prefix,
388477
+ indexes,
388478
+ fields: [],
388479
+ issueOrders: [],
388480
+ firstOrder: group.firstOrder
388481
+ };
388482
+ entry.fields.push(formatValidationPath(group.suffix));
388483
+ entry.issueOrders.push(...group.issueOrders);
388484
+ entry.firstOrder = Math.min(entry.firstOrder, group.firstOrder);
388485
+ combined.set(key, entry);
388486
+ }
388487
+ const lines = [];
388488
+ const consumed = new Set;
388489
+ for (const entry of combined.values()) {
388490
+ const base2 = `${formatValidationPath(entry.prefix)}[${formatIndexSet(entry.indexes)}]`;
388491
+ const fields = [...new Set(entry.fields)];
388492
+ lines.push({
388493
+ order: entry.firstOrder,
388494
+ text: fields.length === 1 ? `The required field ${formatList(fields)} is missing from \`${base2}\`` : `The required fields ${formatList(fields)} are missing from \`${base2}\``
388495
+ });
388496
+ for (const order of entry.issueOrders)
388497
+ consumed.add(order);
388498
+ }
388499
+ for (const { issue: issue2, order } of missing) {
388500
+ if (consumed.has(order))
388501
+ continue;
388502
+ lines.push({
388503
+ order,
388504
+ text: `The required parameter \`${formatValidationPath(issue2.path)}\` is missing`
388505
+ });
388506
+ }
388507
+ return lines.sort((a2, b) => a2.order - b.order).map((line) => line.text);
388508
+ }
388509
+ function formatSizeConstraintErrors(error40) {
388510
+ const result = [];
388511
+ for (const issue2 of error40.issues) {
388512
+ if (issue2.code !== "too_big" && issue2.code !== "too_small")
388513
+ continue;
388514
+ const detail = issue2;
388515
+ const limit = issue2.code === "too_big" ? detail.maximum : detail.minimum;
388516
+ if (limit === undefined) {
388517
+ result.push(issue2.message);
388518
+ continue;
388519
+ }
388520
+ const path14 = formatValidationPath(issue2.path) || "input";
388521
+ const inclusive = detail.inclusive !== false;
388522
+ const comparison = issue2.code === "too_big" ? inclusive ? "at most" : "fewer than" : inclusive ? "at least" : "more than";
388523
+ const unit = detail.origin === "array" ? "items" : detail.origin === "string" ? "characters" : null;
388524
+ result.push(unit ? `The parameter \`${path14}\` must contain ${comparison} ${String(limit)} ${unit}` : `The parameter \`${path14}\` must be ${comparison} ${String(limit)}`);
388525
+ }
388526
+ return [...new Set(result)];
388527
+ }
388528
+ function getAskUserQuestionCorrection(error40) {
388529
+ if (!error40.issues.some((issue2) => issue2.path[0] === "questions"))
388530
+ return null;
388531
+ const inferredCount = error40.issues.reduce((count3, issue2) => {
388532
+ const index2 = issue2.path[0] === "questions" ? issue2.path[1] : undefined;
388533
+ return typeof index2 === "number" ? Math.max(count3, index2 + 1) : count3;
388534
+ }, 0);
388535
+ const countNotice = inferredCount > 4 ? ` This call contains at least ${inferredCount} incomplete question entries.` : "";
388536
+ return "AskUserQuestion requires 1-4 complete question objects. Each object must " + "contain `question`, `header`, and an `options` array with 2-8 " + "objects containing `label`; include `description` only when it adds a " + "useful consequence or trade-off." + countNotice + " Do not invent missing choices or truncate entries. Retry with at most " + "four complete questions, ask remaining decisions in later rounds, and " + "do not repeat the unchanged call.";
388537
+ }
388538
+ function getWriteCorrection(error40) {
388539
+ const missingRequiredField = error40.issues.some((issue2) => issue2.code === "invalid_type" && issue2.message.includes("received undefined") && (issue2.path[0] === "file_path" || issue2.path[0] === "content"));
388540
+ if (!missingRequiredField)
388541
+ return null;
388542
+ return "No file was written. Write requires both `file_path` and `content` in " + "the same structured tool call. Assistant prose outside the call is not " + "file content and will not be copied into it. Retry only after supplying " + "the complete intended file text in `content`; do not repeat the " + "unchanged call or claim the file was created until Write returns success.";
388543
+ }
388238
388544
  function formatZodValidationError(toolName, error40) {
388239
- const missingParams = error40.issues.filter((err2) => err2.code === "invalid_type" && err2.message.includes("received undefined")).map((err2) => formatValidationPath(err2.path));
388240
- const unexpectedParams = error40.issues.filter((err2) => err2.code === "unrecognized_keys").flatMap((err2) => err2.keys);
388545
+ const missingParamErrors = formatMissingParameterErrors(error40);
388546
+ const sizeConstraintErrors = formatSizeConstraintErrors(error40);
388547
+ const unexpectedParams = [
388548
+ ...new Set(error40.issues.filter((err2) => err2.code === "unrecognized_keys").flatMap((err2) => err2.keys))
388549
+ ];
388241
388550
  const typeMismatchParams = error40.issues.filter((err2) => err2.code === "invalid_type" && !err2.message.includes("received undefined")).map((err2) => {
388242
388551
  const typeErr = err2;
388243
388552
  const receivedMatch = err2.message.match(/received (\w+)/);
@@ -388250,10 +388559,8 @@ function formatZodValidationError(toolName, error40) {
388250
388559
  });
388251
388560
  let errorContent = error40.message;
388252
388561
  const errorParts = [];
388253
- if (missingParams.length > 0) {
388254
- const missingParamErrors = missingParams.map((param) => `The required parameter \`${param}\` is missing`);
388255
- errorParts.push(...missingParamErrors);
388256
- }
388562
+ errorParts.push(...sizeConstraintErrors);
388563
+ errorParts.push(...missingParamErrors);
388257
388564
  if (unexpectedParams.length > 0) {
388258
388565
  const unexpectedParamErrors = unexpectedParams.map((param) => `An unexpected parameter \`${param}\` was provided`);
388259
388566
  errorParts.push(...unexpectedParamErrors);
@@ -388266,6 +388573,19 @@ function formatZodValidationError(toolName, error40) {
388266
388573
  errorContent = `${toolName} failed due to the following ${errorParts.length > 1 ? "issues" : "issue"}:
388267
388574
  ${errorParts.join(`
388268
388575
  `)}`;
388576
+ }
388577
+ if (toolName === "AskUserQuestion") {
388578
+ const correction = getAskUserQuestionCorrection(error40);
388579
+ if (correction)
388580
+ errorContent += `
388581
+
388582
+ ${correction}`;
388583
+ } else if (toolName === "Write") {
388584
+ const correction = getWriteCorrection(error40);
388585
+ if (correction)
388586
+ errorContent += `
388587
+
388588
+ ${correction}`;
388269
388589
  }
388270
388590
  return errorContent;
388271
388591
  }
@@ -388292,6 +388612,36 @@ function isPlanArtifactMutationForGate(input) {
388292
388612
  return false;
388293
388613
  }
388294
388614
  }
388615
+ function isLocalPreviewOpenForTaskGate(input) {
388616
+ if (input.toolName !== "Bash" || typeof input.toolInput !== "object" || input.toolInput === null) {
388617
+ return false;
388618
+ }
388619
+ const candidate = input.toolInput;
388620
+ if (typeof candidate.command !== "string" || candidate.command.trim() === "" || candidate.run_in_background === true || candidate.dangerouslyDisableSandbox === true || candidate._simulatedSedEdit !== undefined) {
388621
+ return false;
388622
+ }
388623
+ const command = candidate.command;
388624
+ if (command.includes("$") || command.includes("`") || command.includes("\\") || command.includes(`
388625
+ `) || command.includes("\r") || command.includes("\x00") || hasUnbalancedQuotes(command)) {
388626
+ return false;
388627
+ }
388628
+ const parsed = tryParseShellCommand(command);
388629
+ if (!parsed.success || parsed.tokens.length !== 2 || parsed.tokens.some((token) => typeof token !== "string") || parsed.tokens[0] !== "open") {
388630
+ return false;
388631
+ }
388632
+ try {
388633
+ const url3 = new URL(parsed.tokens[1]);
388634
+ return (url3.protocol === "http:" || url3.protocol === "https:") && LOOPBACK_PREVIEW_HOSTS.has(url3.hostname) && url3.username === "" && url3.password === "";
388635
+ } catch {
388636
+ return false;
388637
+ }
388638
+ }
388639
+ function isMutationRequiringTaskList(input) {
388640
+ return input.isMutating && !isLocalPreviewOpenForTaskGate({
388641
+ toolName: input.toolName,
388642
+ toolInput: input.toolInput
388643
+ });
388644
+ }
388295
388645
  function getTaskListGateConfig() {
388296
388646
  const configured = getInitialSettings()?.tasks?.requireBeforeChanges;
388297
388647
  if (!configured)
@@ -388328,28 +388678,36 @@ function checkTaskListGate(input) {
388328
388678
  return { allowed: true };
388329
388679
  }
388330
388680
  if (input.taskCount === null) {
388681
+ const taskTool2 = input.taskPlanningToolName ?? "TaskCreate";
388331
388682
  return {
388332
388683
  allowed: false,
388333
- reason: `The task list could not be read, so ${input.toolName} was not allowed ` + `to change state without a verifiable plan. Retry TaskList or ` + `TaskCreate, then retry this call. Disable with ` + `tasks.requireBeforeChanges.enabled=false in settings.`
388684
+ reason: `The task list could not be read, so ${input.toolName} was not allowed ` + `to change state without a verifiable plan. Use ${taskTool2} to create ` + `or repair the task list, then retry this call. ` + `${TASK_DECOMPOSITION_RECOVERY} ` + `Disable with ` + `tasks.requireBeforeChanges.enabled=false in settings.`
388334
388685
  };
388335
388686
  }
388336
388687
  if (input.isSubagent || ALWAYS_REQUIRE_PLAN_TOOLS.has(input.toolName)) {
388688
+ const taskTool2 = input.taskPlanningToolName ?? "TaskCreate";
388689
+ const terminalContext = input.totalTaskCount !== null && input.totalTaskCount !== undefined && input.totalTaskCount > 0 ? " The existing task list contains only terminal tasks." : "";
388337
388690
  return {
388338
388691
  allowed: false,
388339
- reason: `No actionable parent task exists for ${input.toolName}. Call ` + `TaskCreate before delegating or changing state, then retry this call. ` + `Disable with tasks.requireBeforeChanges.enabled=false in settings.`
388692
+ reason: `No actionable parent task exists for ${input.toolName}.` + `${terminalContext} Call ${taskTool2} before delegating or changing ` + `state, then retry this call. ` + `${TASK_DECOMPOSITION_RECOVERY} ` + `Disable with tasks.requireBeforeChanges.enabled=false in settings.`
388340
388693
  };
388341
388694
  }
388342
388695
  if (input.readsSoFar < config2.freeReads)
388343
388696
  return { allowed: true };
388697
+ const taskTool = input.taskPlanningToolName ?? "TaskCreate";
388698
+ const hasTerminalTaskList = input.totalTaskCount !== null && input.totalTaskCount !== undefined && input.totalTaskCount > 0;
388699
+ const taskState = hasTerminalTaskList ? "The task list exists, but every tracked task is terminal, so no actionable task remains" : "No actionable task exists";
388700
+ const recovery = taskTool === "TodoWrite" ? "Call TodoWrite first to add a cohesive remaining todo or move the relevant todo back to pending/in_progress" : taskTool === "TaskCreate" ? "Call TaskCreate first to add a cohesive remaining task, or call TaskUpdate to move the relevant task back to pending/in_progress" : `Use ${taskTool} first to add or reopen a cohesive pending/in_progress task`;
388344
388701
  return {
388345
388702
  allowed: false,
388346
- reason: `No task list exists, and ${input.toolName} changes the workspace. ` + `Call TaskCreate first with the steps you intend to take, then retry ` + `this call. Reads are unrestricted, so investigate as much as you need ` + `before writing the list. ` + `Disable with tasks.requireBeforeChanges.enabled=false in settings.`
388703
+ reason: `${taskState}, and ${input.toolName} changes workspace state. ` + `${recovery}, then retry this call. Keep preview, launch, and ` + `verification work actionable until its observable check has actually ` + `run; do not mark that task complete before the check. ` + `${TASK_DECOMPOSITION_RECOVERY} Reads are unrestricted, so investigate ` + `as much as you need before writing the list. ` + `Disable with tasks.requireBeforeChanges.enabled=false in settings.`
388347
388704
  };
388348
388705
  }
388349
- var TASK_LIST_GATE_DEFAULTS, KNOWN_MUTATING_TOOLS, GATE_EXEMPT_TOOLS, ALWAYS_REQUIRE_PLAN_TOOLS, PLAN_ARTIFACT_MUTATING_TOOLS;
388706
+ var TASK_LIST_GATE_DEFAULTS, KNOWN_MUTATING_TOOLS, GATE_EXEMPT_TOOLS, ALWAYS_REQUIRE_PLAN_TOOLS, TASK_DECOMPOSITION_RECOVERY, PLAN_ARTIFACT_MUTATING_TOOLS, LOOPBACK_PREVIEW_HOSTS;
388350
388707
  var init_taskListGate = __esm(() => {
388351
388708
  init_settings2();
388352
388709
  init_path();
388710
+ init_shellQuote();
388353
388711
  TASK_LIST_GATE_DEFAULTS = {
388354
388712
  enabled: true,
388355
388713
  freeReads: 3
@@ -388372,17 +388730,24 @@ var init_taskListGate = __esm(() => {
388372
388730
  "TaskUpdate",
388373
388731
  "TaskList",
388374
388732
  "TaskGet",
388375
- "TodoWrite"
388733
+ "TodoWrite",
388734
+ "ExitPlanMode"
388376
388735
  ]);
388377
388736
  ALWAYS_REQUIRE_PLAN_TOOLS = new Set([
388378
388737
  "Agent",
388379
388738
  "Task"
388380
388739
  ]);
388740
+ TASK_DECOMPOSITION_RECOVERY = "For non-trivial work, create one task per cohesive outcome with its own " + "observable done check; keep one task only when the work is genuinely atomic.";
388381
388741
  PLAN_ARTIFACT_MUTATING_TOOLS = new Set([
388382
388742
  "Write",
388383
388743
  "Edit",
388384
388744
  "MultiEdit"
388385
388745
  ]);
388746
+ LOOPBACK_PREVIEW_HOSTS = new Set([
388747
+ "localhost",
388748
+ "127.0.0.1",
388749
+ "[::1]"
388750
+ ]);
388386
388751
  });
388387
388752
 
388388
388753
  // src/services/tools/repeatedFailureGuard.ts
@@ -389434,10 +389799,18 @@ async function countTasksForGate(toolUseContext) {
389434
389799
  const { getTaskListId: getTaskListId2, inspectTaskListForGate: inspectTaskListForGate2, isTodoV2Enabled: isTodoV2Enabled2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
389435
389800
  if (!isTodoV2Enabled2()) {
389436
389801
  const todoKey = toolUseContext.agentId ?? getSessionId();
389437
- return countActionableTodosForGate(toolUseContext.getAppState().todos?.[todoKey]);
389802
+ const todos = toolUseContext.getAppState().todos?.[todoKey] ?? [];
389803
+ return {
389804
+ actionableCount: countActionableTodosForGate(todos),
389805
+ totalCount: todos.length
389806
+ };
389438
389807
  }
389439
389808
  const inspection = await inspectTaskListForGate2(getTaskListId2());
389440
- return countActionableTasksForGate(inspection.tasks);
389809
+ const userTasks = inspection.tasks.filter((task) => !task.metadata?._internal);
389810
+ return {
389811
+ actionableCount: countActionableTasksForGate(userTasks),
389812
+ totalCount: userTasks.length
389813
+ };
389441
389814
  } catch {
389442
389815
  return null;
389443
389816
  }
@@ -389460,6 +389833,36 @@ function isCurrentPlanArtifactMutation(toolName, input, toolUseContext) {
389460
389833
  return false;
389461
389834
  }
389462
389835
  }
389836
+ function isReadOnlyPlanAgentDelegation(tool, input, toolUseContext) {
389837
+ try {
389838
+ if (toolUseContext.getAppState().toolPermissionContext.mode !== "plan" || !toolMatchesName(tool, AGENT_TOOL_NAME) || typeof input !== "object" || input === null) {
389839
+ return false;
389840
+ }
389841
+ const candidate = input;
389842
+ if (typeof candidate.subagent_type !== "string" || !READ_ONLY_PLAN_AGENT_TYPES.has(candidate.subagent_type) || candidate.name !== undefined || candidate.team_name !== undefined || candidate.mode !== undefined || candidate.isolation !== undefined || candidate.cwd !== undefined || candidate.run_in_background === true) {
389843
+ return false;
389844
+ }
389845
+ const activeAgent = toolUseContext.options.agentDefinitions?.activeAgents?.find((agent) => agent.agentType === candidate.subagent_type);
389846
+ return activeAgent?.source === "built-in";
389847
+ } catch {
389848
+ return false;
389849
+ }
389850
+ }
389851
+ function isBuiltInReadOnlyPlanningSubagent(toolUseContext) {
389852
+ if (!toolUseContext.agentId || !toolUseContext.agentType || !READ_ONLY_PLAN_AGENT_TYPES.has(toolUseContext.agentType)) {
389853
+ return false;
389854
+ }
389855
+ return toolUseContext.options.agentDefinitions?.activeAgents?.find((agent) => agent.agentType === toolUseContext.agentType)?.source === "built-in";
389856
+ }
389857
+ function getTaskPlanningToolName(toolUseContext) {
389858
+ if (toolUseContext.options.tools.some((tool) => toolMatchesName(tool, TASK_CREATE_TOOL_NAME))) {
389859
+ return TASK_CREATE_TOOL_NAME;
389860
+ }
389861
+ if (toolUseContext.options.tools.some((tool) => toolMatchesName(tool, TODO_WRITE_TOOL_NAME))) {
389862
+ return TODO_WRITE_TOOL_NAME;
389863
+ }
389864
+ return "the available task-list tool";
389865
+ }
389463
389866
  function getStopHookInfo(attachment) {
389464
389867
  if (typeof attachment !== "object" || attachment === null || !("command" in attachment) || typeof attachment.command !== "string" || !("durationMs" in attachment) || typeof attachment.durationMs !== "number") {
389465
389868
  return null;
@@ -389920,18 +390323,43 @@ async function checkPermissionsAndCallTool(tool, toolUseID, input, toolUseContex
389920
390323
  const initiallyValidatedCallSig = callSig;
389921
390324
  let isMutating = true;
389922
390325
  try {
389923
- isMutating = !tool.isReadOnly(parsedInput.data);
390326
+ isMutating = !tool.isReadOnly(parsedInput.data) && !isReadOnlyPlanAgentDelegation(tool, parsedInput.data, toolUseContext);
389924
390327
  } catch {
389925
390328
  isMutating = true;
389926
390329
  }
389927
390330
  const isPlanArtifactMutation = isCurrentPlanArtifactMutation(tool.name, parsedInput.data, toolUseContext);
390331
+ const isTaskListGatedMutation = isMutationRequiringTaskList({
390332
+ toolName: tool.name,
390333
+ toolInput: parsedInput.data,
390334
+ isMutating
390335
+ });
390336
+ if (isMutating && isBuiltInReadOnlyPlanningSubagent(toolUseContext)) {
390337
+ recordCallFailure(callSig);
390338
+ return [
390339
+ {
390340
+ message: createUserMessage({
390341
+ content: [
390342
+ {
390343
+ type: "tool_result",
390344
+ content: "<tool_use_error>ReadOnlyPlanningAgent: Built-in Explore and Plan agents may only perform read-only operations. Return your findings to the parent agent instead of changing state.</tool_use_error>",
390345
+ is_error: true,
390346
+ tool_use_id: toolUseID
390347
+ }
390348
+ ]
390349
+ })
390350
+ }
390351
+ ];
390352
+ }
390353
+ const taskCounts = await countTasksForGate(toolUseContext);
389928
390354
  const gate = checkTaskListGate({
389929
390355
  toolName: tool.name,
389930
- taskCount: await countTasksForGate(toolUseContext),
390356
+ taskCount: taskCounts?.actionableCount ?? null,
390357
+ totalTaskCount: taskCounts?.totalCount ?? null,
389931
390358
  readsSoFar: countToolCallsBeforeCurrent(toolUseContext.messages, assistantMessage, toolUseID),
389932
390359
  isSubagent: Boolean(toolUseContext.agentId),
389933
- isMutating,
389934
- isPlanArtifactMutation
390360
+ isMutating: isTaskListGatedMutation,
390361
+ isPlanArtifactMutation,
390362
+ taskPlanningToolName: getTaskPlanningToolName(toolUseContext)
389935
390363
  });
389936
390364
  if (gate.allowed === false) {
389937
390365
  recordCallFailure(callSig);
@@ -390255,7 +390683,7 @@ async function checkPermissionsAndCallTool(tool, toolUseID, input, toolUseContex
390255
390683
  });
390256
390684
  return resultingMessages;
390257
390685
  }
390258
- if (callSig !== initiallyValidatedCallSig) {
390686
+ if (callSig !== initiallyValidatedCallSig || tool.requiresUserInteraction?.()) {
390259
390687
  const finalValidation = await tool.validateInput?.(finalParsedInput.data, {
390260
390688
  ...toolUseContext,
390261
390689
  validationPhase: "post-permission"
@@ -390282,20 +390710,46 @@ async function checkPermissionsAndCallTool(tool, toolUseID, input, toolUseContex
390282
390710
  }
390283
390711
  let finalIsMutating = true;
390284
390712
  try {
390285
- finalIsMutating = !tool.isReadOnly(finalParsedInput.data);
390713
+ finalIsMutating = !tool.isReadOnly(finalParsedInput.data) && !isReadOnlyPlanAgentDelegation(tool, finalParsedInput.data, toolUseContext);
390286
390714
  } catch {
390287
390715
  finalIsMutating = true;
390288
390716
  }
390289
390717
  const finalIsPlanArtifactMutation = isCurrentPlanArtifactMutation(tool.name, finalParsedInput.data, toolUseContext);
390290
- const effectiveCallChanged = callSig !== initiallyValidatedCallSig || finalIsMutating !== isMutating || finalIsPlanArtifactMutation !== isPlanArtifactMutation;
390291
- if (finalIsMutating && !finalIsPlanArtifactMutation) {
390718
+ const finalIsTaskListGatedMutation = isMutationRequiringTaskList({
390719
+ toolName: tool.name,
390720
+ toolInput: finalParsedInput.data,
390721
+ isMutating: finalIsMutating
390722
+ });
390723
+ if (finalIsMutating && isBuiltInReadOnlyPlanningSubagent(toolUseContext)) {
390724
+ recordCallFailure(callSig);
390725
+ finishPreExecutionRejection();
390726
+ resultingMessages.push({
390727
+ message: createUserMessage({
390728
+ content: [
390729
+ {
390730
+ type: "tool_result",
390731
+ content: "<tool_use_error>ReadOnlyPlanningAgent after input update: Built-in Explore and Plan agents may only perform read-only operations. Return your findings to the parent agent instead of changing state.</tool_use_error>",
390732
+ is_error: true,
390733
+ tool_use_id: toolUseID
390734
+ }
390735
+ ],
390736
+ sourceToolAssistantUUID: assistantMessage.uuid
390737
+ })
390738
+ });
390739
+ return resultingMessages;
390740
+ }
390741
+ const effectiveCallChanged = callSig !== initiallyValidatedCallSig || finalIsMutating !== isMutating || finalIsTaskListGatedMutation !== isTaskListGatedMutation || finalIsPlanArtifactMutation !== isPlanArtifactMutation;
390742
+ if (finalIsTaskListGatedMutation && !finalIsPlanArtifactMutation) {
390743
+ const finalTaskCounts = await countTasksForGate(toolUseContext);
390292
390744
  const finalGate = checkTaskListGate({
390293
390745
  toolName: tool.name,
390294
- taskCount: await countTasksForGate(toolUseContext),
390746
+ taskCount: finalTaskCounts?.actionableCount ?? null,
390747
+ totalTaskCount: finalTaskCounts?.totalCount ?? null,
390295
390748
  readsSoFar: countToolCallsBeforeCurrent(toolUseContext.messages, assistantMessage, toolUseID),
390296
390749
  isSubagent: Boolean(toolUseContext.agentId),
390297
- isMutating: finalIsMutating,
390298
- isPlanArtifactMutation: finalIsPlanArtifactMutation
390750
+ isMutating: finalIsTaskListGatedMutation,
390751
+ isPlanArtifactMutation: finalIsPlanArtifactMutation,
390752
+ taskPlanningToolName: getTaskPlanningToolName(toolUseContext)
390299
390753
  });
390300
390754
  if (finalGate.allowed === false) {
390301
390755
  recordCallFailure(callSig);
@@ -390693,6 +391147,7 @@ var init_toolExecution = __esm(() => {
390693
391147
  init_cwd2();
390694
391148
  init_permissionLogging();
390695
391149
  init_Tool();
391150
+ init_constants2();
390696
391151
  init_bashPermissions();
390697
391152
  init_prompt2();
390698
391153
  init_prompt3();
@@ -392270,7 +392725,7 @@ function projectStoreFile(cwd2, directory, name, create2) {
392270
392725
  }
392271
392726
  return create2 || existsSync30(target) ? target : undefined;
392272
392727
  }
392273
- function boundedText(text) {
392728
+ function boundedText2(text) {
392274
392729
  const normalized = text.trim();
392275
392730
  if (!normalized)
392276
392731
  throw new Error("note text cannot be empty");
@@ -392334,7 +392789,7 @@ function rememberInAutoMemory(memoryDir, text) {
392334
392789
  function remember(cwd2, text) {
392335
392790
  append2(memFile(cwd2, true), {
392336
392791
  ts: new Date().toISOString(),
392337
- text: boundedText(text),
392792
+ text: boundedText2(text),
392338
392793
  kind: "note"
392339
392794
  });
392340
392795
  }
@@ -392384,7 +392839,7 @@ function forgetInAutoMemory(memoryDir, texts) {
392384
392839
  function addResearch(cwd2, kind, text) {
392385
392840
  append2(researchFile(cwd2, kind, true), {
392386
392841
  ts: new Date().toISOString(),
392387
- text: boundedText(text),
392842
+ text: boundedText2(text),
392388
392843
  kind
392389
392844
  });
392390
392845
  }
@@ -395688,7 +396143,8 @@ async function createPlanModeAttachmentIfNeeded(context5) {
395688
396143
  reminderType: "full",
395689
396144
  isSubAgent: !!context5.agentId,
395690
396145
  planFilePath,
395691
- planExists
396146
+ planExists,
396147
+ availablePlanAgentTypes: getAvailableReadOnlyPlanAgentTypes(context5)
395692
396148
  });
395693
396149
  }
395694
396150
  async function createAsyncAgentAttachmentsIfNeeded(context5) {
@@ -397180,21 +397636,6 @@ var init_analyzeContext = __esm(() => {
397180
397636
  init_tokens();
397181
397637
  });
397182
397638
 
397183
- // src/utils/zodToJsonSchema.ts
397184
- function zodToJsonSchema3(schema) {
397185
- const hit = cache3.get(schema);
397186
- if (hit)
397187
- return hit;
397188
- const result = toJSONSchema(schema);
397189
- cache3.set(schema, result);
397190
- return result;
397191
- }
397192
- var cache3;
397193
- var init_zodToJsonSchema2 = __esm(() => {
397194
- init_v4();
397195
- cache3 = new WeakMap;
397196
- });
397197
-
397198
397639
  // src/utils/toolSearch.ts
397199
397640
  var exports_toolSearch = {};
397200
397641
  __export(exports_toolSearch, {
@@ -399951,10 +400392,24 @@ async function getPlanModeAttachments(messages, toolUseContext) {
399951
400392
  reminderType,
399952
400393
  isSubAgent: !!toolUseContext.agentId,
399953
400394
  planFilePath,
399954
- planExists: existingPlan !== null
400395
+ planExists: existingPlan !== null,
400396
+ availablePlanAgentTypes: getAvailableReadOnlyPlanAgentTypes(toolUseContext)
399955
400397
  });
399956
400398
  return attachments;
399957
400399
  }
400400
+ function getAvailableReadOnlyPlanAgentTypes(toolUseContext) {
400401
+ const hasAgentTool = toolUseContext.options.tools.some((tool) => toolMatchesName(tool, AGENT_TOOL_NAME));
400402
+ if (!hasAgentTool)
400403
+ return [];
400404
+ try {
400405
+ const definitions = toolUseContext.options.agentDefinitions;
400406
+ const activeAgents = definitions?.activeAgents ?? [];
400407
+ const allowlistedAgents = definitions?.allowedAgentTypes ? activeAgents.filter((agent) => definitions.allowedAgentTypes.includes(agent.agentType)) : activeAgents;
400408
+ return filterDeniedAgents(allowlistedAgents, toolUseContext.getAppState().toolPermissionContext, AGENT_TOOL_NAME).filter((agent) => agent.source === "built-in" && READ_ONLY_PLAN_AGENT_TYPES.has(agent.agentType)).map((agent) => agent.agentType);
400409
+ } catch {
400410
+ return [];
400411
+ }
400412
+ }
399958
400413
  async function getPlanModeExitAttachment(toolUseContext) {
399959
400414
  if (!needsPlanModeExitAttachment()) {
399960
400415
  return [];
@@ -408416,6 +408871,12 @@ function getPlanPhase4Section() {
408416
408871
  return PLAN_PHASE4_CONTROL;
408417
408872
  }
408418
408873
  }
408874
+ function getAvailablePlanAgentTypes(attachment) {
408875
+ if (attachment.availablePlanAgentTypes !== undefined) {
408876
+ return new Set(attachment.availablePlanAgentTypes);
408877
+ }
408878
+ return areExplorePlanAgentsEnabled() ? new Set([EXPLORE_AGENT.agentType, PLAN_AGENT.agentType]) : new Set;
408879
+ }
408419
408880
  function getPlanModeV2Instructions(attachment) {
408420
408881
  if (attachment.isSubAgent) {
408421
408882
  return [];
@@ -408426,26 +408887,21 @@ function getPlanModeV2Instructions(attachment) {
408426
408887
  const agentCount = getPlanModeV2AgentCount();
408427
408888
  const exploreAgentCount = getPlanModeV2ExploreAgentCount();
408428
408889
  const planFileInfo = attachment.planExists ? `A plan file already exists at ${attachment.planFilePath}. You can read it and make incremental edits using the ${FileEditTool.name} tool.` : `No plan file exists yet. You should create your plan at ${attachment.planFilePath} using the ${FileWriteTool.name} tool.`;
408429
- const content = `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.
408430
-
408431
- ## Plan File Info:
408432
- ${planFileInfo}
408433
- You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
408434
-
408435
- ## Plan Workflow
408436
-
408437
- ### Phase 1: Initial Understanding
408890
+ const availablePlanAgents = getAvailablePlanAgentTypes(attachment);
408891
+ const phase1 = availablePlanAgents.has(EXPLORE_AGENT.agentType) ? `### Phase 1: Initial Understanding
408438
408892
  Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the ${EXPLORE_AGENT.agentType} subagent type.
408439
408893
 
408440
408894
  1. Focus on understanding the user's request and the code associated with their request. Actively search for existing functions, utilities, and patterns that can be reused \u2014 avoid proposing new code when suitable implementations already exist.
408441
408895
 
408442
408896
  2. **Launch up to ${exploreAgentCount} ${EXPLORE_AGENT.agentType} agents IN PARALLEL** (single message, multiple tool calls) to efficiently explore the codebase.
408443
408897
  - Use 1 agent when the task is isolated to known files, the user provided specific file paths, or you're making a small targeted change.
408444
- - Use multiple agents when: the scope is uncertain, multiple areas of the codebase are involved, or you need to understand existing patterns before planning.
408445
- - Quality over quantity - ${exploreAgentCount} agents maximum, but you should try to use the minimum number of agents necessary (usually just 1)
408446
- - If using multiple agents: Provide each agent with a specific search focus or area to explore. Example: One agent searches for existing implementations, another explores related components, a third investigating testing patterns
408898
+ - Use multiple agents when the scope is uncertain or independent areas need investigation.
408899
+ - Give every agent a distinct search focus; use the minimum number needed.` : `### Phase 1: Initial Understanding
408900
+ Goal: Gain a comprehensive understanding of the user's request using the read-only tools that are actually available.
408447
408901
 
408448
- ### Phase 2: Design
408902
+ 1. Focus on the request and associated code. Search for existing functions, utilities, and patterns that can be reused.
408903
+ 2. Use ${getReadOnlyToolNames()} directly. Batch independent searches and reads in parallel (up to 8 calls per turn), but keep dependent investigation sequential. Do not call a generic Agent as a planning fallback.`;
408904
+ const phase2 = availablePlanAgents.has(PLAN_AGENT.agentType) ? `### Phase 2: Design
408449
408905
  Goal: Design an implementation approach.
408450
408906
 
408451
408907
  Launch ${PLAN_AGENT.agentType} agent(s) to design the implementation based on the user's intent and your exploration results from Phase 1.
@@ -408457,21 +408913,22 @@ You can launch up to ${agentCount} agent(s) in parallel.
408457
408913
  - **Skip agents**: Only for truly trivial tasks (typo fixes, single-line changes, simple renames)
408458
408914
  ${agentCount > 1 ? `- **Multiple agents**: Use up to ${agentCount} agents for complex tasks that benefit from different perspectives
408459
408915
 
408460
- Examples of when to use multiple agents:
408461
- - The task touches multiple parts of the codebase
408462
- - It's a large refactor or architectural change
408463
- - There are many edge cases to consider
408464
- - You'd benefit from exploring different approaches
408916
+ Examples: simplicity vs performance, root cause vs prevention, or minimal change vs clean architecture.` : ""}
408917
+ In each agent prompt, provide Phase 1 evidence, filenames, code-path traces, requirements, constraints, and the perspective to evaluate.` : `### Phase 2: Design
408918
+ Goal: Design the implementation directly from the Phase 1 evidence.
408465
408919
 
408466
- Example perspectives by task type:
408467
- - New feature: simplicity vs performance vs maintainability
408468
- - Bug fix: root cause vs workaround vs prevention
408469
- - Refactoring: minimal change vs clean architecture
408470
- ` : ""}
408471
- In the agent prompt:
408472
- - Provide comprehensive background context from Phase 1 exploration including filenames and code path traces
408473
- - Describe requirements and constraints
408474
- - Request a detailed implementation plan
408920
+ Compare plausible approaches, choose the smallest approach that fully satisfies the request, identify trade-offs and edge cases, and trace every proposed change to current code. No read-only Plan worker is active, so do not call a generic Agent as a substitute.`;
408921
+ const content = `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.
408922
+
408923
+ ## Plan File Info:
408924
+ ${planFileInfo}
408925
+ You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
408926
+
408927
+ ## Plan Workflow
408928
+
408929
+ ${phase1}
408930
+
408931
+ ${phase2}
408475
408932
 
408476
408933
  ### Phase 3: Review
408477
408934
  Goal: Review the plan(s) from Phase 2 and ensure alignment with the user's intentions.
@@ -408513,7 +408970,7 @@ You are pair-planning with the user. Explore the code to build context, ask the
408513
408970
 
408514
408971
  Repeat this cycle until the plan is complete:
408515
408972
 
408516
- 1. **Explore** \u2014 Use ${getReadOnlyToolNames()} to read code. Look for existing functions, utilities, and patterns to reuse.${areExplorePlanAgentsEnabled() ? ` You can use the ${EXPLORE_AGENT.agentType} agent type to parallelize complex searches without filling your context, though for straightforward queries direct tools are simpler.` : ""}
408973
+ 1. **Explore** \u2014 Use ${getReadOnlyToolNames()} to read code. Look for existing functions, utilities, and patterns to reuse.${getAvailablePlanAgentTypes(attachment).has(EXPLORE_AGENT.agentType) ? ` You can use the ${EXPLORE_AGENT.agentType} agent type to parallelize complex searches without filling your context, though for straightforward queries direct tools are simpler.` : ""}
408517
408974
  2. **Update the plan file** \u2014 After each discovery, immediately capture what you learned. Don't wait until the end.
408518
408975
  3. **Ask the user** \u2014 When you hit an ambiguity or decision you can't resolve from code alone, use ${ASK_USER_QUESTION_TOOL_NAME}. Then go back to step 1.
408519
408976
 
@@ -408535,6 +408992,7 @@ Your plan file should be divided into clear sections using markdown headers, bas
408535
408992
  - Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively
408536
408993
  - Include the paths of critical files to be modified
408537
408994
  - Reference existing functions and utilities you found that should be reused, with their file paths
408995
+ - ${PLAN_TASK_GRAPH_REQUIREMENT}
408538
408996
  - Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)
408539
408997
 
408540
408998
  ### When to Converge
@@ -409923,33 +410381,7 @@ Note: The user's next message may contain a correction or preference. Pay close
409923
410381
  `, PLAN_REJECTION_PREFIX = `The agent proposed a plan that was rejected by the user. The user chose to stay in plan mode rather than proceed with implementation.
409924
410382
 
409925
410383
  Rejected plan:
409926
- `, DENIAL_WORKAROUND_GUIDANCE, NO_RESPONSE_REQUESTED = "No response requested.", SYNTHETIC_TOOL_RESULT_PLACEHOLDER = "[Tool result missing due to internal error]", SYNTHETIC_MODEL = "<synthetic>", SYNTHETIC_MESSAGES, EMPTY_LOOKUPS, EMPTY_STRING_SET, STRIPPED_TAGS_RE, PLAN_PHASE4_CONTROL = `### Phase 4: Final Plan
409927
- Goal: Write your final plan to the plan file (the only file you can edit).
409928
- - Begin with a **Context** section: explain why this change is being made \u2014 the problem or need it addresses, what prompted it, and the intended outcome
409929
- - Include only your recommended approach, not all alternatives
409930
- - Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively
409931
- - Include the paths of critical files to be modified
409932
- - Reference existing functions and utilities you found that should be reused, with their file paths
409933
- - Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)`, PLAN_PHASE4_TRIM = `### Phase 4: Final Plan
409934
- Goal: Write your final plan to the plan file (the only file you can edit).
409935
- - One-line **Context**: what is being changed and why
409936
- - Include only your recommended approach, not all alternatives
409937
- - List the paths of files to be modified
409938
- - Reference existing functions and utilities to reuse, with their file paths
409939
- - End with **Verification**: the single command to run to confirm the change works (no numbered test procedures)`, PLAN_PHASE4_CUT = `### Phase 4: Final Plan
409940
- Goal: Write your final plan to the plan file (the only file you can edit).
409941
- - Do NOT write a Context or Background section. The user just told you what they want.
409942
- - List the paths of files to be modified and what changes in each (one line per file)
409943
- - Reference existing functions and utilities to reuse, with their file paths
409944
- - End with **Verification**: the single command that confirms the change works
409945
- - Most good plans are under 40 lines. Prose is a sign you are padding.`, PLAN_PHASE4_CAP = `### Phase 4: Final Plan
409946
- Goal: Write your final plan to the plan file (the only file you can edit).
409947
- - Do NOT write a Context, Background, or Overview section. The user just told you what they want.
409948
- - Do NOT restate the user's request. Do NOT write prose paragraphs.
409949
- - List the paths of files to be modified and what changes in each (one bullet per file)
409950
- - Reference existing functions to reuse, with file:line
409951
- - End with the single verification command
409952
- - **Hard limit: 40 lines.** If the plan is longer, delete prose \u2014 not file paths.`;
410384
+ `, DENIAL_WORKAROUND_GUIDANCE, NO_RESPONSE_REQUESTED = "No response requested.", SYNTHETIC_TOOL_RESULT_PLACEHOLDER = "[Tool result missing due to internal error]", SYNTHETIC_MODEL = "<synthetic>", SYNTHETIC_MESSAGES, EMPTY_LOOKUPS, EMPTY_STRING_SET, STRIPPED_TAGS_RE, PLAN_PHASE4_CONTROL, PLAN_PHASE4_TRIM, PLAN_PHASE4_CUT, PLAN_PHASE4_CAP;
409953
410385
  var init_messages = __esm(() => {
409954
410386
  init_isObject();
409955
410387
  init_last();
@@ -409980,6 +410412,7 @@ var init_messages = __esm(() => {
409980
410412
  init_prompt();
409981
410413
  init_state();
409982
410414
  init_xml();
410415
+ init_planImplementationContract();
409983
410416
  init_diagnosticTracking();
409984
410417
  init_Tool();
409985
410418
  init_FileReadTool();
@@ -410018,6 +410451,40 @@ var init_messages = __esm(() => {
410018
410451
  };
410019
410452
  EMPTY_STRING_SET = Object.freeze(new Set);
410020
410453
  STRIPPED_TAGS_RE = /<(commit_analysis|context|function_analysis|pr_analysis)>.*?<\/\1>\n?/gs;
410454
+ PLAN_PHASE4_CONTROL = `### Phase 4: Final Plan
410455
+ Goal: Write your final plan to the plan file (the only file you can edit).
410456
+ - Begin with a **Context** section: explain why this change is being made \u2014 the problem or need it addresses, what prompted it, and the intended outcome
410457
+ - Include only your recommended approach, not all alternatives
410458
+ - Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively
410459
+ - Include the paths of critical files to be modified
410460
+ - Reference existing functions and utilities you found that should be reused, with their file paths
410461
+ - ${PLAN_TASK_GRAPH_REQUIREMENT}
410462
+ - Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)`;
410463
+ PLAN_PHASE4_TRIM = `### Phase 4: Final Plan
410464
+ Goal: Write your final plan to the plan file (the only file you can edit).
410465
+ - One-line **Context**: what is being changed and why
410466
+ - Include only your recommended approach, not all alternatives
410467
+ - List the paths of files to be modified
410468
+ - Reference existing functions and utilities to reuse, with their file paths
410469
+ - ${PLAN_TASK_GRAPH_REQUIREMENT}
410470
+ - End with **Verification**: the single command to run to confirm the change works (no numbered test procedures)`;
410471
+ PLAN_PHASE4_CUT = `### Phase 4: Final Plan
410472
+ Goal: Write your final plan to the plan file (the only file you can edit).
410473
+ - Do NOT write a Context or Background section. The user just told you what they want.
410474
+ - List the paths of files to be modified and what changes in each (one line per file)
410475
+ - Reference existing functions and utilities to reuse, with their file paths
410476
+ - ${PLAN_TASK_GRAPH_REQUIREMENT}
410477
+ - End with **Verification**: the single command that confirms the change works
410478
+ - Most good plans are under 40 lines. Prose is a sign you are padding.`;
410479
+ PLAN_PHASE4_CAP = `### Phase 4: Final Plan
410480
+ Goal: Write your final plan to the plan file (the only file you can edit).
410481
+ - Do NOT write a Context, Background, or Overview section. The user just told you what they want.
410482
+ - Do NOT restate the user's request. Do NOT write prose paragraphs.
410483
+ - List the paths of files to be modified and what changes in each (one bullet per file)
410484
+ - Reference existing functions to reuse, with file:line
410485
+ - ${PLAN_TASK_GRAPH_REQUIREMENT}
410486
+ - End with the single verification command
410487
+ - **Hard limit: 40 lines.** If the plan is longer, delete prose \u2014 not file paths.`;
410021
410488
  });
410022
410489
 
410023
410490
  // src/services/api/errors.ts
@@ -418035,7 +418502,7 @@ function Feedback({
418035
418502
  platform: env2.platform,
418036
418503
  gitRepo: envInfo.isGit,
418037
418504
  terminal: env2.terminal,
418038
- version: "1.65.9",
418505
+ version: "1.65.11",
418039
418506
  transcript: normalizeMessagesForAPI(messages),
418040
418507
  errors: sanitizedErrors,
418041
418508
  lastApiRequest: getLastAPIRequest(),
@@ -418227,7 +418694,7 @@ function Feedback({
418227
418694
  ", ",
418228
418695
  env2.terminal,
418229
418696
  ", v",
418230
- "1.65.9"
418697
+ "1.65.11"
418231
418698
  ]
418232
418699
  }, undefined, true, undefined, this)
418233
418700
  ]
@@ -418333,7 +418800,7 @@ ${sanitizedDescription}
418333
418800
  ` + `**Environment Info**
418334
418801
  ` + `- Platform: ${env2.platform}
418335
418802
  ` + `- Terminal: ${env2.terminal}
418336
- ` + `- Version: ${"1.65.9"}
418803
+ ` + `- Version: ${"1.65.11"}
418337
418804
  ` + `- Feedback ID: ${feedbackId}
418338
418805
  ` + `
418339
418806
  **Errors**
@@ -421443,7 +421910,7 @@ function buildPrimarySection() {
421443
421910
  }, undefined, false, undefined, this);
421444
421911
  return [{
421445
421912
  label: "Version",
421446
- value: "1.65.9"
421913
+ value: "1.65.11"
421447
421914
  }, {
421448
421915
  label: "Session name",
421449
421916
  value: nameValue
@@ -424773,7 +425240,7 @@ function Config({
424773
425240
  }
424774
425241
  }, undefined, false, undefined, this)
424775
425242
  }, undefined, false, undefined, this) : showSubmenu === "ChannelDowngrade" ? /* @__PURE__ */ jsx_dev_runtime179.jsxDEV(ChannelDowngradeDialog, {
424776
- currentVersion: "1.65.9",
425243
+ currentVersion: "1.65.11",
424777
425244
  onChoice: (choice) => {
424778
425245
  setShowSubmenu(null);
424779
425246
  setTabsHidden(false);
@@ -424785,7 +425252,7 @@ function Config({
424785
425252
  autoUpdatesChannel: "stable"
424786
425253
  };
424787
425254
  if (choice === "stay") {
424788
- newSettings.minimumVersion = "1.65.9";
425255
+ newSettings.minimumVersion = "1.65.11";
424789
425256
  }
424790
425257
  updateSettingsForSource("userSettings", newSettings);
424791
425258
  setSettingsData((prev_27) => ({
@@ -432849,7 +433316,7 @@ function HelpV2(t0) {
432849
433316
  let t6;
432850
433317
  if ($2[31] !== tabs) {
432851
433318
  t6 = /* @__PURE__ */ jsx_dev_runtime206.jsxDEV(Tabs, {
432852
- title: `UR v${"1.65.9"}`,
433319
+ title: `UR v${"1.65.11"}`,
432853
433320
  color: "professionalBlue",
432854
433321
  defaultTab: "general",
432855
433322
  children: tabs
@@ -433782,7 +434249,7 @@ function buildToolUseContext(tools, readFileStateCache, toolPermissionContext, a
433782
434249
  async function handleInitialize(options2) {
433783
434250
  return {
433784
434251
  name: "UR",
433785
- version: "1.65.9",
434252
+ version: "1.65.11",
433786
434253
  protocolVersion: "0.1.0",
433787
434254
  workspaceRoot: options2.cwd,
433788
434255
  capabilities: {
@@ -450890,7 +451357,7 @@ function getAllReleaseNotes(changelogContent = getStoredChangelogFromMemory()) {
450890
451357
  return [];
450891
451358
  }
450892
451359
  }
450893
- async function checkForReleaseNotes(lastSeenVersion, currentVersion = "1.65.9") {
451360
+ async function checkForReleaseNotes(lastSeenVersion, currentVersion = "1.65.11") {
450894
451361
  if (process.env.USER_TYPE === "ant") {
450895
451362
  const changelog = "";
450896
451363
  if (changelog) {
@@ -450917,7 +451384,7 @@ async function checkForReleaseNotes(lastSeenVersion, currentVersion = "1.65.9")
450917
451384
  releaseNotes
450918
451385
  };
450919
451386
  }
450920
- function checkForReleaseNotesSync(lastSeenVersion, currentVersion = "1.65.9") {
451387
+ function checkForReleaseNotesSync(lastSeenVersion, currentVersion = "1.65.11") {
450921
451388
  if (process.env.USER_TYPE === "ant") {
450922
451389
  const changelog = "";
450923
451390
  if (changelog) {
@@ -453783,7 +454250,7 @@ function getRecentActivitySync() {
453783
454250
  return cachedActivity;
453784
454251
  }
453785
454252
  function getLogoDisplayData() {
453786
- const version2 = process.env.DEMO_VERSION ?? "1.65.9";
454253
+ const version2 = process.env.DEMO_VERSION ?? "1.65.11";
453787
454254
  const serverUrl = getDirectConnectServerUrl();
453788
454255
  const displayPath = process.env.DEMO_VERSION ? "/code/ur" : getDisplayPath(getCwd());
453789
454256
  const cwd2 = serverUrl ? `${displayPath} in ${serverUrl.replace(/^https?:\/\//, "")}` : displayPath;
@@ -454650,7 +455117,7 @@ function LogoV2() {
454650
455117
  if ($2[2] === Symbol.for("react.memo_cache_sentinel")) {
454651
455118
  t2 = () => {
454652
455119
  const currentConfig2 = getGlobalConfig();
454653
- if (currentConfig2.lastReleaseNotesSeen === "1.65.9") {
455120
+ if (currentConfig2.lastReleaseNotesSeen === "1.65.11") {
454654
455121
  return;
454655
455122
  }
454656
455123
  saveGlobalConfig(_temp325);
@@ -455335,12 +455802,12 @@ function LogoV2() {
455335
455802
  return t41;
455336
455803
  }
455337
455804
  function _temp325(current) {
455338
- if (current.lastReleaseNotesSeen === "1.65.9") {
455805
+ if (current.lastReleaseNotesSeen === "1.65.11") {
455339
455806
  return current;
455340
455807
  }
455341
455808
  return {
455342
455809
  ...current,
455343
- lastReleaseNotesSeen: "1.65.9"
455810
+ lastReleaseNotesSeen: "1.65.11"
455344
455811
  };
455345
455812
  }
455346
455813
  function _temp241(s_0) {
@@ -472280,7 +472747,7 @@ function compileAgenticCiWorkflow(specName = "default", options2 = {}) {
472280
472747
  if (spec.name !== specName) {
472281
472748
  throw new Error("Agentic CI workflow spec name does not match");
472282
472749
  }
472283
- const packageVersion = options2.packageVersion ?? (typeof MACRO !== "undefined" ? "1.65.9" : "1.65.9");
472750
+ const packageVersion = options2.packageVersion ?? (typeof MACRO !== "undefined" ? "1.65.11" : "1.65.11");
472284
472751
  if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(packageVersion)) {
472285
472752
  throw new Error("invalid ur-agent package version");
472286
472753
  }
@@ -473273,7 +473740,7 @@ runner; \`ur trigger\` is the inbound parser that decides what to run.
473273
473740
  path: ".github/workflows/ur.yml",
473274
473741
  root: "project",
473275
473742
  content: compileAgenticCiWorkflow("default", {
473276
- packageVersion: typeof MACRO !== "undefined" ? "1.65.9" : "1.65.9"
473743
+ packageVersion: typeof MACRO !== "undefined" ? "1.65.11" : "1.65.11"
473277
473744
  })
473278
473745
  },
473279
473746
  {
@@ -473343,7 +473810,7 @@ function value(tokens, flag) {
473343
473810
  return index2 >= 0 ? tokens[index2 + 1] : undefined;
473344
473811
  }
473345
473812
  function cliVersion() {
473346
- return typeof MACRO !== "undefined" ? "1.65.9" : "1.65.9";
473813
+ return typeof MACRO !== "undefined" ? "1.65.11" : "1.65.11";
473347
473814
  }
473348
473815
  function workflowPath(cwd2) {
473349
473816
  return join159(cwd2, ".github", "workflows", "ur-agentic-ci.yml");
@@ -479208,7 +479675,7 @@ function createAcpStdioApp(deps) {
479208
479675
  }
479209
479676
  },
479210
479677
  authMethods: [],
479211
- agentInfo: { name: "UR-Nexus", version: "1.65.9" }
479678
+ agentInfo: { name: "UR-Nexus", version: "1.65.11" }
479212
479679
  })).onRequest("authenticate", () => ({})).onRequest("session/new", async (context6) => {
479213
479680
  const result = runtime2.newSession(context6.params.cwd, context6.params.mcpServers, context6.params.additionalDirectories);
479214
479681
  await runtime2.announce({
@@ -479305,7 +479772,7 @@ function createAcpStdioAgent(deps) {
479305
479772
  }
479306
479773
  },
479307
479774
  authMethods: [],
479308
- agentInfo: { name: "UR-Nexus", version: "1.65.9" }
479775
+ agentInfo: { name: "UR-Nexus", version: "1.65.11" }
479309
479776
  });
479310
479777
  return;
479311
479778
  case "authenticate":
@@ -481817,20 +482284,31 @@ function boundTaskCount(segments) {
481817
482284
  segments.slice(MAX_PLANNED_TASKS - 1).join("; ")
481818
482285
  ];
481819
482286
  }
482287
+ function splitDirectiveClauses(prompt) {
482288
+ const normalized = prompt.replace(/\s+and\s+then\s+/gi, " then ");
482289
+ const clauses = normalized.split(/;\s*|(?<=[.!?])\s+(?=[A-Z0-9])|\s+(?=(?:then|also|next|finally)\s+)/i).map(compact2).filter(Boolean);
482290
+ if (clauses.length < 2)
482291
+ return [];
482292
+ const segments = [clauses[0]];
482293
+ for (const clause of clauses.slice(1)) {
482294
+ if (STANDALONE_DIRECTIVE_PATTERN.test(clause)) {
482295
+ segments.push(clause);
482296
+ continue;
482297
+ }
482298
+ const last2 = segments.length - 1;
482299
+ segments[last2] = `${segments[last2]} ${clause}`;
482300
+ }
482301
+ return segments.length >= 2 ? segments : [];
482302
+ }
481820
482303
  function splitLongPrompt(prompt) {
481821
482304
  const bulletSegments = splitNumberedOrBulletedLines(prompt);
481822
482305
  if (bulletSegments.length > 0)
481823
482306
  return boundTaskCount(bulletSegments);
481824
482307
  const trimmed = compact2(prompt);
481825
- if (trimmed.length < 220 && !/[;\n]/.test(prompt))
481826
- return [trimmed];
481827
482308
  const lines = prompt.split(/\r?\n+/).map(compact2).filter(Boolean);
481828
482309
  if (lines.length >= 2)
481829
482310
  return boundTaskCount(lines);
481830
- const sentenceSegments = trimmed.split(/(?<=[.!?])\s+(?=[A-Z0-9])/).map(compact2).filter(Boolean);
481831
- if (sentenceSegments.length >= 2)
481832
- return boundTaskCount(sentenceSegments);
481833
- const directiveSegments = trimmed.split(/\s+(?:then|also|next|finally)\s+/i).map(compact2).filter(Boolean);
482311
+ const directiveSegments = splitDirectiveClauses(trimmed);
481834
482312
  return directiveSegments.length >= 2 ? boundTaskCount(directiveSegments) : [trimmed];
481835
482313
  }
481836
482314
  function needsPreviousTask(segment2) {
@@ -481842,10 +482320,12 @@ function needsAllPreviousTasks(segment2, role) {
481842
482320
  return /^(?:finally|once|after\s+(?:all|everything)|when\b.*\b(?:done|complete)|before\s+(?:finishing|completion))\b/i.test(segment2);
481843
482321
  }
481844
482322
  function inferRole(segment2) {
481845
- if (/\b(plan|analy[sz]e|decompose)\b/i.test(segment2))
482323
+ if (/\b(plan|analy[sz]e|decompose|review|audit|inspect)\b/i.test(segment2)) {
481846
482324
  return "planner";
481847
- if (/\b(verify|validate|test|check|prove)\b/i.test(segment2))
482325
+ }
482326
+ if (/\b(verify|validate|tests?|testing|checks?|prove)\b/i.test(segment2)) {
481848
482327
  return "verifier";
482328
+ }
481849
482329
  if (/\b(report|summari[sz]e|release notes|changelog)\b/i.test(segment2)) {
481850
482330
  return "reporter";
481851
482331
  }
@@ -481938,7 +482418,7 @@ function verificationCriteria(segment2, files, requiredFiles) {
481938
482418
  }
481939
482419
  return criteria;
481940
482420
  }
481941
- function makeTask(segment2, index2, previousTaskIds, originalPrompt, workspaceRoot) {
482421
+ function makeTask(segment2, index2, previousTasks, originalPrompt, workspaceRoot) {
481942
482422
  const files = extractReferencedFiles(segment2);
481943
482423
  const requiredFiles = CREATE_TARGET_PATTERN.test(segment2) ? [] : files;
481944
482424
  const outsidePaths = files.filter((file2) => isOutsideWorkspace(file2, workspaceRoot));
@@ -481949,7 +482429,10 @@ function makeTask(segment2, index2, previousTaskIds, originalPrompt, workspaceRo
481949
482429
  const needsScope = SECURITY_PATTERN.test(segment2) && !AUTHORIZED_SECURITY_PATTERN.test(segment2);
481950
482430
  const needsContext = isCriticallyAmbiguous(segment2);
481951
482431
  const role = inferRole(segment2);
481952
- const dependencies = needsAllPreviousTasks(segment2, role) ? [...previousTaskIds] : needsPreviousTask(segment2) && previousTaskIds.length > 0 ? [previousTaskIds[previousTaskIds.length - 1]] : [];
482432
+ const previousTaskIds = previousTasks.map((task) => task.id);
482433
+ const previousTask = previousTasks.at(-1);
482434
+ const followsInvestigation = role === "executor" && previousTask?.assignedAgent === "planner";
482435
+ const dependencies = needsAllPreviousTasks(segment2, role) ? [...previousTaskIds] : followsInvestigation ? [previousTask.id] : needsPreviousTask(segment2) && previousTaskIds.length > 0 ? [previousTaskIds[previousTaskIds.length - 1]] : [];
481953
482436
  const assumptions = needsContext ? ["Critical target/context is missing; ask for clarification before execution."] : [
481954
482437
  "Use the current workspace as the source of truth.",
481955
482438
  files.length === 0 ? "No specific files were named; discover relevant files before changing code." : "Only touch referenced files unless repository inspection proves another file is required."
@@ -481987,10 +482470,10 @@ function decomposePrompt(prompt, config3, workspaceRoot) {
481987
482470
  const originalPrompt = prompt;
481988
482471
  const segments = splitLongPrompt(prompt).filter(Boolean);
481989
482472
  const sourceSegments = segments.length > 0 ? segments : [""];
481990
- const previousTaskIds = [];
482473
+ const previousTasks = [];
481991
482474
  const tasks2 = sourceSegments.map((segment2, index2) => {
481992
- const task = makeTask(segment2, index2, previousTaskIds, originalPrompt, workspaceRoot);
481993
- previousTaskIds.push(task.id);
482475
+ const task = makeTask(segment2, index2, previousTasks, originalPrompt, workspaceRoot);
482476
+ previousTasks.push(task);
481994
482477
  return task;
481995
482478
  });
481996
482479
  return {
@@ -482016,7 +482499,7 @@ var init_planner = __esm(() => {
482016
482499
  SECURITY_PATTERN = /\b(pentest|penetration\s+test|exploit|sqlmap|nmap|metasploit|payload|vulnerabilit(?:y|ies)|cve|xss|csrf|rce|security\s+scan|attack)\b/i;
482017
482500
  AUTHORIZED_SECURITY_PATTERN = /\b(authorized|authorization|owned|own\s+system|my\s+(?:app|site|server|service)|localhost|127\.0\.0\.1|::1|lab|sandbox|ctf|test\s+target)\b/i;
482018
482501
  CREATE_TARGET_PATTERN = /\b(?:create|generate|scaffold)\b|^\s*(?:add|write)\s+(?:a|an|new)\b/i;
482019
- STANDALONE_DIRECTIVE_PATTERN = /^(?:then|after|once|next|finally|before|verify|validate|test|run|create|add|update|fix|implement|build|bump|publish|report|summari[sz]e)\b/i;
482502
+ STANDALONE_DIRECTIVE_PATTERN = /^(?:then|after|once|next|finally|before|verify|validate|test|run|create|add|update|fix|correct|improve|enhance|optimi[sz]e|implement|build|handle|support|document|refactor|remove|rename|migrate|review|audit|inspect|analy[sz]e|bump|publish|report|summari[sz]e)\b/i;
482020
482503
  });
482021
482504
 
482022
482505
  // src/services/promptPlanning/index.ts
@@ -482343,7 +482826,7 @@ function buildExecFinalReport(run3) {
482343
482826
  ]
482344
482827
  };
482345
482828
  }
482346
- function formatList(items, empty, render2) {
482829
+ function formatList2(items, empty, render2) {
482347
482830
  return items.length > 0 ? items.map(render2) : [`- ${empty}`];
482348
482831
  }
482349
482832
  function formatApprovalDecision(decision) {
@@ -482370,46 +482853,46 @@ function formatExecFinalReport(report) {
482370
482853
  `Agents used: ${report.activeAgentsUsed} active / ${report.maxAgentsAllowed} max`,
482371
482854
  "",
482372
482855
  "Finished tasks:",
482373
- ...formatList(report.finishedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482856
+ ...formatList2(report.finishedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482374
482857
  "",
482375
482858
  "Failed tasks:",
482376
- ...formatList(report.failedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482859
+ ...formatList2(report.failedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482377
482860
  "",
482378
482861
  "Waiting on prerequisite tasks:",
482379
- ...formatList(report.blockedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482862
+ ...formatList2(report.blockedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482380
482863
  "",
482381
482864
  "Waiting approval/input tasks:",
482382
- ...formatList(report.waitingApprovalTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482865
+ ...formatList2(report.waitingApprovalTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482383
482866
  "",
482384
482867
  "Skipped tasks:",
482385
- ...formatList(report.skippedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482868
+ ...formatList2(report.skippedTasks, "none", (task) => `- ${task.id} | ${task.agent} | ${task.title}`),
482386
482869
  "",
482387
482870
  "Actual changed files:",
482388
- ...formatList(report.actualChangedFiles, "none observed", (file2) => `- ${file2}`),
482871
+ ...formatList2(report.actualChangedFiles, "none observed", (file2) => `- ${file2}`),
482389
482872
  "",
482390
482873
  "Outside-workspace files accessed:",
482391
- ...formatList(report.outsideWorkspaceFilesAccessed, "none observed", (file2) => `- ${file2}`),
482874
+ ...formatList2(report.outsideWorkspaceFilesAccessed, "none observed", (file2) => `- ${file2}`),
482392
482875
  "",
482393
482876
  "Outside-workspace files modified:",
482394
- ...formatList(report.outsideWorkspaceFilesModified, "none observed", (file2) => `- ${file2}`),
482877
+ ...formatList2(report.outsideWorkspaceFilesModified, "none observed", (file2) => `- ${file2}`),
482395
482878
  "",
482396
482879
  "Unreported changed files:",
482397
- ...formatList(report.unreportedChangedFiles, "none", (file2) => `- ${file2}`),
482880
+ ...formatList2(report.unreportedChangedFiles, "none", (file2) => `- ${file2}`),
482398
482881
  "",
482399
482882
  "Verified commands:",
482400
- ...formatList(report.verifiedCommands, "none observed", (command5) => `- ${command5}`),
482883
+ ...formatList2(report.verifiedCommands, "none observed", (command5) => `- ${command5}`),
482401
482884
  "",
482402
482885
  "Unverified command claims:",
482403
- ...formatList(report.unverifiedCommandClaims, "none", (command5) => `- ${command5}`),
482886
+ ...formatList2(report.unverifiedCommandClaims, "none", (command5) => `- ${command5}`),
482404
482887
  "",
482405
482888
  "Approval decisions:",
482406
- ...formatList(report.approvalDecisions, "none", formatApprovalDecision),
482889
+ ...formatList2(report.approvalDecisions, "none", formatApprovalDecision),
482407
482890
  "",
482408
482891
  "Verification failures:",
482409
- ...formatList(report.verificationFailures, "none", (failure) => `- ${failure.taskId} | ${failure.code} | ${failure.message}`),
482892
+ ...formatList2(report.verificationFailures, "none", (failure) => `- ${failure.taskId} | ${failure.code} | ${failure.message}`),
482410
482893
  "",
482411
482894
  "Warnings:",
482412
- ...formatList(report.warnings, "none", (warning) => `- ${warning.taskId} | ${warning.code} | ${warning.message}`),
482895
+ ...formatList2(report.warnings, "none", (warning) => `- ${warning.taskId} | ${warning.code} | ${warning.message}`),
482413
482896
  "",
482414
482897
  "Remaining limitations:",
482415
482898
  ...report.remainingLimitations.map((item) => `- ${item}`)
@@ -492411,7 +492894,7 @@ function inferTests(goal) {
492411
492894
  }
492412
492895
  function decomposePrompt2(goal) {
492413
492896
  return [
492414
- "Decompose the following engineering goal into atomic subtasks.",
492897
+ "Decompose the following engineering goal into cohesive subtasks with observable completion checks.",
492415
492898
  "Return a JSON object with exactly this shape (no markdown, no commentary):",
492416
492899
  "",
492417
492900
  "{",
@@ -492429,7 +492912,9 @@ function decomposePrompt2(goal) {
492429
492912
  "}",
492430
492913
  "",
492431
492914
  "Guidelines:",
492432
- "- Each subtask should be small enough to implement and verify independently.",
492915
+ "- Use one subtask per cohesive outcome with a clear completion check.",
492916
+ "- Split separately completable deliverables; never hide them in one omnibus task.",
492917
+ "- Return one task when the goal is genuinely atomic. Do not split merely by file, command, tool call, or tiny mechanical step.",
492433
492918
  '- Use "dependsOn" only for real ordering constraints; independent tasks must use an empty array so they can run in parallel.',
492434
492919
  '- "filesTouched" should list the files likely to change.',
492435
492920
  '- "risk" should be high for auth/security/concurrency/destructive changes, medium for refactor/API changes, low for docs/style.',
@@ -676400,7 +676885,7 @@ __export(exports_role_mode, {
676400
676885
  });
676401
676886
  import { existsSync as existsSync94, mkdirSync as mkdirSync65, writeFileSync as writeFileSync65 } from "fs";
676402
676887
  import { join as join212 } from "path";
676403
- function formatList2() {
676888
+ function formatList3() {
676404
676889
  const lines = ["Built-in role modes:", ""];
676405
676890
  for (const mode2 of ROLE_MODES) {
676406
676891
  const scope = mode2.tools ? mode2.tools.join(", ") : "all tools";
@@ -676431,7 +676916,7 @@ var call127 = async (args) => {
676431
676916
  })), null, 2)
676432
676917
  };
676433
676918
  }
676434
- return { type: "text", value: formatList2() };
676919
+ return { type: "text", value: formatList3() };
676435
676920
  }
676436
676921
  if (command5 === "show") {
676437
676922
  const name = positional2[1];
@@ -690447,7 +690932,7 @@ async function captureMemoryDiagnostics(trigger2, dumpNumber = 0) {
690447
690932
  smapsRollup,
690448
690933
  platform: process.platform,
690449
690934
  nodeVersion: process.version,
690450
- ccVersion: "1.65.9"
690935
+ ccVersion: "1.65.11"
690451
690936
  };
690452
690937
  }
690453
690938
  async function performHeapDump(trigger2 = "manual", dumpNumber = 0) {
@@ -691027,7 +691512,7 @@ var init_bridge_kick = __esm(() => {
691027
691512
  var call153 = async () => {
691028
691513
  return {
691029
691514
  type: "text",
691030
- value: "1.65.9"
691515
+ value: "1.65.11"
691031
691516
  };
691032
691517
  }, version2, version_default;
691033
691518
  var init_version = __esm(() => {
@@ -702207,7 +702692,7 @@ function generateHtmlReport(data, insights) {
702207
702692
  </html>`;
702208
702693
  }
702209
702694
  function buildExportData(data, insights, facets, remoteStats) {
702210
- const version3 = typeof MACRO !== "undefined" ? "1.65.9" : "unknown";
702695
+ const version3 = typeof MACRO !== "undefined" ? "1.65.11" : "unknown";
702211
702696
  const remote_hosts_collected = remoteStats?.hosts.filter((h2) => h2.sessionCount > 0).map((h2) => h2.name);
702212
702697
  const facets_summary = {
702213
702698
  total: facets.size,
@@ -706534,7 +707019,7 @@ var init_sessionStorage = __esm(() => {
706534
707019
  init_settings2();
706535
707020
  init_slowOperations();
706536
707021
  init_uuid();
706537
- VERSION7 = typeof MACRO !== "undefined" ? "1.65.9" : "unknown";
707022
+ VERSION7 = typeof MACRO !== "undefined" ? "1.65.11" : "unknown";
706538
707023
  MAX_TOMBSTONE_REWRITE_BYTES = 50 * 1024 * 1024;
706539
707024
  SKIP_FIRST_PROMPT_PATTERN = /^(?:\s*<[a-z][\w-]*[\s>]|\[Request interrupted by user[^\]]*\])/;
706540
707025
  EPHEMERAL_PROGRESS_TYPES = new Set([
@@ -707749,7 +708234,7 @@ var init_filesystem = __esm(() => {
707749
708234
  });
707750
708235
  getBundledSkillsRoot = memoize_default(function getBundledSkillsRoot2() {
707751
708236
  const nonce = randomBytes20(16).toString("hex");
707752
- return join230(getURTempDir(), "bundled-skills", "1.65.9", nonce);
708237
+ return join230(getURTempDir(), "bundled-skills", "1.65.11", nonce);
707753
708238
  });
707754
708239
  getResolvedWorkingDirPaths = memoize_default(getPathsForPermissionCheck);
707755
708240
  });
@@ -713136,7 +713621,7 @@ var CYBER_RISK_INSTRUCTION = `IMPORTANT: Assist with authorized security testing
713136
713621
 
713137
713622
  // src/constants/executionContract.ts
713138
713623
  var EXECUTION_CONTRACT_SECTION = `# Execution contract
713139
- 1. Scope: identify outcome, constraints, dependencies. For 3+ steps, record ordered tasks before implementation; ask only unresolved decisions. Task lists are not plan mode: call ExitPlanMode after EnterPlanMode succeeds.
713624
+ 1. Scope: identify outcome, constraints, dependencies. For 3+ steps, decompose into cohesive, verifiable tasks before implementation; ask only unresolved decisions. Task lists aren't plan mode; ExitPlanMode follows successful EnterPlanMode.
713140
713625
  2. Act: invoke tools through their interface; never substitute printed JSON/XML or commands. Use file tools for edits. Batch independent calls (maximum 8), keep dependencies sequential, inspect every result, update its task, and never emit an empty turn.
713141
713626
  3. Recover: read exact failures; change input, assumptions, or approach. Never repeat an unchanged failure unless external state changed. After three failures on one approach, switch strategy or report the blocker. Distinguish DNS/TLS/auth/rate-limit failures; report external-tool errors honestly.
713142
713627
  4. Verify: run the smallest checks, broader when risk warrants. Match completion claims to successful tool results and observed evidence; state skipped or failing checks.
@@ -713148,21 +713633,26 @@ function getTaskToolGuidance(enabledTools) {
713148
713633
  const canCreate = enabledTools.has(TASK_CREATE_TOOL_NAME);
713149
713634
  const canUpdate = enabledTools.has(TASK_UPDATE_TOOL_NAME);
713150
713635
  const canList = enabledTools.has(TASK_LIST_TOOL_NAME);
713636
+ const canDelegate = enabledTools.has(AGENT_TOOL_NAME);
713637
+ const decomposition = "For non-trivial work, use one task per cohesive outcome with its own observable done check; never hide separately completable deliverables in one omnibus task. Keep genuinely atomic work as one task\u2014do not split by file, tool call, or tiny mechanical step. Make real dependencies explicit and leave unrelated tasks unblocked.";
713638
+ const parallel = canDelegate ? ` If delegating, launch mutually independent tasks through ${AGENT_TOOL_NAME} in parallel only when they have no conflicting shared mutations; keep dependent or conflicting work sequential.` : "";
713151
713639
  if (canCreate && canUpdate) {
713152
- return `Track multi-step work with ${TASK_CREATE_TOOL_NAME} and ${TASK_UPDATE_TOOL_NAME}: create dependency-ordered tasks for the concrete outcomes before implementation; mark one unblocked task in_progress when starting it; mark it completed immediately after implementation and relevant verification succeed; leave blocked or partial work open with its blocker recorded.${canList ? ` Use ${TASK_LIST_TOOL_NAME} to select the next unblocked task.` : ""}`;
713640
+ return `Track multi-step work with ${TASK_CREATE_TOOL_NAME} and ${TASK_UPDATE_TOOL_NAME}. ${decomposition}${parallel} Create the complete dependency graph before implementation; mark each task in_progress when its work starts and completed immediately after its implementation and relevant verification succeed; leave blocked or partial work open with its blocker recorded.${canList ? ` Use ${TASK_LIST_TOOL_NAME} to select the next unblocked task.` : ""}`;
713153
713641
  }
713154
713642
  if (canUpdate) {
713155
713643
  return `Keep assigned tasks current with ${TASK_UPDATE_TOOL_NAME}: mark the task in_progress when starting, completed only after implementation and relevant verification succeed, and leave blocked or partial work open with its blocker recorded.${canList ? ` Use ${TASK_LIST_TOOL_NAME} to select the next unblocked task.` : ""}`;
713156
713644
  }
713157
713645
  if (canCreate) {
713158
- return `For multi-step work, use ${TASK_CREATE_TOOL_NAME} before implementation to record concrete outcomes and their dependency order.`;
713646
+ return `For multi-step work, use ${TASK_CREATE_TOOL_NAME} before implementation. ${decomposition}${parallel}`;
713159
713647
  }
713160
713648
  if (enabledTools.has(TODO_WRITE_TOOL_NAME)) {
713161
- return `Track multi-step work with ${TODO_WRITE_TOOL_NAME}. Keep items dependency-ordered and mark each item completed immediately after its implementation and relevant verification succeed.`;
713649
+ return `Track multi-step work with ${TODO_WRITE_TOOL_NAME}. ${decomposition} Keep items dependency-ordered and mark each item completed immediately after its implementation and relevant verification succeed.`;
713162
713650
  }
713163
713651
  return null;
713164
713652
  }
713165
- var init_taskToolGuidance = () => {};
713653
+ var init_taskToolGuidance = __esm(() => {
713654
+ init_constants2();
713655
+ });
713166
713656
 
713167
713657
  // src/constants/prompts.ts
713168
713658
  import { type as osType2, version as osVersion, release as osRelease2 } from "os";
@@ -714072,7 +714562,7 @@ function computeFingerprint(messageText2, version3) {
714072
714562
  }
714073
714563
  function computeFingerprintFromMessages(messages) {
714074
714564
  const firstMessageText = extractFirstMessageText(messages);
714075
- return computeFingerprint(firstMessageText, "1.65.9");
714565
+ return computeFingerprint(firstMessageText, "1.65.11");
714076
714566
  }
714077
714567
  var FINGERPRINT_SALT = "59cf53e54c78";
714078
714568
  var init_fingerprint = () => {};
@@ -715968,7 +716458,7 @@ async function sideQuery(opts) {
715968
716458
  betas.push(STRUCTURED_OUTPUTS_BETA_HEADER);
715969
716459
  }
715970
716460
  const messageText2 = extractFirstUserMessageText(messages);
715971
- const fingerprint2 = computeFingerprint(messageText2, "1.65.9");
716461
+ const fingerprint2 = computeFingerprint(messageText2, "1.65.11");
715972
716462
  const attributionHeader = getAttributionHeader(fingerprint2);
715973
716463
  const systemBlocks = [
715974
716464
  attributionHeader ? { type: "text", text: attributionHeader } : null,
@@ -720755,7 +721245,7 @@ function buildSystemInitMessage(inputs) {
720755
721245
  slash_commands: inputs.commands.filter((c4) => c4.userInvocable !== false).map((c4) => c4.name),
720756
721246
  apiKeySource: getURHQApiKeyWithSource().source,
720757
721247
  betas: getSdkBetas(),
720758
- ur_version: "1.65.9",
721248
+ ur_version: "1.65.11",
720759
721249
  output_style: outputStyle2,
720760
721250
  agents: inputs.agents.map((agent2) => agent2.agentType),
720761
721251
  skills: inputs.skills.filter((s) => s.userInvocable !== false).map((skill2) => skill2.name),
@@ -721936,6 +722426,29 @@ var init_useNotifyAfterTimeout = __esm(() => {
721936
722426
  import_react196 = __toESM(require_react(), 1);
721937
722427
  });
721938
722428
 
722429
+ // src/components/permissions/AskUserQuestionPermissionRequest/prototypeSafeRecord.ts
722430
+ function createPrototypeSafeRecord() {
722431
+ return Object.create(null);
722432
+ }
722433
+ function clonePrototypeSafeRecord(source) {
722434
+ return Object.assign(Object.create(null), source);
722435
+ }
722436
+ function hasOwnRecordKey(record4, key) {
722437
+ return record4 !== null && record4 !== undefined && hasOwn2.call(record4, key);
722438
+ }
722439
+ function getOwnRecordValue(record4, key) {
722440
+ return hasOwnRecordKey(record4, key) ? record4[key] : undefined;
722441
+ }
722442
+ function setPrototypeSafeRecordValue(record4, key, value2) {
722443
+ const next = Object.assign(createPrototypeSafeRecord(), record4);
722444
+ next[key] = value2;
722445
+ return next;
722446
+ }
722447
+ var hasOwn2;
722448
+ var init_prototypeSafeRecord = __esm(() => {
722449
+ hasOwn2 = Object.prototype.hasOwnProperty;
722450
+ });
722451
+
721939
722452
  // src/components/permissions/AskUserQuestionPermissionRequest/QuestionNavigationBar.tsx
721940
722453
  function QuestionNavigationBar(t0) {
721941
722454
  const $2 = import_compiler_runtime262.c(39);
@@ -722043,7 +722556,7 @@ function QuestionNavigationBar(t0) {
722043
722556
  if ($2[22] !== answers || $2[23] !== currentQuestionIndex || $2[24] !== tabDisplayTexts) {
722044
722557
  t52 = (q_1, index_2) => {
722045
722558
  const isSelected = index_2 === currentQuestionIndex;
722046
- const isAnswered = q_1?.question && !!answers[q_1.question];
722559
+ const isAnswered = q_1?.question && !!getOwnRecordValue(answers, q_1.question);
722047
722560
  const checkbox = isAnswered ? figures_default.checkboxOn : figures_default.checkboxOff;
722048
722561
  const displayText = tabDisplayTexts[index_2] || q_1?.header || `Q${index_2 + 1}`;
722049
722562
  return /* @__PURE__ */ jsx_dev_runtime357.jsxDEV(ThemedBox_default, {
@@ -722165,6 +722678,7 @@ var init_QuestionNavigationBar = __esm(() => {
722165
722678
  init_stringWidth();
722166
722679
  init_ink2();
722167
722680
  init_format2();
722681
+ init_prototypeSafeRecord();
722168
722682
  import_compiler_runtime262 = __toESM(require_compiler_runtime(), 1);
722169
722683
  jsx_dev_runtime357 = __toESM(require_jsx_dev_runtime(), 1);
722170
722684
  });
@@ -722465,20 +722979,34 @@ function PreviewQuestionView({
722465
722979
  const editor = getExternalEditor();
722466
722980
  const editorName = editor ? toIDEDisplayName(editor) : null;
722467
722981
  const questionText = question.question;
722468
- const questionState = questionStates[questionText];
722982
+ const questionState = getOwnRecordValue(questionStates, questionText);
722469
722983
  const allOptions = question.options;
722984
+ const otherIndex = allOptions.length;
722985
+ const optionRowCount = allOptions.length + 1;
722470
722986
  const [focusedIndex, setFocusedIndex] = import_react198.useState(0);
722471
722987
  const prevQuestionText = import_react198.useRef(questionText);
722472
722988
  if (prevQuestionText.current !== questionText) {
722473
722989
  prevQuestionText.current = questionText;
722474
722990
  const selected = questionState?.selectedValue;
722475
- const idx = selected ? allOptions.findIndex((opt) => opt.label === selected) : -1;
722991
+ const idx = selected === PREVIEW_OTHER_VALUE ? otherIndex : selected ? allOptions.findIndex((opt) => opt.label === selected) : -1;
722476
722992
  setFocusedIndex(idx >= 0 ? idx : 0);
722477
722993
  }
722478
722994
  const focusedOption = allOptions[focusedIndex];
722995
+ const isOtherFocused = focusedIndex === otherIndex;
722479
722996
  const selectedValue = questionState?.selectedValue;
722480
722997
  const notesValue = questionState?.textInputValue || "";
722998
+ const otherInputValue = questionState?.otherInputValue || "";
722481
722999
  const handleSelectOption = import_react198.useCallback((index2) => {
723000
+ if (index2 === otherIndex) {
723001
+ setFocusedIndex(index2);
723002
+ onUpdateQuestionState(questionText, {
723003
+ selectedValue: PREVIEW_OTHER_VALUE
723004
+ }, false);
723005
+ onAnswer(questionText, PREVIEW_OTHER_VALUE, "", false);
723006
+ setIsInNotesInput(true);
723007
+ onTextInputFocus(true);
723008
+ return;
723009
+ }
722482
723010
  const option27 = allOptions[index2];
722483
723011
  if (!option27)
722484
723012
  return;
@@ -722487,7 +723015,7 @@ function PreviewQuestionView({
722487
723015
  selectedValue: option27.label
722488
723016
  }, false);
722489
723017
  onAnswer(questionText, option27.label);
722490
- }, [allOptions, questionText, onUpdateQuestionState, onAnswer]);
723018
+ }, [allOptions, otherIndex, questionText, onUpdateQuestionState, onAnswer, onTextInputFocus]);
722491
723019
  const handleNavigate = import_react198.useCallback((direction) => {
722492
723020
  if (isInNotesInput)
722493
723021
  return;
@@ -722497,18 +723025,18 @@ function PreviewQuestionView({
722497
723025
  } else if (direction === "up") {
722498
723026
  newIndex = focusedIndex > 0 ? focusedIndex - 1 : focusedIndex;
722499
723027
  } else {
722500
- newIndex = focusedIndex < allOptions.length - 1 ? focusedIndex + 1 : focusedIndex;
723028
+ newIndex = focusedIndex < optionRowCount - 1 ? focusedIndex + 1 : focusedIndex;
722501
723029
  }
722502
- if (newIndex >= 0 && newIndex < allOptions.length) {
723030
+ if (newIndex >= 0 && newIndex < optionRowCount) {
722503
723031
  setFocusedIndex(newIndex);
722504
723032
  }
722505
- }, [focusedIndex, allOptions.length, isInNotesInput]);
723033
+ }, [focusedIndex, optionRowCount, isInNotesInput]);
722506
723034
  useKeybinding("chat:externalEditor", async () => {
722507
- const currentValue = questionState?.textInputValue || "";
723035
+ const currentValue = isOtherFocused ? otherInputValue : notesValue;
722508
723036
  const result = await editPromptInEditor(currentValue);
722509
723037
  if (result.content !== null && result.content !== currentValue) {
722510
723038
  onUpdateQuestionState(questionText, {
722511
- textInputValue: result.content
723039
+ ...isOtherFocused ? { otherInputValue: result.content } : { textInputValue: result.content }
722512
723040
  }, false);
722513
723041
  }
722514
723042
  }, {
@@ -722525,10 +723053,17 @@ function PreviewQuestionView({
722525
723053
  const handleNotesExit = import_react198.useCallback(() => {
722526
723054
  setIsInNotesInput(false);
722527
723055
  onTextInputFocus(false);
722528
- if (selectedValue) {
723056
+ if (isOtherFocused) {
723057
+ const customAnswer = otherInputValue.trim();
723058
+ if (customAnswer) {
723059
+ onAnswer(questionText, PREVIEW_OTHER_VALUE, customAnswer);
723060
+ }
723061
+ return;
723062
+ }
723063
+ if (selectedValue && selectedValue !== PREVIEW_OTHER_VALUE) {
722529
723064
  onAnswer(questionText, selectedValue);
722530
723065
  }
722531
- }, [selectedValue, questionText, onAnswer, onTextInputFocus]);
723066
+ }, [isOtherFocused, otherInputValue, selectedValue, questionText, onAnswer, onTextInputFocus]);
722532
723067
  const handleDownFromPreview = import_react198.useCallback(() => {
722533
723068
  setIsFooterFocused(true);
722534
723069
  }, []);
@@ -722582,7 +723117,7 @@ function PreviewQuestionView({
722582
723117
  }
722583
723118
  } else if (e.key === "down" || e.ctrl && e.key === "n") {
722584
723119
  e.preventDefault();
722585
- if (focusedIndex === allOptions.length - 1) {
723120
+ if (focusedIndex === optionRowCount - 1) {
722586
723121
  handleDownFromPreview();
722587
723122
  } else {
722588
723123
  handleNavigate("down");
@@ -722592,6 +723127,11 @@ function PreviewQuestionView({
722592
723127
  handleSelectOption(focusedIndex);
722593
723128
  } else if (e.key === "n" && !e.ctrl && !e.meta) {
722594
723129
  e.preventDefault();
723130
+ if (isOtherFocused && selectedValue !== PREVIEW_OTHER_VALUE) {
723131
+ onUpdateQuestionState(questionText, {
723132
+ selectedValue: PREVIEW_OTHER_VALUE
723133
+ }, false);
723134
+ }
722595
723135
  setIsInNotesInput(true);
722596
723136
  onTextInputFocus(true);
722597
723137
  } else if (e.key === "escape") {
@@ -722600,12 +723140,13 @@ function PreviewQuestionView({
722600
723140
  } else if (e.key.length === 1 && e.key >= "1" && e.key <= "9") {
722601
723141
  e.preventDefault();
722602
723142
  const idx_0 = parseInt(e.key, 10) - 1;
722603
- if (idx_0 < allOptions.length) {
723143
+ if (idx_0 < optionRowCount) {
722604
723144
  handleNavigate(idx_0);
722605
723145
  }
722606
723146
  }
722607
- }, [isFooterFocused, footerIndex, isInPlanMode, isInNotesInput, focusedIndex, allOptions.length, handleUpFromFooter, handleDownFromPreview, handleNavigate, handleSelectOption, handleNotesExit, onRespondToUR, onFinishPlanInterview, onCancel, onTextInputFocus]);
722608
- const previewContent = focusedOption?.preview || null;
723147
+ }, [isFooterFocused, footerIndex, isInPlanMode, isInNotesInput, focusedIndex, optionRowCount, isOtherFocused, selectedValue, questionText, handleUpFromFooter, handleDownFromPreview, handleNavigate, handleSelectOption, handleNotesExit, onRespondToUR, onFinishPlanInterview, onCancel, onTextInputFocus, onUpdateQuestionState]);
723148
+ const previewContent = isOtherFocused ? "Enter a custom answer below." : focusedOption?.preview || null;
723149
+ const currentInputValue = isOtherFocused ? otherInputValue : notesValue;
722609
723150
  const LEFT_PANEL_WIDTH = 30;
722610
723151
  const GAP = 4;
722611
723152
  const {
@@ -722644,13 +723185,49 @@ function PreviewQuestionView({
722644
723185
  /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedBox_default, {
722645
723186
  flexDirection: "column",
722646
723187
  width: 30,
722647
- children: allOptions.map((option_0, index_0) => {
722648
- const isFocused = focusedIndex === index_0;
722649
- const isSelected = selectedValue === option_0.label;
722650
- return /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedBox_default, {
723188
+ children: [
723189
+ allOptions.map((option_0, index_0) => {
723190
+ const isFocused = focusedIndex === index_0;
723191
+ const isSelected = selectedValue === option_0.label;
723192
+ return /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedBox_default, {
723193
+ flexDirection: "row",
723194
+ children: [
723195
+ isFocused ? /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
723196
+ color: "suggestion",
723197
+ children: figures_default.pointer
723198
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
723199
+ children: " "
723200
+ }, undefined, false, undefined, this),
723201
+ /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
723202
+ dimColor: true,
723203
+ children: [
723204
+ " ",
723205
+ index_0 + 1,
723206
+ "."
723207
+ ]
723208
+ }, undefined, true, undefined, this),
723209
+ /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
723210
+ color: isSelected ? "success" : isFocused ? "suggestion" : undefined,
723211
+ bold: isFocused,
723212
+ children: [
723213
+ " ",
723214
+ option_0.label
723215
+ ]
723216
+ }, undefined, true, undefined, this),
723217
+ isSelected && /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
723218
+ color: "success",
723219
+ children: [
723220
+ " ",
723221
+ figures_default.tick
723222
+ ]
723223
+ }, undefined, true, undefined, this)
723224
+ ]
723225
+ }, option_0.label, true, undefined, this);
723226
+ }),
723227
+ /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedBox_default, {
722651
723228
  flexDirection: "row",
722652
723229
  children: [
722653
- isFocused ? /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
723230
+ isOtherFocused ? /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
722654
723231
  color: "suggestion",
722655
723232
  children: figures_default.pointer
722656
723233
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
@@ -722660,19 +723237,19 @@ function PreviewQuestionView({
722660
723237
  dimColor: true,
722661
723238
  children: [
722662
723239
  " ",
722663
- index_0 + 1,
723240
+ otherIndex + 1,
722664
723241
  "."
722665
723242
  ]
722666
723243
  }, undefined, true, undefined, this),
722667
723244
  /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
722668
- color: isSelected ? "success" : isFocused ? "suggestion" : undefined,
722669
- bold: isFocused,
723245
+ color: selectedValue === PREVIEW_OTHER_VALUE ? "success" : isOtherFocused ? "suggestion" : undefined,
723246
+ bold: isOtherFocused,
722670
723247
  children: [
722671
723248
  " ",
722672
- option_0.label
723249
+ "Other"
722673
723250
  ]
722674
723251
  }, undefined, true, undefined, this),
722675
- isSelected && /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
723252
+ selectedValue === PREVIEW_OTHER_VALUE && /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
722676
723253
  color: "success",
722677
723254
  children: [
722678
723255
  " ",
@@ -722680,9 +723257,9 @@ function PreviewQuestionView({
722680
723257
  ]
722681
723258
  }, undefined, true, undefined, this)
722682
723259
  ]
722683
- }, option_0.label, true, undefined, this);
722684
- })
722685
- }, undefined, false, undefined, this),
723260
+ }, PREVIEW_OTHER_VALUE, true, undefined, this)
723261
+ ]
723262
+ }, undefined, true, undefined, this),
722686
723263
  /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedBox_default, {
722687
723264
  flexDirection: "column",
722688
723265
  flexGrow: 1,
@@ -722700,14 +723277,14 @@ function PreviewQuestionView({
722700
723277
  children: [
722701
723278
  /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
722702
723279
  color: "suggestion",
722703
- children: "Notes:"
723280
+ children: isOtherFocused ? "Answer:" : "Notes:"
722704
723281
  }, undefined, false, undefined, this),
722705
723282
  isInNotesInput ? /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(TextInput, {
722706
- value: notesValue,
722707
- placeholder: "Add notes on this design\u2026",
723283
+ value: currentInputValue,
723284
+ placeholder: isOtherFocused ? "Type a custom answer\u2026" : "Add notes on this design\u2026",
722708
723285
  onChange: (value2) => {
722709
723286
  onUpdateQuestionState(questionText, {
722710
- textInputValue: value2
723287
+ ...isOtherFocused ? { otherInputValue: value2 } : { textInputValue: value2 }
722711
723288
  }, false);
722712
723289
  },
722713
723290
  onSubmit: handleNotesExit,
@@ -722720,7 +723297,7 @@ function PreviewQuestionView({
722720
723297
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(ThemedText, {
722721
723298
  dimColor: true,
722722
723299
  italic: true,
722723
- children: notesValue || "press n to add notes"
723300
+ children: currentInputValue || (isOtherFocused ? "press Enter to type a custom answer" : "press n to add notes")
722724
723301
  }, undefined, false, undefined, this)
722725
723302
  ]
722726
723303
  }, undefined, true, undefined, this)
@@ -722779,7 +723356,8 @@ function PreviewQuestionView({
722779
723356
  figures_default.arrowUp,
722780
723357
  "/",
722781
723358
  figures_default.arrowDown,
722782
- " to navigate \xB7 n to add notes",
723359
+ " to navigate \xB7 n to edit ",
723360
+ isOtherFocused ? "answer" : "notes",
722783
723361
  questions.length > 1 && /* @__PURE__ */ jsx_dev_runtime359.jsxDEV(jsx_dev_runtime359.Fragment, {
722784
723362
  children: " \xB7 Tab to switch questions"
722785
723363
  }, undefined, false, undefined, this),
@@ -722800,7 +723378,7 @@ function PreviewQuestionView({
722800
723378
  }, undefined, true, undefined, this)
722801
723379
  }, undefined, false, undefined, this);
722802
723380
  }
722803
- var import_react198, jsx_dev_runtime359;
723381
+ var import_react198, jsx_dev_runtime359, PREVIEW_OTHER_VALUE = "__other__";
722804
723382
  var init_PreviewQuestionView = __esm(() => {
722805
723383
  init_figures();
722806
723384
  init_useTerminalSize();
@@ -722813,6 +723391,7 @@ var init_PreviewQuestionView = __esm(() => {
722813
723391
  init_Divider();
722814
723392
  init_TextInput();
722815
723393
  init_PreviewBox();
723394
+ init_prototypeSafeRecord();
722816
723395
  init_QuestionNavigationBar();
722817
723396
  import_react198 = __toESM(require_react(), 1);
722818
723397
  jsx_dev_runtime359 = __toESM(require_jsx_dev_runtime(), 1);
@@ -722846,6 +723425,7 @@ function QuestionView({
722846
723425
  const [isFooterFocused, setIsFooterFocused] = import_react199.useState(false);
722847
723426
  const [footerIndex, setFooterIndex] = import_react199.useState(0);
722848
723427
  const [isOtherFocused, setIsOtherFocused] = import_react199.useState(false);
723428
+ const questionState = getOwnRecordValue(questionStates, question.question);
722849
723429
  const editorName = import_react199.useMemo(() => {
722850
723430
  const editor = getExternalEditor();
722851
723431
  return editor ? toIDEDisplayName(editor) : null;
@@ -722909,7 +723489,7 @@ function QuestionView({
722909
723489
  }
722910
723490
  };
722911
723491
  const placeholder = question.multiSelect ? "Type something" : "Type something.";
722912
- const textInputValue = questionStates[question.question]?.textInputValue ?? "";
723492
+ const textInputValue = questionState?.textInputValue ?? "";
722913
723493
  return [
722914
723494
  ...textOptions,
722915
723495
  {
@@ -722924,7 +723504,7 @@ function QuestionView({
722924
723504
  onOpenEditor: handleOpenEditor
722925
723505
  }
722926
723506
  ];
722927
- }, [question, questionStates, onUpdateQuestionState]);
723507
+ }, [question, questionState, onUpdateQuestionState]);
722928
723508
  const hasAnyPreview = !question.multiSelect && question.options.some((opt) => opt.preview);
722929
723509
  if (hasAnyPreview) {
722930
723510
  return /* @__PURE__ */ jsx_dev_runtime360.jsxDEV(PreviewQuestionView, {
@@ -722987,10 +723567,10 @@ function QuestionView({
722987
723567
  marginTop: 1,
722988
723568
  children: question.multiSelect ? /* @__PURE__ */ jsx_dev_runtime360.jsxDEV(SelectMulti, {
722989
723569
  options: options4,
722990
- defaultValue: questionStates[question.question]?.selectedValue,
723570
+ defaultValue: questionState?.selectedValue,
722991
723571
  onChange: (values2) => {
722992
723572
  onUpdateQuestionState(question.question, { selectedValue: values2 }, true);
722993
- const textInput = values2.includes("__other__") ? questionStates[question.question]?.textInputValue : undefined;
723573
+ const textInput = values2.includes("__other__") ? questionState?.textInputValue : undefined;
722994
723574
  const finalValues = values2.filter((v) => v !== "__other__").concat(textInput ? [textInput] : []);
722995
723575
  onAnswer(question.question, finalValues, undefined, false);
722996
723576
  },
@@ -723005,10 +723585,10 @@ function QuestionView({
723005
723585
  onRemoveImage
723006
723586
  }, question.question, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime360.jsxDEV(Select, {
723007
723587
  options: options4,
723008
- defaultValue: questionStates[question.question]?.selectedValue,
723588
+ defaultValue: questionState?.selectedValue,
723009
723589
  onChange: (value2) => {
723010
723590
  onUpdateQuestionState(question.question, { selectedValue: value2 }, false);
723011
- const textInput = value2 === "__other__" ? questionStates[question.question]?.textInputValue : undefined;
723591
+ const textInput = value2 === "__other__" ? questionState?.textInputValue : undefined;
723012
723592
  onAnswer(question.question, value2, textInput);
723013
723593
  },
723014
723594
  onFocus: handleFocus,
@@ -723112,6 +723692,7 @@ var init_QuestionView = __esm(() => {
723112
723692
  init_FilePathLink();
723113
723693
  init_QuestionNavigationBar();
723114
723694
  init_PreviewQuestionView();
723695
+ init_prototypeSafeRecord();
723115
723696
  import_react199 = __toESM(require_react(), 1);
723116
723697
  jsx_dev_runtime360 = __toESM(require_jsx_dev_runtime(), 1);
723117
723698
  });
@@ -723283,8 +723864,8 @@ function SubmitQuestionsView({
723283
723864
  Object.keys(answers).length > 0 && /* @__PURE__ */ jsx_dev_runtime362.jsxDEV(ThemedBox_default, {
723284
723865
  flexDirection: "column",
723285
723866
  marginBottom: 1,
723286
- children: questions.filter((q) => q?.question && answers[q.question]).map((q) => {
723287
- const answer = answers[q.question];
723867
+ children: questions.filter((q) => q?.question && getOwnRecordValue(answers, q.question)).map((q) => {
723868
+ const answer = getOwnRecordValue(answers, q.question);
723288
723869
  return /* @__PURE__ */ jsx_dev_runtime362.jsxDEV(ThemedBox_default, {
723289
723870
  flexDirection: "column",
723290
723871
  marginLeft: 1,
@@ -723341,12 +723922,13 @@ var init_SubmitQuestionsView = __esm(() => {
723341
723922
  init_CustomSelect();
723342
723923
  init_Divider();
723343
723924
  init_PermissionRuleExplanation();
723925
+ init_prototypeSafeRecord();
723344
723926
  init_QuestionNavigationBar();
723345
723927
  jsx_dev_runtime362 = __toESM(require_jsx_dev_runtime(), 1);
723346
723928
  });
723347
723929
 
723348
723930
  // src/components/permissions/AskUserQuestionPermissionRequest/use-multiple-choice-state.ts
723349
- function reducer2(state2, action3) {
723931
+ function multipleChoiceReducer(state2, action3) {
723350
723932
  switch (action3.type) {
723351
723933
  case "next-question":
723352
723934
  return {
@@ -723361,26 +723943,21 @@ function reducer2(state2, action3) {
723361
723943
  isInTextInput: false
723362
723944
  };
723363
723945
  case "update-question-state": {
723364
- const existing2 = state2.questionStates[action3.questionText];
723946
+ const existing2 = getOwnRecordValue(state2.questionStates, action3.questionText);
723365
723947
  const newState = {
723366
723948
  selectedValue: action3.updates.selectedValue ?? existing2?.selectedValue ?? (action3.isMultiSelect ? [] : undefined),
723367
- textInputValue: action3.updates.textInputValue ?? existing2?.textInputValue ?? ""
723949
+ textInputValue: action3.updates.textInputValue ?? existing2?.textInputValue ?? "",
723950
+ otherInputValue: action3.updates.otherInputValue ?? existing2?.otherInputValue ?? ""
723368
723951
  };
723369
723952
  return {
723370
723953
  ...state2,
723371
- questionStates: {
723372
- ...state2.questionStates,
723373
- [action3.questionText]: newState
723374
- }
723954
+ questionStates: setPrototypeSafeRecordValue(state2.questionStates, action3.questionText, newState)
723375
723955
  };
723376
723956
  }
723377
723957
  case "set-answer": {
723378
723958
  const newState = {
723379
723959
  ...state2,
723380
- answers: {
723381
- ...state2.answers,
723382
- [action3.questionText]: action3.answer
723383
- }
723960
+ answers: setPrototypeSafeRecordValue(state2.answers, action3.questionText, action3.answer)
723384
723961
  };
723385
723962
  if (action3.shouldAdvance) {
723386
723963
  return {
@@ -723398,8 +723975,16 @@ function reducer2(state2, action3) {
723398
723975
  };
723399
723976
  }
723400
723977
  }
723978
+ function createInitialMultipleChoiceState() {
723979
+ return {
723980
+ currentQuestionIndex: 0,
723981
+ answers: createPrototypeSafeRecord(),
723982
+ questionStates: createPrototypeSafeRecord(),
723983
+ isInTextInput: false
723984
+ };
723985
+ }
723401
723986
  function useMultipleChoiceState() {
723402
- const [state2, dispatch5] = import_react200.useReducer(reducer2, INITIAL_STATE2);
723987
+ const [state2, dispatch5] = import_react200.useReducer(multipleChoiceReducer, createInitialMultipleChoiceState());
723403
723988
  const nextQuestion = import_react200.useCallback(() => {
723404
723989
  dispatch5({ type: "next-question" });
723405
723990
  }, []);
@@ -723437,18 +724022,22 @@ function useMultipleChoiceState() {
723437
724022
  setTextInputMode
723438
724023
  };
723439
724024
  }
723440
- var import_react200, INITIAL_STATE2;
724025
+ var import_react200;
723441
724026
  var init_use_multiple_choice_state = __esm(() => {
724027
+ init_prototypeSafeRecord();
723442
724028
  import_react200 = __toESM(require_react(), 1);
723443
- INITIAL_STATE2 = {
723444
- currentQuestionIndex: 0,
723445
- answers: {},
723446
- questionStates: {},
723447
- isInTextInput: false
723448
- };
723449
724029
  });
723450
724030
 
723451
724031
  // src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.tsx
724032
+ function resolveQuestionAnswer(label, textInput, hasImages) {
724033
+ if (Array.isArray(label))
724034
+ return label.join(", ");
724035
+ if (textInput)
724036
+ return hasImages ? `${textInput} (Image attached)` : textInput;
724037
+ if (label === "__other__")
724038
+ return hasImages ? "(Image attached)" : "";
724039
+ return label;
724040
+ }
723452
724041
  function AskUserQuestionPermissionRequest(props) {
723453
724042
  const settings = useSettings();
723454
724043
  if (settings.syntaxHighlightingDisabled) {
@@ -723511,7 +724100,7 @@ function AskUserQuestionPermissionRequestBody({
723511
724100
  }
723512
724101
  }
723513
724102
  const rightPanelHeight = maxPreviewBoxHeight + 2;
723514
- const leftPanelHeight = q.options.length + 2;
724103
+ const leftPanelHeight = q.options.length + 3;
723515
724104
  const sideByHeight = Math.max(leftPanelHeight, rightPanelHeight);
723516
724105
  maxHeight = Math.max(maxHeight, sideByHeight + 7);
723517
724106
  } else {
@@ -723520,7 +724109,7 @@ function AskUserQuestionPermissionRequestBody({
723520
724109
  }
723521
724110
  const globalContentHeight = Math.min(Math.max(maxHeight, MIN_CONTENT_HEIGHT), maxAllowedHeight);
723522
724111
  const globalContentWidth = Math.max(maxWidth, MIN_CONTENT_WIDTH);
723523
- const [pastedContentsByQuestion, setPastedContentsByQuestion] = import_react201.useState({});
724112
+ const [pastedContentsByQuestion, setPastedContentsByQuestion] = import_react201.useState(() => createPrototypeSafeRecord());
723524
724113
  const nextPasteIdRef = import_react201.useRef(0);
723525
724114
  const onImagePaste = import_react201.useCallback((questionText, base64Image, mediaType, filename, dimensions, _sourcePath) => {
723526
724115
  nextPasteIdRef.current += 1;
@@ -723535,22 +724124,19 @@ function AskUserQuestionPermissionRequestBody({
723535
724124
  };
723536
724125
  cacheImagePath(newContent);
723537
724126
  storeImage(newContent);
723538
- setPastedContentsByQuestion((prev) => ({
723539
- ...prev,
723540
- [questionText]: {
723541
- ...prev[questionText] ?? {},
723542
- [pasteId]: newContent
723543
- }
723544
- }));
724127
+ setPastedContentsByQuestion((prev) => {
724128
+ const previousQuestionContents = getOwnRecordValue(prev, questionText);
724129
+ const questionContents = previousQuestionContents ? clonePrototypeSafeRecord(previousQuestionContents) : Object.create(null);
724130
+ questionContents[pasteId] = newContent;
724131
+ return setPrototypeSafeRecordValue(prev, questionText, questionContents);
724132
+ });
723545
724133
  }, []);
723546
724134
  const onRemoveImage = import_react201.useCallback((questionText, id) => {
723547
724135
  setPastedContentsByQuestion((prev) => {
723548
- const questionContents = { ...prev[questionText] ?? {} };
724136
+ const previousQuestionContents = getOwnRecordValue(prev, questionText);
724137
+ const questionContents = previousQuestionContents ? clonePrototypeSafeRecord(previousQuestionContents) : Object.create(null);
723549
724138
  delete questionContents[id];
723550
- return {
723551
- ...prev,
723552
- [questionText]: questionContents
723553
- };
724139
+ return setPrototypeSafeRecordValue(prev, questionText, questionContents);
723554
724140
  });
723555
724141
  }, []);
723556
724142
  const allImageAttachments = import_react201.useMemo(() => Object.values(pastedContentsByQuestion).flatMap(Object.values).filter((c4) => c4.type === "image"), [pastedContentsByQuestion]);
@@ -723568,7 +724154,7 @@ function AskUserQuestionPermissionRequestBody({
723568
724154
  } = state2;
723569
724155
  const currentQuestion = currentQuestionIndex < (questions?.length || 0) ? questions?.[currentQuestionIndex] : null;
723570
724156
  const isInSubmitView = currentQuestionIndex === (questions?.length || 0);
723571
- const allQuestionsAnswered = questions?.every((q) => q?.question && !!answers[q.question]) ?? false;
724157
+ const allQuestionsAnswered = questions?.every((q) => q?.question && !!getOwnRecordValue(answers, q.question)) ?? false;
723572
724158
  const hideSubmitTab = questions.length === 1 && !questions[0]?.multiSelect;
723573
724159
  const handleCancel = import_react201.useCallback(() => {
723574
724160
  if (metadataSource) {
@@ -723585,7 +724171,7 @@ function AskUserQuestionPermissionRequestBody({
723585
724171
  }, [metadataSource, questions.length, isInPlanMode, onDone, onReject, toolUseConfirm]);
723586
724172
  const handleRespondToUR = import_react201.useCallback(async () => {
723587
724173
  const questionsWithAnswers = questions.map((q) => {
723588
- const answer = answers[q.question];
724174
+ const answer = getOwnRecordValue(answers, q.question);
723589
724175
  if (answer) {
723590
724176
  return `- "${q.question}"
723591
724177
  Answer: ${answer}`;
@@ -723615,7 +724201,7 @@ ${questionsWithAnswers}`;
723615
724201
  }, [allImageAttachments, answers, isInPlanMode, metadataSource, onDone, questions, toolUseConfirm]);
723616
724202
  const handleFinishPlanInterview = import_react201.useCallback(async () => {
723617
724203
  const questionsWithAnswers = questions.map((q) => {
723618
- const answer = answers[q.question];
724204
+ const answer = getOwnRecordValue(answers, q.question);
723619
724205
  if (answer) {
723620
724206
  return `- "${q.question}"
723621
724207
  Answer: ${answer}`;
@@ -723651,10 +724237,13 @@ ${questionsWithAnswers}`;
723651
724237
  interviewPhaseEnabled: isInPlanMode && isPlanModeInterviewPhaseEnabled()
723652
724238
  });
723653
724239
  }
723654
- const annotations = {};
724240
+ const annotations = createPrototypeSafeRecord();
723655
724241
  for (const q of questions) {
723656
- const answer = answersToSubmit[q.question];
723657
- const notes = questionStates[q.question]?.textInputValue;
724242
+ const answer = getOwnRecordValue(answersToSubmit, q.question);
724243
+ const questionState = getOwnRecordValue(questionStates, q.question);
724244
+ const selectedValue = questionState?.selectedValue;
724245
+ const selectedOther = selectedValue === "__other__" || Array.isArray(selectedValue) && selectedValue.includes("__other__");
724246
+ const notes = selectedOther ? undefined : questionState?.textInputValue;
723658
724247
  const selectedOption = answer ? q.options.find((opt) => opt.label === answer) : undefined;
723659
724248
  const preview6 = selectedOption?.preview;
723660
724249
  if (preview6 || notes?.trim()) {
@@ -723675,21 +724264,11 @@ ${questionsWithAnswers}`;
723675
724264
  }, [allImageAttachments, isInPlanMode, metadataSource, onDone, questionStates, questions, toolUseConfirm]);
723676
724265
  const handleQuestionAnswer = import_react201.useCallback((questionText, label, textInput, shouldAdvance = true) => {
723677
724266
  const isMultiSelect = Array.isArray(label);
723678
- let answer;
723679
- if (isMultiSelect) {
723680
- answer = label.join(", ");
723681
- } else if (textInput) {
723682
- const questionImages = Object.values(pastedContentsByQuestion[questionText] ?? {}).filter((c4) => c4.type === "image");
723683
- answer = questionImages.length > 0 ? `${textInput} (Image attached)` : textInput;
723684
- } else if (label === "__other__") {
723685
- const questionImages = Object.values(pastedContentsByQuestion[questionText] ?? {}).filter((c4) => c4.type === "image");
723686
- answer = questionImages.length > 0 ? "(Image attached)" : label;
723687
- } else {
723688
- answer = label;
723689
- }
724267
+ const hasImages = Object.values(getOwnRecordValue(pastedContentsByQuestion, questionText) ?? {}).some((content) => content.type === "image");
724268
+ const answer = resolveQuestionAnswer(label, textInput, hasImages);
723690
724269
  const isSingleQuestion = questions.length === 1;
723691
- if (!isMultiSelect && isSingleQuestion && shouldAdvance) {
723692
- const updatedAnswers = { ...answers, [questionText]: answer };
724270
+ if (!isMultiSelect && isSingleQuestion && shouldAdvance && answer) {
724271
+ const updatedAnswers = setPrototypeSafeRecordValue(answers, questionText, answer);
723693
724272
  submitAnswers(updatedAnswers).catch(logError2);
723694
724273
  return;
723695
724274
  }
@@ -723723,7 +724302,7 @@ ${questionsWithAnswers}`;
723723
724302
  isActive: !(isInTextInput && !isInSubmitView)
723724
724303
  });
723725
724304
  if (currentQuestion) {
723726
- const pastedContents = pastedContentsByQuestion[currentQuestion.question] ?? {};
724305
+ const pastedContents = getOwnRecordValue(pastedContentsByQuestion, currentQuestion.question) ?? Object.create(null);
723727
724306
  return /* @__PURE__ */ jsx_dev_runtime363.jsxDEV(PermissionDialog, {
723728
724307
  title: currentQuestion.question,
723729
724308
  onCancel: handleCancel,
@@ -723806,6 +724385,7 @@ var init_AskUserQuestionPermissionRequest = __esm(() => {
723806
724385
  init_planModeV2();
723807
724386
  init_plans();
723808
724387
  init_PermissionDialog();
724388
+ init_prototypeSafeRecord();
723809
724389
  init_QuestionView();
723810
724390
  init_SubmitQuestionsView();
723811
724391
  init_use_multiple_choice_state();
@@ -728472,9 +729052,8 @@ function ExitPlanModePermissionRequest({
728472
729052
  const transcriptHint = `
728473
729053
 
728474
729054
  If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: ${transcriptPath}`;
728475
- const teamHint = isAgentSwarmsEnabled() ? `
728476
-
728477
- If this plan can be broken down into multiple independent tasks, consider using the ${TEAM_CREATE_TOOL_NAME} tool to create a team and parallelize the work.` : "";
729055
+ const implementationCapabilities = getApprovedPlanCapabilities(toolUseConfirm.toolUseContext);
729056
+ const implementationInstruction = getApprovedPlanImplementationInstruction(implementationCapabilities);
728478
729057
  const feedbackSuffix = acceptFeedback ? `
728479
729058
 
728480
729059
  User feedback on this plan: ${acceptFeedback}` : "";
@@ -728485,7 +729064,9 @@ User feedback on this plan: ${acceptFeedback}` : "";
728485
729064
  ...createUserMessage({
728486
729065
  content: `Implement the following plan:
728487
729066
 
728488
- ${currentPlan}${verificationInstruction}${transcriptHint}${teamHint}${feedbackSuffix}`
729067
+ ${currentPlan}
729068
+
729069
+ ${implementationInstruction}${verificationInstruction}${transcriptHint}${feedbackSuffix}`
728489
729070
  }),
728490
729071
  planContent: currentPlan
728491
729072
  },
@@ -728939,6 +729520,7 @@ var import_react211, jsx_dev_runtime374;
728939
729520
  var init_ExitPlanModePermissionRequest = __esm(() => {
728940
729521
  init_figures();
728941
729522
  init_notifications();
729523
+ init_planImplementationContract();
728942
729524
  init_analytics();
728943
729525
  init_AppState();
728944
729526
  init_state();
@@ -728946,7 +729528,6 @@ var init_ExitPlanModePermissionRequest = __esm(() => {
728946
729528
  init_ultraplan();
728947
729529
  init_ink2();
728948
729530
  init_constants2();
728949
- init_agentSwarmsEnabled();
728950
729531
  init_context();
728951
729532
  init_editor();
728952
729533
  init_file();
@@ -734615,7 +735196,7 @@ var init_useVoiceEnabled = __esm(() => {
734615
735196
  function getSemverPart(version3) {
734616
735197
  return `${import_semver13.major(version3, { loose: true })}.${import_semver13.minor(version3, { loose: true })}.${import_semver13.patch(version3, { loose: true })}`;
734617
735198
  }
734618
- function useUpdateNotification(updatedVersion, initialVersion = "1.65.9") {
735199
+ function useUpdateNotification(updatedVersion, initialVersion = "1.65.11") {
734619
735200
  const [lastNotifiedSemver, setLastNotifiedSemver] = import_react222.useState(() => getSemverPart(initialVersion));
734620
735201
  if (!updatedVersion) {
734621
735202
  return null;
@@ -734664,7 +735245,7 @@ function AutoUpdater({
734664
735245
  return;
734665
735246
  }
734666
735247
  if (false) {}
734667
- const currentVersion = "1.65.9";
735248
+ const currentVersion = "1.65.11";
734668
735249
  const channel = getInitialSettings()?.autoUpdatesChannel ?? "latest";
734669
735250
  let latestVersion = await getLatestVersion(channel);
734670
735251
  const isDisabled = isAutoUpdaterDisabled();
@@ -734893,12 +735474,12 @@ function NativeAutoUpdater({
734893
735474
  logEvent("tengu_native_auto_updater_start", {});
734894
735475
  try {
734895
735476
  const maxVersion = await getMaxVersion();
734896
- if (maxVersion && gt("1.65.9", maxVersion)) {
735477
+ if (maxVersion && gt("1.65.11", maxVersion)) {
734897
735478
  const msg = await getMaxVersionMessage();
734898
735479
  setMaxVersionIssue(msg ?? "affects your version");
734899
735480
  }
734900
735481
  const result = await installLatest(channel);
734901
- const currentVersion = "1.65.9";
735482
+ const currentVersion = "1.65.11";
734902
735483
  const latencyMs = Date.now() - startTime;
734903
735484
  if (result.lockFailed) {
734904
735485
  logEvent("tengu_native_auto_updater_lock_contention", {
@@ -735035,17 +735616,17 @@ function PackageManagerAutoUpdater(t0) {
735035
735616
  const maxVersion = await getMaxVersion();
735036
735617
  if (maxVersion && latest && gt(latest, maxVersion)) {
735037
735618
  logForDebugging(`PackageManagerAutoUpdater: maxVersion ${maxVersion} is set, capping update from ${latest} to ${maxVersion}`);
735038
- if (gte("1.65.9", maxVersion)) {
735039
- logForDebugging(`PackageManagerAutoUpdater: current version ${"1.65.9"} is already at or above maxVersion ${maxVersion}, skipping update`);
735619
+ if (gte("1.65.11", maxVersion)) {
735620
+ logForDebugging(`PackageManagerAutoUpdater: current version ${"1.65.11"} is already at or above maxVersion ${maxVersion}, skipping update`);
735040
735621
  setUpdateAvailable(false);
735041
735622
  return;
735042
735623
  }
735043
735624
  latest = maxVersion;
735044
735625
  }
735045
- const hasUpdate = latest && !gte("1.65.9", latest) && !shouldSkipVersion(latest);
735626
+ const hasUpdate = latest && !gte("1.65.11", latest) && !shouldSkipVersion(latest);
735046
735627
  setUpdateAvailable(!!hasUpdate);
735047
735628
  if (hasUpdate) {
735048
- logForDebugging(`PackageManagerAutoUpdater: Update available ${"1.65.9"} -> ${latest}`);
735629
+ logForDebugging(`PackageManagerAutoUpdater: Update available ${"1.65.11"} -> ${latest}`);
735049
735630
  }
735050
735631
  };
735051
735632
  $2[0] = t1;
@@ -735079,7 +735660,7 @@ function PackageManagerAutoUpdater(t0) {
735079
735660
  wrap: "truncate",
735080
735661
  children: [
735081
735662
  "currentVersion: ",
735082
- "1.65.9"
735663
+ "1.65.11"
735083
735664
  ]
735084
735665
  }, undefined, true, undefined, this);
735085
735666
  $2[3] = verbose;
@@ -745799,7 +746380,7 @@ function buildStatusLineCommandInput(permissionMode, exceeds200kTokens, settings
745799
746380
  project_dir: getOriginalCwd(),
745800
746381
  added_dirs: addedDirs
745801
746382
  },
745802
- version: "1.65.9",
746383
+ version: "1.65.11",
745803
746384
  output_style: {
745804
746385
  name: outputStyleName
745805
746386
  },
@@ -745877,7 +746458,7 @@ function StatusLineInner({
745877
746458
  const taskValues = Object.values(tasks2);
745878
746459
  const taskRunningCount = countActiveBackgroundTasks(taskValues);
745879
746460
  const defaultStatusLineText = buildDefaultStatusBar({
745880
- version: "1.65.9",
746461
+ version: "1.65.11",
745881
746462
  providerLabel: providerRuntime.providerLabel,
745882
746463
  authMode: providerRuntime.authLabel,
745883
746464
  model: renderModelName(mainLoopModel) || providerRuntime.model || "",
@@ -746900,7 +747481,7 @@ var init_ghPrStatus = __esm(() => {
746900
747481
 
746901
747482
  // src/hooks/usePrStatus.ts
746902
747483
  function usePrStatus(isLoading, enabled = true) {
746903
- const [prStatus, setPrStatus] = import_react248.useState(INITIAL_STATE3);
747484
+ const [prStatus, setPrStatus] = import_react248.useState(INITIAL_STATE2);
746904
747485
  const timeoutRef = import_react248.useRef(null);
746905
747486
  const disabledRef = import_react248.useRef(false);
746906
747487
  const lastFetchRef = import_react248.useRef(0);
@@ -746964,13 +747545,13 @@ function usePrStatus(isLoading, enabled = true) {
746964
747545
  }, [isLoading, enabled]);
746965
747546
  return prStatus;
746966
747547
  }
746967
- var import_react248, POLL_INTERVAL_MS3 = 60000, SLOW_GH_THRESHOLD_MS = 4000, IDLE_STOP_MS, INITIAL_STATE3;
747548
+ var import_react248, POLL_INTERVAL_MS3 = 60000, SLOW_GH_THRESHOLD_MS = 4000, IDLE_STOP_MS, INITIAL_STATE2;
746968
747549
  var init_usePrStatus = __esm(() => {
746969
747550
  init_state();
746970
747551
  init_ghPrStatus();
746971
747552
  import_react248 = __toESM(require_react(), 1);
746972
747553
  IDLE_STOP_MS = 60 * 60000;
746973
- INITIAL_STATE3 = {
747554
+ INITIAL_STATE2 = {
746974
747555
  number: null,
746975
747556
  url: null,
746976
747557
  reviewState: null,
@@ -758057,7 +758638,7 @@ async function submitTranscriptShare(messages, trigger2, appearanceId) {
758057
758638
  } catch {}
758058
758639
  const data = {
758059
758640
  trigger: trigger2,
758060
- version: "1.65.9",
758641
+ version: "1.65.11",
758061
758642
  platform: process.platform,
758062
758643
  transcript,
758063
758644
  subagentTranscripts: Object.keys(subagentTranscripts).length > 0 ? subagentTranscripts : undefined,
@@ -770422,7 +771003,7 @@ function WelcomeV2() {
770422
771003
  dimColor: true,
770423
771004
  children: [
770424
771005
  "v",
770425
- "1.65.9"
771006
+ "1.65.11"
770426
771007
  ]
770427
771008
  }, undefined, true, undefined, this)
770428
771009
  ]
@@ -771682,7 +772263,7 @@ function completeOnboarding() {
771682
772263
  saveGlobalConfig((current) => ({
771683
772264
  ...current,
771684
772265
  hasCompletedOnboarding: true,
771685
- lastOnboardingVersion: "1.65.9"
772266
+ lastOnboardingVersion: "1.65.11"
771686
772267
  }));
771687
772268
  }
771688
772269
  function showDialog(root2, renderer) {
@@ -776726,7 +777307,7 @@ function appendToLog(path24, message) {
776726
777307
  cwd: getFsImplementation().cwd(),
776727
777308
  userType: process.env.USER_TYPE,
776728
777309
  sessionId: getSessionId(),
776729
- version: "1.65.9"
777310
+ version: "1.65.11"
776730
777311
  };
776731
777312
  getLogWriter(path24).write(messageWithTimestamp);
776732
777313
  }
@@ -780890,8 +781471,8 @@ async function getEnvLessBridgeConfig() {
780890
781471
  }
780891
781472
  async function checkEnvLessBridgeMinVersion() {
780892
781473
  const cfg = await getEnvLessBridgeConfig();
780893
- if (cfg.min_version && lt("1.65.9", cfg.min_version)) {
780894
- return `Your version of UR (${"1.65.9"}) is too old for Remote Control.
781474
+ if (cfg.min_version && lt("1.65.11", cfg.min_version)) {
781475
+ return `Your version of UR (${"1.65.11"}) is too old for Remote Control.
780895
781476
  Version ${cfg.min_version} or higher is required. Run \`ur update\` to update.`;
780896
781477
  }
780897
781478
  return null;
@@ -781365,7 +781946,7 @@ async function initBridgeCore(params) {
781365
781946
  const rawApi = createBridgeApiClient({
781366
781947
  baseUrl,
781367
781948
  getAccessToken,
781368
- runnerVersion: "1.65.9",
781949
+ runnerVersion: "1.65.11",
781369
781950
  onDebug: logForDebugging,
781370
781951
  onAuth401,
781371
781952
  getTrustedDeviceToken
@@ -790838,7 +791419,7 @@ function getAgUiCapabilities() {
790838
791419
  name: "UR-Nexus",
790839
791420
  type: "ur-nexus",
790840
791421
  description: "Provider-flexible, local-first autonomous engineering workflow agent.",
790841
- version: "1.65.9",
791422
+ version: "1.65.11",
790842
791423
  provider: "UR",
790843
791424
  documentationUrl: "https://github.com/Maitham16/UR/blob/master/docs/AG_UI.md"
790844
791425
  },
@@ -791978,7 +792559,7 @@ function createMCPServer(cwd4, debug2, verbose) {
791978
792559
  };
791979
792560
  const server2 = new Server({
791980
792561
  name: "ur-nexus",
791981
- version: "1.65.9"
792562
+ version: "1.65.11"
791982
792563
  }, {
791983
792564
  capabilities: {
791984
792565
  tools: {}
@@ -793136,7 +793717,7 @@ function thrownResponse(error40) {
793136
793717
  }
793137
793718
  async function createUrMcp2026Runtime(options4) {
793138
793719
  const server2 = createMCPServer(options4.cwd, options4.debug === true, options4.verbose === true);
793139
- const client2 = new Client({ name: "ur-mcp-2026-adapter", version: "1.65.9" }, { capabilities: {} });
793720
+ const client2 = new Client({ name: "ur-mcp-2026-adapter", version: "1.65.11" }, { capabilities: {} });
793140
793721
  const [clientTransport, serverTransport] = createLinkedTransportPair();
793141
793722
  try {
793142
793723
  await server2.connect(serverTransport);
@@ -793147,7 +793728,7 @@ async function createUrMcp2026Runtime(options4) {
793147
793728
  }
793148
793729
  const runtime2 = new Mcp2026Runtime({
793149
793730
  cwd: options4.cwd,
793150
- version: "1.65.9",
793731
+ version: "1.65.11",
793151
793732
  backend: {
793152
793733
  listTools: async () => {
793153
793734
  const listed = await client2.listTools();
@@ -795280,7 +795861,7 @@ async function update() {
795280
795861
  logEvent("tengu_update_check", {});
795281
795862
  const diagnostic2 = await getDoctorDiagnostic();
795282
795863
  const result = await checkUpgradeStatus({
795283
- currentVersion: "1.65.9",
795864
+ currentVersion: "1.65.11",
795284
795865
  packageName: UR_AGENT_PACKAGE_NAME,
795285
795866
  installationType: diagnostic2.installationType,
795286
795867
  latestVersion: () => getLatestNpmPackageVersion(UR_AGENT_PACKAGE_NAME)
@@ -796596,7 +797177,7 @@ ${customInstructions}` : customInstructions;
796596
797177
  }
796597
797178
  }
796598
797179
  logForDiagnosticsNoPII("info", "started", {
796599
- version: "1.65.9",
797180
+ version: "1.65.11",
796600
797181
  is_native_binary: isInBundledMode()
796601
797182
  });
796602
797183
  registerCleanup(async () => {
@@ -797382,7 +797963,7 @@ Usage: ur --remote "your task description"`, () => gracefulShutdown(1));
797382
797963
  pendingHookMessages
797383
797964
  }, renderAndRun);
797384
797965
  }
797385
- }).version("1.65.9 (UR-Nexus)", "-v, --version", "Output the version number");
797966
+ }).version("1.65.11 (UR-Nexus)", "-v, --version", "Output the version number");
797386
797967
  program2.option("-w, --worktree [name]", "Create a new git worktree for this session (optionally specify a name)");
797387
797968
  program2.option("--tmux", "Create a tmux session for the worktree (requires --worktree). Uses iTerm2 native panes when available; use --tmux=classic for traditional tmux.");
797388
797969
  if (canUserConfigureAdvisor()) {
@@ -798441,7 +799022,7 @@ if (false) {}
798441
799022
  async function main2() {
798442
799023
  const args = process.argv.slice(2);
798443
799024
  if (args.length === 1 && (args[0] === "--version" || args[0] === "-v" || args[0] === "-V")) {
798444
- console.log(`${"1.65.9"} (UR-Nexus)`);
799025
+ console.log(`${"1.65.11"} (UR-Nexus)`);
798445
799026
  return;
798446
799027
  }
798447
799028
  if (args[0] === "a2a" && args[1] === "serve" && !args.includes("--help") && !args.includes("-h")) {