@qwen-code/qwen-code 0.16.1-preview.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/bundled/new-app/SKILL.md +22 -0
  2. package/bundled/qc-helper/docs/configuration/settings.md +2 -1
  3. package/bundled/qc-helper/docs/features/_meta.ts +1 -0
  4. package/bundled/qc-helper/docs/features/worktree.md +345 -0
  5. package/chunks/{agent-KYFWAFRH.js → agent-RY5EB3XR.js} +15 -15
  6. package/chunks/{anthropicContentGenerator-DUZ2FYDT.js → anthropicContentGenerator-LYI3OHBB.js} +4 -4
  7. package/chunks/{askUserQuestion-KFDJMM66.js → askUserQuestion-R3MKD2JT.js} +2 -2
  8. package/chunks/{ca-UZ7BANMN.js → ca-NMZFEGAU.js} +1 -0
  9. package/chunks/{chunk-DDU3LH4J.js → chunk-4YNZFYJY.js} +59 -21
  10. package/chunks/{chunk-JQF2373J.js → chunk-66CJCYYZ.js} +11 -11
  11. package/chunks/{chunk-ERQCJRX4.js → chunk-6NUSWV4M.js} +1 -1
  12. package/chunks/{chunk-QQAHXZS2.js → chunk-7HM6OB7M.js} +574 -103
  13. package/chunks/{chunk-DZNH4UDC.js → chunk-7YJIR2FX.js} +2 -2
  14. package/chunks/{chunk-W2FLCFLJ.js → chunk-ACBGEKB7.js} +1 -1
  15. package/chunks/{chunk-34S63Y6S.js → chunk-C27V5A2J.js} +1 -1
  16. package/chunks/{chunk-QDV553GJ.js → chunk-EY6BDW7Y.js} +1 -1
  17. package/chunks/{chunk-JZNYPW2X.js → chunk-FO7BIVSR.js} +1 -1
  18. package/chunks/{chunk-F6MJMTLW.js → chunk-GQXXO5HJ.js} +28 -8
  19. package/chunks/{chunk-Q7IZ5RFJ.js → chunk-GVWPJCXU.js} +1 -1
  20. package/chunks/{chunk-SS4HGKS4.js → chunk-JVD46YJV.js} +1 -1
  21. package/chunks/{chunk-33FTD7OV.js → chunk-K72FHBFO.js} +1 -1
  22. package/chunks/{chunk-G5JCK3FG.js → chunk-NQ3E7YLD.js} +1 -1
  23. package/chunks/{chunk-QOVHOYKK.js → chunk-ODPVJ6JJ.js} +1 -1
  24. package/chunks/{chunk-AAJT7L7R.js → chunk-OIL7KDWV.js} +1 -1
  25. package/chunks/{chunk-M5TA67JI.js → chunk-PJLEMR7N.js} +6 -6
  26. package/chunks/{chunk-VMHEBQ5K.js → chunk-TXQI3VZ7.js} +1 -1
  27. package/chunks/{chunk-XS2N5WHQ.js → chunk-UE5LPQF7.js} +5 -5
  28. package/chunks/{chunk-6W4AH2GT.js → chunk-VMOWXTRC.js} +2 -2
  29. package/chunks/{chunk-OEIT4HZX.js → chunk-YMDXEEOW.js} +1 -1
  30. package/chunks/{contextCommand-E3Z6QOHV.js → contextCommand-DDGVLQSF.js} +17 -17
  31. package/chunks/{cron-create-GJT7LK4I.js → cron-create-BTEOGHPH.js} +2 -2
  32. package/chunks/{cron-delete-PWUIASTZ.js → cron-delete-56CEWELN.js} +2 -2
  33. package/chunks/{cron-list-P42BOGWK.js → cron-list-SV6QRZW2.js} +2 -2
  34. package/chunks/{de-V4IE2OOZ.js → de-OIMT3OMI.js} +1 -0
  35. package/chunks/{edit-2CY35GKJ.js → edit-4LLGNYVZ.js} +16 -16
  36. package/chunks/{en-HGJ2SPLM.js → en-2IFZ5THF.js} +1 -0
  37. package/chunks/{enter-worktree-HJGN4DH2.js → enter-worktree-E2R5XAFT.js} +18 -16
  38. package/chunks/{exit-worktree-VAPX5N2E.js → exit-worktree-YVBYYYDD.js} +15 -15
  39. package/chunks/{exitPlanMode-3JP3BRRO.js → exitPlanMode-WD5IH7NS.js} +15 -15
  40. package/chunks/{fr-CJULI7ZX.js → fr-PVELSHTV.js} +1 -0
  41. package/chunks/{geminiContentGenerator-MD7IEVCK.js → geminiContentGenerator-LM65ADWM.js} +3 -3
  42. package/chunks/{glob-C5ZNGH7V.js → glob-6X6OCEWE.js} +15 -15
  43. package/chunks/{grep-GKU7Y77F.js → grep-2UUPSSIQ.js} +15 -15
  44. package/chunks/{ja-L7CHRQEW.js → ja-P5TK5GNN.js} +1 -0
  45. package/chunks/{keychain-token-storage-QCF76A5D.js → keychain-token-storage-3552ENXE.js} +2 -2
  46. package/chunks/{ls-QZTZYURK.js → ls-MYXAM7LJ.js} +3 -3
  47. package/chunks/{lsp-L6NQBEQA.js → lsp-PFGI35JL.js} +2 -2
  48. package/chunks/{monitor-HJTGGH2N.js → monitor-VUHPEGUW.js} +25 -20
  49. package/chunks/{notebook-edit-RWOEUTYU.js → notebook-edit-P4QVLW6I.js} +16 -16
  50. package/chunks/{openaiContentGenerator-FDC4XT77.js → openaiContentGenerator-JH4YNZ3H.js} +10 -10
  51. package/chunks/{pt-M6JULLEQ.js → pt-A5GHG66T.js} +1 -0
  52. package/chunks/{qwenContentGenerator-J3C7NUMH.js → qwenContentGenerator-5FE4UYUT.js} +17 -17
  53. package/chunks/{qwenOAuth2-7JZIHZ7O.js → qwenOAuth2-BAN2EGSH.js} +3 -3
  54. package/chunks/{read-file-5QUJ3ZIL.js → read-file-J7DH4OKV.js} +7 -7
  55. package/chunks/{ripGrep-PA7M7MJS.js → ripGrep-33DECY4F.js} +15 -15
  56. package/chunks/{ru-QILM4HBC.js → ru-66XKB4QX.js} +1 -0
  57. package/chunks/{send-message-GDN76EHU.js → send-message-JUFP62VD.js} +2 -2
  58. package/chunks/{serve-FLSKL5TV.js → serve-7FX7MREA.js} +17 -17
  59. package/chunks/{shell-DA54BZO2.js → shell-ZNTQIRK6.js} +15 -15
  60. package/chunks/{skill-47FZXDEM.js → skill-CFCUIY23.js} +9 -9
  61. package/chunks/{src-6Y7NVY2L.js → src-AHV2CWEQ.js} +21 -15
  62. package/chunks/{syntheticOutput-6N4MZYQT.js → syntheticOutput-AKTXC6FR.js} +3 -3
  63. package/chunks/{task-stop-TTT4O7JR.js → task-stop-2NYFR2ES.js} +2 -2
  64. package/chunks/{todoWrite-GYUYNLKE.js → todoWrite-WHZ2O2XP.js} +3 -3
  65. package/chunks/{tool-search-PONJXBSP.js → tool-search-C2EMLFBJ.js} +7 -7
  66. package/chunks/{web-fetch-V4TWMRQD.js → web-fetch-S6MZXPZ5.js} +4 -4
  67. package/chunks/{write-file-2YX4NB3B.js → write-file-EEPVRS4Q.js} +16 -16
  68. package/chunks/{zh-PWL2NKY3.js → zh-OB5P2ZDO.js} +1 -0
  69. package/chunks/{zh-TW-S3YGWICZ.js → zh-TW-3ND6DQRX.js} +1 -0
  70. package/cli.js +1377 -922
  71. package/locales/ca.js +1 -0
  72. package/locales/de.js +1 -0
  73. package/locales/en.js +1 -0
  74. package/locales/fr.js +1 -0
  75. package/locales/ja.js +1 -0
  76. package/locales/pt.js +1 -0
  77. package/locales/ru.js +1 -0
  78. package/locales/zh-TW.js +1 -0
  79. package/locales/zh.js +1 -0
  80. package/package.json +2 -2
@@ -36,11 +36,11 @@ import {
36
36
  } from "./chunk-TW522KN6.js";
37
37
  import {
38
38
  runSideQuery
39
- } from "./chunk-34S63Y6S.js";
39
+ } from "./chunk-C27V5A2J.js";
40
40
  import {
41
41
  escapeSystemReminderTags,
42
42
  escapeXml
43
- } from "./chunk-QDV553GJ.js";
43
+ } from "./chunk-EY6BDW7Y.js";
44
44
  import {
45
45
  DefaultHookOutput,
46
46
  HOOKS_CONFIG_FIELDS,
@@ -52,14 +52,14 @@ import {
52
52
  StopHookOutput,
53
53
  createHookOutput,
54
54
  getHookKey
55
- } from "./chunk-33FTD7OV.js";
55
+ } from "./chunk-K72FHBFO.js";
56
56
  import {
57
57
  DEFAULT_FILE_FILTERING_OPTIONS
58
58
  } from "./chunk-77WXWU44.js";
