@wonderwhy-er/desktop-commander 0.2.36 → 0.2.38

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 (94) hide show
  1. package/README.md +240 -100
  2. package/dist/command-manager.js +6 -3
  3. package/dist/config-field-definitions.d.ts +41 -0
  4. package/dist/config-field-definitions.js +37 -0
  5. package/dist/config-manager.d.ts +2 -0
  6. package/dist/config-manager.js +22 -2
  7. package/dist/handlers/filesystem-handlers.js +6 -11
  8. package/dist/handlers/macos-control-handlers.d.ts +16 -0
  9. package/dist/handlers/macos-control-handlers.js +81 -0
  10. package/dist/lib.d.ts +10 -0
  11. package/dist/lib.js +10 -0
  12. package/dist/remote-device/remote-channel.d.ts +8 -3
  13. package/dist/remote-device/remote-channel.js +68 -21
  14. package/dist/search-manager.d.ts +13 -0
  15. package/dist/search-manager.js +146 -0
  16. package/dist/server.js +29 -1
  17. package/dist/test-docx.d.ts +1 -0
  18. package/dist/tools/config.d.ts +71 -0
  19. package/dist/tools/config.js +117 -2
  20. package/dist/tools/docx/builders/table.d.ts +2 -0
  21. package/dist/tools/docx/builders/table.js +60 -16
  22. package/dist/tools/docx/dom.d.ts +74 -1
  23. package/dist/tools/docx/dom.js +221 -1
  24. package/dist/tools/docx/index.d.ts +2 -2
  25. package/dist/tools/docx/ops/index.js +3 -0
  26. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +15 -3
  27. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +25 -10
  28. package/dist/tools/docx/ops/replace-table-cell-text.d.ts +25 -0
  29. package/dist/tools/docx/ops/replace-table-cell-text.js +85 -0
  30. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +2 -1
  31. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +9 -8
  32. package/dist/tools/docx/ops/set-color-for-style.d.ts +4 -0
  33. package/dist/tools/docx/ops/set-color-for-style.js +11 -7
  34. package/dist/tools/docx/ops/table-set-cell-text.js +8 -40
  35. package/dist/tools/docx/read.d.ts +2 -2
  36. package/dist/tools/docx/read.js +137 -17
  37. package/dist/tools/docx/types.d.ts +32 -3
  38. package/dist/tools/docx/xml-view-test.d.ts +1 -0
  39. package/dist/tools/docx/xml-view-test.js +63 -0
  40. package/dist/tools/docx/xml-view.d.ts +56 -0
  41. package/dist/tools/docx/xml-view.js +169 -0
  42. package/dist/tools/edit.js +57 -27
  43. package/dist/tools/macos-control/ax-adapter.d.ts +55 -0
  44. package/dist/tools/macos-control/ax-adapter.js +438 -0
  45. package/dist/tools/macos-control/cdp-adapter.d.ts +23 -0
  46. package/dist/tools/macos-control/cdp-adapter.js +402 -0
  47. package/dist/tools/macos-control/orchestrator.d.ts +77 -0
  48. package/dist/tools/macos-control/orchestrator.js +136 -0
  49. package/dist/tools/macos-control/role-aliases.d.ts +5 -0
  50. package/dist/tools/macos-control/role-aliases.js +34 -0
  51. package/dist/tools/macos-control/types.d.ts +129 -0
  52. package/dist/tools/macos-control/types.js +1 -0
  53. package/dist/tools/schemas.d.ts +3 -0
  54. package/dist/tools/schemas.js +2 -1
  55. package/dist/types.d.ts +0 -1
  56. package/dist/ui/config-editor/config-editor-runtime.js +14181 -0
  57. package/dist/ui/config-editor/index.html +13 -0
  58. package/dist/ui/config-editor/src/app.d.ts +43 -0
  59. package/dist/ui/config-editor/src/app.js +840 -0
  60. package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
  61. package/dist/ui/config-editor/src/array-modal.js +185 -0
  62. package/dist/ui/config-editor/src/main.d.ts +1 -0
  63. package/dist/ui/config-editor/src/main.js +2 -0
  64. package/dist/ui/config-editor/styles.css +586 -0
  65. package/dist/ui/file-preview/preview-runtime.js +13337 -752
  66. package/dist/ui/file-preview/shared/preview-file-types.js +3 -1
  67. package/dist/ui/file-preview/src/app.d.ts +5 -1
  68. package/dist/ui/file-preview/src/app.js +114 -200
  69. package/dist/ui/file-preview/src/components/html-renderer.d.ts +1 -5
  70. package/dist/ui/file-preview/src/components/html-renderer.js +11 -27
  71. package/dist/ui/file-preview/styles.css +117 -83
  72. package/dist/ui/resources.d.ts +7 -0
  73. package/dist/ui/resources.js +16 -2
  74. package/dist/ui/shared/compact-row.d.ts +11 -0
  75. package/dist/ui/shared/compact-row.js +18 -0
  76. package/dist/ui/shared/host-context.d.ts +15 -0
  77. package/dist/ui/shared/host-context.js +51 -0
  78. package/dist/ui/shared/tool-bridge.d.ts +30 -0
  79. package/dist/ui/shared/tool-bridge.js +137 -0
  80. package/dist/ui/shared/tool-shell.d.ts +9 -0
  81. package/dist/ui/shared/tool-shell.js +46 -4
  82. package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
  83. package/dist/ui/shared/ui-event-tracker.js +27 -0
  84. package/dist/utils/capture.js +173 -11
  85. package/dist/utils/files/base.d.ts +3 -1
  86. package/dist/utils/files/docx.d.ts +28 -15
  87. package/dist/utils/files/docx.js +622 -88
  88. package/dist/utils/files/factory.d.ts +6 -5
  89. package/dist/utils/files/factory.js +18 -6
  90. package/dist/utils/system-info.js +1 -1
  91. package/dist/utils/usageTracker.js +5 -0
  92. package/dist/version.d.ts +1 -1
  93. package/dist/version.js +1 -1
  94. package/package.json +8 -3
