agentgui 1.0.272 → 1.0.274

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,150 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { execSync, execFileSync } from 'child_process';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const src = __dirname;
8
+ const out = path.join(src, '..', 'wagentgui');
9
+ const nm = path.join(src, 'node_modules');
10
+ const BUN_WIN_CACHE = path.join(process.env.HOME, '.bun', 'install', 'cache', 'bun-windows-x64-v1.3.9');
11
+
12
+ function log(msg) { console.log('[BUILD] ' + msg); }
13
+
14
+ function rmrf(dir) {
15
+ if (!fs.existsSync(dir)) return;
16
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
17
+ const fp = path.join(dir, entry.name);
18
+ if (entry.isDirectory()) fs.rmSync(fp, { recursive: true, force: true });
19
+ else fs.unlinkSync(fp);
20
+ }
21
+ }
22
+
23
+ function copyDir(from, to) {
24
+ if (!fs.existsSync(from)) return;
25
+ fs.mkdirSync(to, { recursive: true });
26
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
27
+ const s = path.join(from, entry.name);
28
+ const d = path.join(to, entry.name);
29
+ if (entry.isDirectory()) copyDir(s, d);
30
+ else fs.copyFileSync(s, d);
31
+ }
32
+ }
33
+
34
+ function copyFile(from, to) {
35
+ fs.mkdirSync(path.dirname(to), { recursive: true });
36
+ fs.copyFileSync(from, to);
37
+ }
38
+
39
+ function sizeOf(dir) {
40
+ if (!fs.existsSync(dir)) return 0;
41
+ let sz = 0;
42
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
43
+ const fp = path.join(dir, e.name);
44
+ sz += e.isDirectory() ? sizeOf(fp) : fs.statSync(fp).size;
45
+ }
46
+ return sz;
47
+ }
48
+
49
+ function ensureWinBun() {
50
+ if (fs.existsSync(BUN_WIN_CACHE)) {
51
+ const stat = fs.statSync(BUN_WIN_CACHE);
52
+ if (stat.size > 50 * 1024 * 1024) {
53
+ // Verify it's a PE file
54
+ const buf = Buffer.alloc(2);
55
+ const fd = fs.openSync(BUN_WIN_CACHE, 'r');
56
+ fs.readSync(fd, buf, 0, 2, 0);
57
+ fs.closeSync(fd);
58
+ if (buf[0] === 0x4D && buf[1] === 0x5A) return BUN_WIN_CACHE;
59
+ }
60
+ }
61
+ log('Downloading Windows Bun binary...');
62
+ execSync(
63
+ `~/.bun/bin/bun build --compile --target=bun-windows-x64 --outfile=/tmp/_bun_trigger.exe /dev/null 2>/dev/null || true`,
64
+ { stdio: 'inherit' }
65
+ );
66
+ if (fs.existsSync(BUN_WIN_CACHE)) return BUN_WIN_CACHE;
67
+ throw new Error(`Windows Bun binary not found at ${BUN_WIN_CACHE}. Run: bun build --compile --target=bun-windows-x64 --outfile=/tmp/x.exe /dev/null`);
68
+ }
69
+
70
+ log('Output: ' + out);
71
+ rmrf(out);
72
+ fs.mkdirSync(out, { recursive: true });
73
+
74
+ log('Building JS bundle...');
75
+ execSync(
76
+ `~/.bun/bin/bun build --minify --target=bun --outfile=${path.join(out, 'bundle.js')} ${path.join(src, 'portable-entry.js')}`,
77
+ { stdio: 'inherit', cwd: src }
78
+ );
79
+ const bundleSize = fs.statSync(path.join(out, 'bundle.js')).size;
80
+ log(`Bundle: ${Math.round(bundleSize / 1024)}KB`);
81
+
82
+ log('Locating Windows Bun binary...');
83
+ const bunWinPath = ensureWinBun();
84
+ log(`Windows Bun: ${bunWinPath} (${Math.round(fs.statSync(bunWinPath).size / 1024 / 1024)}MB)`);
85
+
86
+ log('Injecting bundle into Windows Bun exe (.bun PE section)...');
87
+ const injector = path.join(src, 'scripts', 'inject-pe-section.py');
88
+ execFileSync('python3', [
89
+ injector,
90
+ bunWinPath,
91
+ path.join(out, 'bundle.js'),
92
+ path.join(out, 'agentgui.exe')
93
+ ], { stdio: 'inherit' });
94
+ fs.unlinkSync(path.join(out, 'bundle.js'));
95
+
96
+ const exeSize = fs.statSync(path.join(out, 'agentgui.exe')).size;
97
+ // Verify PE magic
98
+ const buf = Buffer.alloc(2);
99
+ const fd = fs.openSync(path.join(out, 'agentgui.exe'), 'r');
100
+ fs.readSync(fd, buf, 0, 2, 0);
101
+ fs.closeSync(fd);
102
+ if (buf[0] !== 0x4D || buf[1] !== 0x5A) throw new Error('Output exe is not a valid PE file (bad MZ magic)');
103
+ log(`Exe compiled: ${Math.round(exeSize / 1024 / 1024)}MB (valid PE)`);
104
+
105
+ log('Copying static files...');
106
+ copyDir(path.join(src, 'static'), path.join(out, 'static'));
107
+
108
+ const destNm = path.join(out, 'node_modules');
109
+
110
+ log('Copying @huggingface/transformers (dist + win32 natives)...');
111
+ const hfSrc = path.join(nm, '@huggingface', 'transformers');
112
+ const hfDest = path.join(destNm, '@huggingface', 'transformers');
113
+ copyFile(path.join(hfSrc, 'package.json'), path.join(hfDest, 'package.json'));
114
+ copyDir(path.join(hfSrc, 'dist'), path.join(hfDest, 'dist'));
115
+ const hfNmSrc = path.join(hfSrc, 'node_modules');
116
+ const hfNmDest = path.join(hfDest, 'node_modules');
117
+ const hfOnnxSrc = path.join(hfNmSrc, 'onnxruntime-node');
118
+ const hfOnnxDest = path.join(hfNmDest, 'onnxruntime-node');
119
+ copyFile(path.join(hfOnnxSrc, 'package.json'), path.join(hfOnnxDest, 'package.json'));
120
+ copyDir(path.join(hfOnnxSrc, 'dist'), path.join(hfOnnxDest, 'dist'));
121
+ copyDir(path.join(hfOnnxSrc, 'lib'), path.join(hfOnnxDest, 'lib'));
122
+ copyDir(path.join(hfOnnxSrc, 'bin', 'napi-v3', 'win32', 'x64'), path.join(hfOnnxDest, 'bin', 'napi-v3', 'win32', 'x64'));
123
+ copyDir(path.join(hfNmSrc, 'onnxruntime-common'), path.join(hfNmDest, 'onnxruntime-common'));
124
+
125
+ log('Copying webtalk...');
126
+ copyDir(path.join(nm, 'webtalk'), path.join(destNm, 'webtalk'));
127
+
128
+ log('Copying @anthropic-ai/claude-code ripgrep (win32)...');
129
+ const claudeSrc = path.join(nm, '@anthropic-ai', 'claude-code');
130
+ const claudeDest = path.join(destNm, '@anthropic-ai', 'claude-code');
131
+ copyFile(path.join(claudeSrc, 'package.json'), path.join(claudeDest, 'package.json'));
132
+ copyDir(path.join(claudeSrc, 'vendor', 'ripgrep', 'x64-win32'), path.join(claudeDest, 'vendor', 'ripgrep', 'x64-win32'));
133
+
134
+ log('Creating data directory...');
135
+ fs.mkdirSync(path.join(out, 'data'), { recursive: true });
136
+
137
+ fs.writeFileSync(path.join(out, 'README.txt'), [
138
+ '# AgentGUI Portable',
139
+ '',
140
+ 'No installation required. Double-click agentgui.exe to start.',
141
+ '',
142
+ 'Web interface: http://localhost:3000/gm/',
143
+ '',
144
+ 'Data is stored in the data/ folder next to the executable.',
145
+ '',
146
+ 'Requirements: None - fully self-contained.',
147
+ ].join('\n'));
148
+
149
+ const totalMB = Math.round(sizeOf(out) / 1024 / 1024);
150
+ log(`Build complete! Total: ${totalMB}MB Output: ${out}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.272",
3
+ "version": "1.0.274",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -0,0 +1,43 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import fs from 'fs';
4
+ import { spawn } from 'child_process';
5
+
6
+ function getExeDir() {
7
+ if (process.execPath && process.execPath !== process.argv[0]) {
8
+ return path.dirname(process.execPath);
9
+ }
10
+ if (process.argv[1]) {
11
+ const argv1 = path.resolve(process.argv[1]);
12
+ if (fs.existsSync(argv1) || fs.existsSync(argv1 + '.exe')) {
13
+ return path.dirname(argv1);
14
+ }
15
+ }
16
+ return process.cwd();
17
+ }
18
+
19
+ const exeDir = getExeDir();
20
+ const dataDir = path.join(exeDir, 'data');
21
+
22
+ if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
23
+
24
+ process.env.PORTABLE_DATA_DIR = dataDir;
25
+ process.env.PORTABLE_EXE_DIR = exeDir;
26
+ process.env.BASE_URL = process.env.BASE_URL || '/gm';
27
+ process.env.PORT = process.env.PORT || '3000';
28
+ process.env.STARTUP_CWD = process.env.STARTUP_CWD || exeDir;
29
+
30
+ const port = process.env.PORT;
31
+ const baseUrl = process.env.BASE_URL;
32
+ const url = `http://localhost:${port}${baseUrl}/`;
33
+
34
+ console.log(`[AgentGUI Portable] Exe directory: ${exeDir}`);
35
+ console.log(`[AgentGUI Portable] Data directory: ${dataDir}`);
36
+ console.log(`[AgentGUI Portable] Server starting on ${url}`);
37
+
38
+ setTimeout(() => {
39
+ const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
40
+ spawn(cmd, [url], { shell: true, detached: true, stdio: 'ignore' }).unref();
41
+ }, 1500);
42
+
43
+ await import('./server.js');
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Injects a .bun section into a Windows PE executable (bun standalone format).
4
+ Usage: inject-pe-section.py <bun-exe> <bundle-bytes> <output-exe>
5
+ """
6
+
7
+ import sys
8
+ import struct
9
+ import os
10
+
11
+ def align_up(value, alignment):
12
+ return (value + alignment - 1) & ~(alignment - 1)
13
+
14
+ def read_u16(data, offset): return struct.unpack_from('<H', data, offset)[0]
15
+ def read_u32(data, offset): return struct.unpack_from('<I', data, offset)[0]
16
+ def read_u64(data, offset): return struct.unpack_from('<Q', data, offset)[0]
17
+ def write_u16(data, offset, val): struct.pack_into('<H', data, offset, val)
18
+ def write_u32(data, offset, val): struct.pack_into('<I', data, offset, val)
19
+ def write_u64(data, offset, val): struct.pack_into('<Q', data, offset, val)
20
+
21
+ def add_bun_section(pe_bytes, bundle_bytes):
22
+ data = bytearray(pe_bytes)
23
+
24
+ # PE header offset at 0x3C
25
+ e_lfanew = read_u32(data, 0x3C)
26
+ pe_sig = data[e_lfanew:e_lfanew+4]
27
+ assert pe_sig == b'PE\0\0', f"Not a PE file: {pe_sig!r}"
28
+
29
+ coff_offset = e_lfanew + 4
30
+ machine = read_u16(data, coff_offset)
31
+ num_sections = read_u16(data, coff_offset + 2)
32
+ size_of_optional = read_u16(data, coff_offset + 16)
33
+
34
+ opt_offset = coff_offset + 20
35
+ magic = read_u16(data, opt_offset)
36
+ assert magic == 0x20B, f"Expected PE32+ (0x20B), got 0x{magic:X}"
37
+
38
+ # Optional header fields (PE32+)
39
+ file_alignment = read_u32(data, opt_offset + 36)
40
+ section_alignment = read_u32(data, opt_offset + 32)
41
+ size_of_image = read_u32(data, opt_offset + 56)
42
+ size_of_headers = read_u32(data, opt_offset + 60)
43
+ checksum_offset = opt_offset + 64
44
+ # Data directories start at opt_offset + 112 for PE32+
45
+ # Security directory is entry 4
46
+ security_dd_offset = opt_offset + 112 + 4 * 8 # entry 4, each entry 8 bytes
47
+
48
+ # Section headers follow the optional header
49
+ section_headers_offset = opt_offset + size_of_optional
50
+ SECTION_HEADER_SIZE = 40
51
+
52
+ # Parse existing sections
53
+ sections = []
54
+ for i in range(num_sections):
55
+ off = section_headers_offset + i * SECTION_HEADER_SIZE
56
+ name = data[off:off+8]
57
+ virtual_size = read_u32(data, off + 8)
58
+ virtual_addr = read_u32(data, off + 12)
59
+ raw_size = read_u32(data, off + 16)
60
+ raw_ptr = read_u32(data, off + 20)
61
+ chars = read_u32(data, off + 36)
62
+ sections.append({
63
+ 'name': name, 'virtual_size': virtual_size, 'virtual_addr': virtual_addr,
64
+ 'raw_size': raw_size, 'raw_ptr': raw_ptr, 'chars': chars, 'off': off
65
+ })
66
+
67
+ if name == b'.bun\0\0\0\0':
68
+ raise ValueError("PE already has a .bun section")
69
+
70
+ assert num_sections < 96, "Too many sections"
71
+
72
+ # Check header space
73
+ new_sh_off = section_headers_offset + num_sections * SECTION_HEADER_SIZE
74
+ new_sh_end = new_sh_off + SECTION_HEADER_SIZE
75
+ first_raw = min((s['raw_ptr'] for s in sections if s['raw_size'] > 0), default=len(data))
76
+ new_size_of_headers = align_up(new_sh_end, file_alignment)
77
+ assert new_size_of_headers <= first_raw, f"Insufficient header space: need {new_size_of_headers}, first_raw={first_raw}"
78
+
79
+ # Find last file end and last va end
80
+ last_file_end = max((s['raw_ptr'] + s['raw_size'] for s in sections), default=0)
81
+ last_va_end = max(
82
+ (s['virtual_addr'] + align_up(max(s['virtual_size'], s['raw_size']), section_alignment)
83
+ for s in sections),
84
+ default=0
85
+ )
86
+
87
+ payload_len = len(bundle_bytes) + 8 # 8-byte u64 LE length prefix
88
+ raw_size_new = align_up(payload_len, file_alignment)
89
+ new_va = align_up(last_va_end, section_alignment)
90
+ new_raw = align_up(last_file_end, file_alignment)
91
+
92
+ # Extend data buffer
93
+ new_file_size = new_raw + raw_size_new
94
+ if len(data) < new_file_size:
95
+ data.extend(b'\0' * (new_file_size - len(data)))
96
+ else:
97
+ # Zero the new section area
98
+ data[new_raw:new_file_size] = b'\0' * raw_size_new
99
+
100
+ # Write new section header
101
+ IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
102
+ IMAGE_SCN_MEM_READ = 0x40000000
103
+ sh = bytearray(SECTION_HEADER_SIZE)
104
+ sh[0:8] = b'.bun\0\0\0\0'
105
+ struct.pack_into('<I', sh, 8, payload_len) # VirtualSize
106
+ struct.pack_into('<I', sh, 12, new_va) # VirtualAddress
107
+ struct.pack_into('<I', sh, 16, raw_size_new) # SizeOfRawData
108
+ struct.pack_into('<I', sh, 20, new_raw) # PointerToRawData
109
+ struct.pack_into('<I', sh, 36, IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ)
110
+ data[new_sh_off:new_sh_off + SECTION_HEADER_SIZE] = sh
111
+
112
+ # Write payload: u64 LE length + bundle bytes
113
+ write_u64(data, new_raw, len(bundle_bytes))
114
+ data[new_raw + 8: new_raw + 8 + len(bundle_bytes)] = bundle_bytes
115
+
116
+ # Update COFF: number_of_sections
117
+ write_u16(data, coff_offset + 2, num_sections + 1)
118
+
119
+ # Update optional header
120
+ if size_of_headers < new_size_of_headers:
121
+ write_u32(data, opt_offset + 60, new_size_of_headers)
122
+
123
+ # size_of_image: aligned end of new section
124
+ new_size_of_image = align_up(new_va + payload_len, section_alignment)
125
+ write_u32(data, opt_offset + 56, new_size_of_image)
126
+
127
+ # Clear security directory (signature invalidated)
128
+ data[security_dd_offset:security_dd_offset + 8] = b'\0' * 8
129
+
130
+ # Recompute PE checksum
131
+ data = recompute_checksum(data, checksum_offset)
132
+
133
+ return bytes(data)
134
+
135
+
136
+ def recompute_checksum(data, checksum_offset):
137
+ # Zero out the checksum field first
138
+ write_u32(data, checksum_offset, 0)
139
+
140
+ checksum = 0
141
+ # Process data as u16 pairs
142
+ for i in range(0, len(data) - 1, 2):
143
+ val = struct.unpack_from('<H', data, i)[0]
144
+ checksum += val
145
+ if checksum > 0xFFFFFFFF:
146
+ checksum = (checksum & 0xFFFFFFFF) + (checksum >> 32)
147
+
148
+ if len(data) % 2:
149
+ checksum += data[-1]
150
+
151
+ # Fold to 32 bits
152
+ checksum = (checksum & 0xFFFF) + (checksum >> 16)
153
+ checksum += checksum >> 16
154
+ checksum &= 0xFFFF
155
+ checksum += len(data)
156
+
157
+ write_u32(data, checksum_offset, checksum)
158
+ return data
159
+
160
+
161
+ if __name__ == '__main__':
162
+ if len(sys.argv) != 4:
163
+ print(f"Usage: {sys.argv[0]} <bun-exe> <bundle-js> <output-exe>")
164
+ sys.exit(1)
165
+
166
+ bun_path, bundle_path, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
167
+
168
+ with open(bun_path, 'rb') as f:
169
+ pe_bytes = f.read()
170
+
171
+ with open(bundle_path, 'rb') as f:
172
+ bundle_bytes = f.read()
173
+
174
+ print(f"Bun exe: {len(pe_bytes)//1024//1024}MB, Bundle: {len(bundle_bytes)//1024}KB")
175
+
176
+ result = add_bun_section(pe_bytes, bundle_bytes)
177
+
178
+ with open(out_path, 'wb') as f:
179
+ f.write(result)
180
+
181
+ try:
182
+ os.chmod(out_path, 0o755)
183
+ except (PermissionError, OSError):
184
+ pass
185
+ print(f"Output: {len(result)//1024//1024}MB -> {out_path}")
package/server.js CHANGED
@@ -2903,8 +2903,11 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
2903
2903
  }
2904
2904
 
2905
2905
  if (activeExecutions.has(conversationId)) {
2906
- debugLog(`[stream] Conversation ${conversationId} already has active execution, aborting duplicate`);
2907
- return;
2906
+ const existing = activeExecutions.get(conversationId);
2907
+ if (existing.sessionId !== sessionId) {
2908
+ debugLog(`[stream] Conversation ${conversationId} already has active execution (different session), aborting duplicate`);
2909
+ return;
2910
+ }
2908
2911
  }
2909
2912
 
2910
2913
  if (rateLimitState.has(conversationId)) {
@@ -3249,6 +3252,9 @@ function scheduleRetry(conversationId, messageId, content, agentId, model) {
3249
3252
  timestamp: Date.now()
3250
3253
  });
3251
3254
 
3255
+ const startTime = Date.now();
3256
+ activeExecutions.set(conversationId, { pid: null, startTime, sessionId: newSession.id, lastActivity: startTime });
3257
+
3252
3258
  debugLog(`[rate-limit] Calling processMessageWithStreaming for retry`);
3253
3259
  processMessageWithStreaming(conversationId, messageId, newSession.id, content, agentId, model)
3254
3260
  .catch(err => {
@@ -3285,6 +3291,9 @@ function drainMessageQueue(conversationId) {
3285
3291
  timestamp: Date.now()
3286
3292
  });
3287
3293
 
3294
+ const startTime = Date.now();
3295
+ activeExecutions.set(conversationId, { pid: null, startTime, sessionId: session.id, lastActivity: startTime });
3296
+
3288
3297
  processMessageWithStreaming(conversationId, next.messageId, session.id, next.content, next.agentId, next.model)
3289
3298
  .catch(err => debugLog(`[queue] Error processing queued message: ${err.message}`));
3290
3299
  }
package/static/index.html CHANGED
@@ -23,8 +23,8 @@
23
23
  *, *::before, *::after { box-sizing: border-box; }
24
24
 
25
25
  :root {
26
- --color-primary: #3b82f6;
27
- --color-primary-dark: #1e40af;
26
+ --color-primary: #6b7280;
27
+ --color-primary-dark: #4b5563;
28
28
  --color-bg-primary: #ffffff;
29
29
  --color-bg-secondary: #f9fafb;
30
30
  --color-bg-code: #1f2937;
@@ -34,18 +34,20 @@
34
34
  --color-success: #10b981;
35
35
  --color-error: #ef4444;
36
36
  --color-warning: #f59e0b;
37
- --color-info: #0891b2;
37
+ --color-info: #6b7280;
38
38
  --sidebar-width: 300px;
39
39
  --header-height: 52px;
40
40
  --msg-max-width: 100%;
41
41
  }
42
42
 
43
43
  html.dark {
44
- --color-bg-primary: #111827;
45
- --color-bg-secondary: #1f2937;
46
- --color-text-primary: #f9fafb;
47
- --color-text-secondary: #d1d5db;
48
- --color-border: #374151;
44
+ --color-primary: #9ca3af;
45
+ --color-primary-dark: #6b7280;
46
+ --color-bg-primary: #0f0f0f;
47
+ --color-bg-secondary: #1a1a1a;
48
+ --color-text-primary: #e5e5e5;
49
+ --color-text-secondary: #a3a3a3;
50
+ --color-border: #2a2a2a;
49
51
  }
50
52
 
51
53
  html, body {
@@ -740,8 +740,7 @@ class StreamingRenderer {
740
740
  const input = block.input || {};
741
741
 
742
742
  const details = document.createElement('details');
743
- details.className = 'block-tool-use folded-tool permanently-expanded';
744
- details.setAttribute('open', '');
743
+ details.className = 'block-tool-use folded-tool';
745
744
  if (block.id) details.dataset.toolUseId = block.id;
746
745
  details.classList.add(this._getBlockTypeClass('tool_use'));
747
746
  details.classList.add(this._getToolColorClass(toolName));
@@ -1297,8 +1296,7 @@ class StreamingRenderer {
1297
1296
  */
1298
1297
  renderBlockSystem(block, context) {
1299
1298
  const details = document.createElement('details');
1300
- details.className = 'folded-tool folded-tool-info permanently-expanded';
1301
- details.setAttribute('open', '');
1299
+ details.className = 'folded-tool folded-tool-info';
1302
1300
  details.dataset.eventType = 'system';
1303
1301
  details.classList.add(this._getBlockTypeClass('system'));
1304
1302
  const desc = block.model ? this.escapeHtml(block.model) : 'Session';
@@ -1335,8 +1333,7 @@ class StreamingRenderer {
1335
1333
  const statsDesc = [duration, cost, turns ? turns + ' turns' : ''].filter(Boolean).join(' / ');
1336
1334
 
1337
1335
  const details = document.createElement('details');
1338
- details.className = isError ? 'folded-tool folded-tool-error permanently-expanded' : 'folded-tool permanently-expanded';
1339
- details.setAttribute('open', '');
1336
+ details.className = isError ? 'folded-tool folded-tool-error' : 'folded-tool';
1340
1337
  details.dataset.eventType = 'result';
1341
1338
  details.classList.add(this._getBlockTypeClass(isError ? 'error' : 'result'));
1342
1339