tinker-agent 1.0.65 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +6 -0
  4. package/README.md +90 -9
  5. package/bin/tinker.js +39 -0
  6. package/node_modules/ansi-regex/index.d.ts +33 -0
  7. package/node_modules/ansi-regex/index.js +14 -0
  8. package/node_modules/ansi-regex/license +9 -0
  9. package/node_modules/ansi-regex/package.json +61 -0
  10. package/node_modules/ansi-regex/readme.md +66 -0
  11. package/node_modules/ansi-styles/index.d.ts +236 -0
  12. package/node_modules/ansi-styles/index.js +223 -0
  13. package/node_modules/ansi-styles/license +9 -0
  14. package/node_modules/ansi-styles/package.json +54 -0
  15. package/node_modules/ansi-styles/readme.md +173 -0
  16. package/node_modules/chalk/license +9 -0
  17. package/node_modules/chalk/package.json +83 -0
  18. package/node_modules/chalk/readme.md +297 -0
  19. package/node_modules/chalk/source/index.d.ts +325 -0
  20. package/node_modules/chalk/source/index.js +225 -0
  21. package/node_modules/chalk/source/utilities.js +33 -0
  22. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  23. package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  24. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  25. package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  26. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  27. package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  28. package/node_modules/character-entities/index.d.ts +6 -0
  29. package/node_modules/character-entities/index.js +2132 -0
  30. package/node_modules/character-entities/license +22 -0
  31. package/node_modules/character-entities/package.json +78 -0
  32. package/node_modules/character-entities/readme.md +152 -0
  33. package/node_modules/decode-named-character-reference/index.d.ts +13 -0
  34. package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
  35. package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
  36. package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
  37. package/node_modules/decode-named-character-reference/index.dom.js +32 -0
  38. package/node_modules/decode-named-character-reference/index.js +19 -0
  39. package/node_modules/decode-named-character-reference/license +22 -0
  40. package/node_modules/decode-named-character-reference/package.json +90 -0
  41. package/node_modules/decode-named-character-reference/readme.md +136 -0
  42. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  43. package/node_modules/get-east-asian-width/index.js +30 -0
  44. package/node_modules/get-east-asian-width/license +9 -0
  45. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  46. package/node_modules/get-east-asian-width/lookup.js +138 -0
  47. package/node_modules/get-east-asian-width/package.json +71 -0
  48. package/node_modules/get-east-asian-width/readme.md +65 -0
  49. package/node_modules/get-east-asian-width/utilities.js +24 -0
  50. package/node_modules/has-flag/index.d.ts +39 -0
  51. package/node_modules/has-flag/index.js +8 -0
  52. package/node_modules/has-flag/license +9 -0
  53. package/node_modules/has-flag/package.json +49 -0
  54. package/node_modules/has-flag/readme.md +74 -0
  55. package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
  56. package/node_modules/is-fullwidth-code-point/index.js +12 -0
  57. package/node_modules/is-fullwidth-code-point/license +9 -0
  58. package/node_modules/is-fullwidth-code-point/package.json +53 -0
  59. package/node_modules/is-fullwidth-code-point/readme.md +31 -0
  60. package/node_modules/markdansi/LICENSE +21 -0
  61. package/node_modules/markdansi/README.md +167 -0
  62. package/node_modules/markdansi/dist/ast.d.ts +99 -0
  63. package/node_modules/markdansi/dist/ast.js +1 -0
  64. package/node_modules/markdansi/dist/cli.d.ts +17 -0
  65. package/node_modules/markdansi/dist/cli.js +208 -0
  66. package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
  67. package/node_modules/markdansi/dist/hyperlink.js +26 -0
  68. package/node_modules/markdansi/dist/index.d.ts +10 -0
  69. package/node_modules/markdansi/dist/index.js +16 -0
  70. package/node_modules/markdansi/dist/parser.d.ts +2 -0
  71. package/node_modules/markdansi/dist/parser.js +205 -0
  72. package/node_modules/markdansi/dist/render.d.ts +9 -0
  73. package/node_modules/markdansi/dist/render.js +680 -0
  74. package/node_modules/markdansi/dist/stream.d.ts +39 -0
  75. package/node_modules/markdansi/dist/stream.js +192 -0
  76. package/node_modules/markdansi/dist/theme.d.ts +18 -0
  77. package/node_modules/markdansi/dist/theme.js +105 -0
  78. package/node_modules/markdansi/dist/types.d.ts +58 -0
  79. package/node_modules/markdansi/dist/types.js +1 -0
  80. package/node_modules/markdansi/dist/wrap.d.ts +10 -0
  81. package/node_modules/markdansi/dist/wrap.js +73 -0
  82. package/node_modules/markdansi/docs/spec.md +110 -0
  83. package/node_modules/markdansi/package.json +78 -0
  84. package/node_modules/markdansi/tsconfig.json +18 -0
  85. package/node_modules/marked/LICENSE +44 -0
  86. package/node_modules/marked/README.md +115 -0
  87. package/node_modules/marked/bin/main.js +284 -0
  88. package/node_modules/marked/bin/marked.js +16 -0
  89. package/node_modules/marked/lib/marked.d.ts +760 -0
  90. package/node_modules/marked/lib/marked.esm.js +77 -0
  91. package/node_modules/marked/lib/marked.esm.js.map +7 -0
  92. package/node_modules/marked/lib/marked.umd.js +79 -0
  93. package/node_modules/marked/lib/marked.umd.js.map +7 -0
  94. package/node_modules/marked/man/marked.1 +113 -0
  95. package/node_modules/marked/man/marked.1.md +93 -0
  96. package/node_modules/marked/package.json +103 -0
  97. package/node_modules/slice-ansi/index.d.ts +19 -0
  98. package/node_modules/slice-ansi/index.js +317 -0
  99. package/node_modules/slice-ansi/license +10 -0
  100. package/node_modules/slice-ansi/package.json +59 -0
  101. package/node_modules/slice-ansi/readme.md +55 -0
  102. package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
  103. package/node_modules/string-width/index.d.ts +39 -0
  104. package/node_modules/string-width/index.js +203 -0
  105. package/node_modules/string-width/license +9 -0
  106. package/node_modules/string-width/package.json +65 -0
  107. package/node_modules/string-width/readme.md +66 -0
  108. package/node_modules/strip-ansi/index.d.ts +15 -0
  109. package/node_modules/strip-ansi/index.js +19 -0
  110. package/node_modules/strip-ansi/license +9 -0
  111. package/node_modules/strip-ansi/package.json +59 -0
  112. package/node_modules/strip-ansi/readme.md +37 -0
  113. package/node_modules/supports-color/browser.d.ts +1 -0
  114. package/node_modules/supports-color/browser.js +35 -0
  115. package/node_modules/supports-color/index.d.ts +55 -0
  116. package/node_modules/supports-color/index.js +202 -0
  117. package/node_modules/supports-color/license +9 -0
  118. package/node_modules/supports-color/package.json +64 -0
  119. package/node_modules/supports-color/readme.md +75 -0
  120. package/node_modules/supports-hyperlinks/browser.js +10 -0
  121. package/node_modules/supports-hyperlinks/index.d.ts +25 -0
  122. package/node_modules/supports-hyperlinks/index.js +163 -0
  123. package/node_modules/supports-hyperlinks/license +10 -0
  124. package/node_modules/supports-hyperlinks/package.json +47 -0
  125. package/node_modules/supports-hyperlinks/readme.md +45 -0
  126. package/package.json +41 -10
  127. package/patches/markdansi@0.3.2.patch +22 -2
  128. package/src/agent/context-builder.ts +24 -10
  129. package/src/agent/context-meter.ts +89 -5
  130. package/src/agent/loop.ts +107 -10
  131. package/src/agent/runtime-session.ts +1437 -82
  132. package/src/agent/session-ledger.ts +169 -29
  133. package/src/agent/types.ts +12 -1
  134. package/src/agent/user-prompt-projection.ts +62 -0
  135. package/src/cli/config.ts +57 -14
  136. package/src/cli/model-profiles.ts +124 -0
  137. package/src/cli/run-runner.ts +4 -1
  138. package/src/cli/tui-runner.tsx +68 -30
  139. package/src/context/compiled-context-hash.ts +1 -1
  140. package/src/context/compiled-context-validator.ts +92 -15
  141. package/src/context/context-automation-policy.ts +121 -0
  142. package/src/context/context-manager.ts +390 -11
  143. package/src/context/context-policy.ts +8 -0
  144. package/src/context/context-protocol-validator.ts +11 -1
  145. package/src/context/context-revision-compiler.ts +190 -27
  146. package/src/context/context-revision.ts +105 -19
  147. package/src/context/context-surface.ts +329 -0
  148. package/src/context/prefix-retirement-planner.ts +570 -0
  149. package/src/context/protocol-frame.ts +30 -3
  150. package/src/context/recall-retirement-contract.ts +27 -0
  151. package/src/context/swap-planner.ts +34 -10
  152. package/src/events/observation-text-log.ts +3 -3
  153. package/src/events/stdout-event-printer.ts +39 -3
  154. package/src/events/types.ts +172 -35
  155. package/src/ids/runtime-id.ts +6 -0
  156. package/src/ids/uuid-v7.ts +7 -0
  157. package/src/image/image-asset-store.ts +319 -0
  158. package/src/image/image-input-policy.ts +14 -0
  159. package/src/image/image-probe.ts +233 -0
  160. package/src/image/image-types.ts +262 -0
  161. package/src/instructions/project-instructions.ts +4 -1
  162. package/src/mcp/mcp-manager.ts +83 -19
  163. package/src/model/fake-model-client.ts +9 -1
  164. package/src/model/input-token-estimator.ts +25 -0
  165. package/src/model/model-client.ts +60 -0
  166. package/src/model/model-request-preflight.ts +1 -0
  167. package/src/model/moonshot-input-token-estimator.ts +107 -0
  168. package/src/model/openai-chat-mapping.ts +77 -1
  169. package/src/model/openai-chat-model-client.ts +389 -59
  170. package/src/model/openai-chat-stream.ts +272 -0
  171. package/src/model/prompt-prefix-hash.ts +2 -4
  172. package/src/model/token-estimator.ts +4 -0
  173. package/src/observation/observation-builder.ts +29 -0
  174. package/src/session/resume-projection.ts +94 -34
  175. package/src/session/session-catalog.ts +4 -4
  176. package/src/session/session-errors.ts +1 -1
  177. package/src/session/session-history-reader.ts +151 -4
  178. package/src/session/session-last-response-reader.ts +189 -0
  179. package/src/session/session-schema.ts +349 -35
  180. package/src/session/session-store.ts +3266 -255
  181. package/src/session/sqlite-session-ledger.ts +9 -4
  182. package/src/skills/skill-catalog.ts +182 -0
  183. package/src/skills/skill-context.ts +364 -0
  184. package/src/skills/skill-loader.ts +779 -0
  185. package/src/skills/skill-tool.ts +297 -0
  186. package/src/tools/edit.ts +51 -64
  187. package/src/tools/ensure-parent-directory.ts +6 -0
  188. package/src/tools/read.ts +2 -2
  189. package/src/tools/recall.ts +43 -40
  190. package/src/tools/registry.ts +24 -3
  191. package/src/tools/ripgrep.ts +3 -2
  192. package/src/tools/types.ts +43 -4
  193. package/src/tools/write.ts +20 -31
  194. package/src/tui/app.tsx +333 -96
  195. package/src/tui/clipboard.ts +5 -0
  196. package/src/tui/components/mcp-panel.tsx +28 -0
  197. package/src/tui/components/prompt-input.tsx +790 -257
  198. package/src/tui/components/skills-panel.tsx +31 -0
  199. package/src/tui/components/timeline.tsx +30 -1
  200. package/src/tui/event-store.ts +85 -2
  201. package/src/tui/file-mention.ts +234 -0
  202. package/src/tui/line-editor.ts +1 -0
  203. package/src/tui/project-slash-commands.ts +229 -0
  204. package/src/tui/prompt-draft.ts +510 -0
  205. package/src/tui/prompt-history.ts +294 -22
  206. package/src/tui/slash-commands.ts +46 -2
  207. package/src/tui/tui-session-controller.ts +110 -48
  208. package/src/tui/workspace-file-search.ts +94 -0
