test_research4 0.5.0 → 0.5.4

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.
Files changed (58) hide show
  1. package/index.js +1 -1
  2. package/package.json +9 -4
  3. package/src/client.js +1 -1
  4. package/src/config.js +1 -1
  5. package/src/lib/OrgWatcher.js +1 -1
  6. package/src/lib/auditTrailDownloader.js +1 -1
  7. package/src/lib/handlerRegistry.js +1 -1
  8. package/src/lib/icons.js +1 -1
  9. package/src/lib/initialization.js +1 -1
  10. package/src/lib/logger.js +1 -1
  11. package/src/lib/mcpApps/manifest.js +1 -0
  12. package/src/lib/mcpApps/paths.js +1 -0
  13. package/src/lib/mcpApps/registerResources.js +1 -0
  14. package/src/lib/mcpApps/toolMeta.js +1 -0
  15. package/src/lib/networkUtils.js +1 -1
  16. package/src/lib/salesforceServices.js +1 -1
  17. package/src/lib/telemetry.js +1 -1
  18. package/src/lib/tempManager.js +1 -1
  19. package/src/lib/transport.js +1 -1
  20. package/src/lib/versionManager.js +1 -1
  21. package/src/mcp-server.js +1 -1
  22. package/src/prompts/apex_run_script.js +1 -1
  23. package/src/prompts/call_all_tools.js +1 -1
  24. package/src/prompts/org_onboarding.js +1 -1
  25. package/src/state.js +1 -1
  26. package/src/static/agent_instructions.md.pam +1 -1
  27. package/src/static/bundles/mcp-apps/get-record/mcp-app.html +275 -0
  28. package/src/static/bundles/mcp-apps/get-setup-audit-trail/mcp-app.html +336 -0
  29. package/src/tools/apex_debug_logs.js +1 -1
  30. package/src/tools/apex_debug_logs.md.pam +1 -1
  31. package/src/tools/deploy_metadata.js +1 -1
  32. package/src/tools/describe_object.js +1 -1
  33. package/src/tools/describe_object.md.pam +1 -1
  34. package/src/tools/execute_queries_and_dml.js +1 -1
  35. package/src/tools/generate_metadata.js +1 -1
  36. package/src/tools/generate_metadata.md.pam +1 -1
  37. package/src/tools/get_apex_class_code_coverage.js +1 -1
  38. package/src/tools/get_recently_viewed_records.js +1 -1
  39. package/src/tools/get_record.js +1 -1
  40. package/src/tools/get_setup_audit_trail.js +1 -1
  41. package/src/tools/get_setup_audit_trail.md.pam +1 -1
  42. package/src/tools/invoke_apex_rest_resource.js +1 -1
  43. package/src/tools/run_anonymous_apex.js +1 -1
  44. package/src/tools/run_apex_test.js +1 -1
  45. package/src/tools/run_apex_test.md.pam +1 -1
  46. package/src/tools/trigger_execution_order.js +1 -1
  47. package/src/tools/utils.js +1 -1
  48. package/src/tools/utils.md.pam +1 -1
  49. package/src/utils.js +1 -1
  50. package/src/lib/refreshSobjects.js +0 -1
  51. package/src/lib/taskScheduler.js +0 -1
  52. package/src/lib/uiDataCache.js +0 -1
  53. package/src/prompts/code_modification.js +0 -1
  54. package/src/static/audit_trail_app.html +0 -1067
  55. package/src/static/generate_soql_query_tool_sampling.md.pam +0 -1
  56. package/src/static/get_record_app.html +0 -843
  57. package/src/tools/generate_soql_query.js +0 -1
  58. package/src/tools/generate_soql_query.md.pam +0 -1
