@renxqoo/renx-code 0.0.4 → 0.0.6

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 (210) hide show
  1. package/README.md +82 -51
  2. package/bin/renx.cjs +16 -0
  3. package/package.json +2 -45
  4. package/src/agent/runtime/runtime.context-usage.test.ts +4 -5
  5. package/src/agent/runtime/runtime.error-handling.test.ts +4 -5
  6. package/src/agent/runtime/runtime.test.ts +7 -4
  7. package/src/agent/runtime/runtime.ts +3 -9
  8. package/src/agent/runtime/runtime.usage-forwarding.test.ts +4 -5
  9. package/src/agent/runtime/source-modules.test.ts +16 -35
  10. package/src/agent/runtime/source-modules.ts +17 -0
  11. package/vendor/agent-root/src/agent/ENTERPRISE_ACCEPTANCE_CHECKLIST.md +95 -0
  12. package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.html +1345 -0
  13. package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.md +1353 -0
  14. package/vendor/agent-root/src/agent/ERROR_CONTRACT.md +60 -0
  15. package/vendor/agent-root/src/agent/TEST_COVERAGE_ANALYSIS.md +278 -0
  16. package/vendor/agent-root/src/agent/__test__/error-contract.test.ts +72 -0
  17. package/vendor/agent-root/src/agent/__test__/types.test.ts +137 -0
  18. package/vendor/agent-root/src/agent/agent/__test__/abort-runtime.test.ts +83 -0
  19. package/vendor/agent-root/src/agent/agent/__test__/callback-safety.test.ts +34 -0
  20. package/vendor/agent-root/src/agent/agent/__test__/compaction.test.ts +323 -0
  21. package/vendor/agent-root/src/agent/agent/__test__/concurrency.test.ts +290 -0
  22. package/vendor/agent-root/src/agent/agent/__test__/error-normalizer.test.ts +377 -0
  23. package/vendor/agent-root/src/agent/agent/__test__/error.test.ts +212 -0
  24. package/vendor/agent-root/src/agent/agent/__test__/fault-injection.test.ts +295 -0
  25. package/vendor/agent-root/src/agent/agent/__test__/index.test.ts +3607 -0
  26. package/vendor/agent-root/src/agent/agent/__test__/logger.test.ts +35 -0
  27. package/vendor/agent-root/src/agent/agent/__test__/message-utils.test.ts +517 -0
  28. package/vendor/agent-root/src/agent/agent/__test__/telemetry.test.ts +97 -0
  29. package/vendor/agent-root/src/agent/agent/__test__/timeout-budget.test.ts +479 -0
  30. package/vendor/agent-root/src/agent/agent/__test__/tool-call-merge.test.ts +80 -0
  31. package/vendor/agent-root/src/agent/agent/__test__/tool-execution-ledger.test.ts +76 -0
  32. package/vendor/agent-root/src/agent/agent/__test__/write-buffer.test.ts +173 -0
  33. package/vendor/agent-root/src/agent/agent/__test__/write-file-session.test.ts +109 -0
  34. package/vendor/agent-root/src/agent/agent/abort-runtime.ts +71 -0
  35. package/vendor/agent-root/src/agent/agent/callback-safety.ts +33 -0
  36. package/vendor/agent-root/src/agent/agent/compaction.ts +291 -0
  37. package/vendor/agent-root/src/agent/agent/concurrency.ts +103 -0
  38. package/vendor/agent-root/src/agent/agent/error-normalizer.ts +190 -0
  39. package/vendor/agent-root/src/agent/agent/error.ts +198 -0
  40. package/vendor/agent-root/src/agent/agent/index.ts +1772 -0
  41. package/vendor/agent-root/src/agent/agent/logger.ts +65 -0
  42. package/vendor/agent-root/src/agent/agent/message-utils.ts +101 -0
  43. package/vendor/agent-root/src/agent/agent/stream-events.ts +61 -0
  44. package/vendor/agent-root/src/agent/agent/telemetry.ts +123 -0
  45. package/vendor/agent-root/src/agent/agent/timeout-budget.ts +227 -0
  46. package/vendor/agent-root/src/agent/agent/tool-call-merge.ts +111 -0
  47. package/vendor/agent-root/src/agent/agent/tool-execution-ledger.ts +164 -0
  48. package/vendor/agent-root/src/agent/agent/write-buffer.ts +188 -0
  49. package/vendor/agent-root/src/agent/agent/write-file-session.ts +238 -0
  50. package/vendor/agent-root/src/agent/app/__test__/agent-app-service.test.ts +1053 -0
  51. package/vendor/agent-root/src/agent/app/__test__/minimal-agent-application.test.ts +158 -0
  52. package/vendor/agent-root/src/agent/app/__test__/sqlite-agent-app-store.test.ts +437 -0
  53. package/vendor/agent-root/src/agent/app/agent-app-service.ts +748 -0
  54. package/vendor/agent-root/src/agent/app/contracts.ts +109 -0
  55. package/vendor/agent-root/src/agent/app/index.ts +5 -0
  56. package/vendor/agent-root/src/agent/app/minimal-agent-application.ts +151 -0
  57. package/vendor/agent-root/src/agent/app/ports.ts +72 -0
  58. package/vendor/agent-root/src/agent/app/sqlite-agent-app-store.ts +1182 -0
  59. package/vendor/agent-root/src/agent/app/sqlite-client.ts +177 -0
  60. package/vendor/agent-root/src/agent/docs/cli-app-layer/00-README.md +36 -0
  61. package/vendor/agent-root/src/agent/docs/cli-app-layer/01-scope-and-goals.md +33 -0
  62. package/vendor/agent-root/src/agent/docs/cli-app-layer/02-architecture-overview.md +40 -0
  63. package/vendor/agent-root/src/agent/docs/cli-app-layer/03-domain-model-and-contracts.md +91 -0
  64. package/vendor/agent-root/src/agent/docs/cli-app-layer/04-ports-and-interfaces.md +116 -0
  65. package/vendor/agent-root/src/agent/docs/cli-app-layer/05-run-orchestration-and-state-machine.md +52 -0
  66. package/vendor/agent-root/src/agent/docs/cli-app-layer/06-cli-commands-and-ux.md +53 -0
  67. package/vendor/agent-root/src/agent/docs/cli-app-layer/07-storage-design-local.md +52 -0
  68. package/vendor/agent-root/src/agent/docs/cli-app-layer/08-error-and-observability.md +40 -0
  69. package/vendor/agent-root/src/agent/docs/cli-app-layer/09-security-and-policy-boundary.md +19 -0
  70. package/vendor/agent-root/src/agent/docs/cli-app-layer/10-test-plan-and-acceptance.md +28 -0
  71. package/vendor/agent-root/src/agent/docs/cli-app-layer/11-implementation-phases.md +26 -0
  72. package/vendor/agent-root/src/agent/docs/cli-app-layer/12-open-questions-and-risks.md +30 -0
  73. package/vendor/agent-root/src/agent/docs/cli-app-layer/13-sqlite-schema-fields-and-rationale.md +567 -0
  74. package/vendor/agent-root/src/agent/docs/cli-app-layer/14-project-flow-mermaid.md +583 -0
  75. package/vendor/agent-root/src/agent/docs/cli-app-layer/15-openclaw-style-project-blueprint.md +972 -0
  76. package/vendor/agent-root/src/agent/error-contract.ts +154 -0
  77. package/vendor/agent-root/src/agent/prompts/system.ts +246 -0
  78. package/vendor/agent-root/src/agent/prompts/system1.ts +208 -0
  79. package/vendor/agent-root/src/agent/storage/__test__/file-history-store.test.ts +98 -0
  80. package/vendor/agent-root/src/agent/storage/file-history-store.ts +313 -0
  81. package/vendor/agent-root/src/agent/storage/file-storage-config.ts +94 -0
  82. package/vendor/agent-root/src/agent/storage/file-system.ts +31 -0
  83. package/vendor/agent-root/src/agent/storage/file-write-service.ts +21 -0
  84. package/vendor/agent-root/src/agent/tool/__test__/base-tool.test.ts +413 -0
  85. package/vendor/agent-root/src/agent/tool/__test__/bash-policy.test.ts +356 -0
  86. package/vendor/agent-root/src/agent/tool/__test__/bash.mocked-coverage.test.ts +375 -0
  87. package/vendor/agent-root/src/agent/tool/__test__/bash.test.ts +372 -0
  88. package/vendor/agent-root/src/agent/tool/__test__/error.test.ts +108 -0
  89. package/vendor/agent-root/src/agent/tool/__test__/file-edit-tool.test.ts +258 -0
  90. package/vendor/agent-root/src/agent/tool/__test__/file-history-tools.test.ts +121 -0
  91. package/vendor/agent-root/src/agent/tool/__test__/file-read-tool.test.ts +210 -0
  92. package/vendor/agent-root/src/agent/tool/__test__/glob.test.ts +139 -0
  93. package/vendor/agent-root/src/agent/tool/__test__/grep.mocked-coverage.test.ts +456 -0
  94. package/vendor/agent-root/src/agent/tool/__test__/grep.test.ts +192 -0
  95. package/vendor/agent-root/src/agent/tool/__test__/lsp.test.ts +300 -0
  96. package/vendor/agent-root/src/agent/tool/__test__/outside-workspace-confirmation.test.ts +214 -0
  97. package/vendor/agent-root/src/agent/tool/__test__/path-security.test.ts +336 -0
  98. package/vendor/agent-root/src/agent/tool/__test__/skill-loader.test.ts +494 -0
  99. package/vendor/agent-root/src/agent/tool/__test__/skill-parser.test.ts +543 -0
  100. package/vendor/agent-root/src/agent/tool/__test__/skill-tool.test.ts +172 -0
  101. package/vendor/agent-root/src/agent/tool/__test__/task-concurrency-and-version.test.ts +116 -0
  102. package/vendor/agent-root/src/agent/tool/__test__/task-create-get-list-update.test.ts +267 -0
  103. package/vendor/agent-root/src/agent/tool/__test__/task-create.test.ts +519 -0
  104. package/vendor/agent-root/src/agent/tool/__test__/task-errors.test.ts +225 -0
  105. package/vendor/agent-root/src/agent/tool/__test__/task-output-blocking.test.ts +223 -0
  106. package/vendor/agent-root/src/agent/tool/__test__/task-output.test.ts +184 -0
  107. package/vendor/agent-root/src/agent/tool/__test__/task-parent-abort.test.ts +287 -0
  108. package/vendor/agent-root/src/agent/tool/__test__/task-real-runner-adapter.test.ts +190 -0
  109. package/vendor/agent-root/src/agent/tool/__test__/task-run-lifecycle.test.ts +352 -0
  110. package/vendor/agent-root/src/agent/tool/__test__/task-store-runner-branches.test.ts +395 -0
  111. package/vendor/agent-root/src/agent/tool/__test__/task-store.test.ts +391 -0
  112. package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config-integration.test.ts +176 -0
  113. package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config.test.ts +68 -0
  114. package/vendor/agent-root/src/agent/tool/__test__/task-tools-core-edges.test.ts +630 -0
  115. package/vendor/agent-root/src/agent/tool/__test__/task-tools-runtime-edges.test.ts +732 -0
  116. package/vendor/agent-root/src/agent/tool/__test__/task-types.test.ts +494 -0
  117. package/vendor/agent-root/src/agent/tool/__test__/task-utils-branches.test.ts +175 -0
  118. package/vendor/agent-root/src/agent/tool/__test__/tool-manager.test.ts +505 -0
  119. package/vendor/agent-root/src/agent/tool/__test__/types.test.ts +55 -0
  120. package/vendor/agent-root/src/agent/tool/__test__/web-fetch.test.ts +244 -0
  121. package/vendor/agent-root/src/agent/tool/__test__/web-search.test.ts +290 -0
  122. package/vendor/agent-root/src/agent/tool/__test__/write-file.test.ts +368 -0
  123. package/vendor/agent-root/src/agent/tool/base-tool.ts +345 -0
  124. package/vendor/agent-root/src/agent/tool/bash-policy.ts +636 -0
  125. package/vendor/agent-root/src/agent/tool/bash.ts +688 -0
  126. package/vendor/agent-root/src/agent/tool/error.ts +131 -0
  127. package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +264 -0
  128. package/vendor/agent-root/src/agent/tool/file-history-list.ts +103 -0
  129. package/vendor/agent-root/src/agent/tool/file-history-restore.ts +149 -0
  130. package/vendor/agent-root/src/agent/tool/file-read-tool.ts +211 -0
  131. package/vendor/agent-root/src/agent/tool/glob.ts +171 -0
  132. package/vendor/agent-root/src/agent/tool/grep.ts +496 -0
  133. package/vendor/agent-root/src/agent/tool/lsp.ts +481 -0
  134. package/vendor/agent-root/src/agent/tool/path-security.ts +117 -0
  135. package/vendor/agent-root/src/agent/tool/search/common.ts +153 -0
  136. package/vendor/agent-root/src/agent/tool/skill/index.ts +13 -0
  137. package/vendor/agent-root/src/agent/tool/skill/loader.ts +229 -0
  138. package/vendor/agent-root/src/agent/tool/skill/parser.ts +124 -0
  139. package/vendor/agent-root/src/agent/tool/skill/types.ts +27 -0
  140. package/vendor/agent-root/src/agent/tool/skill-tool.ts +143 -0
  141. package/vendor/agent-root/src/agent/tool/task-create.ts +186 -0
  142. package/vendor/agent-root/src/agent/tool/task-errors.ts +42 -0
  143. package/vendor/agent-root/src/agent/tool/task-get.ts +116 -0
  144. package/vendor/agent-root/src/agent/tool/task-graph.ts +78 -0
  145. package/vendor/agent-root/src/agent/tool/task-list.ts +141 -0
  146. package/vendor/agent-root/src/agent/tool/task-mock-runner-adapter.ts +232 -0
  147. package/vendor/agent-root/src/agent/tool/task-output.ts +223 -0
  148. package/vendor/agent-root/src/agent/tool/task-parent-abort.ts +115 -0
  149. package/vendor/agent-root/src/agent/tool/task-real-runner-adapter.ts +336 -0
  150. package/vendor/agent-root/src/agent/tool/task-runner-adapter.ts +55 -0
  151. package/vendor/agent-root/src/agent/tool/task-stop.ts +187 -0
  152. package/vendor/agent-root/src/agent/tool/task-store.ts +217 -0
  153. package/vendor/agent-root/src/agent/tool/task-subagent-config.ts +149 -0
  154. package/vendor/agent-root/src/agent/tool/task-types.ts +264 -0
  155. package/vendor/agent-root/src/agent/tool/task-update.ts +315 -0
  156. package/vendor/agent-root/src/agent/tool/task.ts +209 -0
  157. package/vendor/agent-root/src/agent/tool/tool-manager.ts +362 -0
  158. package/vendor/agent-root/src/agent/tool/tool-prompts.ts +242 -0
  159. package/vendor/agent-root/src/agent/tool/types.ts +116 -0
  160. package/vendor/agent-root/src/agent/tool/web-fetch.ts +227 -0
  161. package/vendor/agent-root/src/agent/tool/web-search.ts +208 -0
  162. package/vendor/agent-root/src/agent/tool/write-file.ts +497 -0
  163. package/vendor/agent-root/src/agent/types.ts +232 -0
  164. package/vendor/agent-root/src/agent/utils/__tests__/index.test.ts +18 -0
  165. package/vendor/agent-root/src/agent/utils/__tests__/message-utils.test.ts +610 -0
  166. package/vendor/agent-root/src/agent/utils/__tests__/message.test.ts +223 -0
  167. package/vendor/agent-root/src/agent/utils/__tests__/token.test.ts +42 -0
  168. package/vendor/agent-root/src/agent/utils/index.ts +16 -0
  169. package/vendor/agent-root/src/agent/utils/message.ts +171 -0
  170. package/vendor/agent-root/src/agent/utils/token.ts +28 -0
  171. package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +129 -0
  172. package/vendor/agent-root/src/config/__tests__/loader.test.ts +247 -0
  173. package/vendor/agent-root/src/config/__tests__/runtime.test.ts +88 -0
  174. package/vendor/agent-root/src/config/index.ts +54 -0
  175. package/vendor/agent-root/src/config/loader.ts +431 -0
  176. package/vendor/agent-root/src/config/paths.ts +30 -0
  177. package/vendor/agent-root/src/config/runtime.ts +163 -0
  178. package/vendor/agent-root/src/config/types.ts +70 -0
  179. package/vendor/agent-root/src/logger/index.ts +57 -0
  180. package/vendor/agent-root/src/logger/logger.ts +819 -0
  181. package/vendor/agent-root/src/logger/types.ts +150 -0
  182. package/vendor/agent-root/src/providers/__tests__/errors.test.ts +441 -0
  183. package/vendor/agent-root/src/providers/__tests__/index.test.ts +16 -0
  184. package/vendor/agent-root/src/providers/__tests__/openai-compatible.options.test.ts +318 -0
  185. package/vendor/agent-root/src/providers/__tests__/openai-compatible.test.ts +600 -0
  186. package/vendor/agent-root/src/providers/__tests__/registry.test.ts +449 -0
  187. package/vendor/agent-root/src/providers/__tests__/responses-adapter.test.ts +298 -0
  188. package/vendor/agent-root/src/providers/adapters/__tests__/anthropic.test.ts +354 -0
  189. package/vendor/agent-root/src/providers/adapters/__tests__/kimi.test.ts +58 -0
  190. package/vendor/agent-root/src/providers/adapters/__tests__/standard.test.ts +261 -0
  191. package/vendor/agent-root/src/providers/adapters/anthropic.ts +572 -0
  192. package/vendor/agent-root/src/providers/adapters/base.ts +131 -0
  193. package/vendor/agent-root/src/providers/adapters/kimi.ts +48 -0
  194. package/vendor/agent-root/src/providers/adapters/responses.ts +732 -0
  195. package/vendor/agent-root/src/providers/adapters/standard.ts +120 -0
  196. package/vendor/agent-root/src/providers/http/__tests__/client.timeout.test.ts +313 -0
  197. package/vendor/agent-root/src/providers/http/client.ts +289 -0
  198. package/vendor/agent-root/src/providers/http/stream-parser.ts +109 -0
  199. package/vendor/agent-root/src/providers/index.ts +76 -0
  200. package/vendor/agent-root/src/providers/kimi-headers.ts +177 -0
  201. package/vendor/agent-root/src/providers/openai-compatible.ts +387 -0
  202. package/vendor/agent-root/src/providers/registry/model-config.ts +230 -0
  203. package/vendor/agent-root/src/providers/registry/provider-factory.ts +123 -0
  204. package/vendor/agent-root/src/providers/registry.ts +135 -0
  205. package/vendor/agent-root/src/providers/types/api.ts +284 -0
  206. package/vendor/agent-root/src/providers/types/config.ts +58 -0
  207. package/vendor/agent-root/src/providers/types/errors.ts +323 -0
  208. package/vendor/agent-root/src/providers/types/index.ts +72 -0
  209. package/vendor/agent-root/src/providers/types/provider.ts +45 -0
  210. package/vendor/agent-root/src/providers/types/registry.ts +88 -0
