@myagentroam/node 0.9.70 → 0.9.71
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/dist/capabilities.js +1 -1
- package/dist/connector.js +1 -2
- package/dist/service/direct-transfer-service.js +5 -14
- package/dist/service/workspace-file-service.js +8 -36
- package/dist/service/workspace-upload-service.js +5 -26
- package/dist/service/workspace-workbench-service.js +3 -23
- package/package.json +3 -3
package/dist/capabilities.js
CHANGED
package/dist/connector.js
CHANGED
|
@@ -1401,7 +1401,6 @@ export class NodeConnector {
|
|
|
1401
1401
|
const upload = await this.workspaceUploadService.createDirect(workspace, {
|
|
1402
1402
|
path: descriptor.resource.path,
|
|
1403
1403
|
size: descriptor.size,
|
|
1404
|
-
sha256: descriptor.sha256,
|
|
1405
1404
|
overwrite: descriptor.overwrite === true,
|
|
1406
1405
|
...(descriptor.composerAttachment === undefined
|
|
1407
1406
|
? {}
|
|
@@ -1410,7 +1409,7 @@ export class NodeConnector {
|
|
|
1410
1409
|
return {
|
|
1411
1410
|
offset: upload.receivedBytes,
|
|
1412
1411
|
write: (bytes) => this.workspaceUploadService.writeDirect(upload.uploadId, bytes),
|
|
1413
|
-
complete: (size
|
|
1412
|
+
complete: (size) => this.workspaceUploadService.completeDirect(upload.uploadId, size),
|
|
1414
1413
|
cancel: () => this.workspaceUploadService.cancel(upload.uploadId)
|
|
1415
1414
|
};
|
|
1416
1415
|
},
|
|
@@ -275,7 +275,6 @@ export class DirectTransferService {
|
|
|
275
275
|
async channelMessage(transfer, data) {
|
|
276
276
|
const descriptor = transfer.command.transfer;
|
|
277
277
|
const expectedSize = transferredSize(descriptor);
|
|
278
|
-
const expectedSha256 = transferredSha256(descriptor);
|
|
279
278
|
if (descriptor.direction === 'DOWNLOAD') {
|
|
280
279
|
if (typeof data !== 'string')
|
|
281
280
|
throw new Error('DIRECT_FRAME_INVALID');
|
|
@@ -285,8 +284,7 @@ export class DirectTransferService {
|
|
|
285
284
|
this.emit(transfer.command.directSessionId, {
|
|
286
285
|
type: 'direct.complete',
|
|
287
286
|
directSessionId: transfer.command.directSessionId,
|
|
288
|
-
size: expectedSize
|
|
289
|
-
sha256: expectedSha256
|
|
287
|
+
size: expectedSize
|
|
290
288
|
});
|
|
291
289
|
await this.removeTransfer(transfer);
|
|
292
290
|
return;
|
|
@@ -295,15 +293,14 @@ export class DirectTransferService {
|
|
|
295
293
|
const control = directDataControlFrameSchema.parse(JSON.parse(data));
|
|
296
294
|
if (control.type !== 'COMPLETE' || transfer.upload === undefined)
|
|
297
295
|
throw new Error('DIRECT_FRAME_INVALID');
|
|
298
|
-
if (control.size !== descriptor.size
|
|
296
|
+
if (control.size !== descriptor.size)
|
|
299
297
|
throw new Error('DIRECT_INTEGRITY_FAILED');
|
|
300
|
-
await transfer.upload.complete(control.size
|
|
298
|
+
await transfer.upload.complete(control.size);
|
|
301
299
|
transfer.uploadCommitted = true;
|
|
302
300
|
this.emit(transfer.command.directSessionId, {
|
|
303
301
|
type: 'direct.complete',
|
|
304
302
|
directSessionId: transfer.command.directSessionId,
|
|
305
|
-
size: control.size
|
|
306
|
-
sha256: control.sha256
|
|
303
|
+
size: control.size
|
|
307
304
|
});
|
|
308
305
|
await this.removeTransfer(transfer);
|
|
309
306
|
return;
|
|
@@ -332,7 +329,6 @@ export class DirectTransferService {
|
|
|
332
329
|
throw new Error('DIRECT_DOWNLOAD_UNAVAILABLE');
|
|
333
330
|
const source = await this.options.openDownload(descriptor);
|
|
334
331
|
const expectedSize = transferredSize(descriptor);
|
|
335
|
-
const expectedSha256 = transferredSha256(descriptor);
|
|
336
332
|
transfer.channel?.send(JSON.stringify({ type: 'ACK', offset: source.offset }));
|
|
337
333
|
this.emit(transfer.command.directSessionId, {
|
|
338
334
|
type: 'direct.opened',
|
|
@@ -350,7 +346,7 @@ export class DirectTransferService {
|
|
|
350
346
|
const end = Math.min(chunk.byteLength, offset + 256 * 1024);
|
|
351
347
|
channel.send(chunk.buffer.slice(chunk.byteOffset + offset, chunk.byteOffset + end));
|
|
352
348
|
}
|
|
353
|
-
transfer.channel?.send(JSON.stringify({ type: 'COMPLETE', size: expectedSize
|
|
349
|
+
transfer.channel?.send(JSON.stringify({ type: 'COMPLETE', size: expectedSize }));
|
|
354
350
|
}
|
|
355
351
|
async waitForSendCapacity(transfer, channel) {
|
|
356
352
|
if (channel.readyState !== 'open')
|
|
@@ -433,11 +429,6 @@ function transferredSize(descriptor) {
|
|
|
433
429
|
? descriptor.range.length
|
|
434
430
|
: descriptor.size;
|
|
435
431
|
}
|
|
436
|
-
function transferredSha256(descriptor) {
|
|
437
|
-
return descriptor.direction === 'DOWNLOAD' && descriptor.range !== undefined
|
|
438
|
-
? descriptor.range.sha256
|
|
439
|
-
: descriptor.sha256;
|
|
440
|
-
}
|
|
441
432
|
function openFrameSessionId(data) {
|
|
442
433
|
if (typeof data !== 'string')
|
|
443
434
|
return undefined;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { tmpdir } from 'node:os';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
2
|
import { isAbsolute, win32 } from 'node:path';
|
|
4
3
|
import { listWorkspaceFiles, mutateWorkspaceFile, normalizeFilePreviewPath, readAllowedFileContentRange, readWorkspaceFileContentRange, readAllowedTextFile, readWorkspaceTextFile, writeWorkspaceTextFile, WorkspaceFileIndex } from '../workspace.js';
|
|
5
4
|
const INDEX_CACHE_LIMIT = 8;
|
|
@@ -62,7 +61,9 @@ export class WorkspaceFileService {
|
|
|
62
61
|
readDirect(workspace, input, workspacePath = workspace.path) {
|
|
63
62
|
const service = this;
|
|
64
63
|
return (async function* () {
|
|
65
|
-
const
|
|
64
|
+
const initial = (await service.readContent(workspace, { path: input.path, offset: input.range?.offset ?? 0, limit: 1 }, workspacePath));
|
|
65
|
+
if (initial.size !== input.size || initial.sourceRevision !== input.revision)
|
|
66
|
+
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
66
67
|
let offset = input.range?.offset ?? 0;
|
|
67
68
|
const endOffset = input.range === undefined ? input.size : input.range.offset + input.range.length;
|
|
68
69
|
while (offset < endOffset) {
|
|
@@ -74,7 +75,6 @@ export class WorkspaceFileService {
|
|
|
74
75
|
if (range.size !== input.size || range.sourceRevision !== input.revision)
|
|
75
76
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
76
77
|
const bytes = Buffer.from(range.contentBase64, 'base64');
|
|
77
|
-
hash.update(bytes);
|
|
78
78
|
offset += bytes.length;
|
|
79
79
|
if (bytes.length > 0)
|
|
80
80
|
yield bytes;
|
|
@@ -83,8 +83,7 @@ export class WorkspaceFileService {
|
|
|
83
83
|
if (range.nextOffset !== offset)
|
|
84
84
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
if (offset !== endOffset || hash.digest('hex') !== expectedHash)
|
|
86
|
+
if (offset !== endOffset)
|
|
88
87
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
89
88
|
})();
|
|
90
89
|
}
|
|
@@ -92,15 +91,13 @@ export class WorkspaceFileService {
|
|
|
92
91
|
if (input.offset !== undefined ||
|
|
93
92
|
input.length !== undefined ||
|
|
94
93
|
input.size !== undefined ||
|
|
95
|
-
input.
|
|
94
|
+
input.revision !== undefined) {
|
|
96
95
|
if (!Number.isSafeInteger(input.offset) ||
|
|
97
96
|
input.offset < 0 ||
|
|
98
97
|
!Number.isSafeInteger(input.length) ||
|
|
99
98
|
input.length < 1 ||
|
|
100
99
|
!Number.isSafeInteger(input.size) ||
|
|
101
100
|
input.size < 0 ||
|
|
102
|
-
typeof input.sha256 !== 'string' ||
|
|
103
|
-
!/^[a-f0-9]{64}$/u.test(input.sha256) ||
|
|
104
101
|
typeof input.revision !== 'string' ||
|
|
105
102
|
!/^[a-f0-9]{64}$/u.test(input.revision))
|
|
106
103
|
throw new Error('FILE_RANGE_INVALID');
|
|
@@ -117,37 +114,12 @@ export class WorkspaceFileService {
|
|
|
117
114
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
118
115
|
return {
|
|
119
116
|
size: expectedSize,
|
|
120
|
-
sha256: input.sha256,
|
|
121
117
|
revision: input.revision,
|
|
122
|
-
range: {
|
|
123
|
-
offset,
|
|
124
|
-
length,
|
|
125
|
-
sha256: createHash('sha256').update(bytes).digest('hex')
|
|
126
|
-
}
|
|
118
|
+
range: { offset, length }
|
|
127
119
|
};
|
|
128
120
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
let size;
|
|
132
|
-
let revision;
|
|
133
|
-
while (true) {
|
|
134
|
-
const range = (await this.readContent(workspace, { ...input, offset, limit: 512 * 1024 }, workspacePath));
|
|
135
|
-
size ??= range.size;
|
|
136
|
-
revision ??= range.sourceRevision;
|
|
137
|
-
if (range.size !== size || range.sourceRevision !== revision)
|
|
138
|
-
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
139
|
-
const bytes = Buffer.from(range.contentBase64, 'base64');
|
|
140
|
-
hash.update(bytes);
|
|
141
|
-
offset += bytes.length;
|
|
142
|
-
if (range.nextOffset === null)
|
|
143
|
-
break;
|
|
144
|
-
if (range.nextOffset !== offset)
|
|
145
|
-
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
146
|
-
}
|
|
147
|
-
if (size === undefined || revision === undefined || offset !== size)
|
|
148
|
-
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
149
|
-
const sha256 = hash.digest('hex');
|
|
150
|
-
return { size, sha256, revision };
|
|
121
|
+
const metadata = (await this.readContent(workspace, { ...input, offset: 0, limit: 1 }, workspacePath));
|
|
122
|
+
return { size: metadata.size, revision: metadata.sourceRevision };
|
|
151
123
|
}
|
|
152
124
|
async write(workspace, input) {
|
|
153
125
|
if (typeof input.path !== 'string' ||
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createReadStream } from 'node:fs';
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
3
2
|
import { lstat, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
4
3
|
import { dirname, join, posix, resolve } from 'node:path';
|
|
5
4
|
import { parseComposerAttachmentUpload, validComposerAttachmentName } from '../util/node-operation-parsers.js';
|
|
@@ -10,7 +9,6 @@ const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
|
10
9
|
export class WorkspaceUploadService {
|
|
11
10
|
state;
|
|
12
11
|
onCompleted;
|
|
13
|
-
directHashes = new Map();
|
|
14
12
|
constructor(state, onCompleted) {
|
|
15
13
|
this.state = state;
|
|
16
14
|
this.onCompleted = onCompleted;
|
|
@@ -114,7 +112,6 @@ export class WorkspaceUploadService {
|
|
|
114
112
|
mime: input.composerAttachment.mime
|
|
115
113
|
})
|
|
116
114
|
});
|
|
117
|
-
this.directHashes.set(status.uploadId, input.sha256);
|
|
118
115
|
return status;
|
|
119
116
|
}
|
|
120
117
|
async writeDirect(uploadId, bytes) {
|
|
@@ -125,19 +122,11 @@ export class WorkspaceUploadService {
|
|
|
125
122
|
});
|
|
126
123
|
return status.receivedBytes;
|
|
127
124
|
}
|
|
128
|
-
async completeDirect(uploadId, size
|
|
125
|
+
async completeDirect(uploadId, size) {
|
|
129
126
|
const upload = this.require(uploadId);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
throw new Error('UPLOAD_HASH_INVALID');
|
|
133
|
-
await upload.handle.sync();
|
|
134
|
-
const actual = await fileSha256(upload.temporaryPath);
|
|
135
|
-
if (actual !== expected) {
|
|
136
|
-
await this.cancel(uploadId);
|
|
137
|
-
throw new Error('UPLOAD_HASH_INVALID');
|
|
138
|
-
}
|
|
127
|
+
if (size !== upload.size)
|
|
128
|
+
throw new Error('UPLOAD_SIZE_INVALID');
|
|
139
129
|
await this.complete(uploadId);
|
|
140
|
-
this.directHashes.delete(uploadId);
|
|
141
130
|
}
|
|
142
131
|
status(uploadId) {
|
|
143
132
|
const upload = this.require(uploadId);
|
|
@@ -203,10 +192,8 @@ export class WorkspaceUploadService {
|
|
|
203
192
|
if (typeof uploadId !== 'string')
|
|
204
193
|
throw new Error('UPLOAD_NOT_FOUND');
|
|
205
194
|
const active = this.state.uploads.get(uploadId);
|
|
206
|
-
if (active !== undefined)
|
|
207
|
-
this.directHashes.delete(active.id);
|
|
195
|
+
if (active !== undefined)
|
|
208
196
|
return this.remove(active);
|
|
209
|
-
}
|
|
210
197
|
const attachment = this.state.composerAttachments.get(uploadId);
|
|
211
198
|
if (attachment === undefined)
|
|
212
199
|
throw new Error('UPLOAD_NOT_FOUND');
|
|
@@ -246,7 +233,6 @@ export class WorkspaceUploadService {
|
|
|
246
233
|
for (const attachment of this.state.composerAttachments.values())
|
|
247
234
|
await this.removeCompleted(attachment);
|
|
248
235
|
this.state.composerAttachments.clear();
|
|
249
|
-
this.directHashes.clear();
|
|
250
236
|
}
|
|
251
237
|
require(id) {
|
|
252
238
|
if (typeof id !== 'string')
|
|
@@ -258,7 +244,6 @@ export class WorkspaceUploadService {
|
|
|
258
244
|
}
|
|
259
245
|
async remove(upload) {
|
|
260
246
|
this.state.uploads.delete(upload.id);
|
|
261
|
-
this.directHashes.delete(upload.id);
|
|
262
247
|
clearTimeout(upload.timer);
|
|
263
248
|
await upload.handle.close().catch(() => undefined);
|
|
264
249
|
await rm(upload.temporaryPath, { force: true }).catch(() => undefined);
|
|
@@ -296,12 +281,6 @@ export class WorkspaceUploadService {
|
|
|
296
281
|
};
|
|
297
282
|
}
|
|
298
283
|
}
|
|
299
|
-
async function fileSha256(path) {
|
|
300
|
-
const hash = createHash('sha256');
|
|
301
|
-
for await (const chunk of createReadStream(path))
|
|
302
|
-
hash.update(chunk);
|
|
303
|
-
return hash.digest('hex');
|
|
304
|
-
}
|
|
305
284
|
async function safeDirectory(path) {
|
|
306
285
|
const value = await lstat(path);
|
|
307
286
|
if (!value.isDirectory() || value.isSymbolicLink())
|
|
@@ -251,7 +251,6 @@ export class WorkspaceWorkbenchService {
|
|
|
251
251
|
chunks: this.options.files.readDirect(workspace, {
|
|
252
252
|
path: descriptor.resource.path,
|
|
253
253
|
size: descriptor.size,
|
|
254
|
-
sha256: descriptor.sha256,
|
|
255
254
|
revision: descriptor.revision,
|
|
256
255
|
...(descriptor.range === undefined ? {} : { range: descriptor.range })
|
|
257
256
|
}, await this.fileRepository(workspace, input))
|
|
@@ -272,7 +271,6 @@ export class WorkspaceWorkbenchService {
|
|
|
272
271
|
return {
|
|
273
272
|
offset: descriptor.range?.offset ?? 0,
|
|
274
273
|
chunks: (async function* () {
|
|
275
|
-
const hash = createHash('sha256');
|
|
276
274
|
let offset = descriptor.range?.offset ?? 0;
|
|
277
275
|
const rangeStart = descriptor.range?.offset ?? 0;
|
|
278
276
|
const rangeEnd = rangeStart + (descriptor.range?.length ?? descriptor.size);
|
|
@@ -281,7 +279,6 @@ export class WorkspaceWorkbenchService {
|
|
|
281
279
|
if (range.size !== descriptor.size)
|
|
282
280
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
283
281
|
const bytes = Buffer.from(range.contentBase64, 'base64');
|
|
284
|
-
hash.update(bytes);
|
|
285
282
|
offset += bytes.length;
|
|
286
283
|
if (bytes.length > 0)
|
|
287
284
|
yield bytes;
|
|
@@ -290,8 +287,7 @@ export class WorkspaceWorkbenchService {
|
|
|
290
287
|
if (range.nextOffset !== offset)
|
|
291
288
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
292
289
|
}
|
|
293
|
-
|
|
294
|
-
if (offset !== rangeEnd || hash.digest('hex') !== expectedHash)
|
|
290
|
+
if (offset !== rangeEnd)
|
|
295
291
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
296
292
|
})()
|
|
297
293
|
};
|
|
@@ -304,7 +300,6 @@ export class WorkspaceWorkbenchService {
|
|
|
304
300
|
if (input.offset !== undefined ||
|
|
305
301
|
input.length !== undefined ||
|
|
306
302
|
input.size !== undefined ||
|
|
307
|
-
input.sha256 !== undefined ||
|
|
308
303
|
input.revision !== undefined) {
|
|
309
304
|
if (!Number.isSafeInteger(input.offset) ||
|
|
310
305
|
input.offset < 0 ||
|
|
@@ -313,8 +308,6 @@ export class WorkspaceWorkbenchService {
|
|
|
313
308
|
input.length > 512 * 1024 ||
|
|
314
309
|
!Number.isSafeInteger(input.size) ||
|
|
315
310
|
input.size < 0 ||
|
|
316
|
-
typeof input.sha256 !== 'string' ||
|
|
317
|
-
!/^[a-f0-9]{64}$/u.test(input.sha256) ||
|
|
318
311
|
input.revision !== revision)
|
|
319
312
|
throw new Error('FILE_RANGE_INVALID');
|
|
320
313
|
const offset = input.offset;
|
|
@@ -328,25 +321,12 @@ export class WorkspaceWorkbenchService {
|
|
|
328
321
|
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
329
322
|
return {
|
|
330
323
|
size,
|
|
331
|
-
sha256: input.sha256,
|
|
332
324
|
revision,
|
|
333
|
-
range: {
|
|
334
|
-
offset,
|
|
335
|
-
length,
|
|
336
|
-
sha256: createHash('sha256').update(bytes).digest('hex')
|
|
337
|
-
}
|
|
325
|
+
range: { offset, length }
|
|
338
326
|
};
|
|
339
327
|
}
|
|
340
328
|
const content = await openGitHistoryFileContent(repository, input.commit, input.path, this.refs(input));
|
|
341
|
-
|
|
342
|
-
let offset = 0;
|
|
343
|
-
for await (const bytes of content.chunks) {
|
|
344
|
-
hash.update(bytes);
|
|
345
|
-
offset += bytes.length;
|
|
346
|
-
}
|
|
347
|
-
if (offset !== content.size)
|
|
348
|
-
throw new Error('DIRECT_FILE_REVISION_CHANGED');
|
|
349
|
-
return { size: content.size, sha256: hash.digest('hex'), revision };
|
|
329
|
+
return { size: content.size, revision };
|
|
350
330
|
}
|
|
351
331
|
async diff(input) {
|
|
352
332
|
if (typeof input.workspaceId !== 'string' ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.71",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"node-pty": "1.1.0",
|
|
29
29
|
"ws": "^8.21.3",
|
|
30
30
|
"zod": "4.4.3",
|
|
31
|
-
"@myagentroam/
|
|
32
|
-
"@myagentroam/
|
|
31
|
+
"@myagentroam/protocol": "0.9.71",
|
|
32
|
+
"@myagentroam/agent": "0.9.71"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/ws": "^8.18.1"
|