@electerm/electerm-react 3.10.0 → 3.11.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/client/common/bookmark-schemas.js +164 -0
- package/client/common/ws.js +25 -6
- package/client/common/zod.js +180 -0
- package/client/components/ai/agent-tool-call-card.jsx +90 -0
- package/client/components/ai/agent-tools.js +193 -0
- package/client/components/ai/agent.js +159 -0
- package/client/components/ai/ai-chat-entry.jsx +11 -0
- package/client/components/ai/ai-chat-history-item.jsx +48 -2
- package/client/components/ai/ai-chat.jsx +25 -6
- package/client/components/ai/ai-config.jsx +45 -4
- package/client/components/ai/ai.styl +73 -0
- package/client/components/main/main.jsx +3 -3
- package/client/components/rdp/file-transfer.js +3 -0
- package/client/components/terminal/terminal-error-handle.jsx +1 -1
- package/client/components/terminal/terminal-interactive-ui.jsx +157 -0
- package/client/components/terminal/terminal-interactive.jsx +64 -163
- package/client/components/terminal-info/terminal-info-entry.jsx +11 -0
- package/client/components/text-editor/text-editor-entry.jsx +11 -0
- package/client/components/widgets/widget-form.jsx +27 -2
- package/client/entry/worker.js +9 -5
- package/package.json +1 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bookmark schemas (ES module version for client)
|
|
3
|
+
* Mirrors src/app/common/bookmark-zod-schemas.js with additional types
|
|
4
|
+
*/
|
|
5
|
+
import { z } from './zod'
|
|
6
|
+
|
|
7
|
+
// const runScriptSchema = z.object({
|
|
8
|
+
// delay: z.number().optional().describe('Delay in ms before executing this command'),
|
|
9
|
+
// script: z.string().describe('Command to execute')
|
|
10
|
+
// })
|
|
11
|
+
|
|
12
|
+
const quickCommandSchema = z.object({
|
|
13
|
+
name: z.string().describe('Quick command name'),
|
|
14
|
+
command: z.string().describe('Command')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const sshTunnelSchema = z.object({
|
|
18
|
+
sshTunnel: z.enum(['forwardRemoteToLocal', 'forwardLocalToRemote', 'dynamicForward']).describe('Tunnel type'),
|
|
19
|
+
sshTunnelLocalHost: z.string().optional().describe('Local host'),
|
|
20
|
+
sshTunnelLocalPort: z.number().optional().describe('Local port'),
|
|
21
|
+
sshTunnelRemoteHost: z.string().optional().describe('Remote host'),
|
|
22
|
+
sshTunnelRemotePort: z.number().optional().describe('Remote port'),
|
|
23
|
+
name: z.string().optional().describe('Tunnel name')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const connectionHoppingSchema = z.object({
|
|
27
|
+
host: z.string().describe('Host address'),
|
|
28
|
+
port: z.number().optional().describe('Port number'),
|
|
29
|
+
username: z.string().optional().describe('Username'),
|
|
30
|
+
password: z.string().optional().describe('Password'),
|
|
31
|
+
privateKey: z.string().optional().describe('Private key'),
|
|
32
|
+
passphrase: z.string().optional().describe('Passphrase'),
|
|
33
|
+
certificate: z.string().optional().describe('Certificate'),
|
|
34
|
+
authType: z.string().optional().describe('Auth type'),
|
|
35
|
+
profile: z.string().optional().describe('Profile id')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const commonNetworkBookmarkProps = {
|
|
39
|
+
title: z.string().describe('Bookmark title'),
|
|
40
|
+
host: z.string().describe('Host address'),
|
|
41
|
+
port: z.number().optional().describe('Port number'),
|
|
42
|
+
username: z.string().optional().describe('Username'),
|
|
43
|
+
password: z.string().optional().describe('Password'),
|
|
44
|
+
description: z.string().optional().describe('Bookmark description'),
|
|
45
|
+
startDirectoryRemote: z.string().optional().describe('Remote starting directory'),
|
|
46
|
+
startDirectoryLocal: z.string().optional().describe('Local starting directory'),
|
|
47
|
+
profile: z.string().optional().describe('Profile id'),
|
|
48
|
+
proxy: z.string().optional().describe('Proxy address (socks5://...)')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const sshBookmarkSchema = {
|
|
52
|
+
...commonNetworkBookmarkProps,
|
|
53
|
+
host: z.string().describe('SSH host address'),
|
|
54
|
+
port: z.number().optional().describe('SSH port (default 22)'),
|
|
55
|
+
username: z.string().optional().describe('SSH username'),
|
|
56
|
+
password: z.string().optional().describe('SSH password'),
|
|
57
|
+
authType: z.enum(['password', 'privateKey', 'profiles']).optional().describe('Authentication type'),
|
|
58
|
+
privateKey: z.string().optional().describe('Private key content or path (for privateKey auth)'),
|
|
59
|
+
passphrase: z.string().optional().describe('Passphrase for private key/certificate'),
|
|
60
|
+
certificate: z.string().optional().describe('Certificate content'),
|
|
61
|
+
enableSsh: z.boolean().optional().describe('Enable ssh, default is true'),
|
|
62
|
+
enableSftp: z.boolean().optional().describe('Enable sftp, default is true'),
|
|
63
|
+
useSshAgent: z.boolean().optional().describe('Use SSH agent, default is true'),
|
|
64
|
+
sshAgent: z.string().optional().describe('SSH agent path'),
|
|
65
|
+
serverHostKey: z.array(z.string()).optional().describe('Server host key algorithms'),
|
|
66
|
+
cipher: z.array(z.string()).optional().describe('Cipher list'),
|
|
67
|
+
quickCommands: z.array(quickCommandSchema).optional().describe('Quick commands'),
|
|
68
|
+
x11: z.boolean().optional().describe('Enable x11 forwarding, default is false'),
|
|
69
|
+
term: z.string().optional().describe('Terminal type, default is xterm-256color'),
|
|
70
|
+
displayRaw: z.boolean().optional().describe('Display raw output, default is false'),
|
|
71
|
+
encode: z.string().optional().describe('Charset, default is utf8'),
|
|
72
|
+
envLang: z.string().optional().describe('ENV LANG, default is en_US.UTF-8'),
|
|
73
|
+
color: z.string().optional().describe('Tag color, like #000000'),
|
|
74
|
+
sshTunnels: z.array(sshTunnelSchema).optional().describe('SSH tunnel definitions'),
|
|
75
|
+
connectionHoppings: z.array(connectionHoppingSchema).optional().describe('Connection hopping definitions')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const telnetBookmarkSchema = {
|
|
79
|
+
...commonNetworkBookmarkProps,
|
|
80
|
+
host: z.string().describe('Telnet host address'),
|
|
81
|
+
port: z.number().optional().describe('Telnet port (default 23)'),
|
|
82
|
+
username: z.string().optional().describe('Telnet username'),
|
|
83
|
+
password: z.string().optional().describe('Telnet password'),
|
|
84
|
+
loginPrompt: z.string().optional().describe('Login prompt regex'),
|
|
85
|
+
passwordPrompt: z.string().optional().describe('Password prompt regex')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const serialBookmarkSchema = {
|
|
89
|
+
title: z.string().describe('Bookmark title'),
|
|
90
|
+
path: z.string().describe('Serial device path, e.g., /dev/ttyUSB0 or COM1'),
|
|
91
|
+
baudRate: z.number().optional().describe('Baud rate (default 9600)'),
|
|
92
|
+
dataBits: z.number().optional().describe('Data bits (default 8)'),
|
|
93
|
+
stopBits: z.number().optional().describe('Stop bits (default 1)'),
|
|
94
|
+
parity: z.enum(['none', 'even', 'odd', 'mark', 'space']).optional().describe('Parity (default none)'),
|
|
95
|
+
rtscts: z.boolean().optional().describe('RTS/CTS flow control'),
|
|
96
|
+
xon: z.boolean().optional().describe('XON flow control'),
|
|
97
|
+
xoff: z.boolean().optional().describe('XOFF flow control'),
|
|
98
|
+
xany: z.boolean().optional().describe('XANY flow control'),
|
|
99
|
+
description: z.string().optional().describe('Bookmark description')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const vncBookmarkSchema = {
|
|
103
|
+
...commonNetworkBookmarkProps,
|
|
104
|
+
host: z.string().describe('VNC host address'),
|
|
105
|
+
port: z.number().optional().describe('VNC port (default 5900)'),
|
|
106
|
+
viewOnly: z.boolean().optional().describe('View only mode, default is false'),
|
|
107
|
+
clipViewport: z.boolean().optional().describe('Clip viewport to window'),
|
|
108
|
+
scaleViewport: z.boolean().optional().describe('Scale viewport to window, default is true'),
|
|
109
|
+
qualityLevel: z.number().optional().describe('VNC quality level 0-9, lower is faster, default 3'),
|
|
110
|
+
compressionLevel: z.number().optional().describe('VNC compression level 0-9, lower is faster, default 1'),
|
|
111
|
+
shared: z.boolean().optional().describe('Shared session, default is true')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const rdpBookmarkSchema = {
|
|
115
|
+
...commonNetworkBookmarkProps,
|
|
116
|
+
host: z.string().describe('RDP host address'),
|
|
117
|
+
port: z.number().optional().describe('RDP port (default 3389)'),
|
|
118
|
+
domain: z.string().optional().describe('Login domain')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const ftpBookmarkSchema = {
|
|
122
|
+
title: z.string().describe('Bookmark title'),
|
|
123
|
+
host: z.string().describe('FTP host address'),
|
|
124
|
+
port: z.number().optional().describe('FTP port (default 21)'),
|
|
125
|
+
user: z.string().optional().describe('FTP username'),
|
|
126
|
+
password: z.string().optional().describe('FTP password'),
|
|
127
|
+
secure: z.boolean().optional().describe('Use secure FTP (FTPS), default is false'),
|
|
128
|
+
encode: z.string().optional().describe('Charset for file names, default is utf-8'),
|
|
129
|
+
profile: z.string().optional().describe('Profile id'),
|
|
130
|
+
description: z.string().optional().describe('Bookmark description')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const webBookmarkSchema = {
|
|
134
|
+
url: z.string().describe('Website URL'),
|
|
135
|
+
title: z.string().optional().describe('Bookmark title'),
|
|
136
|
+
description: z.string().optional().describe('Bookmark description'),
|
|
137
|
+
useragent: z.string().optional().describe('Custom user agent')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const localBookmarkSchema = {
|
|
141
|
+
title: z.string().describe('Bookmark title'),
|
|
142
|
+
description: z.string().optional().describe('Bookmark description'),
|
|
143
|
+
startDirectoryLocal: z.string().optional().describe('Local starting directory')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const spiceBookmarkSchema = {
|
|
147
|
+
...commonNetworkBookmarkProps,
|
|
148
|
+
host: z.string().describe('Spice host address'),
|
|
149
|
+
port: z.number().optional().describe('Spice port (default 5900)'),
|
|
150
|
+
viewOnly: z.boolean().optional().describe('View only mode'),
|
|
151
|
+
scaleViewport: z.boolean().optional().describe('Scale viewport to window, default is true')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const bookmarkSchemas = {
|
|
155
|
+
ssh: sshBookmarkSchema,
|
|
156
|
+
telnet: telnetBookmarkSchema,
|
|
157
|
+
serial: serialBookmarkSchema,
|
|
158
|
+
vnc: vncBookmarkSchema,
|
|
159
|
+
rdp: rdpBookmarkSchema,
|
|
160
|
+
ftp: ftpBookmarkSchema,
|
|
161
|
+
web: webBookmarkSchema,
|
|
162
|
+
local: localBookmarkSchema,
|
|
163
|
+
spice: spiceBookmarkSchema
|
|
164
|
+
}
|
package/client/common/ws.js
CHANGED
|
@@ -57,8 +57,15 @@ class Ws {
|
|
|
57
57
|
|
|
58
58
|
addEventListener (type = 'message', cb = this.cb) {
|
|
59
59
|
this.cb = cb
|
|
60
|
-
const id =
|
|
61
|
-
this.
|
|
60
|
+
const id = generate()
|
|
61
|
+
if (!this.eids) {
|
|
62
|
+
this.eids = new Set()
|
|
63
|
+
}
|
|
64
|
+
if (!this.cbToId) {
|
|
65
|
+
this.cbToId = new Map()
|
|
66
|
+
}
|
|
67
|
+
this.eids.add(id)
|
|
68
|
+
this.cbToId.set(cb, id)
|
|
62
69
|
persists[id] = {
|
|
63
70
|
resolve: cb
|
|
64
71
|
}
|
|
@@ -71,9 +78,15 @@ class Ws {
|
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
removeEventListener (type, cb) {
|
|
74
|
-
|
|
81
|
+
if (!this.cbToId || !this.cbToId.has(cb)) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const id = this.cbToId.get(cb)
|
|
85
|
+
this.cbToId.delete(cb)
|
|
86
|
+
this.eids.delete(id)
|
|
87
|
+
delete persists[id]
|
|
75
88
|
send({
|
|
76
|
-
id
|
|
89
|
+
id,
|
|
77
90
|
wsId: this.id,
|
|
78
91
|
type,
|
|
79
92
|
action: 'removeEventListener'
|
|
@@ -85,8 +98,14 @@ class Ws {
|
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
clearOnces () {
|
|
88
|
-
if (this.
|
|
89
|
-
|
|
101
|
+
if (this.eids) {
|
|
102
|
+
for (const eid of this.eids) {
|
|
103
|
+
delete persists[eid]
|
|
104
|
+
}
|
|
105
|
+
this.eids.clear()
|
|
106
|
+
}
|
|
107
|
+
if (this.cbToId) {
|
|
108
|
+
this.cbToId.clear()
|
|
90
109
|
}
|
|
91
110
|
const ids = [...this.onceIds]
|
|
92
111
|
ids.forEach(k => {
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight zod replacement (ES module version for client)
|
|
3
|
+
* Covers only the API surface used in the project:
|
|
4
|
+
* z.string(), z.number(), z.boolean(), z.any(),
|
|
5
|
+
* z.enum(), z.object(), z.array(), z.record(),
|
|
6
|
+
* .optional(), .describe(), z.toJSONSchema()
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class ZodType {
|
|
10
|
+
constructor (typeName, meta = {}) {
|
|
11
|
+
this._typeName = typeName
|
|
12
|
+
this._optional = false
|
|
13
|
+
this._description = undefined
|
|
14
|
+
this._meta = meta
|
|
15
|
+
this['~standard'] = { type: typeName }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
optional () {
|
|
19
|
+
const clone = this._clone()
|
|
20
|
+
clone._optional = true
|
|
21
|
+
return clone
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe (desc) {
|
|
25
|
+
const clone = this._clone()
|
|
26
|
+
clone._description = desc
|
|
27
|
+
return clone
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_clone () {
|
|
31
|
+
const clone = Object.create(Object.getPrototypeOf(this))
|
|
32
|
+
Object.assign(clone, this)
|
|
33
|
+
clone['~standard'] = { ...this['~standard'] }
|
|
34
|
+
return clone
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_toJsonSchema () {
|
|
38
|
+
throw new Error('_toJsonSchema not implemented for ' + this._typeName)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class ZodString extends ZodType {
|
|
43
|
+
constructor () {
|
|
44
|
+
super('string')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_toJsonSchema () {
|
|
48
|
+
return { type: 'string' }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class ZodNumber extends ZodType {
|
|
53
|
+
constructor () {
|
|
54
|
+
super('number')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_toJsonSchema () {
|
|
58
|
+
return { type: 'number' }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class ZodBoolean extends ZodType {
|
|
63
|
+
constructor () {
|
|
64
|
+
super('boolean')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_toJsonSchema () {
|
|
68
|
+
return { type: 'boolean' }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class ZodEnum extends ZodType {
|
|
73
|
+
constructor (values) {
|
|
74
|
+
super('enum', { values })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_toJsonSchema () {
|
|
78
|
+
return { type: 'string', enum: this._meta.values }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class ZodArray extends ZodType {
|
|
83
|
+
constructor (itemSchema) {
|
|
84
|
+
super('array', { itemSchema })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_toJsonSchema () {
|
|
88
|
+
const items = schemaToJsonSchema(this._meta.itemSchema)
|
|
89
|
+
return { type: 'array', items }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class ZodObject extends ZodType {
|
|
94
|
+
constructor (shape) {
|
|
95
|
+
super('object', { shape })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_toJsonSchema () {
|
|
99
|
+
const properties = {}
|
|
100
|
+
const required = []
|
|
101
|
+
const shape = this._meta.shape || {}
|
|
102
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
103
|
+
properties[key] = schemaToJsonSchema(schema)
|
|
104
|
+
if (schema._description) {
|
|
105
|
+
properties[key].description = schema._description
|
|
106
|
+
}
|
|
107
|
+
if (!schema._optional) {
|
|
108
|
+
required.push(key)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const result = { type: 'object', properties }
|
|
112
|
+
if (required.length > 0) {
|
|
113
|
+
result.required = required
|
|
114
|
+
}
|
|
115
|
+
return result
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function schemaToJsonSchema (schema) {
|
|
120
|
+
if (!schema) {
|
|
121
|
+
return {}
|
|
122
|
+
}
|
|
123
|
+
if (schema instanceof ZodType) {
|
|
124
|
+
const base = schema._toJsonSchema()
|
|
125
|
+
if (schema._description) {
|
|
126
|
+
base.description = schema._description
|
|
127
|
+
}
|
|
128
|
+
return base
|
|
129
|
+
}
|
|
130
|
+
if (typeof schema === 'object' && !Array.isArray(schema)) {
|
|
131
|
+
return objectShapeToJsonSchema(schema)
|
|
132
|
+
}
|
|
133
|
+
return {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function objectShapeToJsonSchema (shape) {
|
|
137
|
+
const properties = {}
|
|
138
|
+
const required = []
|
|
139
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
140
|
+
if (value instanceof ZodType) {
|
|
141
|
+
properties[key] = schemaToJsonSchema(value)
|
|
142
|
+
if (value._description) {
|
|
143
|
+
properties[key].description = value._description
|
|
144
|
+
}
|
|
145
|
+
if (!value._optional) {
|
|
146
|
+
required.push(key)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const result = { type: 'object', properties }
|
|
151
|
+
if (required.length > 0) {
|
|
152
|
+
result.required = required
|
|
153
|
+
}
|
|
154
|
+
return result
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const z = {
|
|
158
|
+
string: () => new ZodString(),
|
|
159
|
+
number: () => new ZodNumber(),
|
|
160
|
+
boolean: () => new ZodBoolean(),
|
|
161
|
+
enum: (values) => new ZodEnum(values),
|
|
162
|
+
object: (shape) => new ZodObject(shape || {}),
|
|
163
|
+
array: (itemSchema) => new ZodArray(itemSchema),
|
|
164
|
+
toJSONSchema: (schema) => {
|
|
165
|
+
if (schema instanceof ZodType) {
|
|
166
|
+
return schema._toJsonSchema()
|
|
167
|
+
}
|
|
168
|
+
if (typeof schema === 'object' && schema !== null) {
|
|
169
|
+
const hasZodValues = Object.values(schema).some(
|
|
170
|
+
v => v instanceof ZodType
|
|
171
|
+
)
|
|
172
|
+
if (hasZodValues) {
|
|
173
|
+
return objectShapeToJsonSchema(schema)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { type: 'object', properties: {} }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { ZodType, schemaToJsonSchema }
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Tag } from 'antd'
|
|
3
|
+
import {
|
|
4
|
+
CaretDownOutlined,
|
|
5
|
+
CaretRightOutlined,
|
|
6
|
+
LoadingOutlined,
|
|
7
|
+
CheckCircleOutlined,
|
|
8
|
+
CloseCircleOutlined,
|
|
9
|
+
CodeOutlined,
|
|
10
|
+
DatabaseOutlined
|
|
11
|
+
} from '@ant-design/icons'
|
|
12
|
+
|
|
13
|
+
const toolIcons = {
|
|
14
|
+
send_terminal_command: CodeOutlined,
|
|
15
|
+
get_terminal_output: CodeOutlined,
|
|
16
|
+
open_local_terminal: CodeOutlined,
|
|
17
|
+
list_tabs: CodeOutlined,
|
|
18
|
+
get_active_tab: CodeOutlined,
|
|
19
|
+
switch_tab: CodeOutlined,
|
|
20
|
+
list_bookmarks: DatabaseOutlined,
|
|
21
|
+
open_bookmark: DatabaseOutlined,
|
|
22
|
+
add_bookmark: DatabaseOutlined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatResult (result) {
|
|
26
|
+
if (!result) return ''
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(result)
|
|
29
|
+
if (parsed.output) return parsed.output
|
|
30
|
+
return JSON.stringify(parsed, null, 2)
|
|
31
|
+
} catch {
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default function AgentToolCallCard ({ toolCall }) {
|
|
37
|
+
const [expanded, setExpanded] = useState(toolCall.status === 'running')
|
|
38
|
+
const { name, args, status, result } = toolCall
|
|
39
|
+
const Icon = toolIcons[name] || CodeOutlined
|
|
40
|
+
|
|
41
|
+
function renderStatus () {
|
|
42
|
+
if (status === 'running') {
|
|
43
|
+
return <LoadingOutlined className='agent-tool-status-running' />
|
|
44
|
+
}
|
|
45
|
+
if (status === 'completed') {
|
|
46
|
+
return <CheckCircleOutlined className='agent-tool-status-completed' />
|
|
47
|
+
}
|
|
48
|
+
return <CloseCircleOutlined className='agent-tool-status-error' />
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function renderTag () {
|
|
52
|
+
const color = status === 'running' ? 'processing' : status === 'completed' ? 'success' : 'error'
|
|
53
|
+
return (
|
|
54
|
+
<Tag color={color} className='agent-tool-tag'>
|
|
55
|
+
{status}
|
|
56
|
+
</Tag>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className={`agent-tool-call-card agent-tool-${status}`}>
|
|
62
|
+
<div
|
|
63
|
+
className='agent-tool-header pointer'
|
|
64
|
+
onClick={() => setExpanded(!expanded)}
|
|
65
|
+
>
|
|
66
|
+
{expanded ? <CaretDownOutlined /> : <CaretRightOutlined />}
|
|
67
|
+
<Icon className='mg1l' />
|
|
68
|
+
<span className='mg1l agent-tool-name'>{name}</span>
|
|
69
|
+
{renderTag()}
|
|
70
|
+
{renderStatus()}
|
|
71
|
+
</div>
|
|
72
|
+
{expanded && (
|
|
73
|
+
<div className='agent-tool-detail'>
|
|
74
|
+
{args && Object.keys(args).length > 0 && (
|
|
75
|
+
<div className='agent-tool-args'>
|
|
76
|
+
<div className='agent-tool-label'>Arguments:</div>
|
|
77
|
+
<pre className='agent-tool-pre'>{JSON.stringify(args, null, 2)}</pre>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
{result && (
|
|
81
|
+
<div className='agent-tool-result'>
|
|
82
|
+
<div className='agent-tool-label'>Result:</div>
|
|
83
|
+
<pre className='agent-tool-pre'>{formatResult(result)}</pre>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from '../../common/zod'
|
|
2
|
+
import { bookmarkSchemas } from '../../common/bookmark-schemas'
|
|
3
|
+
|
|
4
|
+
function buildAddBookmarkParameters () {
|
|
5
|
+
const typeProperties = {}
|
|
6
|
+
for (const [type, schema] of Object.entries(bookmarkSchemas)) {
|
|
7
|
+
typeProperties[type] = z.toJSONSchema(z.object(schema))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
type: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
enum: Object.keys(bookmarkSchemas),
|
|
16
|
+
description: 'Bookmark type'
|
|
17
|
+
},
|
|
18
|
+
...Object.fromEntries(
|
|
19
|
+
Object.entries(typeProperties).map(([type, schema]) => [
|
|
20
|
+
type,
|
|
21
|
+
{ type: 'object', description: `Fields for ${type} bookmark`, ...schema }
|
|
22
|
+
])
|
|
23
|
+
)
|
|
24
|
+
},
|
|
25
|
+
required: ['type']
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const agentTools = [
|
|
30
|
+
{
|
|
31
|
+
type: 'function',
|
|
32
|
+
function: {
|
|
33
|
+
name: 'send_terminal_command',
|
|
34
|
+
description: 'Send a command to a terminal tab and wait for it to finish. Returns the command output.',
|
|
35
|
+
parameters: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
command: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'The shell command to execute'
|
|
41
|
+
},
|
|
42
|
+
tabId: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Terminal tab ID. Omit to use the active terminal.'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
required: ['command']
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'function',
|
|
53
|
+
function: {
|
|
54
|
+
name: 'get_terminal_output',
|
|
55
|
+
description: 'Read the current visible output from a terminal.',
|
|
56
|
+
parameters: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
tabId: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Terminal tab ID. Omit for active terminal.'
|
|
62
|
+
},
|
|
63
|
+
lines: {
|
|
64
|
+
type: 'number',
|
|
65
|
+
description: 'Number of recent lines to read (default 50).'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'function',
|
|
73
|
+
function: {
|
|
74
|
+
name: 'open_local_terminal',
|
|
75
|
+
description: 'Open a new local terminal tab. Returns the new tab ID.',
|
|
76
|
+
parameters: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'function',
|
|
84
|
+
function: {
|
|
85
|
+
name: 'list_tabs',
|
|
86
|
+
description: 'List all open terminal tabs with their IDs, titles, hosts, and types.',
|
|
87
|
+
parameters: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'function',
|
|
95
|
+
function: {
|
|
96
|
+
name: 'get_active_tab',
|
|
97
|
+
description: 'Get the currently active terminal tab.',
|
|
98
|
+
parameters: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'function',
|
|
106
|
+
function: {
|
|
107
|
+
name: 'switch_tab',
|
|
108
|
+
description: 'Switch to a different terminal tab.',
|
|
109
|
+
parameters: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
tabId: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'The tab ID to switch to.'
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
required: ['tabId']
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'function',
|
|
123
|
+
function: {
|
|
124
|
+
name: 'list_bookmarks',
|
|
125
|
+
description: 'List all saved bookmarks (SSH, Telnet, VNC, etc.).',
|
|
126
|
+
parameters: {
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: {}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'function',
|
|
134
|
+
function: {
|
|
135
|
+
name: 'open_bookmark',
|
|
136
|
+
description: 'Open a saved bookmark as a new terminal tab.',
|
|
137
|
+
parameters: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
id: {
|
|
141
|
+
type: 'string',
|
|
142
|
+
description: 'The bookmark ID to open.'
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ['id']
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: 'function',
|
|
151
|
+
function: {
|
|
152
|
+
name: 'add_bookmark',
|
|
153
|
+
description: 'Create a new bookmark. Specify the type and provide type-specific fields. Supported types: ' + Object.keys(bookmarkSchemas).join(', ') + '.',
|
|
154
|
+
parameters: buildAddBookmarkParameters()
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
export async function executeToolCall (toolName, args) {
|
|
160
|
+
const store = window.store
|
|
161
|
+
switch (toolName) {
|
|
162
|
+
case 'send_terminal_command': {
|
|
163
|
+
store.mcpSendTerminalCommand(args)
|
|
164
|
+
const idleResult = await store.mcpWaitForTerminalIdle({
|
|
165
|
+
tabId: args.tabId || store.activeTabId,
|
|
166
|
+
timeout: 30000,
|
|
167
|
+
lines: 100
|
|
168
|
+
})
|
|
169
|
+
return JSON.stringify(idleResult)
|
|
170
|
+
}
|
|
171
|
+
case 'get_terminal_output':
|
|
172
|
+
return JSON.stringify(store.mcpGetTerminalOutput(args))
|
|
173
|
+
case 'open_local_terminal':
|
|
174
|
+
return JSON.stringify(store.mcpOpenLocalTerminal())
|
|
175
|
+
case 'list_tabs':
|
|
176
|
+
return JSON.stringify(store.mcpListTabs())
|
|
177
|
+
case 'get_active_tab':
|
|
178
|
+
return JSON.stringify(store.mcpGetActiveTab())
|
|
179
|
+
case 'switch_tab':
|
|
180
|
+
return JSON.stringify(store.mcpSwitchTab(args))
|
|
181
|
+
case 'list_bookmarks':
|
|
182
|
+
return JSON.stringify(store.mcpListBookmarks())
|
|
183
|
+
case 'open_bookmark':
|
|
184
|
+
return JSON.stringify(store.mcpOpenBookmark(args))
|
|
185
|
+
case 'add_bookmark': {
|
|
186
|
+
const { type } = args
|
|
187
|
+
const typeFields = args[type] || {}
|
|
188
|
+
return JSON.stringify(await store.mcpAddBookmark({ type, ...typeFields }))
|
|
189
|
+
}
|
|
190
|
+
default:
|
|
191
|
+
throw new Error(`Unknown agent tool: ${toolName}`)
|
|
192
|
+
}
|
|
193
|
+
}
|