@myagentroam/node 0.11.29 → 0.11.31
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/connector.js
CHANGED
|
@@ -131,6 +131,7 @@ const HOSTED_CONVERSATION_OPERATIONS = [
|
|
|
131
131
|
'conversation.hosted.rollback',
|
|
132
132
|
'conversation.hosted.interrupt',
|
|
133
133
|
'conversation.hosted.config.update',
|
|
134
|
+
'conversation.hosted.template.switch',
|
|
134
135
|
'conversation.hosted.metadata.update',
|
|
135
136
|
'conversation.hosted.image.read',
|
|
136
137
|
'conversation.hosted.tool-resource.read',
|
|
@@ -30,10 +30,21 @@ export class MarAgentConversationNormalizer {
|
|
|
30
30
|
#subagentDetails = new Map();
|
|
31
31
|
#codeModeObservationIds = new Set();
|
|
32
32
|
#codeModeSourceCallIds = new Set();
|
|
33
|
+
#toolOutputSnapshots = new Map();
|
|
34
|
+
#codeModeOutputCalls = new Map();
|
|
35
|
+
#codeModeOutputSnapshots = new Map();
|
|
36
|
+
#runningCodeModeCells = new Map();
|
|
37
|
+
#codeModeCellWaiters = new Map();
|
|
33
38
|
messageItemId(nativeItemId) {
|
|
34
39
|
return itemId('message', nativeItemId);
|
|
35
40
|
}
|
|
36
41
|
normalize(event, options = {}) {
|
|
42
|
+
if (event.type === 'tool.output')
|
|
43
|
+
return this.#toolOutput(event);
|
|
44
|
+
if (event.type === 'execution.started') {
|
|
45
|
+
this.#startExecution(event.executionId);
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
37
48
|
if (event.type === 'input.received')
|
|
38
49
|
return [this.input(event.entry)];
|
|
39
50
|
if (event.type === 'execution.failed') {
|
|
@@ -93,15 +104,26 @@ export class MarAgentConversationNormalizer {
|
|
|
93
104
|
if (event.type === 'tool.started' ||
|
|
94
105
|
event.type === 'tool.completed' ||
|
|
95
106
|
event.type === 'tool.failed') {
|
|
107
|
+
if (event.parentCallId !== undefined &&
|
|
108
|
+
(event.toolName === 'exec' || event.toolName === 'exec_wait')) {
|
|
109
|
+
if (event.type === 'tool.started')
|
|
110
|
+
this.#trackCodeModeOutputCall(event);
|
|
111
|
+
else
|
|
112
|
+
this.#finishCodeModeOutputCall(event.callId, event.parentCallId);
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
96
115
|
// Read/web calls and generated artifacts stay visible independently of cell observations.
|
|
97
116
|
if (event.parentCallId !== undefined &&
|
|
98
117
|
event.toolName !== 'read' &&
|
|
99
118
|
event.toolName !== 'web_search' &&
|
|
100
119
|
event.toolName !== 'web_fetch' &&
|
|
101
|
-
!isImageGenerationTool(event.toolName))
|
|
120
|
+
!isImageGenerationTool(event.toolName)) {
|
|
121
|
+
this.#finishCodeModeOutputCall(event.callId, event.parentCallId);
|
|
102
122
|
return [];
|
|
123
|
+
}
|
|
103
124
|
if (event.type === 'tool.started')
|
|
104
125
|
return this.#toolStarted(event);
|
|
126
|
+
this.#finishToolCall(event);
|
|
105
127
|
const permissionStop = permissionStopForToolEvent(event);
|
|
106
128
|
const outerItems = event.type === 'tool.completed' ? this.#toolCompleted(event) : this.#toolFailed(event);
|
|
107
129
|
return [
|
|
@@ -145,6 +167,10 @@ export class MarAgentConversationNormalizer {
|
|
|
145
167
|
}
|
|
146
168
|
#toolStarted(event) {
|
|
147
169
|
const ownItemId = itemId('tool', event.callId);
|
|
170
|
+
this.#clearRetainedCodeModeCellsForOtherExecutions(event.executionId);
|
|
171
|
+
this.#toolOutputSnapshots.delete(event.callId);
|
|
172
|
+
this.#codeModeOutputCalls.delete(event.callId);
|
|
173
|
+
this.#clearCodeModeOutputSnapshots(event.callId);
|
|
148
174
|
if (event.toolName === 'exec' || event.toolName === 'exec_wait') {
|
|
149
175
|
const input = record(event.input) ?? {};
|
|
150
176
|
const action = input.action ??
|
|
@@ -170,6 +196,9 @@ export class MarAgentConversationNormalizer {
|
|
|
170
196
|
input: event.input,
|
|
171
197
|
itemId: ownItemId,
|
|
172
198
|
structured: true,
|
|
199
|
+
executionId: event.executionId,
|
|
200
|
+
...(event.turnId === undefined ? {} : { turnId: event.turnId }),
|
|
201
|
+
active: true,
|
|
173
202
|
commandOrigin: event.callId
|
|
174
203
|
});
|
|
175
204
|
return [commandItem(command, 'IN_PROGRESS')];
|
|
@@ -197,6 +226,9 @@ export class MarAgentConversationNormalizer {
|
|
|
197
226
|
input: event.input,
|
|
198
227
|
itemId: ownItemId,
|
|
199
228
|
structured: true,
|
|
229
|
+
executionId: event.executionId,
|
|
230
|
+
...(event.turnId === undefined ? {} : { turnId: event.turnId }),
|
|
231
|
+
active: true,
|
|
200
232
|
commandOrigin: event.callId
|
|
201
233
|
});
|
|
202
234
|
return [];
|
|
@@ -206,6 +238,9 @@ export class MarAgentConversationNormalizer {
|
|
|
206
238
|
input: event.input,
|
|
207
239
|
itemId: ownItemId,
|
|
208
240
|
structured: true,
|
|
241
|
+
executionId: event.executionId,
|
|
242
|
+
...(event.turnId === undefined ? {} : { turnId: event.turnId }),
|
|
243
|
+
active: true,
|
|
209
244
|
commandOrigin: origin
|
|
210
245
|
});
|
|
211
246
|
return [];
|
|
@@ -222,16 +257,228 @@ export class MarAgentConversationNormalizer {
|
|
|
222
257
|
input: event.input,
|
|
223
258
|
itemId: ownItemId,
|
|
224
259
|
structured: 'input' in event,
|
|
260
|
+
executionId: event.executionId,
|
|
261
|
+
...(event.turnId === undefined ? {} : { turnId: event.turnId }),
|
|
262
|
+
active: true,
|
|
225
263
|
...(event.data === undefined ? {} : { presentationData: event.data }),
|
|
226
264
|
...(subagentTitle === undefined ? {} : { subagentTitle }),
|
|
227
265
|
...(subagentDetails === undefined ? {} : { subagentDetails })
|
|
228
266
|
});
|
|
267
|
+
if (event.toolName === 'code_exec_wait')
|
|
268
|
+
this.#trackCodeModeWait(event);
|
|
229
269
|
if (event.toolName === 'apply_patch' && 'input' in event)
|
|
230
270
|
return [fileChangeItem(ownItemId, 'IN_PROGRESS', [])];
|
|
231
271
|
return [
|
|
232
272
|
genericToolItem(ownItemId, event.toolName, 'IN_PROGRESS', event.input, null, null, event.truncated === true, event.data, subagentTitle, subagentDetails)
|
|
233
273
|
];
|
|
234
274
|
}
|
|
275
|
+
#toolOutput(event) {
|
|
276
|
+
const { callId, toolName, executionId, turnId, parentCallId, output } = event;
|
|
277
|
+
const inputOutput = [output.stdout, output.stderr].filter(Boolean).join('\n');
|
|
278
|
+
const snapshot = {
|
|
279
|
+
outputPreview: compactRunnerText(inputOutput, 10_000),
|
|
280
|
+
truncated: output.truncated || inputOutput.length > 10_000
|
|
281
|
+
};
|
|
282
|
+
if (parentCallId !== undefined)
|
|
283
|
+
return this.#codeModeToolOutput(callId, parentCallId, toolName, executionId, turnId, output.processId, snapshot);
|
|
284
|
+
const state = this.#tools.get(callId);
|
|
285
|
+
if (state === undefined ||
|
|
286
|
+
!state.active ||
|
|
287
|
+
state.toolName !== toolName ||
|
|
288
|
+
state.executionId !== executionId ||
|
|
289
|
+
(state.turnId !== undefined && turnId !== undefined && state.turnId !== turnId) ||
|
|
290
|
+
state.commandOrigin === undefined)
|
|
291
|
+
return [];
|
|
292
|
+
const input = record(state.input);
|
|
293
|
+
if (typeof input?.processId === 'string' && input.processId !== output.processId)
|
|
294
|
+
return [];
|
|
295
|
+
const command = this.#commands.get(state.commandOrigin);
|
|
296
|
+
if (command === undefined || command.status !== 'IN_PROGRESS')
|
|
297
|
+
return [];
|
|
298
|
+
this.#toolOutputSnapshots.set(callId, {
|
|
299
|
+
commandOrigin: state.commandOrigin,
|
|
300
|
+
...snapshot
|
|
301
|
+
});
|
|
302
|
+
return [
|
|
303
|
+
commandItem(commandWithOutputSnapshots(command, [...this.#toolOutputSnapshots.values()].filter((entry) => entry.commandOrigin === state.commandOrigin)), 'IN_PROGRESS')
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
#codeModeToolOutput(callId, parentCallId, toolName, executionId, turnId, processId, snapshot) {
|
|
307
|
+
const parent = this.#tools.get(parentCallId);
|
|
308
|
+
const child = this.#codeModeOutputCalls.get(callId);
|
|
309
|
+
if (parent === undefined ||
|
|
310
|
+
(parent.toolName !== 'code_exec' && parent.toolName !== 'code_exec_wait') ||
|
|
311
|
+
parent.executionId !== executionId ||
|
|
312
|
+
(parent.turnId !== undefined && turnId !== undefined && parent.turnId !== turnId) ||
|
|
313
|
+
child === undefined ||
|
|
314
|
+
child.parentCallId !== parentCallId ||
|
|
315
|
+
child.toolName !== toolName ||
|
|
316
|
+
child.executionId !== executionId ||
|
|
317
|
+
(child.turnId !== undefined && turnId !== undefined && child.turnId !== turnId))
|
|
318
|
+
return [];
|
|
319
|
+
const target = parent.active
|
|
320
|
+
? parent
|
|
321
|
+
: this.#runningCodeModeCells.get(parentCallId)?.executionId === executionId
|
|
322
|
+
? this.#activeCodeModeWaiter(this.#runningCodeModeCells.get(parentCallId))
|
|
323
|
+
: undefined;
|
|
324
|
+
if (target === undefined)
|
|
325
|
+
return [];
|
|
326
|
+
const input = record(child.input);
|
|
327
|
+
if (typeof input?.processId === 'string' && input.processId !== processId)
|
|
328
|
+
return [];
|
|
329
|
+
const snapshots = this.#codeModeOutputSnapshots.get(parentCallId) ?? new Map();
|
|
330
|
+
snapshots.set(callId, snapshot);
|
|
331
|
+
this.#codeModeOutputSnapshots.set(parentCallId, snapshots);
|
|
332
|
+
const combined = [...snapshots.values()]
|
|
333
|
+
.map((entry) => entry.outputPreview)
|
|
334
|
+
.filter(Boolean)
|
|
335
|
+
.join('\n');
|
|
336
|
+
const truncated = [...snapshots.values()].some((entry) => entry.truncated) || combined.length > 10_000;
|
|
337
|
+
return [
|
|
338
|
+
genericToolItem(target.itemId, target.toolName, 'IN_PROGRESS', target.input, compactRunnerText(combined, 10_000), null, truncated)
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
#trackCodeModeOutputCall(event) {
|
|
342
|
+
const parentCallId = event.parentCallId;
|
|
343
|
+
if (parentCallId === undefined || (event.toolName !== 'exec' && event.toolName !== 'exec_wait'))
|
|
344
|
+
return;
|
|
345
|
+
const parent = this.#tools.get(parentCallId);
|
|
346
|
+
const retainedCell = this.#runningCodeModeCells.get(parentCallId);
|
|
347
|
+
const canResumeRetainedCell = parent !== undefined && !parent.active && retainedCell?.executionId === event.executionId;
|
|
348
|
+
if (parent === undefined ||
|
|
349
|
+
(parent.toolName !== 'code_exec' && parent.toolName !== 'code_exec_wait') ||
|
|
350
|
+
parent.executionId !== event.executionId ||
|
|
351
|
+
(parent.turnId !== undefined && event.turnId !== undefined && parent.turnId !== event.turnId))
|
|
352
|
+
return;
|
|
353
|
+
if (!parent.active && !canResumeRetainedCell)
|
|
354
|
+
return;
|
|
355
|
+
this.#codeModeOutputCalls.delete(event.callId);
|
|
356
|
+
const snapshots = this.#codeModeOutputSnapshots.get(parentCallId);
|
|
357
|
+
snapshots?.delete(event.callId);
|
|
358
|
+
this.#codeModeOutputCalls.set(event.callId, {
|
|
359
|
+
parentCallId,
|
|
360
|
+
executionId: event.executionId,
|
|
361
|
+
...(event.turnId === undefined ? {} : { turnId: event.turnId }),
|
|
362
|
+
toolName: event.toolName,
|
|
363
|
+
input: event.input
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
#finishCodeModeOutputCall(callId, parentCallId) {
|
|
367
|
+
const call = this.#codeModeOutputCalls.get(callId);
|
|
368
|
+
if (call?.parentCallId === parentCallId) {
|
|
369
|
+
this.#codeModeOutputCalls.delete(callId);
|
|
370
|
+
this.#codeModeOutputSnapshots.get(parentCallId)?.delete(callId);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
#trackCodeModeWait(event) {
|
|
374
|
+
const cellId = record(event.input)?.cellId;
|
|
375
|
+
if (typeof cellId !== 'string' || cellId.length === 0)
|
|
376
|
+
return;
|
|
377
|
+
const cell = [...this.#runningCodeModeCells.values()].find((candidate) => candidate.cellId === cellId && candidate.executionId === event.executionId);
|
|
378
|
+
if (cell === undefined)
|
|
379
|
+
return;
|
|
380
|
+
const waiters = this.#codeModeCellWaiters.get(cellId) ?? new Set();
|
|
381
|
+
waiters.add(event.callId);
|
|
382
|
+
this.#codeModeCellWaiters.set(cellId, waiters);
|
|
383
|
+
}
|
|
384
|
+
#activeCodeModeWaiter(cell) {
|
|
385
|
+
const waiters = this.#codeModeCellWaiters.get(cell.cellId);
|
|
386
|
+
if (waiters === undefined)
|
|
387
|
+
return undefined;
|
|
388
|
+
for (const callId of [...waiters].reverse()) {
|
|
389
|
+
const state = this.#tools.get(callId);
|
|
390
|
+
const input = record(state?.input);
|
|
391
|
+
if (state?.active === true &&
|
|
392
|
+
state.toolName === 'code_exec_wait' &&
|
|
393
|
+
state.executionId === cell.executionId &&
|
|
394
|
+
input?.cellId === cell.cellId)
|
|
395
|
+
return state;
|
|
396
|
+
waiters.delete(callId);
|
|
397
|
+
}
|
|
398
|
+
if (waiters.size === 0)
|
|
399
|
+
this.#codeModeCellWaiters.delete(cell.cellId);
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
#finishToolCall(event) {
|
|
403
|
+
const state = this.#tools.get(event.callId);
|
|
404
|
+
if (state !== undefined)
|
|
405
|
+
state.active = false;
|
|
406
|
+
this.#toolOutputSnapshots.delete(event.callId);
|
|
407
|
+
const data = record(event.data);
|
|
408
|
+
const cellId = typeof data?.cellId === 'string' ? data.cellId : undefined;
|
|
409
|
+
const status = data?.status;
|
|
410
|
+
if (state?.toolName === 'code_exec') {
|
|
411
|
+
if (event.type === 'tool.completed' &&
|
|
412
|
+
event.toolName === 'code_exec' &&
|
|
413
|
+
status === 'running' &&
|
|
414
|
+
cellId !== undefined &&
|
|
415
|
+
cellId.length > 0) {
|
|
416
|
+
this.#runningCodeModeCells.set(event.callId, {
|
|
417
|
+
cellId,
|
|
418
|
+
executionId: event.executionId
|
|
419
|
+
});
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
this.#clearRunningCodeModeCell(event.callId);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (state?.toolName === 'code_exec_wait') {
|
|
426
|
+
const inputCellId = record(state.input)?.cellId;
|
|
427
|
+
if (typeof inputCellId === 'string')
|
|
428
|
+
this.#removeCodeModeWaiter(inputCellId, event.callId);
|
|
429
|
+
if ((status === 'completed' || status === 'failed' || status === 'cancelled') &&
|
|
430
|
+
typeof (cellId ?? inputCellId) === 'string') {
|
|
431
|
+
const finishedCellId = (cellId ?? inputCellId);
|
|
432
|
+
for (const [sourceCallId, cell] of this.#runningCodeModeCells)
|
|
433
|
+
if (cell.cellId === finishedCellId && cell.executionId === event.executionId)
|
|
434
|
+
this.#clearRunningCodeModeCell(sourceCallId);
|
|
435
|
+
}
|
|
436
|
+
this.#clearCodeModeOutputSnapshots(event.callId);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
this.#clearCodeModeOutputSnapshots(event.callId);
|
|
440
|
+
}
|
|
441
|
+
#clearCodeModeOutputSnapshots(parentCallId) {
|
|
442
|
+
this.#codeModeOutputSnapshots.delete(parentCallId);
|
|
443
|
+
for (const [callId, state] of this.#codeModeOutputCalls)
|
|
444
|
+
if (state.parentCallId === parentCallId)
|
|
445
|
+
this.#codeModeOutputCalls.delete(callId);
|
|
446
|
+
}
|
|
447
|
+
#removeCodeModeWaiter(cellId, callId) {
|
|
448
|
+
const waiters = this.#codeModeCellWaiters.get(cellId);
|
|
449
|
+
waiters?.delete(callId);
|
|
450
|
+
if (waiters?.size === 0)
|
|
451
|
+
this.#codeModeCellWaiters.delete(cellId);
|
|
452
|
+
}
|
|
453
|
+
#clearRunningCodeModeCell(sourceCallId) {
|
|
454
|
+
const cell = this.#runningCodeModeCells.get(sourceCallId);
|
|
455
|
+
if (cell !== undefined) {
|
|
456
|
+
this.#runningCodeModeCells.delete(sourceCallId);
|
|
457
|
+
this.#codeModeCellWaiters.delete(cell.cellId);
|
|
458
|
+
}
|
|
459
|
+
this.#clearCodeModeOutputSnapshots(sourceCallId);
|
|
460
|
+
}
|
|
461
|
+
#startExecution(executionId) {
|
|
462
|
+
for (const [callId, state] of this.#tools)
|
|
463
|
+
if (state.executionId !== executionId) {
|
|
464
|
+
state.active = false;
|
|
465
|
+
this.#toolOutputSnapshots.delete(callId);
|
|
466
|
+
}
|
|
467
|
+
for (const [callId, state] of this.#codeModeOutputCalls)
|
|
468
|
+
if (state.executionId !== executionId)
|
|
469
|
+
this.#codeModeOutputCalls.delete(callId);
|
|
470
|
+
for (const [sourceCallId, cell] of this.#runningCodeModeCells)
|
|
471
|
+
if (cell.executionId !== executionId)
|
|
472
|
+
this.#clearRunningCodeModeCell(sourceCallId);
|
|
473
|
+
for (const parentCallId of this.#codeModeOutputSnapshots.keys())
|
|
474
|
+
if (this.#tools.get(parentCallId)?.executionId !== executionId)
|
|
475
|
+
this.#codeModeOutputSnapshots.delete(parentCallId);
|
|
476
|
+
}
|
|
477
|
+
#clearRetainedCodeModeCellsForOtherExecutions(executionId) {
|
|
478
|
+
for (const [sourceCallId, cell] of this.#runningCodeModeCells)
|
|
479
|
+
if (cell.executionId !== executionId)
|
|
480
|
+
this.#clearRunningCodeModeCell(sourceCallId);
|
|
481
|
+
}
|
|
235
482
|
#compactAnchorItemId(event, publicItemIdPrefix) {
|
|
236
483
|
const anchor = event.anchor;
|
|
237
484
|
if (anchor === undefined || !validCompactAnchor(anchor.id))
|
|
@@ -730,6 +977,21 @@ function commandItem(command, status, title) {
|
|
|
730
977
|
merge: true
|
|
731
978
|
};
|
|
732
979
|
}
|
|
980
|
+
function commandWithOutputSnapshots(command, snapshots) {
|
|
981
|
+
const outputPreview = [
|
|
982
|
+
command.outputPreview,
|
|
983
|
+
...snapshots.map((snapshot) => snapshot.outputPreview)
|
|
984
|
+
]
|
|
985
|
+
.filter(Boolean)
|
|
986
|
+
.join('\n');
|
|
987
|
+
return {
|
|
988
|
+
...command,
|
|
989
|
+
outputPreview: compactRunnerText(outputPreview, 10_000),
|
|
990
|
+
truncated: command.truncated ||
|
|
991
|
+
snapshots.some((snapshot) => snapshot.truncated) ||
|
|
992
|
+
outputPreview.length > 10_000
|
|
993
|
+
};
|
|
994
|
+
}
|
|
733
995
|
function applyCommandResult(command, result) {
|
|
734
996
|
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
735
997
|
if (output.length > 0) {
|
|
@@ -6,7 +6,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
6
6
|
import { constants } from 'node:fs';
|
|
7
7
|
import { lstat, mkdir, open, readFile, rename, rm, utimes } from 'node:fs/promises';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
-
import { toolResultResourceInputSchema, toolResultResourcePageSchema, conversationTurnSchema, conversationTemplateSummarySchema, hostedConversationConfigUpdateInputSchema, hostedConversationCreateInputSchema, hostedConversationDeleteResultSchema, hostedConversationFileDeleteInputSchema, hostedConversationFileDeleteResultSchema, hostedConversationFileReadInputSchema, hostedConversationFileReadResultSchema, hostedConversationFilesSchema, hostedConversationHistoryInputSchema, hostedConversationHistoryPageSchema, hostedConversationIdInputSchema, hostedConversationImageReadInputSchema, hostedConversationListInputSchema, hostedConversationListPageSchema, hostedConversationMessageInputSchema, hostedConversationMetadataUpdateInputSchema, hostedConversationModelOptionsInputSchema, hostedConversationModelOptionsSchema, hostedConversationRollbackInputSchema, hostedConversationSnapshotSchema, hostedConversationSummarySchema, hostedConversationUnwatchInputSchema, hostedConversationUploadChunkInputSchema, hostedConversationUploadControlInputSchema, hostedConversationUploadCreateInputSchema, hostedConversationUploadPreflightInputSchema, hostedConversationUploadPreflightResultSchema, hostedConversationUploadSchema, hostedConversationWatchInputSchema, marAgentRuntimeModelConfigurationSchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
|
|
9
|
+
import { toolResultResourceInputSchema, toolResultResourcePageSchema, conversationTurnSchema, conversationTemplateSummarySchema, hostedConversationConfigUpdateInputSchema, hostedConversationTemplateSwitchInputSchema, hostedConversationTemplateSwitchResultSchema, hostedConversationCreateInputSchema, hostedConversationDeleteResultSchema, hostedConversationFileDeleteInputSchema, hostedConversationFileDeleteResultSchema, hostedConversationFileReadInputSchema, hostedConversationFileReadResultSchema, hostedConversationFilesSchema, hostedConversationHistoryInputSchema, hostedConversationHistoryPageSchema, hostedConversationIdInputSchema, hostedConversationImageReadInputSchema, hostedConversationListInputSchema, hostedConversationListPageSchema, hostedConversationMessageInputSchema, hostedConversationMetadataUpdateInputSchema, hostedConversationModelOptionsInputSchema, hostedConversationModelOptionsSchema, hostedConversationRollbackInputSchema, hostedConversationSnapshotSchema, hostedConversationSummarySchema, hostedConversationUnwatchInputSchema, hostedConversationUploadChunkInputSchema, hostedConversationUploadControlInputSchema, hostedConversationUploadCreateInputSchema, hostedConversationUploadPreflightInputSchema, hostedConversationUploadPreflightResultSchema, hostedConversationUploadSchema, hostedConversationWatchInputSchema, marAgentRuntimeModelConfigurationSchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
|
|
10
10
|
import { hostedConversationGeneratedImagesDisplayDirectory, hostedConversationImageRunId, marAgentGeneratedImagesDirectory } from '../runner/mar-agent/generated-images.js';
|
|
11
11
|
import { sessionTitleFromFirstMessage } from '../util/node-operation-parsers.js';
|
|
12
12
|
import { safeErrorCode } from '../util/safe-error.js';
|
|
@@ -62,6 +62,7 @@ export class HostedConversationNodeService {
|
|
|
62
62
|
'conversation.hosted.rollback': (raw) => this.rollback(raw),
|
|
63
63
|
'conversation.hosted.interrupt': (raw) => this.interrupt(raw),
|
|
64
64
|
'conversation.hosted.config.update': (raw) => this.updateConfiguration(raw),
|
|
65
|
+
'conversation.hosted.template.switch': (raw) => this.switchTemplate(raw),
|
|
65
66
|
'conversation.hosted.metadata.update': (raw) => this.updateMetadata(raw),
|
|
66
67
|
'conversation.hosted.image.read': (raw) => this.readImage(raw),
|
|
67
68
|
'conversation.hosted.tool-resource.read': (raw) => this.readToolResource(raw),
|
|
@@ -147,8 +148,11 @@ export class HostedConversationNodeService {
|
|
|
147
148
|
if (pending !== undefined)
|
|
148
149
|
return pending;
|
|
149
150
|
const existing = this.options.store.findConversationByClientRequest(payload.userId, input.clientRequestId);
|
|
150
|
-
if (existing !== undefined)
|
|
151
|
+
if (existing !== undefined) {
|
|
152
|
+
if (existing.templateId !== input.templateId)
|
|
153
|
+
throw new Error('HOSTED_CONVERSATION_TEMPLATE_CONFLICT');
|
|
151
154
|
return this.snapshot(payload.userId, existing.id, models);
|
|
155
|
+
}
|
|
152
156
|
const operation = this.createConversation(payload.userId, input.clientRequestId, template, models, input.modelId, payload.runtimeScopeId);
|
|
153
157
|
this.pendingCreates.set(key, operation);
|
|
154
158
|
void operation.finally(() => this.pendingCreates.delete(key)).catch(() => undefined);
|
|
@@ -233,7 +237,8 @@ export class HostedConversationNodeService {
|
|
|
233
237
|
async get(raw) {
|
|
234
238
|
const payload = hostedRequest(raw);
|
|
235
239
|
const input = hostedConversationIdInputSchema.parse(payload.data);
|
|
236
|
-
|
|
240
|
+
this.assertTemplateContext(payload, this.options.store.requireConversation(payload.userId, input.conversationId));
|
|
241
|
+
return this.snapshot(payload.userId, input.conversationId, runtimeModels(payload.models), payload.templateDeleted, false, undefined, payload.sourceTemplateId);
|
|
237
242
|
}
|
|
238
243
|
async history(raw) {
|
|
239
244
|
const payload = hostedRequest(raw);
|
|
@@ -250,9 +255,9 @@ export class HostedConversationNodeService {
|
|
|
250
255
|
});
|
|
251
256
|
return hostedConversationHistoryPageSchema.parse(page);
|
|
252
257
|
}
|
|
253
|
-
async snapshot(userId, conversationId, availableModels, templateDeleted = false, includeActiveHistoryMutation = false, historyOverride) {
|
|
258
|
+
async snapshot(userId, conversationId, availableModels, templateDeleted = false, includeActiveHistoryMutation = false, historyOverride, sourceTemplateId) {
|
|
254
259
|
if (includeActiveHistoryMutation)
|
|
255
|
-
return this.readSnapshot(userId, conversationId, availableModels, templateDeleted, historyOverride);
|
|
260
|
+
return this.readSnapshot(userId, conversationId, availableModels, templateDeleted, historyOverride, sourceTemplateId);
|
|
256
261
|
while (true) {
|
|
257
262
|
const version = this.historyVersions.get(conversationId) ?? 0;
|
|
258
263
|
const mutation = this.historyMutations.get(conversationId);
|
|
@@ -260,16 +265,20 @@ export class HostedConversationNodeService {
|
|
|
260
265
|
await mutation.catch(() => undefined);
|
|
261
266
|
continue;
|
|
262
267
|
}
|
|
263
|
-
const snapshot = await this.readSnapshot(userId, conversationId, availableModels, templateDeleted);
|
|
268
|
+
const snapshot = await this.readSnapshot(userId, conversationId, availableModels, templateDeleted, undefined, sourceTemplateId);
|
|
264
269
|
if (!this.historyMutations.has(conversationId) &&
|
|
265
270
|
(this.historyVersions.get(conversationId) ?? 0) === version)
|
|
266
271
|
return snapshot;
|
|
267
272
|
}
|
|
268
273
|
}
|
|
269
|
-
async readSnapshot(userId, conversationId, availableModels, templateDeleted, historyOverride) {
|
|
274
|
+
async readSnapshot(userId, conversationId, availableModels, templateDeleted, historyOverride, sourceTemplateId) {
|
|
270
275
|
let conversation = this.options.store.requireConversation(userId, conversationId);
|
|
276
|
+
if (sourceTemplateId !== undefined && conversation.templateId !== sourceTemplateId)
|
|
277
|
+
throw new Error('HOSTED_CONVERSATION_TEMPLATE_CONFLICT');
|
|
278
|
+
const templateId = conversation.templateId;
|
|
279
|
+
const activeVersionId = conversation.activeVersionId;
|
|
271
280
|
if (templateDeleted)
|
|
272
|
-
conversation = await this.options.store.retainConversationTemplate(userId, conversationId, this.now());
|
|
281
|
+
conversation = await this.options.store.retainConversationTemplate(userId, conversationId, this.now(), sourceTemplateId ?? templateId);
|
|
273
282
|
const latestVersionId = conversation.templateDeleted
|
|
274
283
|
? undefined
|
|
275
284
|
: this.latestAvailableTemplateVersionId(conversation.templateId);
|
|
@@ -282,7 +291,11 @@ export class HostedConversationNodeService {
|
|
|
282
291
|
const configuredModelId = conversation.modelId;
|
|
283
292
|
const selected = selectModel(version, availableModels, conversation.modelId);
|
|
284
293
|
if (selected.id !== conversation.modelId)
|
|
285
|
-
conversation = await this.options.store.updateConversation(conversation.id, (current) => current.
|
|
294
|
+
conversation = await this.options.store.updateConversation(conversation.id, (current) => current.templateId === templateId &&
|
|
295
|
+
current.activeVersionId === activeVersionId &&
|
|
296
|
+
current.modelId === configuredModelId
|
|
297
|
+
? { ...current, modelId: selected.id }
|
|
298
|
+
: current);
|
|
286
299
|
}
|
|
287
300
|
const paths = this.options.store.pathsForConversation(userId, conversation.id);
|
|
288
301
|
const history = historyOverride ??
|
|
@@ -293,6 +306,9 @@ export class HostedConversationNodeService {
|
|
|
293
306
|
modelId: conversation.modelId,
|
|
294
307
|
limit: 10
|
|
295
308
|
}));
|
|
309
|
+
const current = this.options.store.requireConversation(userId, conversationId);
|
|
310
|
+
if (current.templateId !== templateId || current.activeVersionId !== activeVersionId)
|
|
311
|
+
throw new Error('HOSTED_CONVERSATION_TEMPLATE_CONFLICT');
|
|
296
312
|
return hostedConversationSnapshotSchema.parse({
|
|
297
313
|
nodeAppVersion: this.options.nodeAppVersion?.() ?? 'unknown',
|
|
298
314
|
conversation: this.summary(conversation),
|
|
@@ -312,6 +328,7 @@ export class HostedConversationNodeService {
|
|
|
312
328
|
const payload = hostedRequest(raw);
|
|
313
329
|
const input = hostedConversationWatchInputSchema.parse(payload.data);
|
|
314
330
|
const conversation = this.options.store.requireConversation(payload.userId, input.conversationId);
|
|
331
|
+
this.assertTemplateContext(payload, conversation);
|
|
315
332
|
if (this.cleanupLocks.has(conversation.id))
|
|
316
333
|
throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
|
|
317
334
|
const sessionHash = requireWatchSessionHash(payload.sessionHash);
|
|
@@ -479,6 +496,7 @@ export class HostedConversationNodeService {
|
|
|
479
496
|
return Promise.reject(new Error('CONVERSATION_HOSTING_UNAVAILABLE'));
|
|
480
497
|
const payload = hostedRequest(raw);
|
|
481
498
|
const input = hostedConversationMessageInputSchema.parse(payload.data);
|
|
499
|
+
this.assertTemplateContext(payload, this.options.store.requireConversation(payload.userId, input.conversationId));
|
|
482
500
|
if (this.historyMutations.has(input.conversationId))
|
|
483
501
|
return Promise.reject(new Error('HOSTED_CONVERSATION_STATE_INVALID'));
|
|
484
502
|
const key = `${input.conversationId}\0${input.clientMessageId}`;
|
|
@@ -561,6 +579,7 @@ export class HostedConversationNodeService {
|
|
|
561
579
|
}
|
|
562
580
|
async startMessage(payload, input, steerPrompt) {
|
|
563
581
|
let conversation = this.options.store.requireConversation(payload.userId, input.conversationId);
|
|
582
|
+
this.assertTemplateContext(payload, conversation);
|
|
564
583
|
if (this.cleanupLocks.has(conversation.id))
|
|
565
584
|
throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
|
|
566
585
|
if (this.historyMutations.has(conversation.id))
|
|
@@ -770,6 +789,7 @@ export class HostedConversationNodeService {
|
|
|
770
789
|
const payload = hostedRequest(raw);
|
|
771
790
|
const input = hostedConversationRollbackInputSchema.parse(payload.data);
|
|
772
791
|
const conversation = this.options.store.requireConversation(payload.userId, input.conversationId);
|
|
792
|
+
this.assertTemplateContext(payload, conversation);
|
|
773
793
|
if (conversation.status !== 'ACTIVE' ||
|
|
774
794
|
this.cleanupLocks.has(conversation.id) ||
|
|
775
795
|
this.historyMutations.has(conversation.id))
|
|
@@ -942,10 +962,94 @@ export class HostedConversationNodeService {
|
|
|
942
962
|
throw error;
|
|
943
963
|
}
|
|
944
964
|
}
|
|
945
|
-
|
|
965
|
+
switchTemplate(raw) {
|
|
966
|
+
if (this.closing || this.disablePrepared)
|
|
967
|
+
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
968
|
+
const payload = hostedRequest(raw);
|
|
969
|
+
const input = hostedConversationTemplateSwitchInputSchema.parse(payload.data);
|
|
970
|
+
const conversation = this.requireWritableConversation(payload.userId, input.conversationId);
|
|
971
|
+
if (this.fileWrites.has(conversation.id) ||
|
|
972
|
+
[...this.uploads.values()].some((upload) => upload.conversationId === conversation.id && upload.status === 'UPLOADING'))
|
|
973
|
+
throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
|
|
974
|
+
if (this.activeRuns.has(conversation.id) ||
|
|
975
|
+
this.runSettlements.has(conversation.id) ||
|
|
976
|
+
[...this.pendingMessages.keys()].some((key) => key.startsWith(`${conversation.id}\0`)))
|
|
977
|
+
throw new Error('HOSTED_CONVERSATION_RUN_ACTIVE');
|
|
978
|
+
const template = conversationTemplateSummarySchema.parse(payload.template);
|
|
979
|
+
if (template.id !== input.templateId ||
|
|
980
|
+
template.nodeId !== conversation.nodeId ||
|
|
981
|
+
template.status !== 'ACTIVE' ||
|
|
982
|
+
!template.available ||
|
|
983
|
+
template.currentVersionId === null)
|
|
984
|
+
throw new Error('HOSTED_CONVERSATION_TEMPLATE_UNAVAILABLE');
|
|
985
|
+
return this.withConversationChange(conversation.id, async () => {
|
|
986
|
+
const current = this.options.store.requireConversation(payload.userId, conversation.id);
|
|
987
|
+
const unchanged = current.templateId === input.templateId;
|
|
988
|
+
if (!unchanged && current.templateId !== input.expectedTemplateId)
|
|
989
|
+
throw new Error('HOSTED_CONVERSATION_TEMPLATE_CONFLICT');
|
|
990
|
+
const version = await this.loadTemplateVersion(unchanged ? current.activeVersionId : template.currentVersionId, template.id);
|
|
991
|
+
const models = networkModels(allowedModels(version, runtimeModels(payload.models)), version.draft.networkMode).filter((model) => model.conversationModel !== false);
|
|
992
|
+
let saved = current;
|
|
993
|
+
if (!unchanged) {
|
|
994
|
+
const selected = selectModel(version, models, current.modelId);
|
|
995
|
+
resolveMcpServers(version, this.options.secretEnvironment ?? process.env);
|
|
996
|
+
const paths = this.options.store.pathsForConversation(payload.userId, current.id);
|
|
997
|
+
this.pathMounts(paths, version);
|
|
998
|
+
this.processExecutor(paths, version);
|
|
999
|
+
if (current.quota.usedBytes > version.draft.quotas.maxStorageBytes)
|
|
1000
|
+
throw new Error('HOSTED_CONVERSATION_QUOTA_EXCEEDED');
|
|
1001
|
+
const updatedAt = this.now();
|
|
1002
|
+
saved = await this.options.store.updateConversation(current.id, (latest) => ({
|
|
1003
|
+
...latest,
|
|
1004
|
+
templateId: template.id,
|
|
1005
|
+
templateName: template.name,
|
|
1006
|
+
templateDeleted: false,
|
|
1007
|
+
activeVersionId: version.id,
|
|
1008
|
+
modelId: selected.id,
|
|
1009
|
+
quota: { ...latest.quota, maxBytes: version.draft.quotas.maxStorageBytes },
|
|
1010
|
+
updatedAt,
|
|
1011
|
+
lastActivityAt: updatedAt
|
|
1012
|
+
}));
|
|
1013
|
+
try {
|
|
1014
|
+
this.publishHostedEvent(saved.id, { conversationId: saved.id, kind: 'template-changed' });
|
|
1015
|
+
}
|
|
1016
|
+
catch {
|
|
1017
|
+
// Committed configuration does not depend on best-effort Watch delivery.
|
|
1018
|
+
}
|
|
1019
|
+
finally {
|
|
1020
|
+
for (const [key, lease] of this.watchLeases)
|
|
1021
|
+
if (lease.conversationId === saved.id)
|
|
1022
|
+
this.removeWatchLease(key);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return hostedConversationTemplateSwitchResultSchema.parse({
|
|
1026
|
+
conversation: this.summary(saved),
|
|
1027
|
+
template: templateSummary(saved, version.draft),
|
|
1028
|
+
models: models.map(modelSummary)
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
withConversationChange(conversationId, change) {
|
|
1033
|
+
if (this.historyMutations.has(conversationId))
|
|
1034
|
+
throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
|
|
1035
|
+
const operation = Promise.resolve().then(change);
|
|
1036
|
+
this.historyMutations.set(conversationId, operation);
|
|
1037
|
+
void operation
|
|
1038
|
+
.finally(() => {
|
|
1039
|
+
if (this.historyMutations.get(conversationId) === operation)
|
|
1040
|
+
this.historyMutations.delete(conversationId);
|
|
1041
|
+
})
|
|
1042
|
+
.catch(() => undefined);
|
|
1043
|
+
return operation;
|
|
1044
|
+
}
|
|
1045
|
+
updateConfiguration(raw) {
|
|
946
1046
|
const payload = hostedRequest(raw);
|
|
947
1047
|
const input = hostedConversationConfigUpdateInputSchema.parse(payload.data);
|
|
948
|
-
const conversation = this.
|
|
1048
|
+
const conversation = this.requireWritableConversation(payload.userId, input.conversationId);
|
|
1049
|
+
this.assertTemplateContext(payload, conversation);
|
|
1050
|
+
return this.withConversationChange(conversation.id, () => this.saveConfiguration(payload, input, conversation));
|
|
1051
|
+
}
|
|
1052
|
+
async saveConfiguration(payload, input, conversation) {
|
|
949
1053
|
if (this.cleanupLocks.has(conversation.id))
|
|
950
1054
|
throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
|
|
951
1055
|
const latestVersionId = conversation.templateDeleted
|
|
@@ -1337,6 +1441,7 @@ export class HostedConversationNodeService {
|
|
|
1337
1441
|
const snapshot = await this.get({
|
|
1338
1442
|
conversationId: lease.conversationId,
|
|
1339
1443
|
__requestUserId: lease.userId,
|
|
1444
|
+
__sourceTemplateId: lease.templateId,
|
|
1340
1445
|
__marAgentModels: lease.models
|
|
1341
1446
|
});
|
|
1342
1447
|
if (this.watchLeases.get(lease.ownerKey) !== lease)
|
|
@@ -1559,12 +1664,17 @@ export class HostedConversationNodeService {
|
|
|
1559
1664
|
}
|
|
1560
1665
|
requireWritableConversation(userId, conversationId) {
|
|
1561
1666
|
const conversation = this.options.store.requireConversation(userId, conversationId);
|
|
1562
|
-
if (this.cleanupLocks.has(conversation.id))
|
|
1667
|
+
if (this.cleanupLocks.has(conversation.id) || this.historyMutations.has(conversation.id))
|
|
1563
1668
|
throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
|
|
1564
1669
|
if (conversation.status !== 'ACTIVE')
|
|
1565
1670
|
throw new Error('HOSTED_CONVERSATION_STATE_INVALID');
|
|
1566
1671
|
return conversation;
|
|
1567
1672
|
}
|
|
1673
|
+
assertTemplateContext(payload, conversation) {
|
|
1674
|
+
if (payload.sourceTemplateId !== undefined &&
|
|
1675
|
+
payload.sourceTemplateId !== conversation.templateId)
|
|
1676
|
+
throw new Error('HOSTED_CONVERSATION_TEMPLATE_CONFLICT');
|
|
1677
|
+
}
|
|
1568
1678
|
requireUpload(userId, conversationId, uploadId) {
|
|
1569
1679
|
this.options.store.requireConversation(userId, conversationId);
|
|
1570
1680
|
const upload = this.uploads.get(uploadId);
|
|
@@ -1800,18 +1910,22 @@ export class HostedConversationNodeService {
|
|
|
1800
1910
|
function hostedRequest(raw) {
|
|
1801
1911
|
if (!record(raw))
|
|
1802
1912
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
1803
|
-
const { __usageGrant, __requestUserId, __template, __marAgentModel, __marAgentModels, __marAgentCompact, __marAgentRuntimeScopeId, __workspaceWatchSessionHash, __templateDeleted, ...data } = raw;
|
|
1913
|
+
const { __usageGrant, __requestUserId, __template, __marAgentModel, __marAgentModels, __marAgentCompact, __marAgentRuntimeScopeId, __workspaceWatchSessionHash, __templateDeleted, __sourceTemplateId, ...data } = raw;
|
|
1804
1914
|
if (typeof __requestUserId !== 'number' ||
|
|
1805
1915
|
!Number.isSafeInteger(__requestUserId) ||
|
|
1806
1916
|
__requestUserId <= 0)
|
|
1807
1917
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
1808
1918
|
if (__templateDeleted !== undefined && __templateDeleted !== true)
|
|
1809
1919
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
1920
|
+
if (__sourceTemplateId !== undefined &&
|
|
1921
|
+
(typeof __sourceTemplateId !== 'string' || __sourceTemplateId.length === 0))
|
|
1922
|
+
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
1810
1923
|
if (__marAgentRuntimeScopeId !== undefined &&
|
|
1811
1924
|
(typeof __marAgentRuntimeScopeId !== 'string' || __marAgentRuntimeScopeId.length === 0))
|
|
1812
1925
|
throw new Error('HOSTED_CONVERSATION_NOT_AUTHORIZED');
|
|
1813
1926
|
return {
|
|
1814
1927
|
data,
|
|
1928
|
+
...(__sourceTemplateId === undefined ? {} : { sourceTemplateId: __sourceTemplateId }),
|
|
1815
1929
|
...(__usageGrant === undefined
|
|
1816
1930
|
? {}
|
|
1817
1931
|
: { usageGrant: modelUsageGrantSchema.parse(__usageGrant) }),
|
|
@@ -385,10 +385,15 @@ export class HostedConversationStore {
|
|
|
385
385
|
await this.writeManifest(paths.manifestPath, manifest);
|
|
386
386
|
return manifest;
|
|
387
387
|
}
|
|
388
|
-
async retainConversationTemplate(userId, conversationId, updatedAt) {
|
|
388
|
+
async retainConversationTemplate(userId, conversationId, updatedAt, expectedTemplateId) {
|
|
389
|
+
return this.withConversationMutation(conversationId, () => this.retainConversationTemplateUnlocked(userId, conversationId, updatedAt, expectedTemplateId));
|
|
390
|
+
}
|
|
391
|
+
async retainConversationTemplateUnlocked(userId, conversationId, updatedAt, expectedTemplateId) {
|
|
389
392
|
if (!Number.isSafeInteger(updatedAt) || updatedAt < 0)
|
|
390
393
|
throw new Error('HOSTED_CONVERSATION_INPUT_INVALID');
|
|
391
394
|
const current = this.requireConversation(userId, conversationId);
|
|
395
|
+
if (expectedTemplateId !== undefined && current.templateId !== expectedTemplateId)
|
|
396
|
+
throw new Error('HOSTED_CONVERSATION_TEMPLATE_CONFLICT');
|
|
392
397
|
const version = this.options.database.raw
|
|
393
398
|
.prepare('SELECT status FROM conversation_template_versions WHERE id=? AND template_id=?')
|
|
394
399
|
.get(current.activeVersionId, current.templateId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.31",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"sharp": "0.35.5",
|
|
39
39
|
"ws": "^8.21.3",
|
|
40
40
|
"zod": "4.4.3",
|
|
41
|
-
"@myagentroam/agent": "0.11.
|
|
41
|
+
"@myagentroam/agent": "0.11.31",
|
|
42
42
|
"@myagentroam/code-navigation": "0.11.0",
|
|
43
|
-
"@myagentroam/
|
|
44
|
-
"@myagentroam/
|
|
43
|
+
"@myagentroam/marctl": "0.11.1",
|
|
44
|
+
"@myagentroam/protocol": "0.11.31"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/ws": "^8.18.1"
|