@@ -1,843 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Salesforce Record</title>
7
- <style>
8
- :root {
9
- /* Adapt to both light and dark IDE themes */
10
- color-scheme: light dark;
11
- }
12
-
13
- * {
14
- box-sizing: border-box;
15
- }
16
-
17
- body {
18
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'IBM Plex Sans', sans-serif;
19
- background-color: transparent;
20
- color: inherit;
21
- margin: 0;
22
- padding: 16px;
23
- line-height: 1.5;
24
- }
25
-
26
- #app-container {
27
- max-width: 100%;
28
- margin: 0;
29
- background: transparent;
30
- padding: 0;
31
- }
32
-
33
- #loading {
34
- text-align: center;
35
- opacity: 0.7;
36
- padding: 40px 0;
37
- }
38
-
39
- #error {
40
- background-color: rgba(218, 30, 40, 0.15);
41
- color: #da1e28;
42
- padding: 12px 16px;
43
- border-radius: 4px;
44
- border-left: 3px solid #da1e28;
45
- margin-bottom: 16px;
46
- font-size: 13px;
47
- }
48
-
49
- .record-header {
50
- margin-bottom: 20px;
51
- border-bottom: 1px solid;
52
- border-bottom-color: rgba(0, 0, 0, 0.1);
53
- padding-bottom: 12px;
54
- }
55
-
56
- @media (prefers-color-scheme: dark) {
57
- .record-header {
58
- border-bottom-color: rgba(255, 255, 255, 0.1);
59
- }
60
- }
61
-
62
- .record-header h1 {
63
- margin: 0 0 6px 0;
64
- font-size: 20px;
65
- font-weight: 600;
66
- color: inherit;
67
- }
68
-
69
- .record-id {
70
- font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
71
- font-size: 11px;
72
- opacity: 0.6;
73
- word-break: break-all;
74
- }
75
-
76
- .fields-container {
77
- margin-top: 12px;
78
- }
79
-
80
- .field-tree {
81
- list-style: none;
82
- padding: 0;
83
- margin: 0;
84
- }
85
-
86
- .field-tree li {
87
- margin: 0;
88
- padding: 0;
89
- }
90
-
91
- details {
92
- margin: 0;
93
- padding: 0;
94
- }
95
-
96
- details > summary {
97
- cursor: pointer;
98
- padding: 6px 8px;
99
- margin: 0;
100
- user-select: none;
101
- border-radius: 2px;
102
- transition: background-color 0.15s ease;
103
- display: flex;
104
- align-items: center;
105
- gap: 6px;
106
- font-size: 13px;
107
- }
108
-
109
- details > summary:hover {
110
- background-color: rgba(0, 0, 0, 0.05);
111
- }
112
-
113
- @media (prefers-color-scheme: dark) {
114
- details > summary:hover {
115
- background-color: rgba(255, 255, 255, 0.08);
116
- }
117
- }
118
-
119
- details > summary::before {
120
- content: '▶';
121
- display: inline-block;
122
- width: 14px;
123
- text-align: center;
124
- font-size: 11px;
125
- opacity: 0.5;
126
- flex-shrink: 0;
127
- }
128
-
129
- details[open] > summary::before {
130
- content: '▼';
131
- }
132
-
133
- .field-name {
134
- font-weight: 500;
135
- color: inherit;
136
- font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
137
- font-size: 12px;
138
- opacity: 0.9;
139
- }
140
-
141
- .field-value {
142
- margin-left: 20px;
143
- margin-top: 4px;
144
- padding: 6px 10px;
145
- background-color: rgba(0, 0, 0, 0.03);
146
- border-left: 2px solid;
147
- border-left-color: rgba(0, 0, 0, 0.1);
148
- border-radius: 2px;
149
- font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
150
- font-size: 11px;
151
- color: inherit;
152
- opacity: 0.8;
153
- word-break: break-word;
154
- white-space: pre-wrap;
155
- }
156
-
157
- @media (prefers-color-scheme: dark) {
158
- .field-value {
159
- background-color: rgba(255, 255, 255, 0.04);
160
- border-left-color: rgba(255, 255, 255, 0.1);
161
- }
162
- }
163
-
164
- .field-value.null {
165
- opacity: 0.5;
166
- font-style: italic;
167
- }
168
-
169
- .field-value.boolean {
170
- opacity: 0.9;
171
- }
172
-
173
- .field-value.number {
174
- opacity: 0.9;
175
- }
176
-
177
- .nested-fields {
178
- margin-left: 10px;
179
- padding-left: 0;
180
- border-left: 1px solid;
181
- border-left-color: rgba(0, 0, 0, 0.1);
182
- margin-top: 4px;
183
- }
184
-
185
- @media (prefers-color-scheme: dark) {
186
- .nested-fields {
187
- border-left-color: rgba(255, 255, 255, 0.1);
188
- }
189
- }
190
-
191
- .controls {
192
- display: flex;
193
- gap: 8px;
194
- margin-bottom: 16px;
195
- flex-wrap: wrap;
196
- }
197
-
198
- button {
199
- padding: 6px 12px;
200
- background-color: transparent;
201
- color: inherit;
202
- border: 1px solid;
203
- border-color: rgba(0, 0, 0, 0.2);
204
- border-radius: 3px;
205
- cursor: pointer;
206
- font-size: 12px;
207
- font-weight: 500;
208
- font-family: inherit;
209
- transition: all 0.15s ease;
210
- opacity: 0.8;
211
- }
212
-
213
- button:hover {
214
- opacity: 1;
215
- background-color: rgba(0, 0, 0, 0.05);
216
- }
217
-
218
- @media (prefers-color-scheme: dark) {
219
- button {
220
- border-color: rgba(255, 255, 255, 0.2);
221
- }
222
-
223
- button:hover {
224
- background-color: rgba(255, 255, 255, 0.08);
225
- }
226
- }
227
-
228
- button:active {
229
- background-color: rgba(0, 0, 0, 0.1);
230
- }
231
-
232
- @media (prefers-color-scheme: dark) {
233
- button:active {
234
- background-color: rgba(255, 255, 255, 0.15);
235
- }
236
- }
237
-
238
- button:disabled {
239
- opacity: 0.4;
240
- cursor: not-allowed;
241
- }
242
-
243
- .refresh-btn {
244
- opacity: 0.9;
245
- }
246
-
247
- .search-container {
248
- margin-bottom: 16px;
249
- display: flex;
250
- gap: 8px;
251
- align-items: center;
252
- }
253
-
254
- #field-search {
255
- flex: 1;
256
- min-width: 0;
257
- padding: 6px 10px;
258
- background-color: transparent;
259
- color: inherit;
260
- border: 1px solid;
261
- border-color: rgba(0, 0, 0, 0.2);
262
- border-radius: 3px;
263
- font-size: 12px;
264
- font-family: inherit;
265
- transition: border-color 0.15s ease;
266
- }
267
-
268
- #field-search:focus {
269
- outline: none;
270
- border-color: rgba(0, 0, 0, 0.4);
271
- }
272
-
273
- @media (prefers-color-scheme: dark) {
274
- #field-search {
275
- border-color: rgba(255, 255, 255, 0.2);
276
- }
277
-
278
- #field-search:focus {
279
- border-color: rgba(255, 255, 255, 0.4);
280
- }
281
- }
282
-
283
- .search-counter {
284
- font-size: 11px;
285
- opacity: 0.6;
286
- white-space: nowrap;
287
- }
288
-
289
- .field-hidden {
290
- display: none !important;
291
- }
292
-
293
- .field-highlighted {
294
- background-color: rgba(255, 193, 7, 0.15);
295
- }
296
-
297
- @media (prefers-color-scheme: dark) {
298
- .field-highlighted {
299
- background-color: rgba(255, 193, 7, 0.2);
300
- }
301
- }
302
- </style>
303
- </head>
304
- <body>
305
- <div id="app-container">
306
- <div id="loading"><p>Loading record...</p></div>
307
- <div id="error" style="display: none;"></div>
308
- <div id="record-view" style="display: none;">
309
- <div class="controls">
310
- <button class="refresh-btn">Refresh</button>
311
- </div>
312
- <div class="record-header">
313
- <h1 id="record-title">Record</h1>
314
- <div class="record-id" id="record-id-display"></div>
315
- </div>
316
- <div class="search-container">
317
- <input type="text" id="field-search" placeholder="Search fields by name or value..." />
318
- <div class="search-counter" id="search-counter"></div>
319
- </div>
320
- <div class="fields-container">
321
- <ul class="field-tree" id="fields-tree"></ul>
322
- </div>
323
- </div>
324
- </div>
325
-
326
- <script type="module">
327
- // Inline MCPApp base class for MCP Apps protocol communication.
328
- // This avoids relying on external MCP resources that are not registered
329
- // for this app iframe, while preserving the same constructor signature.
330
- /**
331
- * MCPApp - Base class for MCP Apps UI implementations
332
- *
333
- * This class handles the MCP Apps protocol communication for interactive UIs.
334
- * It provides initialization, message handling, and tool calling functionality.
335
- */
336
- class MCPApp {
337
- constructor(config) {
338
- this.config = config;
339
- this.ontoolresult = null;
340
- this._requestId = 0;
341
- this._initRequestId = null;
342
- this._pendingRequests = new Map();
343
- this._initialized = false;
344
- this._targetOrigin = null;
345
- this._connected = false;
346
- }
347
-
348
- /**
349
- * Derive parent origin from document.referrer for secure postMessage
350
- */
351
- static _getParentOrigin() {
352
- try {
353
- if (document.referrer) {
354
- const referrerUrl = new URL(document.referrer);
355
- return referrerUrl.origin;
356
- }
357
- } catch (error) {
358
- // Origin cannot be determined from document.referrer
359
- }
360
- return null;
361
- }
362
-
363
- /**
364
- * Connect to the MCP host and begin listening for messages
365
- */
366
- connect() {
367
- // Guard against duplicate connect() calls
368
- if (this._connected) {
369
- console.log('[MCPApp] Already connected, skipping duplicate call');
370
- return;
371
- }
372
-
373
- console.log('[MCPApp] Connecting...');
374
-
375
- // Determine target origin for postMessage (require concrete parent origin; fail closed)
376
- if (!this._targetOrigin) {
377
- this._targetOrigin = MCPApp._getParentOrigin();
378
- }
379
-
380
- if (!this._targetOrigin) {
381
- console.error('[MCPApp] Cannot connect: parent origin could not be determined');
382
- return;
383
- }
384
-
385
- // Only mark as connected after target origin is confirmed
386
- this._connected = true;
387
-
388
- // Listen for messages from parent (host)
389
- window.addEventListener('message', (event) => {
390
- // Validate message source - must come from the parent window
391
- if (event.source !== window.parent) {
392
- return;
393
- }
394
- // Validate origin of incoming messages against expected parent origin
395
- if (event.origin !== this._targetOrigin) {
396
- return;
397
- }
398
- // Validate basic message shape before processing
399
- if (!event.data || typeof event.data !== 'object' || Array.isArray(event.data) || event.data.jsonrpc !== '2.0') {
400
- return;
401
- }
402
- this._handleMessage(event.data);
403
- });
404
-
405
- // Send ui/initialize request to host (required by MCP Apps protocol)
406
- console.log('[MCPApp] Sending ui/initialize request');
407
- this._initRequestId = ++this._requestId;
408
- this._sendMessage({
409
- jsonrpc: '2.0',
410
- id: this._initRequestId,
411
- method: 'ui/initialize',
412
- params: {
413
- protocolVersion: '1',
414
- capabilities: {}
415
- }
416
- });
417
- }
418
-
419
- /**
420
- * Handle incoming messages from the host
421
- */
422
- _handleMessage(data) {
423
- // Handle initialization response
424
- if (data.id === this._initRequestId && !this._initialized) {
425
- console.log('[MCPApp] Received initialization response');
426
- this._initialized = true;
427
-
428
- // Send initialized notification
429
- this._sendMessage({
430
- jsonrpc: '2.0',
431
- method: 'ui/notifications/initialized',
432
- params: {
433
- name: this.config.name,
434
- version: this.config.version
435
- }
436
- });
437
- return;
438
- }
439
-
440
- // Handle tool result notification (VS Code/Cursor sends this)
441
- if (data.method === 'ui/notifications/tool-result') {
442
- const params = data.params || {};
443
-
444
- // Check if result is in different locations
445
- let result = params;
446
- if (!result.content && !result.structuredContent && params.result) {
447
- result = params.result;
448
- }
449
-
450
- // The result should contain the tool result with structuredContent
451
- if (result.content || result.structuredContent) {
452
- if (this.ontoolresult) {
453
- this.ontoolresult(result);
454
- }
455
- return;
456
- }
457
-
458
- console.warn('[MCPApp] Tool result missing content and structuredContent');
459
- }
460
-
461
- // Handle tool input notification (primary way to receive tool data)
462
- if (data.method === 'ui/notifications/tool-input') {
463
- const params = data.params || {};
464
- // The params should contain the tool result with structuredContent
465
- if (params.content || params.structuredContent) {
466
- if (this.ontoolresult) {
467
- this.ontoolresult(params);
468
- }
469
- return;
470
- }
471
- }
472
-
473
- // Handle responses to tool calls we made (JSON-RPC responses)
474
- if (data.id && this._pendingRequests.has(data.id)) {
475
- console.log('[MCPApp] Received response for request ID:', data.id);
476
- const {resolve, reject, timeoutHandle} = this._pendingRequests.get(data.id);
477
- this._pendingRequests.delete(data.id);
478
-
479
- // Clear the timeout since we got a response
480
- if (timeoutHandle) {
481
- clearTimeout(timeoutHandle);
482
- }
483
-
484
- if (data.error) {
485
- console.error('[MCPApp] Tool call error:', data.error);
486
- reject(new Error(data.error.message || 'Unknown error'));
487
- } else {
488
- console.log('[MCPApp] Tool call succeeded');
489
- resolve(data.result);
490
- }
491
- }
492
- }
493
-
494
- /**
495
- * Call a server tool and wait for the result
496
- */
497
- async callServerTool(toolCall) {
498
- const id = ++this._requestId;
499
- console.log('[MCPApp] Calling server tool:', toolCall.name, 'with ID:', id);
500
-
501
- return new Promise((resolve, reject) => {
502
- const timeoutMs = 30000;
503
- const timeoutHandle = setTimeout(() => {
504
- this._pendingRequests.delete(id);
505
- console.error('[MCPApp] Tool call timeout after', timeoutMs, 'ms');
506
- reject(new Error('Tool call timeout after ' + timeoutMs + 'ms'));
507
- }, timeoutMs);
508
-
509
- this._pendingRequests.set(id, {resolve, reject, timeoutHandle});
510
-
511
- this._sendMessage({
512
- jsonrpc: '2.0',
513
- method: 'ui/callTool',
514
- id,
515
- params: {
516
- name: toolCall.name,
517
- arguments: toolCall.arguments || {}
518
- }
519
- });
520
- });
521
- }
522
-
523
- /**
524
- * Send a message to the parent host
525
- */
526
- _sendMessage(data) {
527
- console.log('[MCPApp] Sending message:', data);
528
- if (!this._targetOrigin) {
529
- console.error('[MCPApp] Cannot send message: target origin not set');
530
- return;
531
- }
532
- window.parent.postMessage(data, this._targetOrigin);
533
- }
534
- }
535
- const app = new MCPApp({ name: 'get-record-app', version: '1.0.0' });
536
-
537
- let lastCallArgs = null;
538
-
539
- // Process tool result data
540
- function processToolResult(result) {
541
- try {
542
- if (result.isError) {
543
- showError(result.content?.[0]?.text || 'Unknown error');
544
- return;
545
- }
546
-
547
- let data;
548
-
549
- // Try to get data from structuredContent first (recommended for MCP Apps)
550
- if (result.structuredContent) {
551
- data = result.structuredContent;
552
- } else if (result.content) {
553
- // Fall back to parsing JSON from text content
554
- const textContent = Array.isArray(result.content)
555
- ? result.content.find(c => c.type === 'text')?.text
556
- : result.content;
557
- if (!textContent) {
558
- showError('No data returned from tool');
559
- return;
560
- }
561
- console.log('[ProcessResult] Parsing JSON from text content');
562
- data = typeof textContent === 'string' ? JSON.parse(textContent) : textContent;
563
- } else {
564
- showError('No data returned from tool');
565
- return;
566
- }
567
-
568
- renderRecord(data);
569
- } catch (error) {
570
- showError(`Failed to parse record data: ${error.message}`);
571
- console.error('[ProcessResult] Error:', error);
572
- }
573
- }
574
-
575
- // Handle tool results pushed by host via callback
576
- app.ontoolresult = (result) => {
577
- console.log('[App] ontoolresult callback triggered:', result);
578
- processToolResult(result);
579
- };
580
-
581
- // Establish connection with host (required for MCP Apps protocol)
582
- console.log('[Init] Establishing MCP Apps connection...');
583
- app.connect();
584
- console.log('[Init] App.connect() called');
585
-
586
- // Check if initial data was injected by the server (fallback for direct iframe loading)
587
- if (window.__initialData) {
588
- console.log('[Init] Found initial data injected by server');
589
- if (window.__initialData.error) {
590
- // Error state injected due to expired/unknown cache ID
591
- console.log('[Init] Injected data is an error state:', window.__initialData.message);
592
- showError(window.__initialData.message);
593
- } else {
594
- processToolResult({
595
- content: [{type: 'text', text: JSON.stringify(window.__initialData)}],
596
- structuredContent: window.__initialData
597
- });
598
- }
599
- }
600
-
601
- // Check if initial data was passed via window object (fallback)
602
- if (window.__recordData) {
603
- console.log('[Init] Found record data in window.__recordData');
604
- processToolResult(window.__recordData);
605
- }
606
-
607
- function showError(message) {
608
- console.error('[UI] Showing error:', message);
609
- document.getElementById('loading').style.display = 'none';
610
- document.getElementById('record-view').style.display = 'none';
611
- const errorEl = document.getElementById('error');
612
- errorEl.textContent = message;
613
- errorEl.style.display = 'block';
614
- }
615
-
616
- // Timeout after 10 seconds if no data arrives
617
- let timeoutId = null;
618
- if (!window.__initialData && !window.__recordData) {
619
- timeoutId = setTimeout(() => {
620
- if (document.getElementById('loading').style.display !== 'none') {
621
- console.error('[Timeout] No data received from server after 10 seconds');
622
- showError('Timeout: No data received from server after 10 seconds. Check browser console for debug logs. The host may not support MCP Apps or there may be a configuration issue.');
623
- }
624
- }, 10000);
625
- }
626
-
627
- function renderRecord(data) {
628
- console.log('[Render] Rendering record with data:', data);
629
-
630
- // Clear timeout since we got data
631
- if (timeoutId) {
632
- clearTimeout(timeoutId);
633
- }
634
-
635
- const { id, sObject, fields, error } = data;
636
-
637
- if (error) {
638
- showError(data.message || 'Error fetching record');
639
- return;
640
- }
641
-
642
- // Hide loading, show content
643
- console.log('[Render] Displaying record view');
644
- document.getElementById('loading').style.display = 'none';
645
- document.getElementById('error').style.display = 'none';
646
- document.getElementById('record-view').style.display = 'block';
647
-
648
- // Set header
649
- const title = `${sObject} Record`;
650
- document.getElementById('record-title').textContent = title;
651
- document.getElementById('record-id-display').textContent = `ID: ${id}`;
652
-
653
- // Render fields tree
654
- const fieldsTree = document.getElementById('fields-tree');
655
- fieldsTree.innerHTML = '';
656
-
657
- if (fields && typeof fields === 'object') {
658
- const sortedKeys = Object.keys(fields).sort();
659
- for (const key of sortedKeys) {
660
- const value = fields[key];
661
- const li = createFieldElement(key, value);
662
- fieldsTree.appendChild(li);
663
- }
664
- }
665
-
666
- // Store args for refresh
667
- lastCallArgs = { objectName: sObject, recordId: id };
668
-
669
- // Setup event listeners for controls (after DOM is ready)
670
- setupControlListeners();
671
- }
672
-
673
- function setupControlListeners() {
674
- // Setup search functionality
675
- const searchInput = document.getElementById('field-search');
676
- if (searchInput && !searchInput._listenerAttached) {
677
- searchInput.addEventListener('input', (e) => {
678
- const query = e.target.value.toLowerCase().trim();
679
- filterFields(query);
680
- });
681
- searchInput._listenerAttached = true;
682
- }
683
-
684
- // Setup refresh button listener
685
- setupRefreshListener();
686
- }
687
-
688
- function filterFields(query) {
689
- const allFields = document.querySelectorAll('#fields-tree > li');
690
- let visibleCount = 0;
691
-
692
- allFields.forEach(fieldItem => {
693
- const fieldName = fieldItem.querySelector('.field-name')?.textContent || '';
694
- const fieldValue = fieldItem.querySelector('.field-value')?.textContent || '';
695
- const combinedText = `${fieldName} ${fieldValue}`.toLowerCase();
696
-
697
- // Check if query matches field name or value
698
- const isMatch = query === '' || combinedText.includes(query);
699
-
700
- if (isMatch) {
701
- fieldItem.classList.remove('field-hidden');
702
- // Highlight matching text in field name
703
- if (query && fieldName.toLowerCase().includes(query)) {
704
- fieldItem.classList.add('field-highlighted');
705
- } else {
706
- fieldItem.classList.remove('field-highlighted');
707
- }
708
- visibleCount++;
709
- } else {
710
- fieldItem.classList.add('field-hidden');
711
- fieldItem.classList.remove('field-highlighted');
712
- }
713
- });
714
-
715
- // Update counter
716
- const counter = document.getElementById('search-counter');
717
- if (counter) {
718
- if (query === '') {
719
- counter.textContent = '';
720
- } else {
721
- const total = allFields.length;
722
- counter.textContent = `${visibleCount}/${total}`;
723
- }
724
- }
725
-
726
- console.log('[UI] Search filter applied:', { query, visibleCount, total: allFields.length });
727
- }
728
-
729
- function createFieldElement(key, value) {
730
- const li = document.createElement('li');
731
-
732
- // Check if value is a complex object (not null, not array)
733
- const isComplex = value !== null && typeof value === 'object' && !Array.isArray(value);
734
-
735
- if (isComplex) {
736
- // Create expandable node
737
- const details = document.createElement('details');
738
- const summary = document.createElement('summary');
739
- summary.innerHTML = `<span class="field-name">${escapeHtml(key)}</span> <span style="color: #8d8d8d; font-size: 11px;">object</span>`;
740
-
741
- const nested = document.createElement('ul');
742
- nested.className = 'nested-fields';
743
- nested.style.listStyle = 'none';
744
- nested.style.padding = '0';
745
- nested.style.margin = '0';
746
-
747
- const nestedKeys = Object.keys(value).sort();
748
- for (const nestedKey of nestedKeys) {
749
- const nestedValue = value[nestedKey];
750
- const nestedLi = createFieldElement(nestedKey, nestedValue);
751
- nested.appendChild(nestedLi);
752
- }
753
-
754
- details.appendChild(summary);
755
- details.appendChild(nested);
756
- li.appendChild(details);
757
- } else {
758
- // Render as leaf node
759
- const fieldContainer = document.createElement('div');
760
- fieldContainer.style.padding = '4px 12px';
761
-
762
- const label = document.createElement('span');
763
- label.className = 'field-name';
764
- label.textContent = key;
765
-
766
- const valueEl = document.createElement('div');
767
- valueEl.className = 'field-value';
768
-
769
- if (value === null || value === undefined) {
770
- valueEl.classList.add('null');
771
- valueEl.textContent = 'null';
772
- } else if (typeof value === 'boolean') {
773
- valueEl.classList.add('boolean');
774
- valueEl.textContent = value.toString();
775
- } else if (typeof value === 'number') {
776
- valueEl.classList.add('number');
777
- valueEl.textContent = value.toString();
778
- } else if (Array.isArray(value)) {
779
- valueEl.textContent = `[Array: ${value.length} items]`;
780
- } else {
781
- // textContent treats content as plain text and never interprets HTML,
782
- // so HTML escaping is unnecessary and would show entities literally
783
- valueEl.textContent = String(value);
784
- }
785
-
786
- fieldContainer.appendChild(label);
787
- fieldContainer.appendChild(valueEl);
788
- li.appendChild(fieldContainer);
789
- }
790
-
791
- return li;
792
- }
793
-
794
- function escapeHtml(text) {
795
- const map = {
796
- '&': '&amp;',
797
- '<': '&lt;',
798
- '>': '&gt;',
799
- '"': '&quot;',
800
- "'": '&#039;'
801
- };
802
- return text.replace(/[&<>"']/g, m => map[m]);
803
- }
804
-
805
- // Setup refresh button listener
806
- function setupRefreshListener() {
807
- const refreshBtn = document.querySelector('.refresh-btn');
808
- if (refreshBtn && !refreshBtn._listenerAttached) {
809
- refreshBtn.addEventListener('click', async () => {
810
- console.log('[UI] Refresh button clicked');
811
- if (!lastCallArgs) {
812
- console.warn('[UI] No record to refresh');
813
- alert('No record to refresh');
814
- return;
815
- }
816
-
817
- const btn = document.querySelector('.refresh-btn');
818
- btn.disabled = true;
819
- btn.textContent = 'Refreshing...';
820
- console.log('[UI] Calling server tool with args:', lastCallArgs);
821
-
822
- try {
823
- const result = await app.callServerTool({
824
- name: 'get_record',
825
- arguments: lastCallArgs
826
- });
827
- console.log('[UI] Refresh result:', result);
828
- processToolResult(result);
829
- } catch (error) {
830
- console.error('[UI] Refresh failed:', error);
831
- showError(`Failed to refresh: ${error.message}`);
832
- } finally {
833
- btn.disabled = false;
834
- btn.textContent = 'Refresh';
835
- }
836
- });
837
- refreshBtn._listenerAttached = true;
838
- }
839
- }
840
-
841
- </script>
842
- </body>
843
- </html>