@@ -19,7 +19,7 @@ import type {
19
19
  SessionDisposeReason,
20
20
  } from "../agent/runtime-session";
21
21
  import type {
22
- ReadSnapshotStore,
22
+ FileSnapshotStore,
23
23
  ToolDefinition,
24
24
  ToolExecutionContext,
25
25
  ToolExecutor,
@@ -28,6 +28,9 @@ import type {
28
28
  import type { ToolCall } from "../agent/types";
29
29
  import type { SessionHistoryReader } from "../session/session-history-reader";
30
30
  import { ToolExecutionFatalError } from "./types";
31
+ import type { SkillCatalogSnapshot } from "../skills/skill-loader";
32
+ import type { SkillActivationCoordinator } from "../skills/skill-context";
33
+ import { createSkillToolExecutor } from "../skills/skill-tool";
31
34
 
32
35
  export class ToolRegistry {
33
36
  private readonly tools = new Map<string, ToolExecutor>();
@@ -103,7 +106,7 @@ export class ToolRuntime {
103
106
  export type DefaultTooling = {
104
107
  registry: ToolRegistry;
105
108
  runtime: ToolRuntime;
106
- snapshots: ReadSnapshotStore;
109
+ snapshots: FileSnapshotStore;
107
110
  bashState: BashToolingState;
108
111
  taskManager: ShellTaskManager;
109
112
  dispose(reason?: SessionDisposeReason["type"]): Promise<void>;
@@ -123,8 +126,10 @@ export function createDefaultTooling(options: {
123
126
  exaApiKey?: string;
124
127
  webFetchRefiner?: Refiner;
125
128
  taskStopGraceMs?: number;
129
+ skillCatalog?: SkillCatalogSnapshot;
130
+ skillCoordinator?: SkillActivationCoordinator;
126
131
  }): DefaultTooling {
127
- const snapshots: ReadSnapshotStore = new Map();
132
+ const snapshots: FileSnapshotStore = new Map();
128
133
  const registry = new ToolRegistry();
129
134
  const runtimeSession = options.runtimeSession;
130
135
  const cwdState = createCwdState(options.workspaceRoot);
@@ -154,6 +159,22 @@ export function createDefaultTooling(options: {
154
159
  }),
155
160
  );
156
161
  registry.register(createRecallToolExecutor({ historyReader: options.historyReader }));
162
+ if (options.skillCatalog !== undefined) {
163
+ if (options.skillCatalog.skills.size === 0) {
164
+ throw new Error("An empty Agent Skill catalog must not register tooling.");
165
+ }
166
+ if (options.skillCoordinator === undefined) {
167
+ throw new Error("Agent Skill tooling requires an activation coordinator.");
168
+ }
169
+ registry.register(
170
+ createSkillToolExecutor({
171
+ catalog: options.skillCatalog,
172
+ coordinator: options.skillCoordinator,
173
+ }),
174
+ );
175
+ } else if (options.skillCoordinator !== undefined) {
176
+ throw new Error("Agent Skill coordinator was provided without a catalog.");
177
+ }
157
178
  registry.register(
158
179
  createWriteToolExecutor({
159
180
  workspaceRoot: options.workspaceRoot,
@@ -1,8 +1,9 @@
1
1
  import { execFile } from "node:child_process";
2
+ import { rgPath } from "@vscode/ripgrep";
2
3
  import { cancellationError, throwIfTurnCancelled } from "../agent/turn-cancellation";
3
4
 
4
5
  export const RIPGREP_MISSING_ERROR =
5
- "ripgrep is required. Install rg and ensure it is available on PATH.";
6
+ "Tinker's bundled ripgrep executable is unavailable. Reinstall tinker-agent.";
6
7
 
7
8
  const defaultTimeoutMs = 20_000;
8
9
  const defaultMaxBufferBytes = 20_000_000;
@@ -22,7 +23,7 @@ export type RipgrepOptions = {
22
23
  };
23
24
 
24
25
  export function findRipgrepCommand(): string {
25
- return process.env.TINKER_RIPGREP_PATH ?? "rg";
26
+ return process.env.TINKER_RIPGREP_PATH ?? rgPath;
26
27
  }
27
28
 
28
29
  export async function ripGrep(
@@ -6,6 +6,7 @@ import type {
6
6
  RecallSearchPage,
7
7
  } from "../session/session-history-reader";
8
8
  import type { ShellTaskSnapshot, ShellTaskStatus } from "./bash-task";
9
+ import type { SkillScope } from "../skills/skill-loader";
9
10
 
10
11
  export type JsonSchema = Record<string, unknown>;
11
12
 
@@ -49,7 +50,7 @@ export type WriteFileRawResult = {
49
50
  patchTruncated?: boolean;
50
51
  requiredReadBeforeWrite?: boolean;
51
52
  currentSha256?: string;
52
- lastReadSha256?: string;
53
+ lastObservedSha256?: string;
53
54
  error?: string;
54
55
  };
55
56
 
@@ -66,8 +67,8 @@ export type EditFileRawResult = {
66
67
  patch?: DiffHunk[];
67
68
  patchTruncated?: boolean;
68
69
  requiredReadBeforeEdit?: boolean;
69
- currentMtimeMs?: number;
70
- lastObservedMtimeMs?: number;
70
+ currentSha256?: string;
71
+ lastObservedSha256?: string;
71
72
  error?: string;
72
73
  };
73
74
 
@@ -244,6 +245,43 @@ export type RecallGetRawResult =
244
245
 
245
246
  export type RecallRawResult = RecallSearchRawResult | RecallGetRawResult;
246
247
 
248
+ export type SkillRawResult =
249
+ | {
250
+ ok: true;
251
+ status: "loaded";
252
+ name: string;
253
+ scope: SkillScope;
254
+ directory: string;
255
+ skillFilePath: string;
256
+ content: string;
257
+ byteLength: number;
258
+ sha256: string;
259
+ resources: readonly string[];
260
+ resourcesTruncated: boolean;
261
+ }
262
+ | {
263
+ ok: true;
264
+ status: "already_loaded";
265
+ name: string;
266
+ scope: SkillScope;
267
+ lifecycle: "pending" | "dispatched";
268
+ sha256: string;
269
+ }
270
+ | {
271
+ ok: true;
272
+ status: "already_active";
273
+ name: string;
274
+ scope: SkillScope;
275
+ sha256: string;
276
+ }
277
+ | {
278
+ ok: false;
279
+ status: "failed";
280
+ name: string;
281
+ errorCode: string;
282
+ error: string;
283
+ };
284
+
247
285
  export type McpToolRawResult = {
248
286
  ok: boolean;
249
287
  toolName: string;
@@ -269,6 +307,7 @@ export type ToolRawResultByKind = {
269
307
  web_search: WebSearchRawResult;
270
308
  web_fetch: WebFetchRawResult;
271
309
  recall: RecallRawResult;
310
+ skill: SkillRawResult;
272
311
  mcp: McpToolRawResult;
273
312
  generic: GenericToolRawResult;
274
313
  };
@@ -327,4 +366,4 @@ export type FileSnapshot = {
327
366
  source: "read" | "write" | "edit";
328
367
  };
329
368
 
330
- export type ReadSnapshotStore = Map<string, FileSnapshot>;
369
+ export type FileSnapshotStore = Map<string, FileSnapshot>;
@@ -1,13 +1,13 @@
1
- import path from "node:path";
2
1
  import { readFile, stat, writeFile } from "node:fs/promises";
3
2
  import { Buffer } from "node:buffer";
4
3
  import { throwIfTurnCancelled } from "../agent/turn-cancellation";
5
4
  import { computeFilePatch } from "./file-diff";
5
+ import { ensureParentDirectory } from "./ensure-parent-directory";
6
6
  import { sha256Bytes, sha256Text } from "./hash";
7
7
  import { resolveWorkspacePath } from "./path-safety";
8
8
  import { defineToolExecutor } from "./types";
9
9
  import type {
10
- ReadSnapshotStore,
10
+ FileSnapshotStore,
11
11
  ToolExecutionContext,
12
12
  ToolExecutor,
13
13
  WriteFileRawResult,
@@ -20,7 +20,7 @@ type WriteArgs = {
20
20
 
21
21
  export type WriteToolOptions = {
22
22
  workspaceRoot: string;
23
- snapshots: ReadSnapshotStore;
23
+ snapshots: FileSnapshotStore;
24
24
  };
25
25
 
26
26
  export function createWriteToolExecutor(options: WriteToolOptions): ToolExecutor {
@@ -28,7 +28,7 @@ export function createWriteToolExecutor(options: WriteToolOptions): ToolExecutor
28
28
  definition: {
29
29
  name: "Write",
30
30
  description:
31
- "Write full file content in the local workspace. Existing files must be read first.",
31
+ "Write full file content. Missing parent directories are created automatically. Existing files must be read first.",
32
32
  parameters: {
33
33
  type: "object",
34
34
  additionalProperties: false,
@@ -74,17 +74,6 @@ export function createWriteToolExecutor(options: WriteToolOptions): ToolExecutor
74
74
  };
75
75
  }
76
76
 
77
- const parentPath = path.dirname(absolutePath);
78
- const parentCheck = await directoryExists(parentPath);
79
- if (!parentCheck.ok) {
80
- return {
81
- ok: false,
82
- filePath: input.file_path,
83
- absolutePath,
84
- error: parentCheck.error,
85
- };
86
- }
87
-
88
77
  const target = await targetFileState(absolutePath);
89
78
  if (!target.ok) {
90
79
  return {
@@ -119,9 +108,9 @@ export function createWriteToolExecutor(options: WriteToolOptions): ToolExecutor
119
108
  filePath: input.file_path,
120
109
  absolutePath,
121
110
  currentSha256,
122
- lastReadSha256: lastSnapshot.sha256,
111
+ lastObservedSha256: lastSnapshot.sha256,
123
112
  error:
124
- "File changed after the last successful Read. Read it again before Write.",
113
+ "File changed after it was last observed. Read it again before Write.",
125
114
  };
126
115
  }
127
116
 
@@ -129,6 +118,17 @@ export function createWriteToolExecutor(options: WriteToolOptions): ToolExecutor
129
118
  oldContent = target.content;
130
119
  }
131
120
 
121
+ throwIfTurnCancelled(context.signal);
122
+ try {
123
+ await ensureParentDirectory(absolutePath);
124
+ } catch (error) {
125
+ return {
126
+ ok: false,
127
+ filePath: input.file_path,
128
+ absolutePath,
129
+ error: `Failed to create parent directory: ${errorMessage(error)}`,
130
+ };
131
+ }
132
132
  throwIfTurnCancelled(context.signal);
133
133
  await writeFile(absolutePath, input.content, "utf8");
134
134
  const newSha256 = sha256Text(input.content);
@@ -184,19 +184,6 @@ function parseWriteArgs(
184
184
  };
185
185
  }
186
186
 
187
- async function directoryExists(
188
- directoryPath: string,
189
- ): Promise<{ ok: true } | { ok: false; error: string }> {
190
- try {
191
- const info = await stat(directoryPath);
192
- return info.isDirectory()
193
- ? { ok: true }
194
- : { ok: false, error: "Parent path is not a directory." };
195
- } catch {
196
- return { ok: false, error: "Parent directory does not exist." };
197
- }
198
- }
199
-
200
187
  async function targetFileState(
201
188
  absolutePath: string,
202
189
  ): Promise<
@@ -231,11 +218,13 @@ function isRecord(value: unknown): value is Record<string, unknown> {
231
218
  }
232
219
 
233
220
  function isNotFound(error: unknown): boolean {
221
+ // ENOTDIR means a parent component is a file, so the target file cannot exist.
222
+ // Treat it as missing here so parent creation reports the underlying path error.
234
223
  return (
235
224
  typeof error === "object" &&
236
225
  error !== null &&
237
226
  "code" in error &&
238
- error.code === "ENOENT"
227
+ (error.code === "ENOENT" || error.code === "ENOTDIR")
239
228
  );
240
229
  }
241
230