@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.
@@ -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
+ }
@@ -57,8 +57,15 @@ class Ws {
57
57
 
58
58
  addEventListener (type = 'message', cb = this.cb) {
59
59
  this.cb = cb
60
- const id = this.eid || generate()
61
- this.eid = id
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
- delete persists[this.eid]
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: this.eid,
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.eid) {
89
- delete persists[this.eid]
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
+ }