59
59
  import {
60
60
  BaseTokenStorage,
61
61
  KeychainTokenStorage
62
- } from "./chunk-Q7IZ5RFJ.js";
62
+ } from "./chunk-GVWPJCXU.js";
63
63
  import {
64
64
  AUTH_ENV_MAPPINGS,
65
65
  ApiResponseEvent,
@@ -114,6 +114,7 @@ import {
114
114
  initializeTelemetry,
115
115
  isTelemetrySdkInitialized,
116
116
  isUtf8CompatibleEncoding,
117
+ isWithinRoot,
117
118
  logArenaAgentCompleted,
118
119
  logArenaSessionEnded,
119
120
  logArenaSessionStarted,
@@ -160,7 +161,7 @@ import {
160
161
  truncateSpanError,
161
162
  truncateToolOutput,
162
163
  uiTelemetryService
163
- } from "./chunk-DDU3LH4J.js";
164
+ } from "./chunk-4YNZFYJY.js";
164
165
  import {
165
166
  DEFAULT_QWEN_EMBEDDING_MODEL,
166
167
  DEFAULT_QWEN_MODEL
@@ -173,12 +174,12 @@ import {
173
174
  getAutoMemoryMetadataPath,
174
175
  getAutoMemoryRoot,
175
176
  isAutoMemPath
176
- } from "./chunk-G5JCK3FG.js";
177
+ } from "./chunk-NQ3E7YLD.js";
177
178
  import {
178
179
  DEFAULT_TOKEN_LIMIT,
179
180
  ESCALATED_MAX_TOKENS,
180
181
  tokenLimit
181
- } from "./chunk-ERQCJRX4.js";
182
+ } from "./chunk-6NUSWV4M.js";
182
183
  import {
183
184
  FinishReason,
184
185
  createModelContent,
@@ -188,7 +189,7 @@ import {
188
189
  } from "./chunk-T4VD6OJ4.js";
189
190
  import {
190
191
  STRUCTURED_OUTPUT_REDACTED_ARGS
191
- } from "./chunk-JZNYPW2X.js";
192
+ } from "./chunk-FO7BIVSR.js";
192
193
  import {
193
194
  AgentStatistics,
194
195
  BaseDeclarativeTool,
@@ -203,7 +204,7 @@ import {
203
204
  isTool,
204
205
  require_ajv,
205
206
  require_dist
206
- } from "./chunk-OEIT4HZX.js";
207
+ } from "./chunk-YMDXEEOW.js";
207
208
  import {
208
209
  PATH_ARG_KEYS,
209
210
  QWEN_DIR,
@@ -222,7 +223,7 @@ import {
222
223
  setDebugLogSession,
223
224
  toFriendlyError,
224
225
  unescapePath
225
- } from "./chunk-W2FLCFLJ.js";
226
+ } from "./chunk-ACBGEKB7.js";
226
227
  import {
227
228
  require_undici
228
229
  } from "./chunk-E7E2MFYM.js";
