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.
- package/CHANGELOG.md +36 -0
- package/LICENSE +201 -0
- package/NOTICE +6 -0
- package/README.md +90 -9
- package/bin/tinker.js +39 -0
- package/node_modules/ansi-regex/index.d.ts +33 -0
- package/node_modules/ansi-regex/index.js +14 -0
- package/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-regex/package.json +61 -0
- package/node_modules/ansi-regex/readme.md +66 -0
- package/node_modules/ansi-styles/index.d.ts +236 -0
- package/node_modules/ansi-styles/index.js +223 -0
- package/node_modules/ansi-styles/license +9 -0
- package/node_modules/ansi-styles/package.json +54 -0
- package/node_modules/ansi-styles/readme.md +173 -0
- package/node_modules/chalk/license +9 -0
- package/node_modules/chalk/package.json +83 -0
- package/node_modules/chalk/readme.md +297 -0
- package/node_modules/chalk/source/index.d.ts +325 -0
- package/node_modules/chalk/source/index.js +225 -0
- package/node_modules/chalk/source/utilities.js +33 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/node_modules/character-entities/index.d.ts +6 -0
- package/node_modules/character-entities/index.js +2132 -0
- package/node_modules/character-entities/license +22 -0
- package/node_modules/character-entities/package.json +78 -0
- package/node_modules/character-entities/readme.md +152 -0
- package/node_modules/decode-named-character-reference/index.d.ts +13 -0
- package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.js +32 -0
- package/node_modules/decode-named-character-reference/index.js +19 -0
- package/node_modules/decode-named-character-reference/license +22 -0
- package/node_modules/decode-named-character-reference/package.json +90 -0
- package/node_modules/decode-named-character-reference/readme.md +136 -0
- package/node_modules/get-east-asian-width/index.d.ts +60 -0
- package/node_modules/get-east-asian-width/index.js +30 -0
- package/node_modules/get-east-asian-width/license +9 -0
- package/node_modules/get-east-asian-width/lookup-data.js +21 -0
- package/node_modules/get-east-asian-width/lookup.js +138 -0
- package/node_modules/get-east-asian-width/package.json +71 -0
- package/node_modules/get-east-asian-width/readme.md +65 -0
- package/node_modules/get-east-asian-width/utilities.js +24 -0
- package/node_modules/has-flag/index.d.ts +39 -0
- package/node_modules/has-flag/index.js +8 -0
- package/node_modules/has-flag/license +9 -0
- package/node_modules/has-flag/package.json +49 -0
- package/node_modules/has-flag/readme.md +74 -0
- package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
- package/node_modules/is-fullwidth-code-point/index.js +12 -0
- package/node_modules/is-fullwidth-code-point/license +9 -0
- package/node_modules/is-fullwidth-code-point/package.json +53 -0
- package/node_modules/is-fullwidth-code-point/readme.md +31 -0
- package/node_modules/markdansi/LICENSE +21 -0
- package/node_modules/markdansi/README.md +167 -0
- package/node_modules/markdansi/dist/ast.d.ts +99 -0
- package/node_modules/markdansi/dist/ast.js +1 -0
- package/node_modules/markdansi/dist/cli.d.ts +17 -0
- package/node_modules/markdansi/dist/cli.js +208 -0
- package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
- package/node_modules/markdansi/dist/hyperlink.js +26 -0
- package/node_modules/markdansi/dist/index.d.ts +10 -0
- package/node_modules/markdansi/dist/index.js +16 -0
- package/node_modules/markdansi/dist/parser.d.ts +2 -0
- package/node_modules/markdansi/dist/parser.js +205 -0
- package/node_modules/markdansi/dist/render.d.ts +9 -0
- package/node_modules/markdansi/dist/render.js +680 -0
- package/node_modules/markdansi/dist/stream.d.ts +39 -0
- package/node_modules/markdansi/dist/stream.js +192 -0
- package/node_modules/markdansi/dist/theme.d.ts +18 -0
- package/node_modules/markdansi/dist/theme.js +105 -0
- package/node_modules/markdansi/dist/types.d.ts +58 -0
- package/node_modules/markdansi/dist/types.js +1 -0
- package/node_modules/markdansi/dist/wrap.d.ts +10 -0
- package/node_modules/markdansi/dist/wrap.js +73 -0
- package/node_modules/markdansi/docs/spec.md +110 -0
- package/node_modules/markdansi/package.json +78 -0
- package/node_modules/markdansi/tsconfig.json +18 -0
- package/node_modules/marked/LICENSE +44 -0
- package/node_modules/marked/README.md +115 -0
- package/node_modules/marked/bin/main.js +284 -0
- package/node_modules/marked/bin/marked.js +16 -0
- package/node_modules/marked/lib/marked.d.ts +760 -0
- package/node_modules/marked/lib/marked.esm.js +77 -0
- package/node_modules/marked/lib/marked.esm.js.map +7 -0
- package/node_modules/marked/lib/marked.umd.js +79 -0
- package/node_modules/marked/lib/marked.umd.js.map +7 -0
- package/node_modules/marked/man/marked.1 +113 -0
- package/node_modules/marked/man/marked.1.md +93 -0
- package/node_modules/marked/package.json +103 -0
- package/node_modules/slice-ansi/index.d.ts +19 -0
- package/node_modules/slice-ansi/index.js +317 -0
- package/node_modules/slice-ansi/license +10 -0
- package/node_modules/slice-ansi/package.json +59 -0
- package/node_modules/slice-ansi/readme.md +55 -0
- package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
- package/node_modules/string-width/index.d.ts +39 -0
- package/node_modules/string-width/index.js +203 -0
- package/node_modules/string-width/license +9 -0
- package/node_modules/string-width/package.json +65 -0
- package/node_modules/string-width/readme.md +66 -0
- package/node_modules/strip-ansi/index.d.ts +15 -0
- package/node_modules/strip-ansi/index.js +19 -0
- package/node_modules/strip-ansi/license +9 -0
- package/node_modules/strip-ansi/package.json +59 -0
- package/node_modules/strip-ansi/readme.md +37 -0
- package/node_modules/supports-color/browser.d.ts +1 -0
- package/node_modules/supports-color/browser.js +35 -0
- package/node_modules/supports-color/index.d.ts +55 -0
- package/node_modules/supports-color/index.js +202 -0
- package/node_modules/supports-color/license +9 -0
- package/node_modules/supports-color/package.json +64 -0
- package/node_modules/supports-color/readme.md +75 -0
- package/node_modules/supports-hyperlinks/browser.js +10 -0
- package/node_modules/supports-hyperlinks/index.d.ts +25 -0
- package/node_modules/supports-hyperlinks/index.js +163 -0
- package/node_modules/supports-hyperlinks/license +10 -0
- package/node_modules/supports-hyperlinks/package.json +47 -0
- package/node_modules/supports-hyperlinks/readme.md +45 -0
- package/package.json +41 -10
- package/patches/markdansi@0.3.2.patch +22 -2
- package/src/agent/context-builder.ts +24 -10
- package/src/agent/context-meter.ts +89 -5
- package/src/agent/loop.ts +107 -10
- package/src/agent/runtime-session.ts +1437 -82
- package/src/agent/session-ledger.ts +169 -29
- package/src/agent/types.ts +12 -1
- package/src/agent/user-prompt-projection.ts +62 -0
- package/src/cli/config.ts +57 -14
- package/src/cli/model-profiles.ts +124 -0
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/tui-runner.tsx +68 -30
- package/src/context/compiled-context-hash.ts +1 -1
- package/src/context/compiled-context-validator.ts +92 -15
- package/src/context/context-automation-policy.ts +121 -0
- package/src/context/context-manager.ts +390 -11
- package/src/context/context-policy.ts +8 -0
- package/src/context/context-protocol-validator.ts +11 -1
- package/src/context/context-revision-compiler.ts +190 -27
- package/src/context/context-revision.ts +105 -19
- package/src/context/context-surface.ts +329 -0
- package/src/context/prefix-retirement-planner.ts +570 -0
- package/src/context/protocol-frame.ts +30 -3
- package/src/context/recall-retirement-contract.ts +27 -0
- package/src/context/swap-planner.ts +34 -10
- package/src/events/observation-text-log.ts +3 -3
- package/src/events/stdout-event-printer.ts +39 -3
- package/src/events/types.ts +172 -35
- package/src/ids/runtime-id.ts +6 -0
- package/src/ids/uuid-v7.ts +7 -0
- package/src/image/image-asset-store.ts +319 -0
- package/src/image/image-input-policy.ts +14 -0
- package/src/image/image-probe.ts +233 -0
- package/src/image/image-types.ts +262 -0
- package/src/instructions/project-instructions.ts +4 -1
- package/src/mcp/mcp-manager.ts +83 -19
- package/src/model/fake-model-client.ts +9 -1
- package/src/model/input-token-estimator.ts +25 -0
- package/src/model/model-client.ts +60 -0
- package/src/model/model-request-preflight.ts +1 -0
- package/src/model/moonshot-input-token-estimator.ts +107 -0
- package/src/model/openai-chat-mapping.ts +77 -1
- package/src/model/openai-chat-model-client.ts +389 -59
- package/src/model/openai-chat-stream.ts +272 -0
- package/src/model/prompt-prefix-hash.ts +2 -4
- package/src/model/token-estimator.ts +4 -0
- package/src/observation/observation-builder.ts +29 -0
- package/src/session/resume-projection.ts +94 -34
- package/src/session/session-catalog.ts +4 -4
- package/src/session/session-errors.ts +1 -1
- package/src/session/session-history-reader.ts +151 -4
- package/src/session/session-last-response-reader.ts +189 -0
- package/src/session/session-schema.ts +349 -35
- package/src/session/session-store.ts +3266 -255
- package/src/session/sqlite-session-ledger.ts +9 -4
- package/src/skills/skill-catalog.ts +182 -0
- package/src/skills/skill-context.ts +364 -0
- package/src/skills/skill-loader.ts +779 -0
- package/src/skills/skill-tool.ts +297 -0
- package/src/tools/edit.ts +51 -64
- package/src/tools/ensure-parent-directory.ts +6 -0
- package/src/tools/read.ts +2 -2
- package/src/tools/recall.ts +43 -40
- package/src/tools/registry.ts +24 -3
- package/src/tools/ripgrep.ts +3 -2
- package/src/tools/types.ts +43 -4
- package/src/tools/write.ts +20 -31
- package/src/tui/app.tsx +333 -96
- package/src/tui/clipboard.ts +5 -0
- package/src/tui/components/mcp-panel.tsx +28 -0
- package/src/tui/components/prompt-input.tsx +790 -257
- package/src/tui/components/skills-panel.tsx +31 -0
- package/src/tui/components/timeline.tsx +30 -1
- package/src/tui/event-store.ts +85 -2
- package/src/tui/file-mention.ts +234 -0
- package/src/tui/line-editor.ts +1 -0
- package/src/tui/project-slash-commands.ts +229 -0
- package/src/tui/prompt-draft.ts +510 -0
- package/src/tui/prompt-history.ts +294 -22
- package/src/tui/slash-commands.ts +46 -2
- package/src/tui/tui-session-controller.ts +110 -48
- package/src/tui/workspace-file-search.ts +94 -0
package/src/tools/registry.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
SessionDisposeReason,
|
|
20
20
|
} from "../agent/runtime-session";
|
|
21
21
|
import type {
|
|
22
|
-
|
|
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:
|
|
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:
|
|
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,
|
package/src/tools/ripgrep.ts
CHANGED
|
@@ -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
|
-
"
|
|
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 ??
|
|
26
|
+
return process.env.TINKER_RIPGREP_PATH ?? rgPath;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export async function ripGrep(
|
package/src/tools/types.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
|
369
|
+
export type FileSnapshotStore = Map<string, FileSnapshot>;
|
package/src/tools/write.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
111
|
+
lastObservedSha256: lastSnapshot.sha256,
|
|
123
112
|
error:
|
|
124
|
-
"File changed after
|
|
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
|
|