@namzu/sdk 1.3.0 → 2.0.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 +106 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts +12 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts.map +1 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.js +73 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.js.map +1 -0
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +10 -1
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/config/__tests__/compaction-budget-schema.test.d.ts +18 -0
- package/dist/config/__tests__/compaction-budget-schema.test.d.ts.map +1 -0
- package/dist/config/__tests__/compaction-budget-schema.test.js +96 -0
- package/dist/config/__tests__/compaction-budget-schema.test.js.map +1 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +20 -11
- package/dist/config/runtime.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +2 -1
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +3 -1
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/provider/__tests__/errors.test.d.ts +2 -0
- package/dist/provider/__tests__/errors.test.d.ts.map +1 -0
- package/dist/provider/__tests__/errors.test.js +61 -0
- package/dist/provider/__tests__/errors.test.js.map +1 -0
- package/dist/provider/errors.d.ts +108 -0
- package/dist/provider/errors.d.ts.map +1 -0
- package/dist/provider/errors.js +311 -0
- package/dist/provider/errors.js.map +1 -0
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +1 -0
- package/dist/provider/index.js.map +1 -1
- package/dist/public-runtime.d.ts +11 -11
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +11 -11
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +45 -12
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/tool/execute.test.js +104 -7
- package/dist/registry/tool/execute.test.js.map +1 -1
- package/dist/registry/toolset/catalog.d.ts.map +1 -1
- package/dist/registry/toolset/catalog.js +17 -4
- package/dist/registry/toolset/catalog.js.map +1 -1
- package/dist/registry/toolset/catalog.test.js +101 -0
- package/dist/registry/toolset/catalog.test.js.map +1 -1
- package/dist/runtime/query/__tests__/capability-negotiation.test.js +79 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -1
- package/dist/runtime/query/__tests__/deferred-tools.test.js +100 -0
- package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -1
- package/dist/runtime/query/__tests__/long-document-flow.test.js +12 -12
- package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -1
- package/dist/runtime/query/__tests__/stream-recovery.test.js +55 -2
- package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
- package/dist/runtime/query/executor.js +2 -2
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +10 -0
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts +32 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js +152 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +52 -12
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +11 -5
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +11 -1
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/builtins/__tests__/edit.test.js +216 -10
- package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/payload-budget.test.js +30 -16
- package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/write-file.test.js +37 -13
- package/dist/tools/builtins/__tests__/write-file.test.js.map +1 -1
- package/dist/tools/builtins/atomic-write-file.d.ts +13 -0
- package/dist/tools/builtins/atomic-write-file.d.ts.map +1 -0
- package/dist/tools/builtins/atomic-write-file.js +46 -0
- package/dist/tools/builtins/atomic-write-file.js.map +1 -0
- package/dist/tools/builtins/bash.js +2 -2
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts +2 -5
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +105 -125
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/file-mutation-lock.d.ts +7 -0
- package/dist/tools/builtins/file-mutation-lock.d.ts.map +1 -0
- package/dist/tools/builtins/file-mutation-lock.js +25 -0
- package/dist/tools/builtins/file-mutation-lock.js.map +1 -0
- package/dist/tools/builtins/index.js +2 -2
- package/dist/tools/builtins/index.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts +1 -2
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +66 -40
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/__tests__/task-list.test.js +14 -0
- package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts +6 -8
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +11 -32
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/defineTool.d.ts +3 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +3 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/types/provider/chat.d.ts +9 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/error.d.ts +22 -0
- package/dist/types/provider/error.d.ts.map +1 -0
- package/dist/types/provider/error.js +2 -0
- package/dist/types/provider/error.js.map +1 -0
- package/dist/types/provider/index.d.ts +1 -0
- package/dist/types/provider/index.d.ts.map +1 -1
- package/dist/types/run/entity.d.ts +2 -0
- package/dist/types/run/entity.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +2 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +4 -0
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +20 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/compaction/__tests__/verifier-empty-reply.test.ts +96 -0
- package/src/compaction/verifier.ts +10 -1
- package/src/config/__tests__/compaction-budget-schema.test.ts +114 -0
- package/src/config/runtime.ts +20 -11
- package/src/manager/run/persistence.ts +3 -1
- package/src/provider/__tests__/errors.test.ts +85 -0
- package/src/provider/errors.ts +344 -0
- package/src/provider/index.ts +15 -0
- package/src/public-runtime.ts +53 -10
- package/src/registry/tool/execute.test.ts +121 -7
- package/src/registry/tool/execute.ts +50 -12
- package/src/registry/toolset/catalog.test.ts +118 -0
- package/src/registry/toolset/catalog.ts +26 -4
- package/src/runtime/query/__tests__/capability-negotiation.test.ts +88 -0
- package/src/runtime/query/__tests__/deferred-tools.test.ts +111 -0
- package/src/runtime/query/__tests__/long-document-flow.test.ts +12 -12
- package/src/runtime/query/__tests__/stream-recovery.test.ts +65 -2
- package/src/runtime/query/executor.ts +2 -2
- package/src/runtime/query/iteration/index.ts +15 -0
- package/src/runtime/query/iteration/phases/compaction-safe-cut.test.ts +186 -0
- package/src/runtime/query/iteration/phases/compaction.ts +54 -12
- package/src/runtime/query/iteration/stream-turn.ts +11 -6
- package/src/runtime/query/result.ts +11 -1
- package/src/tools/builtins/__tests__/edit.test.ts +270 -10
- package/src/tools/builtins/__tests__/payload-budget.test.ts +40 -20
- package/src/tools/builtins/__tests__/write-file.test.ts +52 -13
- package/src/tools/builtins/atomic-write-file.ts +58 -0
- package/src/tools/builtins/bash.ts +2 -2
- package/src/tools/builtins/edit.ts +116 -162
- package/src/tools/builtins/file-mutation-lock.ts +27 -0
- package/src/tools/builtins/index.ts +2 -2
- package/src/tools/builtins/write-file.ts +68 -44
- package/src/tools/coordinator/__tests__/task-list.test.ts +16 -0
- package/src/tools/coordinator/agent.ts +6 -8
- package/src/tools/coordinator/index.ts +11 -34
- package/src/tools/defineTool.ts +6 -0
- package/src/types/provider/chat.ts +14 -1
- package/src/types/provider/error.ts +29 -0
- package/src/types/provider/index.ts +5 -0
- package/src/types/run/entity.ts +2 -0
- package/src/types/run/events.ts +7 -1
- package/src/types/sandbox/index.ts +4 -0
- package/src/types/tool/index.ts +20 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../../src/runtime/query/result.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAkB,MAAM,oBAAoB,CAAA;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../../src/runtime/query/result.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAkB,MAAM,oBAAoB,CAAA;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAEnE,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAE7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,cAAc,CAAA;IACtB,WAAW,EAAE,WAAW,CAAA;IACxB,aAAa,EAAE,aAAa,CAAA;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,SAAS,CAAA;IACpB,YAAY,EAAE,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAA;CACvC;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,MAAM,CAAuB;gBAEzB,MAAM,EAAE,qBAAqB;IAIlC,WAAW,CAAC,QAAQ,EAAE,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC;IA8BrD,eAAe,CAAC,QAAQ,EAAE,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC;IAIzD,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC;IAsCpE,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC;CAI9B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
+
import { isProviderRequestError } from '../../provider/errors.js';
|
|
2
3
|
import { GENAI, NAMZU } from '../../telemetry/attributes.js';
|
|
3
4
|
import { toErrorMessage } from '../../utils/error.js';
|
|
4
5
|
export class ResultAssembler {
|
|
@@ -37,7 +38,15 @@ export class ResultAssembler {
|
|
|
37
38
|
async *handleError(err, rootSpan) {
|
|
38
39
|
const { runMgr, planManager, log, emitEvent, drainPending } = this.config;
|
|
39
40
|
const errorMessage = toErrorMessage(err);
|
|
40
|
-
|
|
41
|
+
const providerError = isProviderRequestError(err)
|
|
42
|
+
? {
|
|
43
|
+
kind: err.kind,
|
|
44
|
+
providerId: err.providerId,
|
|
45
|
+
...(err.status !== undefined ? { status: err.status } : {}),
|
|
46
|
+
...(err.retryAfterMs !== undefined ? { retryAfterMs: err.retryAfterMs } : {}),
|
|
47
|
+
}
|
|
48
|
+
: undefined;
|
|
49
|
+
runMgr.markFailed(errorMessage, providerError);
|
|
41
50
|
if (planManager.isActive) {
|
|
42
51
|
planManager.failPlan(errorMessage);
|
|
43
52
|
}
|
|
@@ -45,6 +54,7 @@ export class ResultAssembler {
|
|
|
45
54
|
type: 'run_failed',
|
|
46
55
|
runId: runMgr.id,
|
|
47
56
|
error: errorMessage,
|
|
57
|
+
...(providerError ? { providerError } : {}),
|
|
48
58
|
});
|
|
49
59
|
yield* drainPending();
|
|
50
60
|
rootSpan.setAttributes({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../../src/runtime/query/result.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,cAAc,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../../src/runtime/query/result.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { ToolRegistry } from '../../../registry/tool/execute.js';
|
|
6
|
+
import { atomicWriteFile } from '../atomic-write-file.js';
|
|
5
7
|
import { EditTool } from '../edit.js';
|
|
6
8
|
function makeContext(workingDirectory) {
|
|
7
9
|
return {
|
|
@@ -13,26 +15,230 @@ function makeContext(workingDirectory) {
|
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
describe('EditTool', () => {
|
|
16
|
-
it('
|
|
18
|
+
it('publishes one closed canonical replacement contract', () => {
|
|
19
|
+
const schema = EditTool.modelInputSchema;
|
|
20
|
+
expect(schema).toEqual({
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
path: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Path to the file to edit. Must not be empty.',
|
|
26
|
+
},
|
|
27
|
+
old_string: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Exact unique text from the file, without read-tool line-number prefixes. Must not be empty.',
|
|
30
|
+
},
|
|
31
|
+
new_string: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Exact replacement text. May be empty to delete old_string. Keep under 12000 characters.',
|
|
34
|
+
},
|
|
35
|
+
replace_all: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
description: 'Replace every occurrence instead of requiring one unique match.',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ['path', 'old_string', 'new_string'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
});
|
|
43
|
+
expect(EditTool.inputSchema.safeParse({
|
|
44
|
+
path: 'doc.md',
|
|
45
|
+
old_string: 'old',
|
|
46
|
+
new_string: 'new',
|
|
47
|
+
replace_all: true,
|
|
48
|
+
}).success).toBe(true);
|
|
49
|
+
for (const legacy of [
|
|
50
|
+
{ path: 'doc.md', oldStr: 'old', newStr: 'new' },
|
|
51
|
+
{ path: 'doc.md', insertLine: 'end', new_string: 'append' },
|
|
52
|
+
{
|
|
53
|
+
path: 'doc.md',
|
|
54
|
+
old_string: 'old',
|
|
55
|
+
new_string: 'new',
|
|
56
|
+
newStr: 'legacy',
|
|
57
|
+
},
|
|
58
|
+
]) {
|
|
59
|
+
expect(EditTool.inputSchema.safeParse(legacy).success, JSON.stringify(legacy)).toBe(false);
|
|
60
|
+
}
|
|
61
|
+
expect(EditTool.inputSchema.safeParse({
|
|
62
|
+
path: ' ',
|
|
63
|
+
old_string: 'old',
|
|
64
|
+
new_string: 'new',
|
|
65
|
+
}).success).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it('replaces one exact unique string', async () => {
|
|
17
68
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'));
|
|
18
69
|
writeFileSync(join(dir, 'doc.md'), 'alpha\nbeta\n');
|
|
19
|
-
const result = await EditTool.execute({
|
|
70
|
+
const result = await EditTool.execute({
|
|
71
|
+
path: 'doc.md',
|
|
72
|
+
old_string: 'beta',
|
|
73
|
+
new_string: 'gamma',
|
|
74
|
+
replace_all: false,
|
|
75
|
+
}, makeContext(dir));
|
|
20
76
|
expect(result.success).toBe(true);
|
|
21
77
|
expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('alpha\ngamma\n');
|
|
22
78
|
});
|
|
23
|
-
it('
|
|
79
|
+
it('validates a path without rewriting edge whitespace', async () => {
|
|
24
80
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'));
|
|
25
|
-
writeFileSync(join(dir, 'doc.md'), 'alpha\
|
|
26
|
-
const result = await EditTool.execute({
|
|
81
|
+
writeFileSync(join(dir, ' doc.md '), 'alpha\n');
|
|
82
|
+
const result = await EditTool.execute({
|
|
83
|
+
path: ' doc.md ',
|
|
84
|
+
old_string: 'alpha',
|
|
85
|
+
new_string: 'beta',
|
|
86
|
+
replace_all: false,
|
|
87
|
+
}, makeContext(dir));
|
|
27
88
|
expect(result.success).toBe(true);
|
|
28
|
-
expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('
|
|
89
|
+
expect(readFileSync(join(dir, ' doc.md '), 'utf-8')).toBe('beta\n');
|
|
90
|
+
expect(existsSync(join(dir, 'doc.md'))).toBe(false);
|
|
29
91
|
});
|
|
30
|
-
it('
|
|
92
|
+
it('allows deletion with an empty new_string', async () => {
|
|
31
93
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'));
|
|
32
|
-
writeFileSync(join(dir, 'doc.md'), 'alpha\n');
|
|
33
|
-
const result = await EditTool.execute({
|
|
94
|
+
writeFileSync(join(dir, 'doc.md'), 'alpha\nremove me\nomega\n');
|
|
95
|
+
const result = await EditTool.execute({
|
|
96
|
+
path: 'doc.md',
|
|
97
|
+
old_string: 'remove me\n',
|
|
98
|
+
new_string: '',
|
|
99
|
+
replace_all: false,
|
|
100
|
+
}, makeContext(dir));
|
|
34
101
|
expect(result.success).toBe(true);
|
|
35
102
|
expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('alpha\nomega\n');
|
|
36
103
|
});
|
|
104
|
+
it('replaces every exact occurrence only when replace_all is true', async () => {
|
|
105
|
+
const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'));
|
|
106
|
+
writeFileSync(join(dir, 'doc.md'), 'alpha alpha alpha');
|
|
107
|
+
const result = await EditTool.execute({
|
|
108
|
+
path: 'doc.md',
|
|
109
|
+
old_string: 'alpha',
|
|
110
|
+
new_string: 'beta',
|
|
111
|
+
replace_all: true,
|
|
112
|
+
}, makeContext(dir));
|
|
113
|
+
expect(result.success).toBe(true);
|
|
114
|
+
expect(result.data).toMatchObject({ replacements: 3 });
|
|
115
|
+
expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('beta beta beta');
|
|
116
|
+
});
|
|
117
|
+
it('refuses ambiguous exact matches instead of guessing', async () => {
|
|
118
|
+
const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'));
|
|
119
|
+
writeFileSync(join(dir, 'doc.md'), 'alpha\nalpha\n');
|
|
120
|
+
const result = await EditTool.execute({
|
|
121
|
+
path: 'doc.md',
|
|
122
|
+
old_string: 'alpha',
|
|
123
|
+
new_string: 'beta',
|
|
124
|
+
replace_all: false,
|
|
125
|
+
}, makeContext(dir));
|
|
126
|
+
expect(result.success).toBe(false);
|
|
127
|
+
expect(result.error).toContain('old_string is not unique');
|
|
128
|
+
expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('alpha\nalpha\n');
|
|
129
|
+
});
|
|
130
|
+
it('normalizes consistent CRLF/LF boundaries without fuzzy matching', async () => {
|
|
131
|
+
const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'));
|
|
132
|
+
writeFileSync(join(dir, 'doc.md'), 'alpha\r\nbeta\r\nomega\r\n');
|
|
133
|
+
const result = await EditTool.execute({
|
|
134
|
+
path: 'doc.md',
|
|
135
|
+
old_string: 'alpha\nbeta',
|
|
136
|
+
new_string: 'gamma\ndelta',
|
|
137
|
+
replace_all: false,
|
|
138
|
+
}, makeContext(dir));
|
|
139
|
+
expect(result.success).toBe(true);
|
|
140
|
+
expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('gamma\r\ndelta\r\nomega\r\n');
|
|
141
|
+
});
|
|
142
|
+
it('rejects legacy and extra fields even when execute is called directly', async () => {
|
|
143
|
+
const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'));
|
|
144
|
+
writeFileSync(join(dir, 'doc.md'), 'alpha\n');
|
|
145
|
+
for (const input of [
|
|
146
|
+
{ path: 'doc.md', oldStr: 'alpha', newStr: 'beta' },
|
|
147
|
+
{
|
|
148
|
+
path: 'doc.md',
|
|
149
|
+
old_string: 'alpha',
|
|
150
|
+
new_string: 'beta',
|
|
151
|
+
insertLine: 'end',
|
|
152
|
+
},
|
|
153
|
+
]) {
|
|
154
|
+
const result = await EditTool.execute(input, makeContext(dir));
|
|
155
|
+
expect(result.success, JSON.stringify(input)).toBe(false);
|
|
156
|
+
expect(result.error).toContain('Invalid edit input');
|
|
157
|
+
}
|
|
158
|
+
expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('alpha\n');
|
|
159
|
+
});
|
|
160
|
+
it('returns one canonical recovery shape after registry validation fails', async () => {
|
|
161
|
+
const registry = new ToolRegistry();
|
|
162
|
+
registry.register(EditTool);
|
|
163
|
+
const result = await registry.execute('edit', { insertLine: '"end"', newStr: 'content' }, makeContext('/tmp'));
|
|
164
|
+
expect(result.success).toBe(false);
|
|
165
|
+
expect(result.error).toContain('Validation failed for "edit":');
|
|
166
|
+
expect(result.error).toContain('Required: path: string');
|
|
167
|
+
expect(result.error).toContain('{"path":"file.md","old_string":"exact unique text","new_string":"replacement text"}');
|
|
168
|
+
expect(result.error).not.toContain('Accepted shapes');
|
|
169
|
+
});
|
|
170
|
+
it('serializes independent EditTool read-modify-write cycles for one sandbox path', async () => {
|
|
171
|
+
let body = Buffer.from('alpha');
|
|
172
|
+
let reads = 0;
|
|
173
|
+
let writes = 0;
|
|
174
|
+
let releaseFirstWrite = () => { };
|
|
175
|
+
let markFirstWriteStarted = () => { };
|
|
176
|
+
const firstWriteStarted = new Promise((resolve) => {
|
|
177
|
+
markFirstWriteStarted = resolve;
|
|
178
|
+
});
|
|
179
|
+
const firstWriteGate = new Promise((resolve) => {
|
|
180
|
+
releaseFirstWrite = resolve;
|
|
181
|
+
});
|
|
182
|
+
const sandbox = {
|
|
183
|
+
id: 'sbx_edit_lock',
|
|
184
|
+
status: 'ready',
|
|
185
|
+
rootDir: '/workspace',
|
|
186
|
+
environment: 'basic',
|
|
187
|
+
async readFile() {
|
|
188
|
+
reads += 1;
|
|
189
|
+
return Buffer.from(body);
|
|
190
|
+
},
|
|
191
|
+
async writeFile(_path, content) {
|
|
192
|
+
writes += 1;
|
|
193
|
+
if (writes === 1) {
|
|
194
|
+
markFirstWriteStarted();
|
|
195
|
+
await firstWriteGate;
|
|
196
|
+
}
|
|
197
|
+
body = Buffer.isBuffer(content) ? Buffer.from(content) : Buffer.from(content);
|
|
198
|
+
},
|
|
199
|
+
async exec() {
|
|
200
|
+
throw new Error('not used');
|
|
201
|
+
},
|
|
202
|
+
async listFiles() {
|
|
203
|
+
return [];
|
|
204
|
+
},
|
|
205
|
+
async destroy() { },
|
|
206
|
+
};
|
|
207
|
+
const firstContext = { ...makeContext('/workspace'), sandbox };
|
|
208
|
+
const secondContext = { ...makeContext('/workspace'), sandbox };
|
|
209
|
+
const first = EditTool.execute({
|
|
210
|
+
path: 'doc.md',
|
|
211
|
+
old_string: 'alpha',
|
|
212
|
+
new_string: 'beta',
|
|
213
|
+
replace_all: false,
|
|
214
|
+
}, firstContext);
|
|
215
|
+
await firstWriteStarted;
|
|
216
|
+
const second = EditTool.execute({
|
|
217
|
+
path: 'doc.md',
|
|
218
|
+
old_string: 'beta',
|
|
219
|
+
new_string: 'gamma',
|
|
220
|
+
replace_all: false,
|
|
221
|
+
}, secondContext);
|
|
222
|
+
await Promise.resolve();
|
|
223
|
+
expect(reads).toBe(1);
|
|
224
|
+
releaseFirstWrite();
|
|
225
|
+
const results = await Promise.all([first, second]);
|
|
226
|
+
expect(results.every((result) => result.success)).toBe(true);
|
|
227
|
+
expect(reads).toBe(2);
|
|
228
|
+
expect(writes).toBe(2);
|
|
229
|
+
expect(body.toString('utf-8')).toBe('gamma');
|
|
230
|
+
});
|
|
231
|
+
it('leaves the original intact when an atomic local write fails before commit', async () => {
|
|
232
|
+
const dir = mkdtempSync(join(tmpdir(), 'namzu-atomic-write-'));
|
|
233
|
+
const filePath = join(dir, 'doc.md');
|
|
234
|
+
writeFileSync(filePath, 'original');
|
|
235
|
+
await expect(atomicWriteFile(filePath, 'replacement', {
|
|
236
|
+
beforeCommit: async () => {
|
|
237
|
+
throw new Error('injected pre-commit failure');
|
|
238
|
+
},
|
|
239
|
+
})).rejects.toThrow('injected pre-commit failure');
|
|
240
|
+
expect(readFileSync(filePath, 'utf-8')).toBe('original');
|
|
241
|
+
expect(readdirSync(dir).filter((entry) => entry.includes('.namzu-'))).toEqual([]);
|
|
242
|
+
});
|
|
37
243
|
});
|
|
38
244
|
//# sourceMappingURL=edit.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit.test.js","sourceRoot":"","sources":["../../../../src/tools/builtins/__tests__/edit.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"edit.test.js","sourceRoot":"","sources":["../../../../src/tools/builtins/__tests__/edit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,8 +11,8 @@ describe('filesystem tool payload budgeting', () => {
|
|
|
11
11
|
expect(() => WriteFileTool.inputSchema.parse({ path: 'outputs/long.md', content: oversized })).not.toThrow();
|
|
12
12
|
expect(() => EditTool.inputSchema.parse({
|
|
13
13
|
path: 'outputs/long.md',
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
old_string: '{{SECTION}}',
|
|
15
|
+
new_string: oversized,
|
|
16
16
|
replace_all: false,
|
|
17
17
|
})).not.toThrow();
|
|
18
18
|
});
|
|
@@ -21,7 +21,7 @@ describe('filesystem tool payload budgeting', () => {
|
|
|
21
21
|
expect(names).toContain('edit');
|
|
22
22
|
expect(names).not.toContain('append');
|
|
23
23
|
});
|
|
24
|
-
it('assembles long documents
|
|
24
|
+
it('assembles replay-safe long documents by advancing an exact marker', async () => {
|
|
25
25
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-long-doc-'));
|
|
26
26
|
const ctx = {
|
|
27
27
|
runId: 'run_test',
|
|
@@ -30,12 +30,12 @@ describe('filesystem tool payload budgeting', () => {
|
|
|
30
30
|
env: {},
|
|
31
31
|
log: () => { },
|
|
32
32
|
};
|
|
33
|
-
const opening = '# Long document regression\n\n{{
|
|
33
|
+
const opening = '# Long document regression\n\n{{CHUNK_001}}\n';
|
|
34
34
|
const chunks = Array.from({ length: 6 }, (_, sectionIndex) => {
|
|
35
35
|
const lines = Array.from({ length: 45 }, (_, lineIndex) => {
|
|
36
36
|
const section = sectionIndex + 1;
|
|
37
37
|
const line = lineIndex + 1;
|
|
38
|
-
return `Section ${section}.${line}: this bounded paragraph proves long-form output grows through
|
|
38
|
+
return `Section ${section}.${line}: this bounded paragraph proves long-form output grows through exact marker edits rather than one oversized JSON tool argument.`;
|
|
39
39
|
});
|
|
40
40
|
const chunk = [`## Section ${sectionIndex + 1}`, ...lines, ''].join('\n');
|
|
41
41
|
expect(chunk.length).toBeLessThan(12_000);
|
|
@@ -43,24 +43,38 @@ describe('filesystem tool payload budgeting', () => {
|
|
|
43
43
|
});
|
|
44
44
|
const writeResult = await WriteFileTool.execute({ path: 'outputs/regression-long-document.md', content: opening }, ctx);
|
|
45
45
|
expect(writeResult.success).toBe(true);
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
newStr: chunks[0],
|
|
50
|
-
replace_all: false,
|
|
51
|
-
}, ctx);
|
|
52
|
-
expect(firstEdit.success).toBe(true);
|
|
53
|
-
for (const chunk of chunks.slice(1)) {
|
|
46
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
47
|
+
const currentMarker = `{{CHUNK_${String(index + 1).padStart(3, '0')}}}`;
|
|
48
|
+
const nextMarker = `{{CHUNK_${String(index + 2).padStart(3, '0')}}}`;
|
|
54
49
|
const result = await EditTool.execute({
|
|
55
50
|
path: 'outputs/regression-long-document.md',
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
old_string: currentMarker,
|
|
52
|
+
new_string: `${chunk}\n${nextMarker}`,
|
|
58
53
|
replace_all: false,
|
|
59
54
|
}, ctx);
|
|
60
55
|
expect(result.success).toBe(true);
|
|
56
|
+
if (index === 0) {
|
|
57
|
+
const beforeReplay = readFileSync(join(dir, 'outputs/regression-long-document.md'), 'utf-8');
|
|
58
|
+
const replay = await EditTool.execute({
|
|
59
|
+
path: 'outputs/regression-long-document.md',
|
|
60
|
+
old_string: currentMarker,
|
|
61
|
+
new_string: `${chunk}\n${nextMarker}`,
|
|
62
|
+
replace_all: false,
|
|
63
|
+
}, ctx);
|
|
64
|
+
expect(replay.success).toBe(false);
|
|
65
|
+
expect(readFileSync(join(dir, 'outputs/regression-long-document.md'), 'utf-8')).toBe(beforeReplay);
|
|
66
|
+
}
|
|
61
67
|
}
|
|
68
|
+
const finalMarker = `{{CHUNK_${String(chunks.length + 1).padStart(3, '0')}}}`;
|
|
69
|
+
const finalize = await EditTool.execute({
|
|
70
|
+
path: 'outputs/regression-long-document.md',
|
|
71
|
+
old_string: finalMarker,
|
|
72
|
+
new_string: '',
|
|
73
|
+
replace_all: false,
|
|
74
|
+
}, ctx);
|
|
75
|
+
expect(finalize.success).toBe(true);
|
|
62
76
|
const final = readFileSync(join(dir, 'outputs/regression-long-document.md'), 'utf-8');
|
|
63
|
-
expect(final).not.toContain('{{
|
|
77
|
+
expect(final).not.toContain('{{CHUNK_');
|
|
64
78
|
expect(final.split('\n').length).toBeGreaterThan(250);
|
|
65
79
|
expect(final).toContain('## Section 1');
|
|
66
80
|
expect(final).toContain('## Section 6');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload-budget.test.js","sourceRoot":"","sources":["../../../../src/tools/builtins/__tests__/payload-budget.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEhD,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IAClD,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAChF,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAEpC,MAAM,CAAC,GAAG,EAAE,CACX,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAChF,CAAC,GAAG,CAAC,OAAO,EAAE,CAAA;QACf,MAAM,CAAC,GAAG,EAAE,CACX,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC;YAC1B,IAAI,EAAE,iBAAiB;YACvB,
|
|
1
|
+
{"version":3,"file":"payload-budget.test.js","sourceRoot":"","sources":["../../../../src/tools/builtins/__tests__/payload-budget.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,10 +6,10 @@ import { WriteFileTool } from '../write-file.js';
|
|
|
6
6
|
function makeTracker() {
|
|
7
7
|
const set = new Set();
|
|
8
8
|
return {
|
|
9
|
-
recordRead: (
|
|
10
|
-
set.add(
|
|
9
|
+
recordRead: (key) => {
|
|
10
|
+
set.add(key);
|
|
11
11
|
},
|
|
12
|
-
hasRead: (
|
|
12
|
+
hasRead: (key) => set.has(key),
|
|
13
13
|
keys: () => Array.from(set),
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -23,7 +23,27 @@ function makeContext(workingDirectory, tracker) {
|
|
|
23
23
|
fileReadTracker: tracker,
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
describe('WriteFileTool — read-before-overwrite invariant', () => {
|
|
26
|
+
describe('WriteFileTool — canonical contract and read-before-overwrite invariant', () => {
|
|
27
|
+
it('publishes one closed path + content contract', () => {
|
|
28
|
+
expect(WriteFileTool.modelInputSchema).toEqual({
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
path: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Non-empty path to the file to write.',
|
|
34
|
+
},
|
|
35
|
+
content: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Complete bounded file body. May be empty only for an intentionally empty file.',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ['path', 'content'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
});
|
|
43
|
+
expect(WriteFileTool.inputSchema.safeParse({ path: 'doc.md', content: '' }).success).toBe(true);
|
|
44
|
+
expect(WriteFileTool.inputSchema.safeParse({ path: 'doc.md', newStr: 'legacy' }).success).toBe(false);
|
|
45
|
+
expect(WriteFileTool.inputSchema.safeParse({ path: ' ', content: 'body' }).success).toBe(false);
|
|
46
|
+
});
|
|
27
47
|
it('writes a new file without requiring a prior read', async () => {
|
|
28
48
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-write-'));
|
|
29
49
|
const tracker = makeTracker();
|
|
@@ -33,13 +53,17 @@ describe('WriteFileTool — read-before-overwrite invariant', () => {
|
|
|
33
53
|
expect(readFileSync(join(dir, 'fresh.txt'), 'utf-8')).toBe('hello');
|
|
34
54
|
expect(tracker.hasRead(join(dir, 'fresh.txt'))).toBe(true);
|
|
35
55
|
});
|
|
36
|
-
it('
|
|
56
|
+
it('preserves edge whitespace in a valid path', async () => {
|
|
37
57
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-write-'));
|
|
38
|
-
const
|
|
39
|
-
const ctx = makeContext(dir, tracker);
|
|
40
|
-
const result = await WriteFileTool.execute({ path: 'fresh.txt', newStr: 'hello' }, ctx);
|
|
58
|
+
const result = await WriteFileTool.execute({ path: ' fresh.txt ', content: 'hello' }, makeContext(dir));
|
|
41
59
|
expect(result.success).toBe(true);
|
|
42
|
-
expect(readFileSync(join(dir, 'fresh.txt'), 'utf-8')).toBe('hello');
|
|
60
|
+
expect(readFileSync(join(dir, ' fresh.txt '), 'utf-8')).toBe('hello');
|
|
61
|
+
});
|
|
62
|
+
it('rejects legacy content aliases even when execute is called directly', async () => {
|
|
63
|
+
const dir = mkdtempSync(join(tmpdir(), 'namzu-write-'));
|
|
64
|
+
const result = await WriteFileTool.execute({ path: 'fresh.txt', newStr: 'legacy' }, makeContext(dir));
|
|
65
|
+
expect(result.success).toBe(false);
|
|
66
|
+
expect(result.error).toContain('Invalid write input');
|
|
43
67
|
});
|
|
44
68
|
it('refuses to overwrite an existing file the agent has not read', async () => {
|
|
45
69
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-write-'));
|
|
@@ -62,13 +86,13 @@ describe('WriteFileTool — read-before-overwrite invariant', () => {
|
|
|
62
86
|
expect(result.success).toBe(true);
|
|
63
87
|
expect(readFileSync(filePath, 'utf-8')).toBe('replaced');
|
|
64
88
|
});
|
|
65
|
-
it('
|
|
89
|
+
it('preserves existing hosts without a file read tracker', async () => {
|
|
66
90
|
const dir = mkdtempSync(join(tmpdir(), 'namzu-write-'));
|
|
67
|
-
writeFileSync(join(dir, '
|
|
91
|
+
writeFileSync(join(dir, 'existing.txt'), 'before');
|
|
68
92
|
const ctx = makeContext(dir);
|
|
69
|
-
const result = await WriteFileTool.execute({ path: '
|
|
93
|
+
const result = await WriteFileTool.execute({ path: 'existing.txt', content: 'after' }, ctx);
|
|
70
94
|
expect(result.success).toBe(true);
|
|
71
|
-
expect(readFileSync(join(dir, '
|
|
95
|
+
expect(readFileSync(join(dir, 'existing.txt'), 'utf-8')).toBe('after');
|
|
72
96
|
});
|
|
73
97
|
});
|
|
74
98
|
//# sourceMappingURL=write-file.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-file.test.js","sourceRoot":"","sources":["../../../../src/tools/builtins/__tests__/write-file.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEhD,SAAS,WAAW;IACnB,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,OAAO;QACN,UAAU,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"write-file.test.js","sourceRoot":"","sources":["../../../../src/tools/builtins/__tests__/write-file.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface AtomicWriteHooks {
|
|
2
|
+
/** Internal fault-injection seam; production callers never pass this. */
|
|
3
|
+
beforeCommit?: () => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Commit a complete file body without exposing a truncated destination.
|
|
7
|
+
*
|
|
8
|
+
* The temp file lives beside the destination so rename stays on one
|
|
9
|
+
* filesystem. A failed pre-commit write leaves the original untouched.
|
|
10
|
+
*/
|
|
11
|
+
export declare function atomicWriteFile(filePath: string, content: string, hooks?: AtomicWriteHooks): Promise<void>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=atomic-write-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write-file.d.ts","sourceRoot":"","sources":["../../../src/tools/builtins/atomic-write-file.ts"],"names":[],"mappings":"AAIA,UAAU,gBAAgB;IACzB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,gBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, rename, stat, unlink } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Commit a complete file body without exposing a truncated destination.
|
|
6
|
+
*
|
|
7
|
+
* The temp file lives beside the destination so rename stays on one
|
|
8
|
+
* filesystem. A failed pre-commit write leaves the original untouched.
|
|
9
|
+
*/
|
|
10
|
+
export async function atomicWriteFile(filePath, content, hooks = {}) {
|
|
11
|
+
const directory = dirname(filePath);
|
|
12
|
+
await mkdir(directory, { recursive: true });
|
|
13
|
+
const existingMode = await stat(filePath)
|
|
14
|
+
.then((value) => value.mode)
|
|
15
|
+
.catch(() => undefined);
|
|
16
|
+
const tempPath = join(directory, `.${basename(filePath)}.namzu-${process.pid}-${randomUUID()}.tmp`);
|
|
17
|
+
let handle;
|
|
18
|
+
let committed = false;
|
|
19
|
+
try {
|
|
20
|
+
handle = await open(tempPath, 'wx', existingMode ?? 0o666);
|
|
21
|
+
await handle.writeFile(content, 'utf-8');
|
|
22
|
+
await handle.sync();
|
|
23
|
+
await handle.close();
|
|
24
|
+
handle = undefined;
|
|
25
|
+
await hooks.beforeCommit?.();
|
|
26
|
+
await rename(tempPath, filePath);
|
|
27
|
+
committed = true;
|
|
28
|
+
await syncDirectory(directory);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await handle?.close().catch(() => undefined);
|
|
32
|
+
if (!committed)
|
|
33
|
+
await unlink(tempPath).catch(() => undefined);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function syncDirectory(directory) {
|
|
37
|
+
let handle;
|
|
38
|
+
try {
|
|
39
|
+
handle = await open(directory, 'r');
|
|
40
|
+
await handle.sync();
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await handle?.close().catch(() => undefined);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=atomic-write-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write-file.js","sourceRoot":"","sources":["../../../src/tools/builtins/atomic-write-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAmB,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAOnD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,QAAgB,EAChB,OAAe,EACf,QAA0B,EAAE;IAE5B,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACnC,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE3C,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;SACvC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC3B,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IACxB,MAAM,QAAQ,GAAG,IAAI,CACpB,SAAS,EACT,IAAI,QAAQ,CAAC,QAAQ,CAAC,UAAU,OAAO,CAAC,GAAG,IAAI,UAAU,EAAE,MAAM,CACjE,CAAA;IACD,IAAI,MAA8B,CAAA;IAClC,IAAI,SAAS,GAAG,KAAK,CAAA;IAErB,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,YAAY,IAAI,KAAK,CAAC,CAAA;QAC1D,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACxC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QACnB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QACpB,MAAM,GAAG,SAAS,CAAA;QAClB,MAAM,KAAK,CAAC,YAAY,EAAE,EAAE,CAAA;QAC5B,MAAM,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAChC,SAAS,GAAG,IAAI,CAAA;QAChB,MAAM,aAAa,CAAC,SAAS,CAAC,CAAA;IAC/B,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAC5C,IAAI,CAAC,SAAS;YAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAC9D,CAAC;AACF,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,SAAiB;IAC7C,IAAI,MAA8B,CAAA;IAClC,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;QACnC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;IACpB,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAC7C,CAAC;AACF,CAAC"}
|