@@ -15589,11 +15590,11 @@ var require_extract_zip = __commonJS({
15589
15590
  var { createWriteStream: createWriteStream3, promises: fs81 } = __require("fs");
15590
15591
  var getStream = require_get_stream();
15591
15592
  var path84 = __require("path");
15592
- var { promisify: promisify5 } = __require("util");
15593
+ var { promisify: promisify6 } = __require("util");
15593
15594
  var stream2 = __require("stream");
15594
15595
  var yauzl = require_yauzl();
15595
- var openZip = promisify5(yauzl.open);
15596
- var pipeline = promisify5(stream2.pipeline);
15596
+ var openZip = promisify6(yauzl.open);
15597
+ var pipeline = promisify6(stream2.pipeline);
15597
15598
  var Extractor = class {
15598
15599
  static {
15599
15600
  __name(this, "Extractor");
@@ -15660,14 +15661,14 @@ var require_extract_zip = __commonJS({
15660
15661
  const IFMT2 = 61440;
15661
15662
  const IFDIR2 = 16384;
15662
15663
  const IFLNK2 = 40960;
15663
- const symlink = (mode & IFMT2) === IFLNK2;
15664
+ const symlink2 = (mode & IFMT2) === IFLNK2;
15664
15665
  let isDir = (mode & IFMT2) === IFDIR2;
15665
15666
  if (!isDir && entry.fileName.endsWith("/")) {
15666
15667
  isDir = true;
15667
15668
  }
15668
15669
  const madeBy = entry.versionMadeBy >> 8;
15669
15670
  if (!isDir) isDir = madeBy === 0 && entry.externalFileAttributes === 16;
15670
- debug2("extracting entry", { filename: entry.fileName, isDir, isSymlink: symlink });
15671
+ debug2("extracting entry", { filename: entry.fileName, isDir, isSymlink: symlink2 });
15671
15672
  const procMode = this.getExtractedMode(mode, isDir) & 511;
15672
15673
  const destDir = isDir ? dest : path84.dirname(dest);
15673
15674
  const mkdirOptions = { recursive: true };
@@ -15678,8 +15679,8 @@ var require_extract_zip = __commonJS({
15678
15679
  await fs81.mkdir(destDir, mkdirOptions);
15679
15680
  if (isDir) return;
15680
15681
  debug2("opening read stream", dest);
15681
- const readStream = await promisify5(this.zipfile.openReadStream.bind(this.zipfile))(entry);
15682
- if (symlink) {
15682
+ const readStream = await promisify6(this.zipfile.openReadStream.bind(this.zipfile))(entry);
15683
+ if (symlink2) {
15683
15684
  const link2 = await getStream(readStream);
15684
15685
  debug2("creating symlink", link2, dest);
15685
15686
  await fs81.symlink(link2, dest);
@@ -34206,22 +34207,7 @@ IMPORTANT: Always use the ${ToolNames.TODO_WRITE} tool to plan and track tasks t
34206
34207
 
34207
34208
  ## New Applications
34208
34209
 
34209
- **Goal:** Autonomously implement and deliver a visually appealing, substantially complete, and functional prototype. Utilize all tools at your disposal to implement the application. Some tools you may especially find useful are '${ToolNames.WRITE_FILE}', '${ToolNames.EDIT}' and '${ToolNames.SHELL}'.
34210
-
34211
- 1. **Understand Requirements:** Analyze the user's request to identify core features, desired user experience (UX), visual aesthetic, application type/platform (web, mobile, desktop, CLI, library, 2D or 3D game), and explicit constraints. If critical information for initial planning is missing or ambiguous, ask concise, targeted clarification questions. Use the ${ToolNames.ASK_USER_QUESTION} tool to ask questions, clarify and gather information as needed.
34212
- 2. **Propose Plan:** Formulate an internal development plan. Present a clear, concise, high-level summary to the user. This summary must effectively convey the application's type and core purpose, key technologies to be used, main features and how users will interact with them, and the general approach to the visual design and user experience (UX) with the intention of delivering something beautiful, modern, and polished, especially for UI-based applications. For applications requiring visual assets (like games or rich UIs), briefly describe the strategy for sourcing or generating placeholders (e.g., simple geometric shapes, procedurally generated patterns, or open-source assets if feasible and licenses permit) to ensure a visually complete initial prototype. Ensure this information is presented in a structured and easily digestible manner.
34213
- - When key technologies aren't specified, prefer the following:
34214
- - **Websites (Frontend):** React (JavaScript/TypeScript) with Bootstrap CSS, incorporating Material Design principles for UI/UX.
34215
- - **Back-End APIs:** Node.js with Express.js (JavaScript/TypeScript) or Python with FastAPI.
34216
- - **Full-stack:** Next.js (React/Node.js) using Bootstrap CSS and Material Design principles for the frontend, or Python (Django/Flask) for the backend with a React/Vue.js frontend styled with Bootstrap CSS and Material Design principles.
34217
- - **CLIs:** Python or Go.
34218
- - **Mobile App:** Compose Multiplatform (Kotlin Multiplatform) or Flutter (Dart) using Material Design libraries and principles, when sharing code between Android and iOS. Jetpack Compose (Kotlin JVM) with Material Design principles or SwiftUI (Swift) for native apps targeted at either Android or iOS, respectively.
34219
- - **3d Games:** HTML/CSS/JavaScript with Three.js.
34220
- - **2d Games:** HTML/CSS/JavaScript.
34221
- 3. **User Approval:** Obtain user approval for the proposed plan.
34222
- 4. **Implementation:** Use the '${ToolNames.TODO_WRITE}' tool to convert the approved plan into a structured todo list with specific, actionable tasks, then autonomously implement each task utilizing all available tools. When starting ensure you scaffold the application using '${ToolNames.SHELL}' for commands like 'npm init', 'npx create-react-app'. Aim for full scope completion. Proactively create or source necessary placeholder assets (e.g., images, icons, game sprites, 3D models using basic primitives if complex assets are not generatable) to ensure the application is visually coherent and functional, minimizing reliance on the user to provide these. If the model can generate simple assets (e.g., a uniformly colored square sprite, a simple 3D cube), it should do so. Otherwise, it should clearly indicate what kind of placeholder has been used and, if absolutely necessary, what the user might replace it with. Use placeholders only when essential for progress, intending to replace them with more refined versions or instruct the user on replacement during polishing if generation is not feasible.
34223
- 5. **Verify:** Review work against the original request, the approved plan. Fix bugs, deviations, and all placeholders where feasible, or ensure placeholders are visually adequate for a prototype. Ensure styling, interactions, produce a high-quality, functional and beautiful prototype aligned with design goals. Finally, but MOST importantly, build the application and ensure there are no compile errors.
34224
- 6. **Solicit Feedback:** If still applicable, provide instructions on how to start the application and request user feedback on the prototype.
34210
+ When a user wants to create a new application, project, website, game, or library from scratch, use the '${ToolNames.SKILL}' tool with skill="new-app" to load the detailed workflow and tech-stack guidance.
34225
34211
 
34226
34212
  # Operational Guidelines
34227
34213
 
@@ -38116,6 +38102,9 @@ function evaluateShellSegment(segment) {
38116
38102
  if (!segment.trim()) {
38117
38103
  return true;
38118
38104
  }
38105
+ if (detectCommandSubstitution(segment)) {
38106
+ return false;
38107
+ }
38119
38108
  const stripped = stripShellWrapper(segment);
38120
38109
  if (!stripped) {
38121
38110
  return true;
@@ -50849,7 +50838,8 @@ init_esbuild_shims();
50849
50838
  import * as fs15 from "node:fs/promises";
50850
50839
  import * as path15 from "node:path";
50851
50840
  import { randomBytes as randomBytes2, randomInt } from "node:crypto";
50852
- import { execSync as execSync3 } from "node:child_process";
50841
+ import { execFile as execFile2, execSync as execSync3 } from "node:child_process";
50842
+ import { promisify as promisify2 } from "node:util";
50853
50843
 
50854
50844
  // node_modules/simple-git/dist/esm/index.js
50855
50845
  init_esbuild_shims();
@@ -55668,6 +55658,7 @@ async function initRepositoryWithMainBranch(git) {
55668
55658
  __name(initRepositoryWithMainBranch, "initRepositoryWithMainBranch");
55669
55659
 
55670
55660
  // packages/core/src/services/gitWorktreeService.ts
55661
+ var execFileAsync2 = promisify2(execFile2);
55671
55662
  var debugLogger25 = createDebugLogger("GIT_WORKTREE_SERVICE");
55672
55663
  var WORKTREE_BRANCH_PREFIX = "worktree-";
55673
55664
  function worktreeBranchForSlug(slug) {
@@ -55864,6 +55855,25 @@ var GitWorktreeService = class _GitWorktreeService {
55864
55855
  const hash = await this.git.revparse(["HEAD"]);
55865
55856
  return hash.trim();
55866
55857
  }
55858
+ /**
55859
+ * Resolves a git ref name to a 40-char commit SHA. Returns `null` when
55860
+ * the ref is unknown / unborn / not a commit.
55861
+ *
55862
+ * Used by Phase D-3 to lock in `FETCH_HEAD` immediately after
55863
+ * `fetchPullRequestRef` succeeds, so the SHA passed to
55864
+ * `git worktree add` is immutable against a concurrent `git fetch` from
55865
+ * another process sharing the same repo, AND so `WorktreeExitDialog`'s
55866
+ * `rev-list <originalHeadCommit>..HEAD` counts only THIS session's new
55867
+ * work rather than every commit in the fetched PR.
55868
+ */
55869
+ async resolveRef(ref) {
55870
+ try {
55871
+ const out2 = (await this.git.raw(["rev-parse", "--verify", ref])).trim();
55872
+ return /^[0-9a-f]{40}$/.test(out2) ? out2 : null;
55873
+ } catch {
55874
+ return null;
55875
+ }
55876
+ }
55867
55877
  /**
55868
55878
  * Creates a single worktree.
55869
55879
  */
@@ -56353,6 +56363,207 @@ var GitWorktreeService = class _GitWorktreeService {
56353
56363
  const suffix = randomBytes2(3).toString("hex");
56354
56364
  return `${adj}-${noun}-${suffix}`;
56355
56365
  }
56366
+ /**
56367
+ * Parses a PR reference from a string. Recognised forms:
56368
+ *
56369
+ * - `#123` — shorthand PR number
56370
+ * - `https://github.com/<owner>/<repo>/pull/123` — full GitHub URL
56371
+ * (any host, any query string, any fragment)
56372
+ *
56373
+ * Returns the parsed PR number on match, `null` otherwise. The slug for
56374
+ * a PR worktree is derived by callers as `pr-<N>` and the branch as
56375
+ * `worktree-pr-<N>` (see `createUserWorktree`).
56376
+ *
56377
+ * Mirrors claude-code's `parsePRReference` (utils/worktree.ts:633) so
56378
+ * cross-CLI muscle memory transfers.
56379
+ */
56380
+ static parsePRReference(input) {
56381
+ if (typeof input !== "string") return null;
56382
+ const trimmed2 = input.trim();
56383
+ const urlMatch = trimmed2.match(
56384
+ /^https?:\/\/[^/]+\/[^/]+\/[^/]+\/pull\/(\d+)(?:\/[^?#]*)?(?:[?#].*)?$/i
56385
+ );
56386
+ if (urlMatch?.[1]) {
56387
+ const n = parseInt(urlMatch[1], 10);
56388
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
56389
+ }
56390
+ const hashMatch = trimmed2.match(/^#([1-9]\d*)$/);
56391
+ if (hashMatch?.[1]) {
56392
+ const n = parseInt(hashMatch[1], 10);
56393
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
56394
+ }
56395
+ return null;
56396
+ }
56397
+ /**
56398
+ * Identifies the registered worktree at `worktreePath` as a member of
56399
+ * THIS repository (`sourceRepoPath`). Returns the branch + HEAD commit
56400
+ * SHA on success, or `null` when the path is not a worktree of this
56401
+ * repo.
56402
+ *
56403
+ * Used by Phase D-1's re-attach path: when `--worktree foo` is passed
56404
+ * and `<repoRoot>/.qwen/worktrees/foo` already exists on disk, we
56405
+ * verify it really IS a Qwen-managed worktree of the current repo (not
56406
+ * a standalone `git init` someone dropped at that path) before
56407
+ * assuming it's safe to chdir into. Returning the HEAD SHA in the
56408
+ * same call avoids a second subprocess to recapture it after chdir.
56409
+ *
56410
+ * Implementation — a single `git rev-parse` returning four lines:
56411
+ * 1. `HEAD` → the worktree's HEAD commit SHA (must come BEFORE
56412
+ * `--abbrev-ref` since the flag sticks for all subsequent refs).
56413
+ * 2. `--abbrev-ref HEAD` → the branch name. A detached HEAD produces
56414
+ * `HEAD` here, which we treat as "no real branch" and return null
56415
+ * — the caller's re-attach gate will then refuse, since the
56416
+ * slug-derived branch couldn't possibly be `HEAD`.
56417
+ * 3. `--git-common-dir` → the common `.git` directory. For a real
56418
+ * linked worktree of this repo that's `<sourceRepoPath>/.git`;
56419
+ * for a sibling `git init` it resolves to `<worktreePath>/.git`.
56420
+ * We compare against this repo's own common-dir to reject the
56421
+ * latter.
56422
+ * 4. `--show-toplevel` → git's idea of the worktree top. For a real
56423
+ * linked worktree this equals `worktreePath`; for a plain
56424
+ * directory living UNDER the main repo (e.g. `mkdir
56425
+ * <repo>/.qwen/worktrees/foo`) git walks up to the outer `.git`
56426
+ * and returns the OUTER repo's root — which would otherwise pass
56427
+ * the common-dir check and let us "re-attach" to a non-worktree
56428
+ * directory. Compare paths to reject this.
56429
+ */
56430
+ async getRegisteredWorktreeBranch(worktreePath) {
56431
+ let resolvedWorktreePath;
56432
+ try {
56433
+ const stat16 = await fs15.stat(worktreePath);
56434
+ if (!stat16.isDirectory()) return null;
56435
+ resolvedWorktreePath = await fs15.realpath(worktreePath);
56436
+ } catch {
56437
+ return null;
56438
+ }
56439
+ const probeGit = simpleGit(worktreePath);
56440
+ let ourCommonDir;
56441
+ let headCommit;
56442
+ let branch;
56443
+ let probeCommonDir;
56444
+ let probeToplevel;
56445
+ try {
56446
+ const [ourRaw, probeRaw] = await Promise.all([
56447
+ this.git.raw(["rev-parse", "--git-common-dir"]),
56448
+ probeGit.raw([
56449
+ "rev-parse",
56450
+ "HEAD",
56451
+ "--abbrev-ref",
56452
+ "HEAD",
56453
+ "--git-common-dir",
56454
+ "--show-toplevel"
56455
+ ])
56456
+ ]);
56457
+ ourCommonDir = path15.resolve(this.sourceRepoPath, ourRaw.trim());
56458
+ const lines = probeRaw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
56459
+ if (lines.length < 4) return null;
56460
+ headCommit = lines[0];
56461
+ branch = lines[1];
56462
+ probeCommonDir = path15.resolve(worktreePath, lines[2]);
56463
+ probeToplevel = path15.resolve(lines[3]);
56464
+ } catch (error) {
56465
+ debugLogger25.debug(
56466
+ `getRegisteredWorktreeBranch: probe at ${worktreePath} failed: ${error}`
56467
+ );
56468
+ return null;
56469
+ }
56470
+ if (probeCommonDir !== ourCommonDir) {
56471
+ debugLogger25.debug(
56472
+ `getRegisteredWorktreeBranch: ${worktreePath} belongs to a different repo (common-dir=${probeCommonDir}, expected ${ourCommonDir})`
56473
+ );
56474
+ return null;
56475
+ }
56476
+ if (probeToplevel !== resolvedWorktreePath) {
56477
+ debugLogger25.debug(
56478
+ `getRegisteredWorktreeBranch: ${worktreePath} is not a registered worktree (toplevel=${probeToplevel}, expected ${resolvedWorktreePath})`
56479
+ );
56480
+ return null;
56481
+ }
56482
+ if (!branch || branch === "HEAD") return null;
56483
+ return { branch, headCommit };
56484
+ }
56485
+ /**
56486
+ * Fetches the GitHub PR ref `refs/pull/<N>/head` from the `origin` remote
56487
+ * so a subsequent `createUserWorktree(..., 'FETCH_HEAD')` call can branch
56488
+ * off the PR's tip (Phase D-3). Returns `{ success: true }` on success,
56489
+ * or `{ success: false, error }` with a user-facing reason on failure.
56490
+ *
56491
+ * Implementation notes:
56492
+ *
56493
+ * - Uses `git fetch origin pull/<N>/head` (no `gh` CLI dependency).
56494
+ * - Hard timeout of 30s by default — overridable for tests. A hung git
56495
+ * process on a misconfigured corporate proxy would otherwise stall
56496
+ * the entire startup sequence.
56497
+ * - Does NOT create a local branch — leaves the ref accessible only
56498
+ * via `FETCH_HEAD`. Subsequent `git worktree add -b <branch> <wt>
56499
+ * FETCH_HEAD` materialises the worktree branch off it.
56500
+ *
56501
+ * Error message taxonomy is friendly because this is the user's first
56502
+ * impression when their `--worktree=#<N>` fails:
56503
+ * - missing `origin` → tell them the remote is required + how to fix
56504
+ * - timeout → mention the configured timeout so they can blame the network
56505
+ * - generic failure → "PR may not exist or origin is unreachable"
56506
+ */
56507
+ async fetchPullRequestRef(prNumber, options2) {
56508
+ if (!Number.isSafeInteger(prNumber) || prNumber <= 0 || prNumber > 1e9) {
56509
+ return {
56510
+ success: false,
56511
+ error: `Invalid PR number: ${prNumber}.`
56512
+ };
56513
+ }
56514
+ const timeoutMs = options2?.timeoutMs ?? 3e4;
56515
+ const prNumberStr = String(prNumber);
56516
+ if (!/^[1-9][0-9]*$/.test(prNumberStr)) {
56517
+ return {
56518
+ success: false,
56519
+ error: `Invalid PR number: ${prNumber}.`
56520
+ };
56521
+ }
56522
+ const refspec = `pull/${prNumberStr}/head`;
56523
+ try {
56524
+ await execFileAsync2(
56525
+ "git",
56526
+ ["fetch", "--end-of-options", "origin", refspec],
56527
+ {
56528
+ cwd: this.sourceRepoPath,
56529
+ timeout: timeoutMs,
56530
+ env: { ...process.env, LANG: "C", LC_ALL: "C" }
56531
+ }
56532
+ );
56533
+ return { success: true };
56534
+ } catch (error) {
56535
+ const err2 = error;
56536
+ const stderr = typeof err2.stderr === "string" ? err2.stderr : err2.stderr instanceof Buffer ? err2.stderr.toString("utf8") : "";
56537
+ const lower = stderr.toLowerCase();
56538
+ if (err2.signal === "SIGTERM") {
56539
+ return {
56540
+ success: false,
56541
+ error: `Failed to fetch PR #${prNumber}: timed out after ${Math.round(timeoutMs / 1e3)}s. Check network connectivity and any HTTP(S) proxy settings.`
56542
+ };
56543
+ }
56544
+ if (lower.includes("does not appear to be a git repository") || lower.includes("could not read from remote repository") || lower.includes("'origin' does not appear")) {
56545
+ return {
56546
+ success: false,
56547
+ error: `--worktree=#${prNumber} requires an "origin" remote that points at GitHub. Add one with \`git remote add origin <url>\` and retry.`
56548
+ };
56549
+ }
56550
+ if (lower.includes("no such ref") || lower.includes("couldn't find remote ref") || lower.includes("couldn't find remote ref pull/")) {
56551
+ return {
56552
+ success: false,
56553
+ error: `Failed to fetch PR #${prNumber}: the PR does not exist on origin, or origin is not a GitHub repository (only GitHub exposes refs/pull/<N>/head).`
56554
+ };
56555
+ }
56556
+ const firstLine = stderr.split("\n").find((l) => l.trim().length > 0);
56557
+ const detail = firstLine ? ` (${firstLine.trim()})` : "";
56558
+ debugLogger25.warn(
56559
+ `fetchPullRequestRef: git fetch pull/${prNumber}/head failed: ${error}`
56560
+ );
56561
+ return {
56562
+ success: false,
56563
+ error: `Failed to fetch PR #${prNumber}: PR may not exist, or origin remote is unreachable${detail}.`
56564
+ };
56565
+ }
56566
+ }
56356
56567
  /**
56357
56568
  * Validates a worktree slug. Returns null on success, or an error message.
56358
56569
  *
@@ -56398,7 +56609,7 @@ var GitWorktreeService = class _GitWorktreeService {
56398
56609
  * silently resetting the branch. The previous `-B` form would have
56399
56610
  * dropped any commits unique to that branch — see review #4073.
56400
56611
  */
56401
- async createUserWorktree(slug, baseBranch) {
56612
+ async createUserWorktree(slug, baseBranch, options2) {
56402
56613
  const validationError = _GitWorktreeService.validateUserWorktreeSlug(slug);
56403
56614
  if (validationError) {
56404
56615
  debugLogger25.warn(
@@ -56437,6 +56648,17 @@ var GitWorktreeService = class _GitWorktreeService {
56437
56648
  `createUserWorktree: failed to configure core.hooksPath for ${slug}: ${error}`
56438
56649
  );
56439
56650
  });
56651
+ const symlinkPaths = options2?.symlinkDirectories ?? [];
56652
+ if (symlinkPaths.length > 0) {
56653
+ await this.symlinkConfiguredDirectories(
56654
+ worktreePath,
56655
+ symlinkPaths
56656
+ ).catch((error) => {
56657
+ debugLogger25.warn(
56658
+ `createUserWorktree: symlinkConfiguredDirectories failed for ${slug}: ${error}`
56659
+ );
56660
+ });
56661
+ }
56440
56662
  const worktree = {
56441
56663
  id: slug,
56442
56664
  name: slug,
@@ -56509,6 +56731,172 @@ var GitWorktreeService = class _GitWorktreeService {
56509
56731
  );
56510
56732
  }
56511
56733
  }
56734
+ /**
56735
+ * Phase D-2 symlink loop. For each configured directory under the main
56736
+ * repository, creates a symbolic link from the new worktree to the
56737
+ * main-repo location (`<worktreePath>/<dir>` → `<repoRoot>/<dir>`).
56738
+ *
56739
+ * Fail-open semantics — the worktree IS already on disk and usable by
56740
+ * the time this runs, so a symlink failure must NOT abort the parent
56741
+ * `createUserWorktree` call. Per-entry failures are logged at debug or
56742
+ * warn level depending on cause:
56743
+ *
56744
+ * - **ENOENT on source** (the main repo does not have the directory):
56745
+ * debug log, skip. Typical for users who configure `node_modules`
56746
+ * but launch from a fresh clone where `npm install` hasn't run yet.
56747
+ * - **EEXIST on destination** (something already lives at the symlink
56748
+ * target inside the worktree): debug log, skip. No overwrite; the
56749
+ * existing content (whether file, dir, or stale link) wins.
56750
+ * - **Absolute path or path traversal in the configured value**:
56751
+ * warn log, skip the entry. Configured values must stay relative to
56752
+ * the repo root to prevent a setting from redirecting writes onto
56753
+ * `/etc`, `~`, or anywhere outside the repo subtree.
56754
+ * - **Other I/O errors**: warn log, continue to the next entry.
56755
+ *
56756
+ * Mirrors claude-code's `symlinkDirectories` helper (utils/worktree.ts).
56757
+ */
56758
+ async symlinkConfiguredDirectories(worktreePath, configured) {
56759
+ let repoRootAbs;
56760
+ try {
56761
+ repoRootAbs = await fs15.realpath(this.sourceRepoPath);
56762
+ } catch {
56763
+ debugLogger25.warn(
56764
+ `symlinkConfiguredDirectories: cannot realpath sourceRepoPath "${this.sourceRepoPath}", skipping all entries`
56765
+ );
56766
+ return;
56767
+ }
56768
+ const gitDirAbs = path15.join(repoRootAbs, ".git");
56769
+ const qwenDirAbs = path15.join(repoRootAbs, ".qwen");
56770
+ const realWorktreePath = await fs15.realpath(worktreePath).catch(() => worktreePath);
56771
+ for (const raw2 of configured) {
56772
+ if (typeof raw2 !== "string" || raw2.length === 0) {
56773
+ debugLogger25.warn(
56774
+ `symlinkConfiguredDirectories: skipping non-string / empty entry: ${JSON.stringify(raw2)}`
56775
+ );
56776
+ continue;
56777
+ }
56778
+ if (path15.isAbsolute(raw2)) {
56779
+ debugLogger25.warn(
56780
+ `symlinkConfiguredDirectories: refusing absolute path "${raw2}"`
56781
+ );
56782
+ continue;
56783
+ }
56784
+ if (raw2.split(/[\\/]/).includes("..")) {
56785
+ debugLogger25.warn(
56786
+ `symlinkConfiguredDirectories: refusing path "${raw2}" \u2014 contains '..' segment`
56787
+ );
56788
+ continue;
56789
+ }
56790
+ const sourceAbs = path15.resolve(repoRootAbs, raw2);
56791
+ if (sourceAbs === repoRootAbs) {
56792
+ debugLogger25.warn(
56793
+ `symlinkConfiguredDirectories: refusing empty / repo-root path "${raw2}"`
56794
+ );
56795
+ continue;
56796
+ }
56797
+ if (!isWithinRoot(sourceAbs, repoRootAbs)) {
56798
+ debugLogger25.warn(
56799
+ `symlinkConfiguredDirectories: refusing path "${raw2}" \u2014 resolves outside repo root (${sourceAbs} vs ${repoRootAbs})`
56800
+ );
56801
+ continue;
56802
+ }
56803
+ if (isWithinRoot(sourceAbs, gitDirAbs)) {
56804
+ debugLogger25.warn(
56805
+ `symlinkConfiguredDirectories: refusing git-internal path "${raw2}"`
56806
+ );
56807
+ continue;
56808
+ }
56809
+ if (isWithinRoot(sourceAbs, qwenDirAbs)) {
56810
+ debugLogger25.warn(
56811
+ `symlinkConfiguredDirectories: refusing path "${raw2}" \u2014 the .qwen tree is CLI-managed; symlinking any of it could create a worktrees-inside-worktrees loop or alias CLI metadata.`
56812
+ );
56813
+ continue;
56814
+ }
56815
+ let sourceStat = null;
56816
+ try {
56817
+ sourceStat = await fs15.stat(sourceAbs);
56818
+ } catch (error) {
56819
+ if (isNodeError(error) && error.code === "ENOENT") {
56820
+ debugLogger25.debug(
56821
+ `symlinkConfiguredDirectories: source missing, skipping: ${sourceAbs}`
56822
+ );
56823
+ } else {
56824
+ debugLogger25.warn(
56825
+ `symlinkConfiguredDirectories: cannot stat ${sourceAbs}: ${error}`
56826
+ );
56827
+ }
56828
+ continue;
56829
+ }
56830
+ let realSource;
56831
+ try {
56832
+ realSource = await fs15.realpath(sourceAbs);
56833
+ } catch (error) {
56834
+ debugLogger25.warn(
56835
+ `symlinkConfiguredDirectories: cannot realpath source "${sourceAbs}": ${error}`
56836
+ );
56837
+ continue;
56838
+ }
56839
+ if (!isWithinRoot(realSource, repoRootAbs)) {
56840
+ debugLogger25.warn(
56841
+ `symlinkConfiguredDirectories: refusing path "${raw2}" \u2014 real source ${realSource} escapes repo root ${repoRootAbs}`
56842
+ );
56843
+ continue;
56844
+ }
56845
+ if (isWithinRoot(realSource, gitDirAbs)) {
56846
+ debugLogger25.warn(
56847
+ `symlinkConfiguredDirectories: refusing path "${raw2}" \u2014 real source ${realSource} resolves inside .git`
56848
+ );
56849
+ continue;
56850
+ }
56851
+ if (isWithinRoot(realSource, qwenDirAbs)) {
56852
+ debugLogger25.warn(
56853
+ `symlinkConfiguredDirectories: refusing path "${raw2}" \u2014 real source ${realSource} resolves inside .qwen`
56854
+ );
56855
+ continue;
56856
+ }
56857
+ const destAbs = path15.join(worktreePath, raw2);
56858
+ try {
56859
+ await fs15.mkdir(path15.dirname(destAbs), { recursive: true });
56860
+ } catch (error) {
56861
+ debugLogger25.warn(
56862
+ `symlinkConfiguredDirectories: cannot mkdir parent of ${destAbs}: ${error}`
56863
+ );
56864
+ continue;
56865
+ }
56866
+ let realDestParent;
56867
+ try {
56868
+ realDestParent = await fs15.realpath(path15.dirname(destAbs));
56869
+ } catch (error) {
56870
+ debugLogger25.warn(
56871
+ `symlinkConfiguredDirectories: cannot realpath dest parent for "${raw2}" (${path15.dirname(destAbs)}): ${error}`
56872
+ );
56873
+ continue;
56874
+ }
56875
+ if (!isWithinRoot(realDestParent, realWorktreePath)) {
56876
+ debugLogger25.warn(
56877
+ `symlinkConfiguredDirectories: refusing path "${raw2}" \u2014 dest parent ${realDestParent} escapes worktree root ${realWorktreePath} (committed-symlink chain)`
56878
+ );
56879
+ continue;
56880
+ }
56881
+ try {
56882
+ const symlinkType = sourceStat.isDirectory() ? process.platform === "win32" ? "junction" : "dir" : "file";
56883
+ await fs15.symlink(realSource, destAbs, symlinkType);
56884
+ debugLogger25.debug(
56885
+ `symlinkConfiguredDirectories: linked ${destAbs} \u2192 ${realSource} (${symlinkType})`
56886
+ );
56887
+ } catch (error) {
56888
+ if (isNodeError(error) && error.code === "EEXIST") {
56889
+ debugLogger25.debug(
56890
+ `symlinkConfiguredDirectories: destination exists, skipping: ${destAbs}`
56891
+ );
56892
+ } else {
56893
+ debugLogger25.warn(
56894
+ `symlinkConfiguredDirectories: failed to link ${destAbs} \u2192 ${realSource}: ${error}`
56895
+ );
56896
+ }
56897
+ }
56898
+ }
56899
+ }
56512
56900
  /**
56513
56901
  * Returns true if a local branch with the given name exists.
56514
56902
  *
@@ -58681,7 +59069,9 @@ var AgentToolInvocation = class extends BaseToolInvocation {
58681
59069
  `[Agent] getCurrentBranch failed at ${projectRoot}: ${error}`
58682
59070
  );
58683
59071
  }
58684
- const created = await wtService.createUserWorktree(slug, parentBranch);
59072
+ const created = await wtService.createUserWorktree(slug, parentBranch, {
59073
+ symlinkDirectories: this.config.getWorktreeSymlinkDirectories()
59074
+ });
58685
59075
  if (!created.success || !created.worktree) {
58686
59076
  return failWorktreeProvisioning(
58687
59077
  `Failed to create isolation worktree: ${created.error ?? "unknown error"}`
@@ -65179,9 +65569,9 @@ function evaluateAwkReadOnly(args2) {
65179
65569
  }
65180
65570
  __name(evaluateAwkReadOnly, "evaluateAwkReadOnly");
65181
65571
  function evaluateStatementReadOnly(node) {
65572
+ if (containsCommandSubstitutionAST(node)) return false;
65182
65573
  switch (node.type) {
65183
65574
  case "command":
65184
- if (containsCommandSubstitutionAST(node)) return false;
65185
65575
  return evaluateCommandReadOnly(node);
65186
65576
  case "pipeline": {
65187
65577
  for (const child of node.namedChildren) {
@@ -69393,12 +69783,12 @@ import { dirname as dirname9, isAbsolute as isAbsolute6, join as join21, relativ
69393
69783
 
69394
69784
  // packages/core/src/utils/gitDiff.ts
69395
69785
  init_esbuild_shims();
69396
- import { execFile as execFile2 } from "node:child_process";
69786
+ import { execFile as execFile3 } from "node:child_process";
69397
69787
  import * as nodeFs from "node:fs";
69398
69788
  import { access as access3, lstat as lstat3, open, readFile as readFile14, stat as stat6 } from "node:fs/promises";
69399
69789
  import * as path31 from "node:path";
69400
- import { promisify as promisify2 } from "node:util";
69401
- var execFileAsync2 = promisify2(execFile2);
69790
+ import { promisify as promisify3 } from "node:util";
69791
+ var execFileAsync3 = promisify3(execFile3);
69402
69792
  var GIT_TIMEOUT_MS = 5e3;
69403
69793
  var MAX_FILES = 50;
69404
69794
  var MAX_DIFF_SIZE_BYTES = 1e6;
@@ -69919,7 +70309,7 @@ __name(mapWithConcurrency, "mapWithConcurrency");
69919
70309
  async function runGit(args2, cwd2) {
69920
70310
  const fullArgs = ["-c", "core.quotepath=false", ...args2];
69921
70311
  try {
69922
- const { stdout } = await execFileAsync2("git", fullArgs, {
70312
+ const { stdout } = await execFileAsync3("git", fullArgs, {
69923
70313
  cwd: cwd2,
69924
70314
  timeout: GIT_TIMEOUT_MS,
69925
70315
  maxBuffer: 64 * 1024 * 1024,
@@ -73430,7 +73820,7 @@ import { posix as posix3, win32 } from "node:path";
73430
73820
  import { fileURLToPath as fileURLToPath3 } from "node:url";
73431
73821
  import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync as readlinkSync2, realpathSync as rps } from "fs";
73432
73822
  import * as actualFS from "node:fs";
73433
- import { lstat as lstat4, readdir as readdir7, readlink as readlink2, realpath as realpath3 } from "node:fs/promises";
73823
+ import { lstat as lstat4, readdir as readdir7, readlink as readlink2, realpath as realpath4 } from "node:fs/promises";
73434
73824
 
73435
73825
  // node_modules/minipass/dist/esm/index.js
73436
73826
  init_esbuild_shims();
@@ -74332,7 +74722,7 @@ var defaultFS = {
74332
74722
  lstat: lstat4,
74333
74723
  readdir: readdir7,
74334
74724
  readlink: readlink2,
74335
- realpath: realpath3
74725
+ realpath: realpath4
74336
74726
  }
74337
74727
  };
74338
74728
  var fsFromOption = /* @__PURE__ */ __name((fsOption) => !fsOption || fsOption === defaultFS || fsOption === actualFS ? defaultFS : {
@@ -78528,11 +78918,11 @@ import { URL as URL3 } from "node:url";
78528
78918
 
78529
78919
  // packages/core/src/utils/secure-browser-launcher.ts
78530
78920
  init_esbuild_shims();
78531
- import { execFile as execFile3 } from "node:child_process";
78532
- import { promisify as promisify3 } from "node:util";
78921
+ import { execFile as execFile4 } from "node:child_process";
78922
+ import { promisify as promisify4 } from "node:util";
78533
78923
  import { platform } from "node:os";
78534
78924
  import { URL as URL2 } from "node:url";
78535
- var execFileAsync3 = promisify3(execFile3);
78925
+ var execFileAsync4 = promisify4(execFile4);
78536
78926
  function validateUrl(url2) {
78537
78927
  let parsedUrl;
78538
78928
  try {
@@ -78592,7 +78982,7 @@ async function openBrowserSecurely(url2) {
78592
78982
  stdio: "ignore"
78593
78983
  };
78594
78984
  try {
78595
- await execFileAsync3(command, args2, options2);
78985
+ await execFileAsync4(command, args2, options2);
78596
78986
  } catch (_error) {
78597
78987
  if ((platformName === "linux" || platformName === "freebsd" || platformName === "openbsd") && command === "xdg-open") {
78598
78988
  const fallbackCommands = [
@@ -78605,7 +78995,7 @@ async function openBrowserSecurely(url2) {
78605
78995
  ];
78606
78996
  for (const fallbackCommand of fallbackCommands) {
78607
78997
  try {
78608
- await execFileAsync3(fallbackCommand, [url2], options2);
78998
+ await execFileAsync4(fallbackCommand, [url2], options2);
78609
78999
  return;
78610
79000
  } catch {
78611
79001
  continue;
@@ -78791,7 +79181,7 @@ var HybridTokenStorage = class extends BaseTokenStorage {
78791
79181
  const forceFileStorage = process.env[FORCE_FILE_STORAGE_ENV_VAR] === "true";
78792
79182
  if (!forceFileStorage) {
78793
79183
  try {
78794
- const { KeychainTokenStorage: KeychainTokenStorage2 } = await import("./keychain-token-storage-QCF76A5D.js");
79184
+ const { KeychainTokenStorage: KeychainTokenStorage2 } = await import("./keychain-token-storage-3552ENXE.js");
78795
79185
  const keychainStorage = new KeychainTokenStorage2(this.serviceName);
78796
79186
  const isAvailable = await keychainStorage.isAvailable();
78797
79187
  if (isAvailable) {
@@ -81074,7 +81464,7 @@ __name(isEnabled, "isEnabled");
81074
81464
  init_esbuild_shims();
81075
81465
  import path39 from "node:path";
81076
81466
  import { fileURLToPath as fileURLToPath6 } from "node:url";
81077
- import { execFile as execFile4 } from "node:child_process";
81467
+ import { execFile as execFile5 } from "node:child_process";
81078
81468
 
81079
81469
  // packages/core/src/utils/bundlePaths.ts
81080
81470
  init_esbuild_shims();
@@ -81219,7 +81609,7 @@ async function runRipgrep(args2, signal) {
81219
81609
  }
81220
81610
  await ensureRipgrepHealthy(selection);
81221
81611
  return new Promise((resolve31) => {
81222
- const child = execFile4(
81612
+ const child = execFile5(
81223
81613
  selection.command,
81224
81614
  args2,
81225
81615
  {
@@ -83163,7 +83553,7 @@ import * as sysPath2 from "path";
83163
83553
 
83164
83554
  // node_modules/readdirp/esm/index.js
83165
83555
  init_esbuild_shims();
83166
- import { stat as stat9, lstat as lstat5, readdir as readdir9, realpath as realpath4 } from "node:fs/promises";
83556
+ import { stat as stat9, lstat as lstat5, readdir as readdir9, realpath as realpath5 } from "node:fs/promises";
83167
83557
  import { Readable } from "node:stream";
83168
83558
  import { resolve as presolve, relative as prelative, join as pjoin, sep as psep } from "node:path";
83169
83559
  var EntryTypes = {
@@ -83342,7 +83732,7 @@ var ReaddirpStream = class extends Readable {
83342
83732
  if (stats && stats.isSymbolicLink()) {
83343
83733
  const full = entry.fullPath;
83344
83734
  try {
83345
- const entryRealPath = await realpath4(full);
83735
+ const entryRealPath = await realpath5(full);
83346
83736
  const entryRealPathStats = await lstat5(entryRealPath);
83347
83737
  if (entryRealPathStats.isFile()) {
83348
83738
  return "file";
@@ -84051,7 +84441,7 @@ var NodeFsHandler = class {
84051
84441
  * @param realpath
84052
84442
  * @returns closer for the watcher instance.
84053
84443
  */
84054
- async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath6) {
84444
+ async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath7) {
84055
84445
  const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir));
84056
84446
  const tracked = parentDir.has(sysPath.basename(dir));
84057
84447
  if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
@@ -84062,7 +84452,7 @@ var NodeFsHandler = class {
84062
84452
  let throttler;
84063
84453
  let closer;
84064
84454
  const oDepth = this.fsw.options.depth;
84065
- if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath6)) {
84455
+ if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath7)) {
84066
84456
  if (!target) {
84067
84457
  await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
84068
84458
  if (this.fsw.closed)
@@ -87847,9 +88237,8 @@ var PermissionManager = class _PermissionManager {
87847
88237
  *
87848
88238
  * When a sub-command returns 'default' (no rule matches), it is resolved to
87849
88239
  * the actual default permission using AST analysis:
87850
- * - Command substitution detected → 'deny'
87851
88240
  * - Read-only command (cd, ls, git status, etc.) → 'allow'
87852
- * - Otherwise → 'ask'
88241
+ * - Otherwise (including command substitution) → 'ask'
87853
88242
  *
87854
88243
  * Example: with rules `allow: [git checkout *]`
87855
88244
  * - "cd /path && git checkout -b feature" → allow (cd) + allow (rule) → allow
@@ -87883,19 +88272,26 @@ var PermissionManager = class _PermissionManager {
87883
88272
  * Resolve 'default' permission to actual permission using AST analysis.
87884
88273
  * This mirrors the logic in ShellToolInvocation.getDefaultPermission().
87885
88274
  *
88275
+ * Command substitution ($(), ``, <(), >()) is NOT a hard deny here — it
88276
+ * falls through to 'ask' along with every other non-read-only command, so
88277
+ * the user (or YOLO mode) can decide. The user-facing warning is surfaced
88278
+ * by ShellToolInvocation.getConfirmationDetails so the confirmation prompt
88279
+ * still flags the substitution clearly. See issue #4093 for why a hard
88280
+ * deny here is wrong: it (a) cannot be overridden by YOLO mode and (b)
88281
+ * fires inconsistently based on whether the PermissionManager has
88282
+ * "relevant" rules for the surrounding compound command.
88283
+ *
87886
88284
  * @param command - The shell command to analyze.
87887
- * @returns 'deny' for command substitution, 'allow' for read-only, 'ask' otherwise.
88285
+ * @returns 'allow' for read-only, 'ask' otherwise.
87888
88286
  */
87889
88287
  async resolveDefaultPermission(command) {
87890
- if (detectCommandSubstitution(command)) {
87891
- return "deny";
87892
- }
87893
88288
  try {
87894
88289
  const isReadOnly = await isShellCommandReadOnlyAST(command);
87895
88290
  if (isReadOnly) {
87896
88291
  return "allow";
87897
88292
  }
87898
- } catch {
88293
+ } catch (e) {
88294
+ debugLogger60.warn("AST read-only check failed, falling back to ask:", e);
87899
88295
  }
87900
88296
  return "ask";
87901
88297
  }
@@ -94342,9 +94738,9 @@ var SymlinkError = class extends Error {
94342
94738
  symlink;
94343
94739
  syscall = "symlink";
94344
94740
  code = "TAR_SYMLINK_ERROR";
94345
- constructor(symlink, path84) {
94741
+ constructor(symlink2, path84) {
94346
94742
  super("TAR_SYMLINK_ERROR: Cannot extract through symbolic link");
94347
- this.symlink = symlink;
94743
+ this.symlink = symlink2;
94348
94744
  this.path = path84;
94349
94745
  }
94350
94746
  get name() {
@@ -105544,6 +105940,20 @@ var Config = class {
105544
105940
  }
105545
105941
  sessionId;
105546
105942
  sessionData;
105943
+ /**
105944
+ * One-shot notice produced by `setupStartupWorktree` (Phase D-1) when the
105945
+ * CLI was launched with `--worktree`. The active entry point (TUI XOR
105946
+ * headless) reads it via {@link consumePendingStartupWorktreeNotice} on
105947
+ * the model's first prompt and skips Phase C's `restoreWorktreeContext`
105948
+ * for that turn — startup wins over the resumed-session sidecar. ACP is
105949
+ * gated out earlier in `gemini.tsx` (mutex with `--worktree`) so it
105950
+ * never reaches this slot.
105951
+ *
105952
+ * @invariant At most one consumer per process. If a future entry path
105953
+ * sets this slot without ever consuming, the string persists until
105954
+ * process exit (which dies with the process — no leak).
105955
+ */
105956
+ pendingStartupWorktreeNotice = null;
105547
105957
  debugLogger;
105548
105958
  toolRegistry;
105549
105959
  /**
@@ -105670,6 +106080,7 @@ var Config = class {
105670
106080
  arenaManagerChangeCallback = null;
105671
106081
  arenaAgentClient;
105672
106082
  agentsSettings;
106083
+ worktreeSettings;
105673
106084
  skipLoopDetection;
105674
106085
  skipStartupContext;
105675
106086
  bareMode;
@@ -105859,6 +106270,7 @@ var Config = class {
105859
106270
  this.eventEmitter = params.eventEmitter;
105860
106271
  this.arenaAgentClient = ArenaAgentClient.create();
105861
106272
  this.agentsSettings = params.agents ?? {};
106273
+ this.worktreeSettings = params.worktree ?? {};
105862
106274
  if (params.contextFileName) {
105863
106275
  setGeminiMdFilename(params.contextFileName);
105864
106276
  }
@@ -105888,8 +106300,8 @@ var Config = class {
105888
106300
  isWorkspaceTrusted: this.isTrustedFolder()
105889
106301
  });
105890
106302
  this.enableManagedAutoMemory = params.enableManagedAutoMemory ?? true;
105891
- this.enableManagedAutoDream = params.enableManagedAutoDream ?? false;
105892
- this.enableAutoSkill = params.enableAutoSkill ?? false;
106303
+ this.enableManagedAutoDream = params.enableManagedAutoDream ?? true;
106304
+ this.enableAutoSkill = params.enableAutoSkill ?? true;
105893
106305
  this.fastModel = params.fastModel || void 0;
105894
106306
  this.disableAllHooks = params.disableAllHooks ?? false;
105895
106307
  this.stopHookBlockingCap = resolveStopHookBlockingCap(
@@ -106645,6 +107057,27 @@ var Config = class {
106645
107057
  getTargetDir() {
106646
107058
  return this.targetDir;
106647
107059
  }
107060
+ /**
107061
+ * Stashes a one-shot context message that the next user prompt will
107062
+ * inject into the model (see {@link pendingStartupWorktreeNotice}). Called
107063
+ * from `gemini.tsx` right after `loadCliConfig` when `--worktree` produced
107064
+ * a valid worktree. Pass `null` to clear (rarely needed).
107065
+ */
107066
+ setPendingStartupWorktreeNotice(notice) {
107067
+ this.pendingStartupWorktreeNotice = notice;
107068
+ }
107069
+ /**
107070
+ * Reads and clears the pending startup-worktree notice. Returns `null`
107071
+ * when nothing is stashed (the common case). Each entry point (TUI /
107072
+ * headless / ACP) calls this on the model's first prompt; a non-null
107073
+ * return means the entry point should NOT additionally call
107074
+ * `restoreWorktreeContext()` for that prompt — startup overrides resume.
107075
+ */
107076
+ consumePendingStartupWorktreeNotice() {
107077
+ const v = this.pendingStartupWorktreeNotice;
107078
+ this.pendingStartupWorktreeNotice = null;
107079
+ return v;
107080
+ }
106648
107081
  getProjectRoot() {
106649
107082
  return this.targetDir;
106650
107083
  }
@@ -106926,6 +107359,17 @@ var Config = class {
106926
107359
  getAgentsSettings() {
106927
107360
  return this.agentsSettings;
106928
107361
  }
107362
+ /**
107363
+ * Convenience accessor for `worktree.symlinkDirectories` — returns an
107364
+ * empty array when the setting is unset, so callers can pass the
107365
+ * result directly into the GitWorktreeService loop without nullchecks.
107366
+ *
107367
+ * (No general `getWorktreeSettings()` getter yet — add one when a
107368
+ * second field on `WorktreeSettings` justifies the broader API.)
107369
+ */
107370
+ getWorktreeSymlinkDirectories() {
107371
+ return this.worktreeSettings.symlinkDirectories ?? [];
107372
+ }
106929
107373
  /**
106930
107374
  * Clean up Arena runtime. When `force` is true (e.g., /arena select --discard),
106931
107375
  * always removes worktrees regardless of preserveArtifacts.
@@ -107765,25 +108209,25 @@ var Config = class {
107765
108209
  if (options2?.forSubAgent) return;
107766
108210
  const schema = this.jsonSchema;
107767
108211
  await registerLazy(ToolNames.STRUCTURED_OUTPUT, async () => {
107768
- const { SyntheticOutputTool } = await import("./syntheticOutput-6N4MZYQT.js");
108212
+ const { SyntheticOutputTool } = await import("./syntheticOutput-AKTXC6FR.js");
107769
108213
  return new SyntheticOutputTool(schema);
107770
108214
  });
107771
108215
  }, "registerStructuredOutputIfRequested");
107772
108216
  if (this.getBareMode()) {
107773
108217
  await registerLazy(ToolNames.READ_FILE, async () => {
107774
- const { ReadFileTool } = await import("./read-file-5QUJ3ZIL.js");
108218
+ const { ReadFileTool } = await import("./read-file-J7DH4OKV.js");
107775
108219
  return new ReadFileTool(this);
107776
108220
  });
107777
108221
  await registerLazy(ToolNames.EDIT, async () => {
107778
- const { EditTool } = await import("./edit-2CY35GKJ.js");
108222
+ const { EditTool } = await import("./edit-4LLGNYVZ.js");
107779
108223
  return new EditTool(this);
107780
108224
  });
107781
108225
  await registerLazy(ToolNames.NOTEBOOK_EDIT, async () => {
107782
- const { NotebookEditTool } = await import("./notebook-edit-RWOEUTYU.js");
108226
+ const { NotebookEditTool } = await import("./notebook-edit-P4QVLW6I.js");
107783
108227
  return new NotebookEditTool(this);
107784
108228
  });
107785
108229
  await registerLazy(ToolNames.SHELL, async () => {
107786
- const { ShellTool: ShellTool2 } = await import("./shell-DA54BZO2.js");
108230
+ const { ShellTool: ShellTool2 } = await import("./shell-ZNTQIRK6.js");
107787
108231
  return new ShellTool2(this);
107788
108232
  });
107789
108233
  await registerStructuredOutputIfRequested();
@@ -107793,31 +108237,31 @@ var Config = class {
107793
108237
  return registry;
107794
108238
  }
107795
108239
  await registerLazy(ToolNames.TOOL_SEARCH, async () => {
107796
- const { ToolSearchTool } = await import("./tool-search-PONJXBSP.js");
108240
+ const { ToolSearchTool } = await import("./tool-search-C2EMLFBJ.js");
107797
108241
  return new ToolSearchTool(this);
107798
108242
  });
107799
108243
  await registerLazy(ToolNames.AGENT, async () => {
107800
- const { AgentTool: AgentTool2 } = await import("./agent-KYFWAFRH.js");
108244
+ const { AgentTool: AgentTool2 } = await import("./agent-RY5EB3XR.js");
107801
108245
  return new AgentTool2(this);
107802
108246
  });
107803
108247
  await registerLazy(ToolNames.TASK_STOP, async () => {
107804
- const { TaskStopTool } = await import("./task-stop-TTT4O7JR.js");
108248
+ const { TaskStopTool } = await import("./task-stop-2NYFR2ES.js");
107805
108249
  return new TaskStopTool(this);
107806
108250
  });
107807
108251
  await registerLazy(ToolNames.SEND_MESSAGE, async () => {
107808
- const { SendMessageTool } = await import("./send-message-GDN76EHU.js");
108252
+ const { SendMessageTool } = await import("./send-message-JUFP62VD.js");
107809
108253
  return new SendMessageTool(this);
107810
108254
  });
107811
108255
  await registerLazy(ToolNames.SKILL, async () => {
107812
- const { SkillTool } = await import("./skill-47FZXDEM.js");
108256
+ const { SkillTool } = await import("./skill-CFCUIY23.js");
107813
108257
  return new SkillTool(this);
107814
108258
  });
107815
108259
  await registerLazy(ToolNames.LS, async () => {
107816
- const { LSTool } = await import("./ls-QZTZYURK.js");
108260
+ const { LSTool } = await import("./ls-MYXAM7LJ.js");
107817
108261
  return new LSTool(this);
107818
108262
  });
107819
108263
  await registerLazy(ToolNames.READ_FILE, async () => {
107820
- const { ReadFileTool } = await import("./read-file-5QUJ3ZIL.js");
108264
+ const { ReadFileTool } = await import("./read-file-J7DH4OKV.js");
107821
108265
  return new ReadFileTool(this);
107822
108266
  });
107823
108267
  if (this.getUseRipgrep()) {
@@ -107830,7 +108274,7 @@ var Config = class {
107830
108274
  }
107831
108275
  if (useRipgrep) {
107832
108276
  await registerLazy(ToolNames.GREP, async () => {
107833
- const { RipGrepTool: RipGrepTool2 } = await import("./ripGrep-PA7M7MJS.js");
108277
+ const { RipGrepTool: RipGrepTool2 } = await import("./ripGrep-33DECY4F.js");
107834
108278
  return new RipGrepTool2(this);
107835
108279
  });
107836
108280
  } else {
@@ -107843,85 +108287,85 @@ var Config = class {
107843
108287
  )
107844
108288
  );
107845
108289
  await registerLazy(ToolNames.GREP, async () => {
107846
- const { GrepTool } = await import("./grep-GKU7Y77F.js");
108290
+ const { GrepTool } = await import("./grep-2UUPSSIQ.js");
107847
108291
  return new GrepTool(this);
107848
108292
  });
107849
108293
  }
107850
108294
  } else {
107851
108295
  await registerLazy(ToolNames.GREP, async () => {
107852
- const { GrepTool } = await import("./grep-GKU7Y77F.js");
108296
+ const { GrepTool } = await import("./grep-2UUPSSIQ.js");
107853
108297
  return new GrepTool(this);
107854
108298
  });
107855
108299
  }
107856
108300
  await registerLazy(ToolNames.GLOB, async () => {
107857
- const { GlobTool } = await import("./glob-C5ZNGH7V.js");
108301
+ const { GlobTool } = await import("./glob-6X6OCEWE.js");
107858
108302
  return new GlobTool(this);
107859
108303
  });
107860
108304
  await registerLazy(ToolNames.EDIT, async () => {
107861
- const { EditTool } = await import("./edit-2CY35GKJ.js");
108305
+ const { EditTool } = await import("./edit-4LLGNYVZ.js");
107862
108306
  return new EditTool(this);
107863
108307
  });
107864
108308
  await registerLazy(ToolNames.NOTEBOOK_EDIT, async () => {
107865
- const { NotebookEditTool } = await import("./notebook-edit-RWOEUTYU.js");
108309
+ const { NotebookEditTool } = await import("./notebook-edit-P4QVLW6I.js");
107866
108310
  return new NotebookEditTool(this);
107867
108311
  });
107868
108312
  await registerLazy(ToolNames.WRITE_FILE, async () => {
107869
- const { WriteFileTool } = await import("./write-file-2YX4NB3B.js");
108313
+ const { WriteFileTool } = await import("./write-file-EEPVRS4Q.js");
107870
108314
  return new WriteFileTool(this);
107871
108315
  });
107872
108316
  await registerLazy(ToolNames.SHELL, async () => {
107873
- const { ShellTool: ShellTool2 } = await import("./shell-DA54BZO2.js");
108317
+ const { ShellTool: ShellTool2 } = await import("./shell-ZNTQIRK6.js");
107874
108318
  return new ShellTool2(this);
107875
108319
  });
107876
108320
  await registerLazy(ToolNames.TODO_WRITE, async () => {
107877
- const { TodoWriteTool } = await import("./todoWrite-GYUYNLKE.js");
108321
+ const { TodoWriteTool } = await import("./todoWrite-WHZ2O2XP.js");
107878
108322
  return new TodoWriteTool(this);
107879
108323
  });
107880
108324
  await registerLazy(ToolNames.ASK_USER_QUESTION, async () => {
107881
- const { AskUserQuestionTool } = await import("./askUserQuestion-KFDJMM66.js");
108325
+ const { AskUserQuestionTool } = await import("./askUserQuestion-R3MKD2JT.js");
107882
108326
  return new AskUserQuestionTool(this);
107883
108327
  });
107884
108328
  if (!this.sdkMode) {
107885
108329
  await registerLazy(ToolNames.EXIT_PLAN_MODE, async () => {
107886
- const { ExitPlanModeTool } = await import("./exitPlanMode-3JP3BRRO.js");
108330
+ const { ExitPlanModeTool } = await import("./exitPlanMode-WD5IH7NS.js");
107887
108331
  return new ExitPlanModeTool(this);
107888
108332
  });
107889
108333
  }
107890
108334
  await registerLazy(ToolNames.ENTER_WORKTREE, async () => {
107891
- const { EnterWorktreeTool } = await import("./enter-worktree-HJGN4DH2.js");
108335
+ const { EnterWorktreeTool } = await import("./enter-worktree-E2R5XAFT.js");
107892
108336
  return new EnterWorktreeTool(this);
107893
108337
  });
107894
108338
  await registerLazy(ToolNames.EXIT_WORKTREE, async () => {
107895
- const { ExitWorktreeTool } = await import("./exit-worktree-VAPX5N2E.js");
108339
+ const { ExitWorktreeTool } = await import("./exit-worktree-YVBYYYDD.js");
107896
108340
  return new ExitWorktreeTool(this);
107897
108341
  });
107898
108342
  await registerLazy(ToolNames.WEB_FETCH, async () => {
107899
- const { WebFetchTool } = await import("./web-fetch-V4TWMRQD.js");
108343
+ const { WebFetchTool } = await import("./web-fetch-S6MZXPZ5.js");
107900
108344
  return new WebFetchTool(this);
107901
108345
  });
107902
108346
  if (this.isLspEnabled() && this.getLspClient()) {
107903
108347
  await registerLazy(ToolNames.LSP, async () => {
107904
- const { LspTool } = await import("./lsp-L6NQBEQA.js");
108348
+ const { LspTool } = await import("./lsp-PFGI35JL.js");
107905
108349
  return new LspTool(this);
107906
108350
  });
107907
108351
  }
107908
108352
  await registerStructuredOutputIfRequested();
107909
108353
  if (this.isCronEnabled()) {
107910
108354
  await registerLazy(ToolNames.CRON_CREATE, async () => {
107911
- const { CronCreateTool } = await import("./cron-create-GJT7LK4I.js");
108355
+ const { CronCreateTool } = await import("./cron-create-BTEOGHPH.js");
107912
108356
  return new CronCreateTool(this);
107913
108357
  });
107914
108358
  await registerLazy(ToolNames.CRON_LIST, async () => {
107915
- const { CronListTool } = await import("./cron-list-P42BOGWK.js");
108359
+ const { CronListTool } = await import("./cron-list-SV6QRZW2.js");
107916
108360
  return new CronListTool(this);
107917
108361
  });
107918
108362
  await registerLazy(ToolNames.CRON_DELETE, async () => {
107919
- const { CronDeleteTool } = await import("./cron-delete-PWUIASTZ.js");
108363
+ const { CronDeleteTool } = await import("./cron-delete-56CEWELN.js");
107920
108364
  return new CronDeleteTool(this);
107921
108365
  });
107922
108366
  }
107923
108367
  await registerLazy(ToolNames.MONITOR, async () => {
107924
- const { MonitorTool } = await import("./monitor-HJTGGH2N.js");
108368
+ const { MonitorTool } = await import("./monitor-VUHPEGUW.js");
107925
108369
  return new MonitorTool(this);
107926
108370
  });
107927
108371
  if (this.pendingMcpBudgetCallback) {
@@ -117323,9 +117767,9 @@ var formatMemoryUsage = /* @__PURE__ */ __name((bytes) => {
117323
117767
  // packages/core/src/utils/memoryDiagnostics.ts
117324
117768
  init_esbuild_shims();
117325
117769
  import { readdir as readdir15, readFile as readFile26 } from "node:fs/promises";
117326
- import { execFile as execFile5 } from "node:child_process";
117770
+ import { execFile as execFile6 } from "node:child_process";
117327
117771
  import process7 from "node:process";
117328
- import { promisify as promisify4 } from "node:util";
117772
+ import { promisify as promisify5 } from "node:util";
117329
117773
  import v8 from "node:v8";
117330
117774
  var RSS_HEAP_GAP_RATIO = 10;
117331
117775
  var RSS_HEAP_GAP_MIN_BYTES = 256 * 1024 * 1024;
@@ -117334,7 +117778,7 @@ var ACTIVE_HANDLES_THRESHOLD = 256;
117334
117778
  var ACTIVE_REQUESTS_THRESHOLD = 100;
117335
117779
  var OPEN_FD_THRESHOLD = 500;
117336
117780
  var debugLogger91 = createDebugLogger("MEMORY_DIAGNOSTICS");
117337
- var execFileAsync4 = promisify4(execFile5);
117781
+ var execFileAsync5 = promisify5(execFile6);
117338
117782
  async function collectMemoryDiagnostics(options2 = {}) {
117339
117783
  const now = options2.now ?? (() => /* @__PURE__ */ new Date());
117340
117784
  const platform8 = options2.platform ?? process7.platform;
@@ -117463,7 +117907,7 @@ async function collectProcessTreeMemoryUsage(platform8) {
117463
117907
  if (platform8 === "win32") {
117464
117908
  throw new Error("process tree RSS probe is unavailable on win32");
117465
117909
  }
117466
- const { stdout } = await execFileAsync4("ps", ["-axo", "pid=,ppid=,rss="], {
117910
+ const { stdout } = await execFileAsync5("ps", ["-axo", "pid=,ppid=,rss="], {
117467
117911
  maxBuffer: 1024 * 1024,
117468
117912
  timeout: 5e3
117469
117913
  });
@@ -118816,7 +119260,7 @@ __name(doesToolInvocationMatch, "doesToolInvocationMatch");
118816
119260
 
118817
119261
  // packages/core/src/utils/shell-utils.ts
118818
119262
  import {
118819
- execFile as execFile6,
119263
+ execFile as execFile7,
118820
119264
  execFileSync
118821
119265
  } from "node:child_process";
118822
119266
  import { accessSync, constants as fsConstants } from "node:fs";
@@ -119621,6 +120065,21 @@ function detectCommandSubstitution(command) {
119621
120065
  return false;
119622
120066
  }
119623
120067
  __name(detectCommandSubstitution, "detectCommandSubstitution");
120068
+ var COMMAND_SUBSTITUTION_WARNING = "Contains command substitution ($(...), backticks, <(...), or >(...)).";
120069
+ function hasShellSubstitution(rawCommand) {
120070
+ if (typeof rawCommand !== "string" || rawCommand.length === 0) return false;
120071
+ if (detectCommandSubstitution(rawCommand)) return true;
120072
+ const stripped = stripShellWrapper(rawCommand);
120073
+ return stripped !== rawCommand && detectCommandSubstitution(stripped);
120074
+ }
120075
+ __name(hasShellSubstitution, "hasShellSubstitution");
120076
+ function buildShellExecWarnings(strippedCommand, rawCommand) {
120077
+ if (hasShellSubstitution(rawCommand) || detectCommandSubstitution(strippedCommand)) {
120078
+ return [COMMAND_SUBSTITUTION_WARNING];
120079
+ }
120080
+ return void 0;
120081
+ }
120082
+ __name(buildShellExecWarnings, "buildShellExecWarnings");
119624
120083
  async function checkCommandPermissions(command, config, sessionAllowlist) {
119625
120084
  if (detectCommandSubstitution(command)) {
119626
120085
  return {
@@ -119772,7 +120231,7 @@ async function checkCommandPermissions(command, config, sessionAllowlist) {
119772
120231
  __name(checkCommandPermissions, "checkCommandPermissions");
119773
120232
  function execCommand(command, args2, options2 = {}) {
119774
120233
  return new Promise((resolve31, reject) => {
119775
- const child = execFile6(
120234
+ const child = execFile7(
119776
120235
  command,
119777
120236
  args2,
119778
120237
  { encoding: "utf8", ...options2 },
@@ -121720,10 +122179,15 @@ var ShellToolInvocation = class extends BaseToolInvocation {
121720
122179
  }
121721
122180
  /**
121722
122181
  * AST-based permission check for the shell command.
122182
+ * - Substitution-bearing commands (any form, including inside an
122183
+ * env-prefix wrapper that `stripShellWrapper` would discard) → 'ask'
121723
122184
  * - Read-only commands (via AST analysis) → 'allow'
121724
122185
  * - All other commands → 'ask'
121725
122186
  */
121726
122187
  async getDefaultPermission() {
122188
+ if (hasShellSubstitution(this.params.command)) {
122189
+ return "ask";
122190
+ }
121727
122191
  const command = stripShellWrapper(this.params.command);
121728
122192
  try {
121729
122193
  const isReadOnly = await isShellCommandReadOnlyAST(command);
@@ -121784,6 +122248,7 @@ var ShellToolInvocation = class extends BaseToolInvocation {
121784
122248
  } catch (e) {
121785
122249
  debugLogger93.warn("Failed to extract command rules:", e);
121786
122250
  }
122251
+ const warnings = buildShellExecWarnings(command, this.params.command);
121787
122252
  const confirmationDetails = {
121788
122253
  type: "exec",
121789
122254
  title: "Confirm Shell Command",
@@ -121793,6 +122258,9 @@ var ShellToolInvocation = class extends BaseToolInvocation {
121793
122258
  onConfirm: /* @__PURE__ */ __name(async (_outcome, _payload) => {
121794
122259
  }, "onConfirm")
121795
122260
  };
122261
+ if (warnings) {
122262
+ confirmationDetails.warnings = warnings;
122263
+ }
121796
122264
  return confirmationDetails;
121797
122265
  }
121798
122266
  async execute(signal, updateOutput, shellExecutionConfig, setPidCallback, setPromoteAbortControllerCallback) {
@@ -123464,6 +123932,9 @@ export {
123464
123932
  normalizeMonitorCommand,
123465
123933
  hasUnsafeMonitorBackgroundOperator,
123466
123934
  detectCommandSubstitution,
123935
+ COMMAND_SUBSTITUTION_WARNING,
123936
+ hasShellSubstitution,
123937
+ buildShellExecWarnings,
123467
123938
  checkCommandPermissions,
123468
123939
  execCommand,
123469
123940
  resolveCommandPath,