@myagentroam/node 0.9.108 → 0.9.110
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 +11 -3
- package/dist/connector.js +11 -0
- package/dist/runner/mar-agent/runtime-model-factory.js +2 -0
- package/dist/runner/mar-agent-runner.js +6 -4
- package/dist/service/direct-transfer-service.js +25 -0
- package/dist/service/hosted-conversation-node-service.js +1 -0
- package/dist/service/node-control-message-service.js +3 -0
- package/dist/service/terminal-direct-service.js +202 -0
- package/dist/service/terminal-service.js +14 -1
- package/dist/service/workbench-manifest-service.js +1 -0
- package/package.json +4 -4
package/dist/capabilities.js
CHANGED
|
@@ -8,6 +8,7 @@ const nodeAppVersion = typeof nodePackage.version === 'string' ? nodePackage.ver
|
|
|
8
8
|
const runtimeCredentialCapability = () => ({
|
|
9
9
|
apiVersion: 1,
|
|
10
10
|
websocket: true,
|
|
11
|
+
fastMode: true,
|
|
11
12
|
httpVersions: ['1.1', '2']
|
|
12
13
|
});
|
|
13
14
|
const workspaceDirectTransferCapability = () => ({
|
|
@@ -19,6 +20,10 @@ const workspaceDirectTransferCapability = () => ({
|
|
|
19
20
|
gitCommitBlob: true,
|
|
20
21
|
maxChunkBytes: 256 * 1024
|
|
21
22
|
});
|
|
23
|
+
const terminalDirectCapability = () => ({
|
|
24
|
+
apiVersion: 1,
|
|
25
|
+
transport: true
|
|
26
|
+
});
|
|
22
27
|
export function reportedPlatform(platform = process.platform) {
|
|
23
28
|
if (platform === 'win32')
|
|
24
29
|
return 'windows';
|
|
@@ -47,7 +52,8 @@ export function unavailableCapabilities() {
|
|
|
47
52
|
openCode: { available: false },
|
|
48
53
|
marAgent: { available: true, version: nodeAppVersion },
|
|
49
54
|
marAgentRuntimeCredentials: runtimeCredentialCapability(),
|
|
50
|
-
workspaceDirectTransfer: workspaceDirectTransferCapability()
|
|
55
|
+
workspaceDirectTransfer: workspaceDirectTransferCapability(),
|
|
56
|
+
terminalDirect: terminalDirectCapability()
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
export function detectCapabilities(commands = {}) {
|
|
@@ -80,7 +86,8 @@ export function detectCapabilities(commands = {}) {
|
|
|
80
86
|
},
|
|
81
87
|
marAgent: { available: true, version: nodeAppVersion },
|
|
82
88
|
marAgentRuntimeCredentials: runtimeCredentialCapability(),
|
|
83
|
-
workspaceDirectTransfer: workspaceDirectTransferCapability()
|
|
89
|
+
workspaceDirectTransfer: workspaceDirectTransferCapability(),
|
|
90
|
+
terminalDirect: terminalDirectCapability()
|
|
84
91
|
};
|
|
85
92
|
}
|
|
86
93
|
export async function detectCapabilitiesAsync(commands = {}) {
|
|
@@ -117,6 +124,7 @@ export async function detectCapabilitiesAsync(commands = {}) {
|
|
|
117
124
|
},
|
|
118
125
|
marAgent: { available: true, version: nodeAppVersion },
|
|
119
126
|
marAgentRuntimeCredentials: runtimeCredentialCapability(),
|
|
120
|
-
workspaceDirectTransfer: workspaceDirectTransferCapability()
|
|
127
|
+
workspaceDirectTransfer: workspaceDirectTransferCapability(),
|
|
128
|
+
terminalDirect: terminalDirectCapability()
|
|
121
129
|
};
|
|
122
130
|
}
|
package/dist/connector.js
CHANGED
|
@@ -1133,6 +1133,7 @@ export class NodeConnector {
|
|
|
1133
1133
|
handleNodeRequest: (envelope) => this.nodeRequests.handle(envelope),
|
|
1134
1134
|
handleRuntimeCredentialResponse: (envelope) => this.runtimeCredentials.handle(envelope),
|
|
1135
1135
|
handleDirectTransfer: (envelope) => this.handleDirectTransfer(envelope),
|
|
1136
|
+
handleTerminalDirect: (envelope) => this.handleTerminalDirect(envelope),
|
|
1136
1137
|
reconcileRuns: (envelope) => this.runEventBridge.reconcile(envelope),
|
|
1137
1138
|
runners: this.runners,
|
|
1138
1139
|
deliverSessionChannelMessage: (envelope) => this.runnerChannelMessages.handle(envelope),
|
|
@@ -1456,6 +1457,9 @@ export class NodeConnector {
|
|
|
1456
1457
|
return new DirectTransferService({
|
|
1457
1458
|
nodeGeneration: () => this.nodeGeneration,
|
|
1458
1459
|
send: (type, payload) => this.send(type, payload),
|
|
1460
|
+
terminalManager: this.terminalManager,
|
|
1461
|
+
sendTerminalReady: (directSessionId) => this.send('terminal.direct.ready', { directSessionId }),
|
|
1462
|
+
sendTerminalTakeover: (directSessionId) => this.send('terminal.direct.takeover', { directSessionId }),
|
|
1459
1463
|
udpPortRange: () => directTransferUdpPortRange(this.config),
|
|
1460
1464
|
openUpload: async (descriptor) => {
|
|
1461
1465
|
if (descriptor.direction !== 'UPLOAD' || descriptor.resource.kind !== 'WORKSPACE_FILE')
|
|
@@ -1512,6 +1516,13 @@ export class NodeConnector {
|
|
|
1512
1516
|
});
|
|
1513
1517
|
return true;
|
|
1514
1518
|
}
|
|
1519
|
+
handleTerminalDirect(envelope) {
|
|
1520
|
+
const command = {
|
|
1521
|
+
type: envelope.type,
|
|
1522
|
+
...(typeof envelope.payload === 'object' && envelope.payload !== null ? envelope.payload : {})
|
|
1523
|
+
};
|
|
1524
|
+
return this.directTransferService?.handleTerminal(command) ?? false;
|
|
1525
|
+
}
|
|
1515
1526
|
clearMissionRetry(sessionId) {
|
|
1516
1527
|
const retry = this.missionRetryTimers.get(sessionId);
|
|
1517
1528
|
if (retry !== undefined)
|
|
@@ -64,6 +64,7 @@ export class MarAgentRuntimeModelFactory {
|
|
|
64
64
|
titleGeneration: configuration.titleGeneration,
|
|
65
65
|
codeMode: configuration.codeMode,
|
|
66
66
|
highDensityCompaction: configuration.highDensityCompaction,
|
|
67
|
+
...(configuration.fastMode === true ? { fastMode: true } : {}),
|
|
67
68
|
hostedWebSearch: configuration.hostedWebSearch,
|
|
68
69
|
...(imageGeneration === undefined ? {} : { imageGeneration }),
|
|
69
70
|
...(configuration.responsesTransport === undefined
|
|
@@ -107,6 +108,7 @@ export class MarAgentRuntimeModelFactory {
|
|
|
107
108
|
}
|
|
108
109
|
: {}),
|
|
109
110
|
responsesEncoding: configuration.responsesEncoding ?? 'STANDARD',
|
|
111
|
+
codexCredential: true,
|
|
110
112
|
credentialProvider: provider
|
|
111
113
|
};
|
|
112
114
|
});
|
|
@@ -17,7 +17,12 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
17
17
|
this.homeDirectory = homeDirectory;
|
|
18
18
|
}
|
|
19
19
|
configureModels(value) {
|
|
20
|
-
this.#models = Array.isArray(value)
|
|
20
|
+
this.#models = Array.isArray(value)
|
|
21
|
+
? value.flatMap((model) => {
|
|
22
|
+
const parsed = marAgentNodeModelSummarySchema.safeParse(model);
|
|
23
|
+
return parsed.success ? [parsed.data] : [];
|
|
24
|
+
})
|
|
25
|
+
: [];
|
|
21
26
|
}
|
|
22
27
|
setSessionResourceReleaser(release) {
|
|
23
28
|
this.#releaseSessionResources = release;
|
|
@@ -439,6 +444,3 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
439
444
|
function firstSequenceForTurn(entries, turnId) {
|
|
440
445
|
return entries.find((entry) => entry.kind === 'user' ? entry.turnId === turnId : entry.event.turnId === turnId)?.sequence;
|
|
441
446
|
}
|
|
442
|
-
function isModelSummary(value) {
|
|
443
|
-
return marAgentNodeModelSummarySchema.safeParse(value).success;
|
|
444
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { directDataControlFrameSchema, directNodeCommandSchema } from '@myagentroam/protocol';
|
|
2
|
+
import { TerminalDirectService } from './terminal-direct-service.js';
|
|
2
3
|
import { IceUdpMuxListener as NativeIceUdpMuxListener } from 'node-datachannel';
|
|
3
4
|
import { RTCPeerConnection } from 'node-datachannel/polyfill';
|
|
4
5
|
const MAX_HOST_CANDIDATES = 32;
|
|
@@ -18,6 +19,7 @@ export class DirectTransferService {
|
|
|
18
19
|
peerConfiguration;
|
|
19
20
|
leaseGraceMs;
|
|
20
21
|
leaseCheckMs;
|
|
22
|
+
terminalDirect;
|
|
21
23
|
disposed = false;
|
|
22
24
|
constructor(options) {
|
|
23
25
|
this.options = options;
|
|
@@ -37,6 +39,17 @@ export class DirectTransferService {
|
|
|
37
39
|
((configuration) => new RTCPeerConnection(configuration));
|
|
38
40
|
this.leaseGraceMs = options.leaseGraceMs ?? LEASE_GRACE_MS;
|
|
39
41
|
this.leaseCheckMs = options.leaseCheckMs ?? LEASE_CHECK_MS;
|
|
42
|
+
if (options.terminalManager !== undefined)
|
|
43
|
+
this.terminalDirect = new TerminalDirectService(options.terminalManager, (id) => options.sendTerminalReady?.(id), (id) => options.sendTerminalTakeover?.(id), (command, peer) => {
|
|
44
|
+
const transport = this.transports.get(command.transportSessionId);
|
|
45
|
+
return (transport?.peer === peer &&
|
|
46
|
+
transport.command.workbenchConnectionId === command.workbenchConnectionId &&
|
|
47
|
+
transport.command.nodeGeneration === command.nodeGeneration &&
|
|
48
|
+
command.nodeGeneration === options.nodeGeneration() &&
|
|
49
|
+
peer.connectionState === 'connected' &&
|
|
50
|
+
command.browserFingerprint === fingerprint(peer.remoteDescription?.sdp) &&
|
|
51
|
+
command.nodeFingerprint === fingerprint(peer.localDescription?.sdp));
|
|
52
|
+
}, this.now);
|
|
40
53
|
}
|
|
41
54
|
handle(value) {
|
|
42
55
|
const parsed = directNodeCommandSchema.safeParse(value);
|
|
@@ -54,7 +67,11 @@ export class DirectTransferService {
|
|
|
54
67
|
});
|
|
55
68
|
return true;
|
|
56
69
|
}
|
|
70
|
+
handleTerminal(value) {
|
|
71
|
+
return this.terminalDirect?.handle(value, (id) => this.transports.get(id)?.peer) ?? false;
|
|
72
|
+
}
|
|
57
73
|
close() {
|
|
74
|
+
this.terminalDirect?.close();
|
|
58
75
|
for (const transfer of [...this.transfers.values()])
|
|
59
76
|
void this.removeTransfer(transfer, 'DIRECT_NODE_STOPPED');
|
|
60
77
|
for (const transport of [...this.transports.values()])
|
|
@@ -65,6 +82,7 @@ export class DirectTransferService {
|
|
|
65
82
|
return;
|
|
66
83
|
this.disposed = true;
|
|
67
84
|
this.close();
|
|
85
|
+
this.terminalDirect?.dispose();
|
|
68
86
|
this.iceUdpMuxListener.stop();
|
|
69
87
|
}
|
|
70
88
|
async dispatch(command) {
|
|
@@ -236,6 +254,12 @@ export class DirectTransferService {
|
|
|
236
254
|
if (attached)
|
|
237
255
|
return;
|
|
238
256
|
attached = true;
|
|
257
|
+
if (channel.label.startsWith('mar-terminal-')) {
|
|
258
|
+
this.terminalDirect?.open(transport.command.directSessionId, channel, event.data);
|
|
259
|
+
if (this.terminalDirect === undefined)
|
|
260
|
+
channel.close();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
239
263
|
void this.openChannel(transport, channel, event.data).catch((error) => {
|
|
240
264
|
channel.close();
|
|
241
265
|
const id = openFrameSessionId(event.data);
|
|
@@ -396,6 +420,7 @@ export class DirectTransferService {
|
|
|
396
420
|
if (!this.transports.delete(transport.command.directSessionId))
|
|
397
421
|
return;
|
|
398
422
|
clearInterval(transport.leaseTimer);
|
|
423
|
+
this.terminalDirect?.removeTransport(transport.command.directSessionId);
|
|
399
424
|
for (const transfer of [...this.transfers.values()])
|
|
400
425
|
if (transfer.command.transportSessionId === transport.command.directSessionId)
|
|
401
426
|
void this.removeTransfer(transfer, code ?? 'DIRECT_CONNECTION_FAILED');
|
|
@@ -2004,6 +2004,7 @@ function modelSummary(model, index) {
|
|
|
2004
2004
|
titleGeneration: model.titleGeneration,
|
|
2005
2005
|
codeMode: model.codeMode,
|
|
2006
2006
|
highDensityCompaction: model.highDensityCompaction,
|
|
2007
|
+
...(model.fastMode === true ? { fastMode: true } : {}),
|
|
2007
2008
|
hostedWebSearch: model.hostedWebSearch,
|
|
2008
2009
|
...(model.responsesTransport === undefined
|
|
2009
2010
|
? {}
|
|
@@ -38,6 +38,9 @@ export class NodeControlMessageService {
|
|
|
38
38
|
if (envelope.type.startsWith('direct.') &&
|
|
39
39
|
this.options.handleDirectTransfer?.(envelope) === true)
|
|
40
40
|
return;
|
|
41
|
+
if (envelope.type.startsWith('terminal.direct.') &&
|
|
42
|
+
this.options.handleTerminalDirect?.(envelope) === true)
|
|
43
|
+
return;
|
|
41
44
|
if (envelope.type === 'ack')
|
|
42
45
|
return;
|
|
43
46
|
if (envelope.type === 'runs.reconcile') {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { terminalDataFrameSchema, terminalDirectNodeCommandSchema, terminalDirectOpenFrameSchema } from '@myagentroam/protocol';
|
|
2
|
+
const MAX_SESSIONS = 128;
|
|
3
|
+
const MAX_BUFFERED_BYTES = 1024 * 1024;
|
|
4
|
+
const MAX_FRAME_BYTES = 128 * 1024;
|
|
5
|
+
const HEARTBEAT_GRACE_MS = 30_000;
|
|
6
|
+
export class TerminalDirectService {
|
|
7
|
+
manager;
|
|
8
|
+
sendReady;
|
|
9
|
+
sendTakeover;
|
|
10
|
+
verifyTransport;
|
|
11
|
+
sessions = new Map();
|
|
12
|
+
timer;
|
|
13
|
+
stopListening;
|
|
14
|
+
now;
|
|
15
|
+
constructor(manager, sendReady, sendTakeover, verifyTransport, now = Date.now) {
|
|
16
|
+
this.manager = manager;
|
|
17
|
+
this.sendReady = sendReady;
|
|
18
|
+
this.sendTakeover = sendTakeover;
|
|
19
|
+
this.verifyTransport = verifyTransport;
|
|
20
|
+
this.now = now;
|
|
21
|
+
this.stopListening = manager.onFrame((frame) => {
|
|
22
|
+
for (const session of this.sessions.values())
|
|
23
|
+
if (session.opened && session.command.terminalId === frame.terminalId)
|
|
24
|
+
this.sendFrame(session, frame);
|
|
25
|
+
});
|
|
26
|
+
this.timer = setInterval(() => {
|
|
27
|
+
for (const session of [...this.sessions.values()])
|
|
28
|
+
if (session.expiresAt <= this.now() ||
|
|
29
|
+
this.now() - session.lastHeartbeat > HEARTBEAT_GRACE_MS)
|
|
30
|
+
this.remove(session);
|
|
31
|
+
}, 5_000);
|
|
32
|
+
this.timer.unref?.();
|
|
33
|
+
}
|
|
34
|
+
handle(value, getPeer) {
|
|
35
|
+
const parsed = terminalDirectNodeCommandSchema.safeParse(value);
|
|
36
|
+
if (!parsed.success)
|
|
37
|
+
return false;
|
|
38
|
+
const command = parsed.data;
|
|
39
|
+
if (command.type === 'terminal.direct.prepare') {
|
|
40
|
+
const peer = getPeer(command.transportSessionId);
|
|
41
|
+
if (peer === undefined ||
|
|
42
|
+
!this.verifyTransport(command, peer) ||
|
|
43
|
+
this.sessions.size >= MAX_SESSIONS ||
|
|
44
|
+
this.sessions.has(command.directSessionId) ||
|
|
45
|
+
command.openExpiresAt <= this.now() ||
|
|
46
|
+
command.expiresAt <= this.now())
|
|
47
|
+
return true;
|
|
48
|
+
this.sessions.set(command.directSessionId, {
|
|
49
|
+
command,
|
|
50
|
+
peer,
|
|
51
|
+
opened: false,
|
|
52
|
+
expiresAt: command.expiresAt,
|
|
53
|
+
lastHeartbeat: this.now()
|
|
54
|
+
});
|
|
55
|
+
this.sendReady(command.directSessionId);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const session = this.sessions.get(command.directSessionId);
|
|
59
|
+
if (session === undefined)
|
|
60
|
+
return true;
|
|
61
|
+
if (command.type === 'terminal.direct.cancel')
|
|
62
|
+
this.remove(session);
|
|
63
|
+
else if (session.expiresAt > this.now() && command.expiresAt > this.now()) {
|
|
64
|
+
session.expiresAt = command.expiresAt;
|
|
65
|
+
session.lastHeartbeat = this.now();
|
|
66
|
+
}
|
|
67
|
+
else
|
|
68
|
+
this.remove(session);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
open(transportId, channel, raw) {
|
|
72
|
+
if (typeof raw !== 'string' || Buffer.byteLength(raw) > 4_096) {
|
|
73
|
+
channel.close();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let parsed;
|
|
77
|
+
try {
|
|
78
|
+
parsed = terminalDirectOpenFrameSchema.safeParse(JSON.parse(raw));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
channel.close();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const session = parsed.success ? this.sessions.get(parsed.data.directSessionId) : undefined;
|
|
85
|
+
if (session === undefined ||
|
|
86
|
+
session.channel !== undefined ||
|
|
87
|
+
session.opened ||
|
|
88
|
+
session.command.transportSessionId !== transportId ||
|
|
89
|
+
session.command.grant !== (parsed.success ? parsed.data.grant : undefined) ||
|
|
90
|
+
session.command.openExpiresAt <= this.now() ||
|
|
91
|
+
session.expiresAt <= this.now() ||
|
|
92
|
+
!this.verifyTransport(session.command, session.peer)) {
|
|
93
|
+
channel.close();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
session.channel = channel;
|
|
97
|
+
channel.onclose = () => this.remove(session);
|
|
98
|
+
try {
|
|
99
|
+
const terminal = this.manager.get(session.command.terminalId);
|
|
100
|
+
if (terminal.state !== 'RUNNING' ||
|
|
101
|
+
terminal.workspaceId !== (session.command.workspaceId ?? null))
|
|
102
|
+
throw new Error('TERMINAL_NOT_FOUND');
|
|
103
|
+
const attachment = this.manager.authorizeAttach({
|
|
104
|
+
terminalId: session.command.terminalId,
|
|
105
|
+
afterSequence: session.command.afterSequence,
|
|
106
|
+
readonly: session.command.readonly,
|
|
107
|
+
takeover: session.command.takeover
|
|
108
|
+
});
|
|
109
|
+
session.attachmentId = attachment.attachmentId;
|
|
110
|
+
session.opened = true;
|
|
111
|
+
channel.send(JSON.stringify({
|
|
112
|
+
type: 'ATTACHED',
|
|
113
|
+
terminalId: session.command.terminalId,
|
|
114
|
+
afterSequence: session.command.afterSequence
|
|
115
|
+
}));
|
|
116
|
+
for (const frame of attachment.frames)
|
|
117
|
+
this.sendFrame(session, frame);
|
|
118
|
+
if (!session.command.readonly && session.command.takeover) {
|
|
119
|
+
for (const other of [...this.sessions.values()])
|
|
120
|
+
if (other !== session &&
|
|
121
|
+
other.opened &&
|
|
122
|
+
!other.command.readonly &&
|
|
123
|
+
other.command.terminalId === session.command.terminalId)
|
|
124
|
+
this.remove(other, 'TERMINAL_RELAY_REPLACED');
|
|
125
|
+
this.sendTakeover(session.command.directSessionId);
|
|
126
|
+
}
|
|
127
|
+
channel.onmessage = (event) => this.accept(session, event.data);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
this.remove(session, 'TERMINAL_ATTACH_FAILED');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
removeTransport(transportId) {
|
|
134
|
+
for (const session of [...this.sessions.values()])
|
|
135
|
+
if (session.command.transportSessionId === transportId)
|
|
136
|
+
this.remove(session);
|
|
137
|
+
}
|
|
138
|
+
close() {
|
|
139
|
+
for (const session of [...this.sessions.values()])
|
|
140
|
+
this.remove(session);
|
|
141
|
+
}
|
|
142
|
+
dispose() {
|
|
143
|
+
this.close();
|
|
144
|
+
clearInterval(this.timer);
|
|
145
|
+
this.stopListening();
|
|
146
|
+
}
|
|
147
|
+
accept(session, raw) {
|
|
148
|
+
if (!session.opened ||
|
|
149
|
+
typeof raw !== 'string' ||
|
|
150
|
+
Buffer.byteLength(raw) > MAX_FRAME_BYTES ||
|
|
151
|
+
session.expiresAt <= this.now()) {
|
|
152
|
+
this.remove(session, 'TERMINAL_FRAME_REJECTED');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const frame = terminalDataFrameSchema.parse(JSON.parse(raw));
|
|
157
|
+
if (frame.terminalId !== session.command.terminalId ||
|
|
158
|
+
!['input', 'resize', 'close'].includes(frame.type) ||
|
|
159
|
+
session.attachmentId === undefined)
|
|
160
|
+
throw new Error('TERMINAL_FRAME_INVALID');
|
|
161
|
+
void this.manager
|
|
162
|
+
.acceptFrame(session.attachmentId, frame)
|
|
163
|
+
.catch(() => this.remove(session, 'TERMINAL_FRAME_REJECTED'));
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
this.remove(session, 'TERMINAL_FRAME_REJECTED');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
sendFrame(session, frame) {
|
|
170
|
+
const channel = session.channel;
|
|
171
|
+
if (channel?.readyState !== 'open') {
|
|
172
|
+
this.remove(session);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const value = JSON.stringify(frame);
|
|
176
|
+
if (channel.bufferedAmount + Buffer.byteLength(value) > MAX_BUFFERED_BYTES) {
|
|
177
|
+
this.remove(session, 'TERMINAL_BACKPRESSURE');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
channel.send(value);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
this.remove(session);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
remove(session, code) {
|
|
188
|
+
if (!this.sessions.delete(session.command.directSessionId))
|
|
189
|
+
return;
|
|
190
|
+
if (session.attachmentId !== undefined)
|
|
191
|
+
this.manager.detach(session.attachmentId);
|
|
192
|
+
if (code !== undefined && session.channel?.readyState === 'open') {
|
|
193
|
+
try {
|
|
194
|
+
session.channel.send(JSON.stringify({ type: 'error', terminalId: session.command.terminalId, code }));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// A closing channel must not prevent attachment cleanup.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
session.channel?.close();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -26,7 +26,8 @@ export class TerminalService {
|
|
|
26
26
|
data?.__requestUserId)
|
|
27
27
|
}),
|
|
28
28
|
'terminal.close': (data) => this.close(data),
|
|
29
|
-
'terminal.attach.authorize': (data) => this.authorizeAttach(data)
|
|
29
|
+
'terminal.attach.authorize': (data) => this.authorizeAttach(data),
|
|
30
|
+
'terminal.direct.preflight': (data) => this.preflightDirect(data)
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
open(data) {
|
|
@@ -82,6 +83,18 @@ export class TerminalService {
|
|
|
82
83
|
const attachment = this.terminalManager.authorizeAttach(data);
|
|
83
84
|
return { attachment, terminal: this.terminalManager.get(attachment.terminalId) };
|
|
84
85
|
}
|
|
86
|
+
preflightDirect(data) {
|
|
87
|
+
if (data === null || typeof data !== 'object')
|
|
88
|
+
throw new Error('TERMINAL_ATTACH_INVALID');
|
|
89
|
+
const input = data;
|
|
90
|
+
this.assertOwner(input);
|
|
91
|
+
const terminal = this.terminalManager.get(String(input.terminalId));
|
|
92
|
+
if (terminal.state !== 'RUNNING')
|
|
93
|
+
throw new Error('TERMINAL_CLOSED');
|
|
94
|
+
if (terminal.workspaceId !== (input.workspaceId ?? null))
|
|
95
|
+
throw new Error('TERMINAL_WORKSPACE_MISMATCH');
|
|
96
|
+
return { terminalId: terminal.id, workspaceId: terminal.workspaceId };
|
|
97
|
+
}
|
|
85
98
|
assertOwner(input) {
|
|
86
99
|
const owner = this.gitOwners.get(String(input.terminalId));
|
|
87
100
|
if (owner !== undefined && owner.userId !== input.__requestUserId)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.110",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"npm": "11.12.1",
|
|
31
31
|
"ws": "^8.21.3",
|
|
32
32
|
"zod": "4.4.3",
|
|
33
|
-
"@myagentroam/agent": "0.9.
|
|
34
|
-
"@myagentroam/
|
|
35
|
-
"@myagentroam/
|
|
33
|
+
"@myagentroam/agent": "0.9.110",
|
|
34
|
+
"@myagentroam/code-navigation": "0.9.110",
|
|
35
|
+
"@myagentroam/protocol": "0.9.110"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/ws": "^8.18.1"
|