@@ -0,0 +1,497 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { z } from 'zod';
5
+ import { BaseTool, type ToolConfirmDetails, type ToolResult } from './base-tool';
6
+ import type { ToolExecutionContext } from './types';
7
+ import {
8
+ appendContent,
9
+ createWriteBufferSession,
10
+ finalizeWriteBufferSession,
11
+ loadWriteBufferSession,
12
+ cleanupWriteBufferSessionFiles,
13
+ } from '../agent/write-buffer';
14
+ import { ToolExecutionError } from './error';
15
+ import { WRITE_FILE_TOOL_DESCRIPTION } from './tool-prompts';
16
+ import { assessPathAccess } from './path-security';
17
+ import { createConfiguredFileHistoryStore, FileHistoryStore } from '../storage/file-history-store';
18
+ import {
19
+ getWriteBufferCandidateDirs,
20
+ resolveWriteBufferBaseDir,
21
+ } from '../storage/file-storage-config';
22
+ import { writeTextFileWithHistory } from '../storage/file-write-service';
23
+
24
+ const writeModeSchema = z.enum(['direct', 'finalize']);
25
+
26
+ const schema = z.object({
27
+ path: z
28
+ .string()
29
+ .min(1)
30
+ .optional()
31
+ .describe('Target path. Required for direct; optional for finalize'),
32
+ content: z.string().optional().describe('Plain text content chunk for this call'),
33
+ mode: writeModeSchema.default('direct').describe('Write mode: direct or finalize'),
34
+ bufferId: z.string().optional().describe('Buffer session id used for finalize'),
35
+ });
36
+
37
+ type WriteFileArgs = z.infer<typeof schema>;
38
+ type WriteMode = z.infer<typeof writeModeSchema>;
39
+
40
+ interface WriteFileToolOptions {
41
+ allowedDirectories?: string[];
42
+ maxChunkBytes?: number;
43
+ bufferBaseDir?: string;
44
+ historyStore?: FileHistoryStore;
45
+ }
46
+
47
+ interface WriteBufferInfo {
48
+ bufferId: string;
49
+ path: string;
50
+ bufferedBytes: number;
51
+ maxChunkBytes: number;
52
+ }
53
+
54
+ interface WriteFileResponse {
55
+ ok: boolean;
56
+ code:
57
+ | 'OK'
58
+ | 'WRITE_FILE_PARTIAL_BUFFERED'
59
+ | 'WRITE_FILE_NEED_FINALIZE'
60
+ | 'WRITE_FILE_FINALIZE_OK';
61
+ message: string;
62
+ buffer?: WriteBufferInfo;
63
+ nextAction: 'finalize' | 'none';
64
+ }
65
+
66
+ interface SessionPointer {
67
+ metaPath: string;
68
+ }
69
+
70
+ interface LoadedBufferSession {
71
+ session: {
72
+ contentPath: string;
73
+ metaPath: string;
74
+ rawArgsPath: string;
75
+ targetPath?: string;
76
+ bufferId: string;
77
+ contentBytes: number;
78
+ };
79
+ }
80
+
81
+ export class WriteFileTool extends BaseTool<typeof schema> {
82
+ name = 'write_file';
83
+ description = WRITE_FILE_TOOL_DESCRIPTION;
84
+ parameters = schema;
85
+
86
+ private readonly allowedDirectories: string[];
87
+ private readonly maxChunkBytes: number;
88
+ private readonly bufferBaseDir: string;
89
+ private readonly historyStore: FileHistoryStore;
90
+
91
+ constructor(options: WriteFileToolOptions = {}) {
92
+ super();
93
+ this.allowedDirectories = (
94
+ options.allowedDirectories?.length ? options.allowedDirectories : [process.cwd()]
95
+ ).map((dir) => this.normalizeAllowedDirectory(dir));
96
+ this.maxChunkBytes =
97
+ options.maxChunkBytes && options.maxChunkBytes > 0 ? options.maxChunkBytes : 32768;
98
+ this.bufferBaseDir = resolveWriteBufferBaseDir(options.bufferBaseDir);
99
+ this.historyStore = options.historyStore ?? createConfiguredFileHistoryStore();
100
+ fs.mkdirSync(this.bufferBaseDir, { recursive: true });
101
+ }
102
+
103
+ override getConfirmDetails(args: WriteFileArgs): ToolConfirmDetails | null {
104
+ if (!args.path) {
105
+ return null;
106
+ }
107
+ const resolved = path.isAbsolute(args.path)
108
+ ? path.resolve(args.path)
109
+ : path.resolve(process.cwd(), args.path);
110
+ const assessment = assessPathAccess(resolved, this.allowedDirectories, 'PATH_NOT_ALLOWED');
111
+ if (assessment.allowed) {
112
+ return null;
113
+ }
114
+ return {
115
+ reason: assessment.message,
116
+ metadata: {
117
+ requestedPath: resolved,
118
+ allowedDirectories: this.allowedDirectories,
119
+ errorCode: 'PATH_NOT_ALLOWED',
120
+ },
121
+ };
122
+ }
123
+
124
+ async execute(args: WriteFileArgs, context?: ToolExecutionContext): Promise<ToolResult> {
125
+ try {
126
+ const mode: WriteMode = args.mode || 'direct';
127
+ const content = args.content || '';
128
+
129
+ if (mode === 'direct') {
130
+ const targetPath = this.validateAndResolveRequiredPath(args.path, mode, context);
131
+ return this.handleDirect(targetPath, content, context);
132
+ }
133
+ return this.handleFinalize(args.path, args.bufferId, context);
134
+ } catch (error) {
135
+ const message = error instanceof Error ? error.message : String(error);
136
+ return {
137
+ success: false,
138
+ error: new ToolExecutionError(message),
139
+ output: message,
140
+ };
141
+ }
142
+ }
143
+
144
+ private async handleDirect(
145
+ targetPath: string,
146
+ content: string,
147
+ context?: ToolExecutionContext
148
+ ): Promise<ToolResult> {
149
+ const contentBytes = Buffer.byteLength(content, 'utf8');
150
+ if (contentBytes <= this.maxChunkBytes) {
151
+ await this.writeAtomically(targetPath, content);
152
+ return this.successResponse({
153
+ ok: true,
154
+ code: 'OK',
155
+ message: 'File written successfully',
156
+ nextAction: 'none',
157
+ });
158
+ }
159
+
160
+ const session = await this.createOrLoadSession(
161
+ targetPath,
162
+ context?.toolCallId,
163
+ undefined,
164
+ targetPath
165
+ );
166
+ await appendContent(session, content);
167
+ const latest = await loadWriteBufferSession(session.metaPath);
168
+
169
+ return this.successResponse({
170
+ ok: false,
171
+ code: 'WRITE_FILE_PARTIAL_BUFFERED',
172
+ message: `Content exceeds maxChunkBytes=${this.maxChunkBytes}; buffered full content`,
173
+ buffer: {
174
+ bufferId: latest.bufferId,
175
+ path: targetPath,
176
+ bufferedBytes: latest.contentBytes,
177
+ maxChunkBytes: this.maxChunkBytes,
178
+ },
179
+ nextAction: 'finalize',
180
+ });
181
+ }
182
+
183
+ private async handleFinalize(
184
+ inputPath: string | undefined,
185
+ bufferId: string | undefined,
186
+ context?: ToolExecutionContext
187
+ ): Promise<ToolResult> {
188
+ if (!bufferId) {
189
+ return this.successResponse({
190
+ ok: false,
191
+ code: 'WRITE_FILE_NEED_FINALIZE',
192
+ message: 'bufferId is required for finalize mode',
193
+ nextAction: 'finalize',
194
+ });
195
+ }
196
+
197
+ const loaded = await this.loadBufferedSession(bufferId);
198
+ if (!loaded) {
199
+ return this.successResponse({
200
+ ok: false,
201
+ code: 'WRITE_FILE_NEED_FINALIZE',
202
+ message: `Buffer session not found for bufferId=${bufferId}`,
203
+ nextAction: 'finalize',
204
+ });
205
+ }
206
+
207
+ const session = loaded.session;
208
+ const targetPath = this.resolveFinalizeTargetPath(inputPath, session.targetPath, context);
209
+ const normalizedSessionTargetPath = session.targetPath
210
+ ? this.validateAndResolvePath(session.targetPath, context)
211
+ : undefined;
212
+ if (normalizedSessionTargetPath && normalizedSessionTargetPath !== targetPath) {
213
+ return this.successResponse({
214
+ ok: false,
215
+ code: 'WRITE_FILE_NEED_FINALIZE',
216
+ message: 'Target path does not match existing buffer session',
217
+ buffer: {
218
+ bufferId: session.bufferId,
219
+ path: normalizedSessionTargetPath,
220
+ bufferedBytes: session.contentBytes,
221
+ maxChunkBytes: this.maxChunkBytes,
222
+ },
223
+ nextAction: 'finalize',
224
+ });
225
+ }
226
+ await finalizeWriteBufferSession({
227
+ contentPath: session.contentPath,
228
+ metaPath: session.metaPath,
229
+ targetPath,
230
+ });
231
+ await cleanupWriteBufferSessionFiles(session);
232
+ await this.removePointer(session.bufferId);
233
+
234
+ return this.successResponse({
235
+ ok: true,
236
+ code: 'WRITE_FILE_FINALIZE_OK',
237
+ message: 'Buffered content finalized to target file',
238
+ nextAction: 'none',
239
+ });
240
+ }
241
+
242
+ private async createOrLoadSession(
243
+ targetPath: string,
244
+ sessionSeedId?: string,
245
+ explicitBufferId?: string,
246
+ expectedTargetPath?: string
247
+ ): Promise<{ contentPath: string; metaPath: string; rawArgsPath: string }> {
248
+ if (explicitBufferId) {
249
+ const loadedSession = await this.loadBufferedSession(explicitBufferId);
250
+ if (loadedSession) {
251
+ const loaded = loadedSession.session;
252
+ if (
253
+ expectedTargetPath &&
254
+ loaded.targetPath &&
255
+ path.resolve(loaded.targetPath) !== path.resolve(expectedTargetPath)
256
+ ) {
257
+ throw new Error('bufferId target path mismatch');
258
+ }
259
+ return loaded;
260
+ }
261
+ }
262
+
263
+ const requestedId = explicitBufferId || sessionSeedId || `write_file_${randomUUID()}`;
264
+ const session = await createWriteBufferSession({
265
+ messageId: `write_file_${Date.now()}`,
266
+ toolCallId: requestedId,
267
+ targetPath,
268
+ baseDir: this.bufferBaseDir,
269
+ });
270
+ await this.savePointer(session.bufferId, session.metaPath);
271
+ return session;
272
+ }
273
+
274
+ private successResponse(payload: WriteFileResponse): ToolResult {
275
+ return {
276
+ success: payload.ok,
277
+ output: JSON.stringify(payload),
278
+ metadata: payload as unknown as Record<string, unknown>,
279
+ };
280
+ }
281
+
282
+ private validateAndResolveRequiredPath(
283
+ inputPath: string | undefined,
284
+ mode: 'direct',
285
+ context?: ToolExecutionContext
286
+ ): string {
287
+ if (!inputPath) {
288
+ throw new Error(`path is required for ${mode} mode`);
289
+ }
290
+ return this.validateAndResolvePath(inputPath, context);
291
+ }
292
+
293
+ private validateAndResolvePath(inputPath: string, context?: ToolExecutionContext): string {
294
+ const resolved = path.isAbsolute(inputPath)
295
+ ? path.resolve(inputPath)
296
+ : path.resolve(process.cwd(), inputPath);
297
+ const assessment = assessPathAccess(resolved, this.allowedDirectories, 'PATH_NOT_ALLOWED');
298
+ if (!assessment.allowed && context?.confirmationApproved !== true) {
299
+ throw new Error(`Path is outside allowed directories: ${inputPath}`);
300
+ }
301
+ return assessment.normalizedCandidate;
302
+ }
303
+
304
+ private resolveFinalizeTargetPath(
305
+ inputPath: string | undefined,
306
+ sessionTargetPath: string | undefined,
307
+ context?: ToolExecutionContext
308
+ ): string {
309
+ if (inputPath) {
310
+ return this.validateAndResolvePath(inputPath, context);
311
+ }
312
+ if (sessionTargetPath) {
313
+ return this.validateAndResolvePath(sessionTargetPath, context);
314
+ }
315
+ throw new Error('path is required for finalize mode when buffer session has no target path');
316
+ }
317
+
318
+ private normalizeAllowedDirectory(dir: string): string {
319
+ const resolved = path.resolve(dir);
320
+ try {
321
+ return fs.realpathSync(resolved);
322
+ } catch {
323
+ return resolved;
324
+ }
325
+ }
326
+
327
+ private async writeAtomically(targetPath: string, content: string): Promise<void> {
328
+ await writeTextFileWithHistory(targetPath, content, {
329
+ source: 'write_file',
330
+ historyStore: this.historyStore,
331
+ });
332
+ }
333
+
334
+ private pointerPath(bufferId: string): string {
335
+ const safeId = bufferId.replace(/[^a-zA-Z0-9_-]/g, '_');
336
+ return path.join(this.bufferBaseDir, `${safeId}.pointer.json`);
337
+ }
338
+
339
+ private async savePointer(bufferId: string, metaPath: string): Promise<void> {
340
+ const pointerPath = this.pointerPath(bufferId);
341
+ const pointer: SessionPointer = { metaPath };
342
+ await fs.promises.writeFile(pointerPath, JSON.stringify(pointer), 'utf8');
343
+ }
344
+
345
+ private async loadBufferedSession(bufferId: string): Promise<LoadedBufferSession | null> {
346
+ for (const dir of this.getCandidateBufferDirs()) {
347
+ const pointer = await this.readPointer(path.join(dir, this.pointerFileName(bufferId)));
348
+ if (pointer) {
349
+ const session = await loadWriteBufferSession(pointer.metaPath);
350
+ return { session };
351
+ }
352
+ }
353
+
354
+ const fallback = await this.findFallbackSessionByBufferId(bufferId);
355
+ if (!fallback) {
356
+ return null;
357
+ }
358
+ return { session: fallback };
359
+ }
360
+
361
+ private async findFallbackSessionByBufferId(bufferId: string): Promise<{
362
+ contentPath: string;
363
+ metaPath: string;
364
+ rawArgsPath: string;
365
+ targetPath?: string;
366
+ bufferId: string;
367
+ contentBytes: number;
368
+ } | null> {
369
+ const safeId = bufferId.replace(/[^a-zA-Z0-9_-]/g, '_');
370
+ for (const dir of this.getCandidateBufferDirs()) {
371
+ let entries: string[] = [];
372
+ try {
373
+ entries = await fs.promises.readdir(dir);
374
+ } catch {
375
+ continue;
376
+ }
377
+
378
+ const candidates = entries
379
+ .filter((entry) => entry.endsWith('.meta.json') && entry.includes(safeId))
380
+ .sort()
381
+ .reverse();
382
+
383
+ for (const entry of candidates) {
384
+ try {
385
+ const session = await loadWriteBufferSession(path.join(dir, entry));
386
+ if (session.bufferId !== bufferId) {
387
+ continue;
388
+ }
389
+ if (!session.targetPath) {
390
+ const inferredTargetPath = await this.extractTargetPathFromRawArgs(session.rawArgsPath);
391
+ if (inferredTargetPath) {
392
+ session.targetPath = inferredTargetPath;
393
+ }
394
+ }
395
+ return session;
396
+ } catch {
397
+ continue;
398
+ }
399
+ }
400
+ }
401
+ return null;
402
+ }
403
+
404
+ private async extractTargetPathFromRawArgs(rawArgsPath: string): Promise<string | undefined> {
405
+ try {
406
+ const rawArgs = await fs.promises.readFile(rawArgsPath, 'utf8');
407
+ return this.extractJsonStringField(rawArgs, 'path');
408
+ } catch {
409
+ return undefined;
410
+ }
411
+ }
412
+
413
+ private extractJsonStringField(raw: string, fieldName: string): string | undefined {
414
+ const markerMatch = new RegExp(`"${fieldName}"\\s*:\\s*"`, 'm').exec(raw);
415
+ if (!markerMatch || typeof markerMatch.index !== 'number') {
416
+ return undefined;
417
+ }
418
+
419
+ let cursor = markerMatch.index + markerMatch[0].length;
420
+ let output = '';
421
+
422
+ while (cursor < raw.length) {
423
+ const ch = raw[cursor];
424
+ if (ch === '"') {
425
+ return output;
426
+ }
427
+ if (ch !== '\\') {
428
+ output += ch;
429
+ cursor += 1;
430
+ continue;
431
+ }
432
+
433
+ if (cursor + 1 >= raw.length) {
434
+ return output;
435
+ }
436
+
437
+ const esc = raw[cursor + 1];
438
+ if (esc === '"' || esc === '\\' || esc === '/') {
439
+ output += esc;
440
+ cursor += 2;
441
+ } else if (esc === 'b') {
442
+ output += '\b';
443
+ cursor += 2;
444
+ } else if (esc === 'f') {
445
+ output += '\f';
446
+ cursor += 2;
447
+ } else if (esc === 'n') {
448
+ output += '\n';
449
+ cursor += 2;
450
+ } else if (esc === 'r') {
451
+ output += '\r';
452
+ cursor += 2;
453
+ } else if (esc === 't') {
454
+ output += '\t';
455
+ cursor += 2;
456
+ } else if (esc === 'u') {
457
+ const unicodeHex = raw.slice(cursor + 2, cursor + 6);
458
+ if (!/^[0-9a-fA-F]{4}$/.test(unicodeHex)) {
459
+ return output;
460
+ }
461
+ output += String.fromCharCode(parseInt(unicodeHex, 16));
462
+ cursor += 6;
463
+ } else {
464
+ output += esc;
465
+ cursor += 2;
466
+ }
467
+ }
468
+
469
+ return output || undefined;
470
+ }
471
+
472
+ private pointerFileName(bufferId: string): string {
473
+ const safeId = bufferId.replace(/[^a-zA-Z0-9_-]/g, '_');
474
+ return `${safeId}.pointer.json`;
475
+ }
476
+
477
+ private getCandidateBufferDirs(): string[] {
478
+ return getWriteBufferCandidateDirs(this.bufferBaseDir);
479
+ }
480
+
481
+ private async readPointer(pointerPath: string): Promise<SessionPointer | null> {
482
+ try {
483
+ const content = await fs.promises.readFile(pointerPath, 'utf8');
484
+ return JSON.parse(content) as SessionPointer;
485
+ } catch {
486
+ return null;
487
+ }
488
+ }
489
+
490
+ private async removePointer(bufferId: string): Promise<void> {
491
+ await Promise.all(
492
+ this.getCandidateBufferDirs().map((dir) =>
493
+ fs.promises.rm(path.join(dir, this.pointerFileName(bufferId)), { force: true })
494
+ )
495
+ );
496
+ }
497
+ }