@@ -3,28 +3,36 @@
3
3
  */
4
4
  :root {
5
5
  color-scheme: light dark;
6
+
7
+ /* Spec variables with light-dark() fallbacks (following customer-segmentation pattern).
8
+ When a host provides --color-* via applyHostStyleVariables, the var() picks it up;
9
+ otherwise the light-dark() fallback keeps the UI legible. */
6
10
  --bg: transparent;
7
- --panel: var(--color-background-primary, Canvas);
8
- --panel-subtle: var(--color-background-tertiary, ButtonFace);
9
- --panel-muted: var(--color-background-muted, ButtonFace);
10
- --text: var(--color-text-primary, CanvasText);
11
- --text-secondary: var(--color-text-secondary, CanvasText);
12
- --muted: var(--color-text-tertiary, GrayText);
13
- --border: var(--color-border-default, GrayText);
14
- --accent: var(--color-accent-primary, LinkText);
15
- --accent-hover: var(--color-accent-primary-hover, var(--accent));
16
- --accent-soft: var(--color-accent-subtle, var(--panel-subtle));
17
- --focus-ring: var(--color-border-focused, var(--accent));
11
+ --panel: var(--color-background-primary, light-dark(#ffffff, #1e1e2e));
12
+ --panel-subtle: var(--color-background-tertiary, light-dark(#f5f5f5, #2a2a3c));
13
+ --panel-muted: var(--color-background-tertiary, light-dark(#f5f5f5, #2a2a3c));
14
+ --text: var(--color-text-primary, light-dark(#1a1a2e, #e4e4ef));
15
+ --text-secondary: var(--color-text-secondary, light-dark(#4a4a5a, #a0a0b8));
16
+ --muted: var(--color-text-tertiary, light-dark(#94a3b8, #64748b));
17
+ --border: var(--color-border-primary, light-dark(#e2e8f0, #334155));
18
+ --accent: var(--color-text-info, light-dark(#2563eb, #60a5fa));
19
+ --accent-hover: var(--color-text-info, var(--accent));
20
+ --accent-soft: var(--color-background-info, var(--panel-subtle));
21
+ --focus-ring: var(--color-border-primary, var(--accent));
18
22
  --shadow-sm: var(--shadow-sm, none);
19
23
  --success-bg: var(--color-background-success, var(--panel-subtle));
20
24
  --success-border: var(--color-border-success, var(--border));
21
- --success-text: var(--color-text-success, var(--text));
25
+ --success-text: var(--color-text-success, light-dark(#059669, #34d399));
22
26
  --error-bg: var(--color-background-danger, var(--panel-subtle));
23
27
  --error-border: var(--color-border-danger, var(--border));
24
- --error-text: var(--color-text-danger, var(--text));
28
+ --error-text: var(--color-text-danger, light-dark(#dc2626, #f87171));
25
29
  --warning-bg: var(--color-background-warning, var(--panel-subtle));
26
30
  --warning-border: var(--color-border-warning, var(--border));
27
- --warning-text: var(--color-text-warning, var(--text));
31
+ --warning-text: var(--color-text-warning, light-dark(#d97706, #fbbf24));
32
+ }
33
+
34
+ html {
35
+ background: var(--bg);
28
36
  }
29
37
 
30
38
  * {
@@ -64,6 +72,73 @@ body.dc-ready {
64
72
  }
65
73
 
66
74
 
75
+ .compact-row {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 6px;
79
+ padding: 6px 2px;
80
+ font-size: 13px;
81
+ color: var(--muted);
82
+ line-height: 1;
83
+ user-select: none;
84
+ }
85
+
86
+ .compact-row--ready {
87
+ cursor: pointer;
88
+ border-radius: 8px;
89
+ transition: color 150ms ease;
90
+ }
91
+
92
+ .compact-row--ready:hover {
93
+ color: var(--text-secondary);
94
+ }
95
+
96
+ .compact-row--ready:hover .compact-chevron {
97
+ color: var(--text-secondary);
98
+ }
99
+
100
+ .compact-chevron {
101
+ width: 14px;
102
+ height: 14px;
103
+ fill: currentColor;
104
+ color: var(--muted);
105
+ flex-shrink: 0;
106
+ transition: transform 200ms ease, color 150ms ease;
107
+ }
108
+
109
+ .tool-shell.expanded .compact-chevron {
110
+ transform: rotate(90deg);
111
+ }
112
+
113
+ .compact-label {
114
+ color: inherit;
115
+ }
116
+
117
+ .compact-filename {
118
+ color: var(--text);
119
+ font-weight: 600;
120
+ }
121
+
122
+ .compact-row--ready .compact-label::after {
123
+ content: ' · ';
124
+ }
125
+
126
+ .compact-row--loading {
127
+ animation: fade-pulse 1.5s ease-in-out infinite;
128
+ }
129
+
130
+ @keyframes fade-pulse {
131
+ 0%,
132
+ 100% {
133
+ opacity: 0.5;
134
+ }
135
+
136
+ 50% {
137
+ opacity: 1;
138
+ }
139
+ }
140
+
141
+
67
142
  /*
68
143
  * Reusable styles for the shared tool header component. It provides consistent spacing, alignment, and action presentation across apps.
69
144
  */
@@ -276,69 +351,23 @@ body.dc-ready {
276
351
  :root {
277
352
  --code-bg: var(--panel);
278
353
  --code-text: var(--color-text-primary, var(--text));
279
- --hljs-keyword: var(--color-text-accent, #8b5cf6);
280
- --hljs-string: var(--color-text-success, #059669);
281
- --hljs-comment: var(--color-text-tertiary, #94a3b8);
282
- --hljs-type: var(--color-text-info, #0f766e);
283
- --hljs-number: var(--color-text-warning, #b45309);
284
- --hljs-attr: var(--color-text-info, #2563eb);
285
- --hljs-built-in: var(--color-text-accent, #6366f1);
286
- --hljs-tag: var(--color-text-info, #0ea5a8);
354
+ --hljs-keyword: light-dark(#8b5cf6, #a78bfa);
355
+ --hljs-string: var(--color-text-success, light-dark(#059669, #34d399));
356
+ --hljs-comment: var(--color-text-tertiary, light-dark(#94a3b8, #64748b));
357
+ --hljs-type: var(--color-text-info, light-dark(#0f766e, #5eead4));
358
+ --hljs-number: var(--color-text-warning, light-dark(#b45309, #fbbf24));
359
+ --hljs-attr: var(--color-text-info, light-dark(#2563eb, #60a5fa));
360
+ --hljs-built-in: light-dark(#6366f1, #818cf8);
361
+ --hljs-tag: var(--color-text-info, light-dark(#0ea5a8, #67e8f9));
287
362
  --content-height: min(82vh, 920px);
288
363
  --markdown-text: var(--text);
289
364
  --markdown-muted: var(--text-secondary);
290
365
  --inline-code-bg: var(--panel-subtle);
291
366
  --inline-code-border: var(--border);
292
367
  --inline-code-text: var(--text);
293
- --notice-bg: var(--warning-bg);
294
- --notice-border: var(--warning-border);
295
- --notice-text: var(--warning-text);
296
- }
297
-
298
- /* ── Compact row ── */
299
-
300
- .compact-row {
301
- display: flex;
302
- align-items: center;
303
- gap: 6px;
304
- padding: 6px 2px;
305
- font-size: 13px;
306
- color: var(--muted);
307
- line-height: 1;
308
- user-select: none;
309
- }
310
-
311
- .compact-row--ready {
312
- cursor: pointer;
313
- border-radius: 8px;
314
- transition: color 150ms ease;
315
- }
316
-
317
- .compact-row--ready:hover { color: var(--text-secondary); }
318
- .compact-row--ready:hover .compact-chevron { color: var(--text-secondary); }
319
-
320
- .compact-chevron {
321
- width: 14px;
322
- height: 14px;
323
- fill: currentColor;
324
- color: var(--muted);
325
- flex-shrink: 0;
326
- transition: transform 200ms ease, color 150ms ease;
327
- }
328
-
329
- .tool-shell.expanded .compact-chevron { transform: rotate(90deg); }
330
-
331
- .compact-label { color: inherit; }
332
- .compact-filename { color: var(--text-secondary); font-weight: 500; }
333
- .compact-row--ready .compact-label::after { content: ' · '; }
334
-
335
- .compact-row--loading {
336
- animation: fade-pulse 1.5s ease-in-out infinite;
337
- }
338
-
339
- @keyframes fade-pulse {
340
- 0%, 100% { opacity: 0.5; }
341
- 50% { opacity: 1; }
368
+ --notice-bg: var(--panel-subtle);
369
+ --notice-border: var(--border);
370
+ --notice-text: var(--text-secondary);
342
371
  }
343
372
 
344
373
  /* ── Panel (Claude-style card) ── */
@@ -351,18 +380,25 @@ body.dc-ready {
351
380
  overflow: hidden;
352
381
  }
353
382
 
354
- @media (prefers-color-scheme: dark) {
355
- .panel {
356
- background: color-mix(in srgb, var(--panel), white 6%);
357
- }
358
- }
359
-
360
- [data-theme="dark"] .panel {
361
- background: color-mix(in srgb, var(--panel), white 6%);
362
- }
383
+ /* Panel background comes directly from host --color-background-primary.
384
+ No dark-mode adjustment — the host provides the correct color. */
363
385
 
364
386
  .tool-shell.collapsed .panel { display: none; }
365
387
 
388
+ /* When the host hides the summary row (hideSummaryRow: true), it means the host
389
+ provides its own outer frame/card. Strip inner chrome so only content remains. */
390
+ .tool-shell.host-framed .compact-row { display: none; }
391
+ .tool-shell.host-framed .panel {
392
+ margin-top: 0;
393
+ border: none;
394
+ border-radius: 0;
395
+ background: transparent;
396
+ }
397
+ .tool-shell.host-framed .panel-topbar { display: none; }
398
+ .tool-shell.host-framed .panel-footer { display: none; }
399
+ .tool-shell.host-framed .panel-content-wrapper { max-height: none; overflow: hidden; }
400
+ .tool-shell.host-framed .image-content { background: transparent; }
401
+
366
402
  /* ── Top bar ── */
367
403
 
368
404
  .panel-topbar {
@@ -495,9 +531,7 @@ body.dc-ready {
495
531
 
496
532
  .image-preview {
497
533
  width: 100%;
498
- height: var(--content-height);
499
534
  max-width: 920px;
500
- max-height: 100%;
501
535
  display: flex;
502
536
  align-items: center;
503
537
  justify-content: center;
@@ -505,7 +539,7 @@ body.dc-ready {
505
539
 
506
540
  .image-preview img {
507
541
  max-width: 100%;
508
- max-height: 100%;
542
+ max-height: var(--content-height);
509
543
  object-fit: contain;
510
544
  border-radius: 8px;
511
545
  border: 1px solid var(--border);
@@ -4,7 +4,14 @@ export declare const FILE_PREVIEW_RESOURCE: {
4
4
  description: string;
5
5
  mimeType: string;
6
6
  };
7
+ export declare const CONFIG_EDITOR_RESOURCE: {
8
+ uri: string;
9
+ name: string;
10
+ description: string;
11
+ mimeType: string;
12
+ };
7
13
  export declare function getFilePreviewResourceText(): Promise<string>;
14
+ export declare function getConfigEditorResourceText(): Promise<string>;
8
15
  export declare function listUiResources(): {
9
16
  uri: string;
10
17
  name: string;
@@ -4,7 +4,7 @@
4
4
  import fs from 'fs/promises';
5
5
  import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
- import { FILE_PREVIEW_RESOURCE_URI } from './contracts.js';
7
+ import { CONFIG_EDITOR_RESOURCE_URI, FILE_PREVIEW_RESOURCE_URI } from './contracts.js';
8
8
  const UI_RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app';
9
9
  export const FILE_PREVIEW_RESOURCE = {
10
10
  uri: FILE_PREVIEW_RESOURCE_URI,
@@ -12,9 +12,16 @@ export const FILE_PREVIEW_RESOURCE = {
12
12
  description: 'Markdown-first preview surface for read_file structured content.',
13
13
  mimeType: UI_RESOURCE_MIME_TYPE
14
14
  };
15
+ export const CONFIG_EDITOR_RESOURCE = {
16
+ uri: CONFIG_EDITOR_RESOURCE_URI,
17
+ name: 'Desktop Commander Config Editor',
18
+ description: 'Interactive editor for Desktop Commander configuration values.',
19
+ mimeType: UI_RESOURCE_MIME_TYPE
20
+ };
15
21
  const __filename = fileURLToPath(import.meta.url);
16
22
  const __dirname = path.dirname(__filename);
17
23
  const DIST_FILE_PREVIEW_DIR = path.resolve(__dirname, 'file-preview');
24
+ const DIST_CONFIG_EDITOR_DIR = path.resolve(__dirname, 'config-editor');
18
25
  function replaceOrThrow(source, pattern, replacement, context) {
19
26
  if (!pattern.test(source)) {
20
27
  throw new Error(`UI template is missing expected ${context}`);
@@ -43,14 +50,21 @@ async function readInlinedResourceHtml(distDir, runtimeFileName) {
43
50
  export async function getFilePreviewResourceText() {
44
51
  return readInlinedResourceHtml(DIST_FILE_PREVIEW_DIR, 'preview-runtime.js');
45
52
  }
53
+ export async function getConfigEditorResourceText() {
54
+ return readInlinedResourceHtml(DIST_CONFIG_EDITOR_DIR, 'config-editor-runtime.js');
55
+ }
46
56
  const READABLE_UI_RESOURCES = {
47
57
  [FILE_PREVIEW_RESOURCE_URI]: {
48
58
  mimeType: FILE_PREVIEW_RESOURCE.mimeType,
49
59
  getText: getFilePreviewResourceText
60
+ },
61
+ [CONFIG_EDITOR_RESOURCE_URI]: {
62
+ mimeType: CONFIG_EDITOR_RESOURCE.mimeType,
63
+ getText: getConfigEditorResourceText
50
64
  }
51
65
  };
52
66
  export function listUiResources() {
53
- return [FILE_PREVIEW_RESOURCE];
67
+ return [FILE_PREVIEW_RESOURCE, CONFIG_EDITOR_RESOURCE];
54
68
  }
55
69
  export async function readUiResource(uri) {
56
70
  const resource = READABLE_UI_RESOURCES[uri];
@@ -0,0 +1,11 @@
1
+ interface RenderCompactRowOptions {
2
+ id?: string;
3
+ label: string;
4
+ filename?: string;
5
+ variant?: 'ready' | 'loading' | 'status';
6
+ expandable?: boolean;
7
+ expanded?: boolean;
8
+ interactive?: boolean;
9
+ }
10
+ export declare function renderCompactRow(options: RenderCompactRowOptions): string;
11
+ export {};
@@ -0,0 +1,18 @@
1
+ import { escapeHtml } from './escape-html.js';
2
+ export function renderCompactRow(options) {
3
+ const variant = options.variant ?? 'ready';
4
+ const classNames = `compact-row compact-row--${variant}`;
5
+ const id = options.id ? ` id="${escapeHtml(options.id)}"` : '';
6
+ const interactive = options.interactive ?? variant === 'ready';
7
+ const role = interactive ? ' role="button" tabindex="0"' : '';
8
+ const ariaExpanded = typeof options.expanded === 'boolean'
9
+ ? ` aria-expanded="${String(options.expanded)}"`
10
+ : '';
11
+ const chevron = options.expandable
12
+ ? '<svg class="compact-chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M10 6l6 6-6 6z"/></svg>'
13
+ : '';
14
+ const filename = options.filename
15
+ ? `<span class="compact-filename">${escapeHtml(options.filename)}</span>`
16
+ : '';
17
+ return `<div class="${classNames}"${id}${role}${ariaExpanded}>${chevron}<span class="compact-label">${escapeHtml(options.label)}</span>${filename}</div>`;
18
+ }
@@ -0,0 +1,15 @@
1
+ import { App } from '@modelcontextprotocol/ext-apps';
2
+ export interface UiChromeState {
3
+ expanded: boolean;
4
+ hideSummaryRow: boolean;
5
+ compact?: boolean;
6
+ }
7
+ export interface ConnectWithSharedHostContextOptions {
8
+ app: App;
9
+ chrome: UiChromeState;
10
+ onContextApplied?: () => void;
11
+ onConnected?: () => void | Promise<void>;
12
+ }
13
+ export declare function isObjectRecord(value: unknown): value is Record<string, unknown>;
14
+ export declare function applySharedHostContext(context: unknown, chrome: UiChromeState): void;
15
+ export declare function connectWithSharedHostContext(options: ConnectWithSharedHostContextOptions): Promise<void>;
@@ -0,0 +1,51 @@
1
+ import { applyDocumentTheme, applyHostFonts, applyHostStyleVariables } from '@modelcontextprotocol/ext-apps';
2
+ export function isObjectRecord(value) {
3
+ return typeof value === 'object' && value !== null;
4
+ }
5
+ export function applySharedHostContext(context, chrome) {
6
+ if (!isObjectRecord(context)) {
7
+ return;
8
+ }
9
+ if (context.theme === 'light' || context.theme === 'dark') {
10
+ applyDocumentTheme(context.theme);
11
+ }
12
+ const styles = isObjectRecord(context.styles) ? context.styles : null;
13
+ const variables = isObjectRecord(styles?.variables) ? styles.variables : null;
14
+ const css = isObjectRecord(styles?.css) ? styles.css : null;
15
+ const fonts = typeof css?.fonts === 'string' ? css.fonts : null;
16
+ if (variables) {
17
+ applyHostStyleVariables(variables);
18
+ }
19
+ if (fonts) {
20
+ applyHostFonts(fonts);
21
+ }
22
+ if (typeof context.initiallyExpanded === 'boolean') {
23
+ chrome.expanded = context.initiallyExpanded;
24
+ }
25
+ if (typeof context.compact === 'boolean' && typeof chrome.compact === 'boolean') {
26
+ chrome.compact = context.compact;
27
+ }
28
+ if (typeof context.hideSummaryRow === 'boolean') {
29
+ chrome.hideSummaryRow = context.hideSummaryRow;
30
+ if (context.hideSummaryRow) {
31
+ chrome.expanded = true;
32
+ if (typeof chrome.compact === 'boolean') {
33
+ chrome.compact = true;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ export async function connectWithSharedHostContext(options) {
39
+ const { app, chrome, onContextApplied, onConnected } = options;
40
+ app.onhostcontextchanged = (context) => {
41
+ applySharedHostContext(context, chrome);
42
+ onContextApplied?.();
43
+ };
44
+ await app.connect();
45
+ const hostContext = app.getHostContext();
46
+ if (hostContext) {
47
+ applySharedHostContext(hostContext, chrome);
48
+ onContextApplied?.();
49
+ }
50
+ await onConnected?.();
51
+ }
@@ -0,0 +1,30 @@
1
+ type ToolArgs = Record<string, unknown>;
2
+ type ToolHelper = {
3
+ callTool: (name: string, args: ToolArgs) => Promise<unknown> | unknown;
4
+ };
5
+ type MessageEventLike = {
6
+ data: unknown;
7
+ origin?: string;
8
+ source?: unknown;
9
+ };
10
+ type MessageListener = (event: MessageEventLike) => void;
11
+ type MessageTarget = {
12
+ postMessage: (message: unknown, targetOrigin?: string) => void;
13
+ };
14
+ type BridgeHost = {
15
+ openai?: ToolHelper;
16
+ mcp?: ToolHelper;
17
+ parent?: MessageTarget;
18
+ addEventListener?: (type: 'message', listener: MessageListener) => void;
19
+ removeEventListener?: (type: 'message', listener: MessageListener) => void;
20
+ };
21
+ export interface ToolBridgeOptions {
22
+ host?: BridgeHost;
23
+ requestTimeoutMs?: number;
24
+ targetOrigin?: string;
25
+ idPrefix?: string;
26
+ }
27
+ export declare function createToolBridge(options?: ToolBridgeOptions): {
28
+ callTool: (name: string, args?: ToolArgs) => Promise<unknown>;
29
+ };
30
+ export {};
@@ -0,0 +1,137 @@
1
+ const DEFAULT_TIMEOUT_MS = 5000;
2
+ function getDefaultTargetOrigin() {
3
+ if (typeof document === 'undefined') {
4
+ return '*';
5
+ }
6
+ const referrer = document.referrer;
7
+ if (typeof referrer !== 'string' || referrer.trim().length === 0) {
8
+ return '*';
9
+ }
10
+ try {
11
+ return new URL(referrer).origin;
12
+ }
13
+ catch {
14
+ return '*';
15
+ }
16
+ }
17
+ function normalizeTargetOrigin(value) {
18
+ if (value === '*') {
19
+ return value;
20
+ }
21
+ try {
22
+ return new URL(value).origin;
23
+ }
24
+ catch {
25
+ throw new Error(`Invalid targetOrigin: ${value}`);
26
+ }
27
+ }
28
+ function isObject(value) {
29
+ return typeof value === 'object' && value !== null;
30
+ }
31
+ function normalizeToolArgs(args) {
32
+ return args ?? {};
33
+ }
34
+ function extractErrorMessage(error) {
35
+ if (error instanceof Error && error.message) {
36
+ return error.message;
37
+ }
38
+ return String(error);
39
+ }
40
+ function isHelperUnavailableError(error) {
41
+ if (!isObject(error)) {
42
+ return false;
43
+ }
44
+ const code = typeof error.code === 'string' ? error.code.toLowerCase() : '';
45
+ if (code === 'not_implemented' || code === 'not_supported' || code === 'unavailable') {
46
+ return true;
47
+ }
48
+ const message = extractErrorMessage(error).toLowerCase();
49
+ return message.includes('not implemented')
50
+ || message.includes('not supported')
51
+ || message.includes('unavailable')
52
+ || message.includes('not available');
53
+ }
54
+ export function createToolBridge(options = {}) {
55
+ const host = options.host ?? globalThis;
56
+ const timeoutMs = options.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
57
+ const targetOrigin = normalizeTargetOrigin(options.targetOrigin ?? getDefaultTargetOrigin());
58
+ const idPrefix = options.idPrefix ?? 'tool-bridge';
59
+ let requestCounter = 0;
60
+ async function callViaFallback(name, args) {
61
+ if (!host.parent || !host.addEventListener || !host.removeEventListener) {
62
+ throw new Error('JSON-RPC fallback is unavailable in this host environment.');
63
+ }
64
+ const parent = host.parent;
65
+ const addListener = (type, listener) => {
66
+ host.addEventListener?.(type, listener);
67
+ };
68
+ const removeListener = (type, listener) => {
69
+ host.removeEventListener?.(type, listener);
70
+ };
71
+ requestCounter += 1;
72
+ const requestId = `${idPrefix}:${requestCounter}`;
73
+ return new Promise((resolve, reject) => {
74
+ const timeoutHandle = setTimeout(() => {
75
+ removeListener('message', onMessage);
76
+ reject(new Error(`Tool call fallback timed out after ${timeoutMs}ms (request: ${requestId})`));
77
+ }, timeoutMs);
78
+ const onMessage = (event) => {
79
+ const payload = event.data;
80
+ if (event.source !== parent) {
81
+ return;
82
+ }
83
+ if (targetOrigin !== '*' && event.origin !== targetOrigin) {
84
+ return;
85
+ }
86
+ if (!isObject(payload) || payload.id !== requestId) {
87
+ return;
88
+ }
89
+ clearTimeout(timeoutHandle);
90
+ removeListener('message', onMessage);
91
+ if (isObject(payload.error)) {
92
+ const rawMessage = payload.error.message;
93
+ const message = typeof rawMessage === 'string'
94
+ ? rawMessage
95
+ : 'Unknown tools/call fallback error';
96
+ reject(new Error(`Tool call fallback failed: ${message}`));
97
+ return;
98
+ }
99
+ resolve(payload.result);
100
+ };
101
+ addListener('message', onMessage);
102
+ parent.postMessage({
103
+ jsonrpc: '2.0',
104
+ id: requestId,
105
+ method: 'tools/call',
106
+ params: {
107
+ name,
108
+ arguments: args,
109
+ },
110
+ }, targetOrigin);
111
+ });
112
+ }
113
+ async function callTool(name, args) {
114
+ const normalizedArgs = normalizeToolArgs(args);
115
+ const helperCandidates = [host.openai, host.mcp].filter((candidate) => Boolean(candidate?.callTool));
116
+ for (const helper of helperCandidates) {
117
+ try {
118
+ return await helper.callTool(name, normalizedArgs);
119
+ }
120
+ catch (error) {
121
+ if (isHelperUnavailableError(error)) {
122
+ continue;
123
+ }
124
+ throw new Error(`Tool helper call failed: ${extractErrorMessage(error)}`);
125
+ }
126
+ }
127
+ try {
128
+ return await callViaFallback(name, normalizedArgs);
129
+ }
130
+ catch (fallbackError) {
131
+ throw fallbackError;
132
+ }
133
+ }
134
+ return {
135
+ callTool,
136
+ };
137
+ }
@@ -12,5 +12,14 @@ interface CreateToolShellControllerOptions {
12
12
  onScrollAfterExpand?: () => void;
13
13
  onRender?: () => void;
14
14
  }
15
+ interface CreateCompactRowShellControllerOptions {
16
+ shell: HTMLElement | null;
17
+ compactRow: HTMLElement | null;
18
+ initialExpanded: boolean;
19
+ onToggle?: (expanded: boolean) => void;
20
+ onScrollAfterExpand?: () => void;
21
+ onRender?: () => void;
22
+ }
15
23
  export declare function createToolShellController(options: CreateToolShellControllerOptions): ToolShellController;
24
+ export declare function createCompactRowShellController(options: CreateCompactRowShellControllerOptions): ToolShellController;
16
25
  export {};
@@ -23,6 +23,7 @@ export function createToolShellController(options) {
23
23
  const { shell, toggleButton, initialExpanded, onToggle, onScrollAfterExpand, onRender } = options;
24
24
  let isExpanded = initialExpanded;
25
25
  let scrollTrackedForCurrentExpand = false;
26
+ const shouldTrackScroll = typeof onScrollAfterExpand === 'function';
26
27
  const applyExpandedState = (nextExpanded) => {
27
28
  const wasExpanded = isExpanded;
28
29
  isExpanded = nextExpanded;
@@ -50,16 +51,57 @@ export function createToolShellController(options) {
50
51
  };
51
52
  applyExpandedState(isExpanded);
52
53
  toggleButton?.addEventListener('click', toggle);
53
- shell?.addEventListener('scroll', handleScroll, { passive: true });
54
- document.addEventListener('scroll', handleScroll, { passive: true, capture: true });
54
+ if (shouldTrackScroll) {
55
+ shell?.addEventListener('scroll', handleScroll, { passive: true });
56
+ document.addEventListener('scroll', handleScroll, { passive: true, capture: true });
57
+ }
55
58
  return {
56
59
  getExpanded: () => isExpanded,
57
60
  setExpanded: applyExpandedState,
58
61
  toggle,
59
62
  dispose: () => {
60
63
  toggleButton?.removeEventListener('click', toggle);
61
- shell?.removeEventListener('scroll', handleScroll);
62
- document.removeEventListener('scroll', handleScroll, true);
64
+ if (shouldTrackScroll) {
65
+ shell?.removeEventListener('scroll', handleScroll);
66
+ document.removeEventListener('scroll', handleScroll, true);
67
+ }
68
+ },
69
+ };
70
+ }
71
+ export function createCompactRowShellController(options) {
72
+ const { shell, compactRow, initialExpanded, onToggle, onScrollAfterExpand, onRender } = options;
73
+ const controller = createToolShellController({
74
+ shell,
75
+ toggleButton: null,
76
+ initialExpanded,
77
+ onToggle: (expanded) => {
78
+ compactRow?.setAttribute('aria-expanded', String(expanded));
79
+ onToggle?.(expanded);
80
+ },
81
+ onScrollAfterExpand,
82
+ onRender,
83
+ });
84
+ compactRow?.setAttribute('aria-expanded', String(initialExpanded));
85
+ const handleCompactClick = () => {
86
+ controller.toggle();
87
+ };
88
+ const handleCompactKeydown = (event) => {
89
+ if (event.key !== 'Enter' && event.key !== ' ') {
90
+ return;
91
+ }
92
+ event.preventDefault();
93
+ controller.toggle();
94
+ };
95
+ compactRow?.addEventListener('click', handleCompactClick);
96
+ compactRow?.addEventListener('keydown', handleCompactKeydown);
97
+ return {
98
+ getExpanded: controller.getExpanded,
99
+ setExpanded: controller.setExpanded,
100
+ toggle: controller.toggle,
101
+ dispose: () => {
102
+ compactRow?.removeEventListener('click', handleCompactClick);
103
+ compactRow?.removeEventListener('keydown', handleCompactKeydown);
104
+ controller.dispose();
63
105
  },
64
106
  };
65
107
  }
@@ -0,0 +1,9 @@
1
+ type UiEventParamValue = string | number | boolean | null;
2
+ export type UiEventParams = Record<string, UiEventParamValue>;
3
+ type ToolCaller = (name: string, args: Record<string, unknown>) => Promise<unknown>;
4
+ export interface UiEventTrackerOptions {
5
+ component: string;
6
+ baseParams?: UiEventParams;
7
+ }
8
+ export declare function createUiEventTracker(callTool: ToolCaller, options: UiEventTrackerOptions): (event: string, params?: Record<string, unknown>) => void;
9
+ export {};