@aryacode/dsh-plugin-arya-computer-use 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +18 -0
- package/README.md +108 -2
- package/client.js +492 -0
- package/cordis.patch.yml +16 -0
- package/docs/PLAN.md +151 -0
- package/icon.svg +6 -0
- package/index.js +543 -0
- package/locale/en.json +6 -0
- package/mcp/activity.mjs +299 -0
- package/mcp/adapter-server.mjs +626 -0
- package/mcp/browser-launcher.mjs +66 -0
- package/mcp/settings.mjs +87 -0
- package/mcp/takeover.mjs +253 -0
- package/package.json +88 -4
- package/scripts/ocr.ps1 +95 -0
- package/scripts/windows-takeover.cs +1190 -0
- package/scripts/windows-takeover.ps1 +25 -0
- package/skills/arya-desktop/SKILL.md +75 -0
package/client.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
// Browser module: Arya Computer Use settings UI + chat activity rows.
|
|
2
|
+
// Derived from dsh-computer-use-win (MIT, Yu-tao-Li) — browser-policy, tool-preset,
|
|
3
|
+
// and community sections removed; behavior switches (headed/overlay/driver lock) added.
|
|
4
|
+
window.__ModuleLoader__.load({
|
|
5
|
+
id: '@aryacode/dsh-plugin-arya-computer-use',
|
|
6
|
+
factory: (require) => {
|
|
7
|
+
const React = require('react'), h = React.createElement;
|
|
8
|
+
const NS = 'aryaComputerUse.settings';
|
|
9
|
+
const defaults = {
|
|
10
|
+
progressOverlayEnabled: true, progressOverlayMode: 'source', progressOverlayOpacity: 60,
|
|
11
|
+
progressOverlayWidth: 360, progressOverlayHeight: 240, progressOverlayPosition: 'top-right',
|
|
12
|
+
progressOverlayX: 80, progressOverlayY: 80, progressOverlayLocked: true, progressLockShortcut: '',
|
|
13
|
+
progressPreviewToken: 0, indicatorEnabled: true, cursorIndicatorEnabled: true,
|
|
14
|
+
remoteIndicatorVisible: true, indicatorText: 'Arya is controlling your computer', pointerLabel: 'Arya', indicatorPosition: 'top',
|
|
15
|
+
indicatorIdleMs: 800, stopShortcut: '', resumeToken: 0, previewToken: 0,
|
|
16
|
+
headedDefault: false, overlayDefault: true, driverLockEnabled: true,
|
|
17
|
+
toolPreset: 'lite',
|
|
18
|
+
browserTools: 'core', browserProfile: '', browserSession: '', browserStateFile: '', browserCdp: '', browserHeaded: false,
|
|
19
|
+
};
|
|
20
|
+
const equal = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
21
|
+
const dictionaries = {
|
|
22
|
+
zh: {
|
|
23
|
+
nav: '电脑操控', title: 'Arya 电脑操控', pluginDescription: '通过无障碍控件驱动 Windows 应用,接管时显示提示并可用 Esc 停止。',
|
|
24
|
+
openSettings: '打开设置', intro: '查看接管提示,使用 Esc 停止桌面操作。',
|
|
25
|
+
loadingSettings: '正在加载设置', notLoaded: '设置尚未加载', reloadSettings: '刷新',
|
|
26
|
+
indicatorTitle: '接管提示', indicator: '为应用窗口描边框', indicatorHint: '用边框标出 agent 正在操作的窗口,并显示提示条。不会高亮你自己的鼠标。',
|
|
27
|
+
cursorIndicator: '显示虚拟光标', cursorIndicatorHint: '在 agent 操作的位置画一个虚拟光标。不会跟随或高亮你的真实鼠标。',
|
|
28
|
+
appearance: '提示的更多选项', text: '提示文字', position: '提示位置', top: '顶部', bottom: '底部',
|
|
29
|
+
delay: '操作后保留提示', delayHint: '连续动作保持显示,减少闪烁。200–3000 毫秒。', ms: '毫秒',
|
|
30
|
+
cursorLabel: 'Agent 名称', cursorLabelHint: '用于虚拟光标标签和进度面板标题,1–16 个字符。',
|
|
31
|
+
remoteVisible: '远程与录屏可见', remoteVisibleHint: '让远程桌面、屏幕共享和录屏看到接管提示与浮窗。关闭后请求 Windows 把它们排除在捕获之外。',
|
|
32
|
+
preview: '预览接管提示', previewed: '已请求预览;如果之前按 Esc 暂停,请先恢复控制。', previewHint: '请先保存更改,再预览屏幕上的实际效果。',
|
|
33
|
+
progressTitle: '操作浮窗', progressOverlay: '显示操作浮窗', progressOverlayHint: '在屏幕上显示当前步骤、模型的思考与回复。',
|
|
34
|
+
progressLocked: '锁定浮窗', progressLockedHint: '锁定后浮窗不抢焦点、不可拖动;可用快捷键解锁。',
|
|
35
|
+
progressSync: '浮窗位置与大小与接管浮窗同步。', openProgress: '显示浮窗',
|
|
36
|
+
progressOpacity: '背景不透明度(%)', progressWidth: '宽度(像素)', progressHeight: '高度(像素)',
|
|
37
|
+
progressPosition: '浮窗位置', progress_topRight: '右上', progress_topLeft: '左上', progress_bottomRight: '右下', progress_bottomLeft: '左下', progress_custom: '自定义',
|
|
38
|
+
progressX: 'X 坐标', progressY: 'Y 坐标', progressShortcut: '浮窗解锁快捷键', progressShortcutHint: '按下后临时解锁浮窗以便拖动。留空表示不设置。',
|
|
39
|
+
toolSurfaceTitle: '工具范围', toolPreset: '工具预设', toolPresetHint: 'lite 直接列出全部 16 个工具;lazy 只暴露 tools / snapshot / click,其余按需加载。修改后需重启 MCP 生效。', toolPresetLite: 'lite · 全部 16 个', toolPresetLazy: 'lazy · 按需加载',
|
|
40
|
+
behaviorTitle: '操作方式', headedDefault: 'Real mouse and keyboard', headedDefaultHint: 'Off by default: input goes through background accessibility patterns and never moves your pointer or steals focus. Turn on to use real keyboard/mouse input, which briefly focuses the target window.',
|
|
41
|
+
overlayDefault: '引擎会话叠加层', overlayDefaultHint: '启动桌面会话时启用引擎自带的光标叠加层。Arya 的虚拟光标不依赖它。',
|
|
42
|
+
driverLock: '单会话驱动', driverLockHint: '同一时间只允许一个对话操作桌面;其他对话调用桌面工具会收到 BUSY 提示。关闭后最近使用的对话自动接管。',
|
|
43
|
+
browserTitle: '网页通道', browserTools: '工具范围', browserToolsHint: 'agent-browser 向 MCP 暴露的工具集。修改后需重启 MCP 生效。',
|
|
44
|
+
browserProfile: 'Chrome 配置', browserProfileHint: '配置名称(如 Default)或目录路径,让网页通道复用你的登录状态。',
|
|
45
|
+
browserSession: '会话名', browserSessionHint: '隔离的会话键;不同会话名的 Cookie 与标签页彼此独立。',
|
|
46
|
+
browserCdp: '连接我的浏览器', browserCdpHint: 'CDP 地址,例如 http://127.0.0.1:9222。用 --remote-debugging-port=9222 启动 Chrome 即可共享已登录的窗口。',
|
|
47
|
+
browserStateFile: '已保存的登录状态', browserStateFileHint: '用 agent-browser state save 保存的 JSON 状态文件路径。',
|
|
48
|
+
browserHeaded: '显示浏览器窗口', browserHeadedHint: '打开可见的 Chromium 窗口,而不是无头运行。',
|
|
49
|
+
browserRestart: '网页通道设置在下一次 MCP 重启后生效。',
|
|
50
|
+
invalidBrowser: '会话名只能包含字母、数字、连字符和下划线;CDP 地址需为端口、http(s):// 或 ws:// 地址;状态文件需为绝对路径。',
|
|
51
|
+
stop: '停止与恢复', escTitle: '按 Esc 停止操作', escAlways: 'Esc 始终可用',
|
|
52
|
+
stopHint: '停止后保持暂停,准备好再恢复。普通鼠标移动不会停止接管。',
|
|
53
|
+
extraStop: '自定义停止方式', shortcutHint: '点击“录制快捷键”,再按下想用的按键或组合键,例如 F8 或 Ctrl + Alt + K。保存后生效;恢复默认后仅保留 Esc。',
|
|
54
|
+
record: '录制快捷键', rerecord: '重新录制', cancelRecord: '取消录制', clearShortcut: '恢复默认(Esc)',
|
|
55
|
+
noShortcut: 'Esc(默认)', listening: '请按下快捷键…(Esc 取消)', listeningCancel: '按 Esc 取消',
|
|
56
|
+
unsupportedKey: '请使用字母、数字、功能键或常用按键,可搭配 Ctrl、Alt、Shift、Win。',
|
|
57
|
+
recordCancelled: '已取消录制', recordTimedOut: '录制已超时,请点击按钮重试。',
|
|
58
|
+
save: '保存更改', discard: '放弃更改', dirty: '有未保存的更改', clean: '更改后点击保存',
|
|
59
|
+
saved: '已保存', failed: '保存失败', unavailable: '当前宿主未提供可写设置,请在本机 DSH 中打开此页面。',
|
|
60
|
+
invalid: '请填写 1–120 字的单行提示文字,并将隐藏延时设为 200–3000 的整数。',
|
|
61
|
+
persistFailed: '宿主未确认设置已保存,请刷新后重试。', moreInfo: '更多说明',
|
|
62
|
+
see: '查看了屏幕', read: '读取了界面控件', windows: '查看了打开的窗口', findControl: '查找了界面控件',
|
|
63
|
+
clickAction: '点击了目标', valueAction: '填写了控件内容', typeAction: '输入了文本', keyAction: '发送了按键',
|
|
64
|
+
scrollAction: '滚动了页面', waitAction: '等待了界面响应', ocrAction: '识别了屏幕文字',
|
|
65
|
+
launchAction: '请求启动应用', toolsAction: '查看了工具配置', working: '正在操作…', completed: '已完成',
|
|
66
|
+
failedAction: '未完成', stoppedAction: '已停止', seriesTitle: '本轮桌面操作', sentOnly: '操作已发送,效果待确认',
|
|
67
|
+
showDetails: '查看步骤和截图', seriesCount: '步操作', stepError: '操作失败', and: ',并',
|
|
68
|
+
openPage: '打开了网页', readPage: '读取了网页', lookPage: '查看了网页', clickPage: '点击了网页元素', typePage: '在网页中输入',
|
|
69
|
+
keyPage: '发送了按键', shotPage: '截取了网页', evalPage: '执行了页面脚本', tabsPage: '管理了标签页', closePage: '关闭了浏览器',
|
|
70
|
+
waitPage: '等待网页响应', infoPage: '读取了页面信息', scrollPage: '滚动了网页', navPage: '导航到新地址', selectPage: '选择了选项',
|
|
71
|
+
togglePage: '切换了页面控件',
|
|
72
|
+
},
|
|
73
|
+
en: {
|
|
74
|
+
nav: 'Computer Use', title: 'Arya Computer Use', pluginDescription: 'Drives Windows apps through accessibility controls, shows a takeover indicator, and stops on Esc.',
|
|
75
|
+
openSettings: 'Open settings', intro: 'See when the agent controls your desktop. Press Esc to stop.',
|
|
76
|
+
loadingSettings: 'Loading settings', notLoaded: 'Settings are not loaded', reloadSettings: 'Reload',
|
|
77
|
+
indicatorTitle: 'Takeover indicator', indicator: 'Frame the app window', indicatorHint: 'A rim marks the window the agent is driving, with a banner. Your own pointer is never highlighted.',
|
|
78
|
+
cursorIndicator: 'Show a ghost cursor', cursorIndicatorHint: 'Draws a synthetic cursor where the agent acts. Your real pointer is never followed or highlighted.',
|
|
79
|
+
appearance: 'More indicator options', text: 'Banner text', position: 'Banner position', top: 'Top', bottom: 'Bottom',
|
|
80
|
+
delay: 'Keep visible after an action', delayHint: 'Stay visible between consecutive actions. 200–3000 milliseconds.', ms: 'ms',
|
|
81
|
+
cursorLabel: 'Agent name', cursorLabelHint: 'Used for the ghost cursor tag and the progress panel header. 1–16 characters.',
|
|
82
|
+
remoteVisible: 'Visible in remote sessions and recordings', remoteVisibleHint: 'Show the takeover indicator and overlay in remote access, screen sharing and recordings; turning it off asks Windows to exclude them from capture.',
|
|
83
|
+
preview: 'Preview indicator', previewed: 'Preview requested; resume first if you previously stopped with Esc.', previewHint: 'Save changes before previewing the actual screen indicator.',
|
|
84
|
+
progressTitle: 'Progress overlay', progressOverlay: 'Show the progress overlay', progressOverlayHint: 'Show the current step, reasoning and replies on screen.',
|
|
85
|
+
progressLocked: 'Lock the overlay', progressLockedHint: 'A locked overlay never steals focus and cannot be dragged; a shortcut unlocks it.',
|
|
86
|
+
progressSync: 'Overlay size and position stay in sync with the takeover overlay.', openProgress: 'Show overlay',
|
|
87
|
+
progressOpacity: 'Background opacity (%)', progressWidth: 'Width (px)', progressHeight: 'Height (px)',
|
|
88
|
+
progressPosition: 'Overlay position', progress_topRight: 'Top right', progress_topLeft: 'Top left', progress_bottomRight: 'Bottom right', progress_bottomLeft: 'Bottom left', progress_custom: 'Custom',
|
|
89
|
+
progressX: 'X coordinate', progressY: 'Y coordinate', progressShortcut: 'Overlay unlock shortcut', progressShortcutHint: 'Press to temporarily unlock the overlay so you can drag it. Empty means none.',
|
|
90
|
+
toolSurfaceTitle: 'Tool surface', toolPreset: 'Tool preset', toolPresetHint: 'lite lists all 16 tools up front; lazy exposes tools / snapshot / click and loads the rest on demand. Changes apply after the next MCP restart.', toolPresetLite: 'lite · all 16', toolPresetLazy: 'lazy · load on demand',
|
|
91
|
+
behaviorTitle: 'Control style', headedDefault: 'Real mouse and keyboard', headedDefaultHint: 'Off by default: input goes through background accessibility patterns and never moves your pointer or steals focus. Turn on to use real keyboard/mouse input, which briefly focuses the target window.',
|
|
92
|
+
overlayDefault: 'Engine session overlay', overlayDefaultHint: "Starts the desktop session with the engine's own cursor overlay. Arya's ghost cursor does not depend on it; leave it off unless you want the engine marker too.",
|
|
93
|
+
driverLock: 'One conversation drives at a time', driverLockHint: 'While one conversation is driving, desktop calls from other conversations receive a BUSY notice. Turn off to let the most recent conversation take over automatically.',
|
|
94
|
+
browserTitle: 'Web lane', browserTools: 'Tool surface', browserToolsHint: 'Which agent-browser tool profiles the MCP server exposes. Changing this applies on the next MCP restart.',
|
|
95
|
+
browserProfile: 'Chrome profile', browserProfileHint: 'A profile name such as Default, or a directory path, so the web lane reuses your sign-ins.',
|
|
96
|
+
browserSession: 'Session', browserSessionHint: 'An isolated session key; cookies and tabs stay separate per name.',
|
|
97
|
+
browserCdp: 'Connect to my browser', browserCdpHint: 'A CDP endpoint such as http://127.0.0.1:9222. Start Chrome with --remote-debugging-port=9222 to share a signed-in window.',
|
|
98
|
+
browserStateFile: 'Saved sign-in state', browserStateFileHint: 'Path to a JSON state file written by agent-browser state save.',
|
|
99
|
+
browserHeaded: 'Show the browser window', browserHeadedHint: 'Open a visible Chromium window instead of running headless.',
|
|
100
|
+
browserRestart: 'Web lane settings apply after the next MCP restart.',
|
|
101
|
+
invalidBrowser: 'The session may contain letters, numbers, hyphens and underscores; the CDP value must be a port, an http(s):// URL or a ws:// URL; the state file must be an absolute path.',
|
|
102
|
+
stop: 'Stop and resume', escTitle: 'Press Esc to stop', escAlways: 'Esc always works',
|
|
103
|
+
stopHint: 'Control stays paused until you resume. Ordinary mouse movement does not stop it.',
|
|
104
|
+
extraStop: 'Custom stop shortcut', shortcutHint: 'Click Record shortcut, then press a key or combination, such as F8 or Ctrl + Alt + K. Save to apply. Restoring the default keeps only Esc.',
|
|
105
|
+
record: 'Record shortcut', rerecord: 'Record again', cancelRecord: 'Cancel recording', clearShortcut: 'Restore default (Esc)',
|
|
106
|
+
noShortcut: 'Esc (default)', listening: 'Press your shortcut… (Esc to cancel)', listeningCancel: 'Press Esc to cancel',
|
|
107
|
+
unsupportedKey: 'Use a letter, number, function key or common key, optionally with Ctrl, Alt, Shift or Win.',
|
|
108
|
+
recordCancelled: 'Recording cancelled', recordTimedOut: 'Recording timed out. Click the button to try again.',
|
|
109
|
+
save: 'Save changes', discard: 'Discard changes', dirty: 'Unsaved changes', clean: 'Save after making changes',
|
|
110
|
+
saved: 'Saved', failed: 'Save failed', unavailable: 'Writable settings are unavailable. Open this page in your local DSH host.',
|
|
111
|
+
invalid: 'Use 1–120 characters on one line and an integer hide delay between 200 and 3000.',
|
|
112
|
+
persistFailed: 'The host did not confirm the saved settings. Refresh and retry.', moreInfo: 'More information',
|
|
113
|
+
see: 'Viewed the screen', read: 'Read interface controls', windows: 'Listed open windows', findControl: 'Looked for controls',
|
|
114
|
+
clickAction: 'Clicked the target', valueAction: 'Filled in a control', typeAction: 'Entered text', keyAction: 'Sent a key press',
|
|
115
|
+
scrollAction: 'Scrolled the page', waitAction: 'Waited for the interface', ocrAction: 'Read screen text',
|
|
116
|
+
launchAction: 'Requested app launch', toolsAction: 'Viewed tool settings', working: 'Working…', completed: 'Completed',
|
|
117
|
+
failedAction: 'Incomplete', stoppedAction: 'Stopped', seriesTitle: 'Desktop activity', sentOnly: 'Input sent; effect unconfirmed',
|
|
118
|
+
showDetails: 'Steps and screenshots', seriesCount: 'steps', stepError: 'Action failed', and: ', then ',
|
|
119
|
+
openPage: 'Opened a page', readPage: 'Read the page', lookPage: 'Looked at the page', clickPage: 'Clicked in the page', typePage: 'Typed in the page',
|
|
120
|
+
keyPage: 'Sent a key press', shotPage: 'Captured the page', evalPage: 'Evaluated on the page', tabsPage: 'Managed tabs', closePage: 'Closed the browser',
|
|
121
|
+
waitPage: 'Waited for the page', infoPage: 'Read page information', scrollPage: 'Scrolled the page', navPage: 'Navigated', selectPage: 'Selected an option',
|
|
122
|
+
togglePage: 'Toggled a page control',
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
function shortcutFromEvent(event) {
|
|
126
|
+
const code = event.code || '', special = { Space: 'Space', Enter: 'Enter', NumpadEnter: 'Enter', Tab: 'Tab', Backspace: 'Backspace', Delete: 'Delete', Insert: 'Insert', Home: 'Home', End: 'End', PageUp: 'PageUp', PageDown: 'PageDown', ArrowUp: 'Up', ArrowDown: 'Down', ArrowLeft: 'Left', ArrowRight: 'Right', Pause: 'Pause', CapsLock: 'CapsLock', NumLock: 'NumLock', ScrollLock: 'ScrollLock' };
|
|
127
|
+
let key = /^Key[A-Z]$/.test(code) ? code.slice(3) : /^Digit[0-9]$/.test(code) ? code.slice(5) : /^F(?:[1-9]|1[0-9]|2[0-4])$/.test(code) ? code : special[code];
|
|
128
|
+
if (!key && event.keyCode >= 65 && event.keyCode <= 90) key = String.fromCharCode(event.keyCode);
|
|
129
|
+
if (!key && event.keyCode >= 48 && event.keyCode <= 57) key = String.fromCharCode(event.keyCode);
|
|
130
|
+
if (!key && event.keyCode >= 112 && event.keyCode <= 135) key = 'F' + (event.keyCode - 111);
|
|
131
|
+
return key ? [event.ctrlKey && 'Ctrl', event.altKey && 'Alt', event.shiftKey && 'Shift', event.metaKey && 'Win', key].filter(Boolean).join('+') : null;
|
|
132
|
+
}
|
|
133
|
+
const css = `
|
|
134
|
+
[data-arya-settings]{--arya-card:var(--dsw-alias-settings-card-fill,var(--dsw-alias-bg-layer-2,Canvas));--arya-sunken:var(--dsw-alias-bg-module-platform,color-mix(in srgb,CanvasText 4%,Canvas));--arya-control:var(--dsw-alias-bg-layer-1,Canvas);--arya-fg:var(--dsw-alias-label-primary,CanvasText);--arya-muted:var(--dsw-alias-label-secondary,#61666b);--arya-subtle:var(--dsw-alias-label-tertiary,#81858c);--arya-line:var(--dsw-alias-border-l2,rgba(128,128,128,.2));--arya-line-strong:var(--dsw-alias-border-l3,rgba(128,128,128,.3));--arya-hover:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.1));--arya-accent:var(--dsw-alias-state-business-primary,#4176e6);--arya-primary:var(--dsw-alias-button-primary-fill,#0f1115);--arya-primary-fg:var(--dsw-alias-label-primary-foreground,#fff);--arya-tip-bg:var(--dsw-alias-tooltip-bg,#2b2f36);--arya-tip-fg:#fff;color:var(--arya-fg);font:14px/1.55 var(--dsw-font-family,-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',sans-serif);max-width:720px;list-style:none;box-sizing:border-box;margin:0;padding:0 0 8px}
|
|
135
|
+
[data-arya-settings] *{box-sizing:border-box}
|
|
136
|
+
[data-arya-settings] h2{font-size:18px;line-height:1.4;font-weight:600;margin:0}
|
|
137
|
+
[data-arya-settings] h3{font-size:15px;line-height:22px;font-weight:600;margin:0}
|
|
138
|
+
[data-arya-settings] p{margin:4px 0 0;color:var(--arya-muted);font-size:13px;line-height:1.55}
|
|
139
|
+
[data-arya-settings] .arya-header{padding:0 0 12px}
|
|
140
|
+
[data-arya-settings] .arya-header>p{color:var(--arya-subtle)}
|
|
141
|
+
[data-arya-settings] .arya-section{padding:0 0 18px}
|
|
142
|
+
[data-arya-settings] .arya-section-head{display:flex;align-items:center;gap:6px;margin:0 0 6px}
|
|
143
|
+
[data-arya-settings] .arya-section-head .arya-end{margin-left:auto;display:flex;gap:6px}
|
|
144
|
+
[data-arya-settings] .arya-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 18px}
|
|
145
|
+
[data-arya-settings] .arya-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 18px;padding:8px 0 2px}
|
|
146
|
+
[data-arya-settings] .arya-item{display:flex;gap:10px;align-items:flex-start;min-width:0}
|
|
147
|
+
[data-arya-settings] .arya-item-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
148
|
+
[data-arya-settings] .arya-item-title{font-size:13.5px;font-weight:500;line-height:1.5}
|
|
149
|
+
[data-arya-settings] .arya-art{flex:none;display:flex;align-items:center;justify-content:center;min-width:56px;padding-top:2px}
|
|
150
|
+
[data-arya-settings] .arya-art svg{width:56px;height:35px}
|
|
151
|
+
[data-arya-settings] .arya-art svg *{fill:none;stroke:var(--arya-muted);stroke-width:1.4}
|
|
152
|
+
[data-arya-settings] .arya-art svg .a-acc{stroke:var(--arya-accent);fill:color-mix(in srgb,var(--arya-accent) 16%,transparent)}
|
|
153
|
+
[data-arya-settings] .arya-art svg .a-glow{stroke:var(--arya-accent);opacity:.55}
|
|
154
|
+
[data-arya-settings] .arya-art svg .a-ink{stroke:var(--arya-subtle);opacity:.6}
|
|
155
|
+
[data-arya-settings] .arya-art svg .a-ink2{stroke:var(--arya-subtle);opacity:.35}
|
|
156
|
+
[data-arya-settings] .arya-art svg .a-win{fill:var(--arya-control)}
|
|
157
|
+
[data-arya-settings] .arya-art svg .a-bar{fill:var(--arya-sunken);stroke:none}
|
|
158
|
+
[data-arya-settings] .arya-art svg .a-pill{fill:var(--arya-control)}
|
|
159
|
+
[data-arya-settings] .arya-art svg .a-dot{fill:var(--arya-accent);stroke:none}
|
|
160
|
+
[data-arya-settings] .arya-art svg .a-text{fill:var(--arya-subtle);stroke:none}
|
|
161
|
+
[data-arya-settings] .arya-art svg .a-key{fill:var(--arya-sunken)}
|
|
162
|
+
[data-arya-settings] .arya-art svg .a-cap{fill:var(--arya-muted);stroke:none;font-size:9px}
|
|
163
|
+
[data-arya-settings] .arya-art svg .a-tag{fill:var(--arya-primary-fg);stroke:none;font-size:7px}
|
|
164
|
+
[data-arya-settings] input[type=radio],[data-arya-settings] input[type=checkbox]{accent-color:var(--arya-accent);width:16px;height:16px;flex:none;margin:2px 0 0}
|
|
165
|
+
[data-arya-settings] input[type=checkbox][role=switch]{width:34px;height:20px;border-radius:10px;appearance:none;background:var(--arya-sunken);border:.5px solid var(--arya-line-strong);position:relative;cursor:pointer;transition:background .15s;margin:0;flex:none}
|
|
166
|
+
[data-arya-settings] input[type=checkbox][role=switch]:checked{background:var(--arya-accent);border-color:var(--arya-accent)}
|
|
167
|
+
[data-arya-settings] input[type=checkbox][role=switch]:after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:7px;background:#fff;transition:left .15s;box-shadow:0 1px 2px rgba(0,0,0,.35)}
|
|
168
|
+
[data-arya-settings] input[type=checkbox][role=switch]:checked:after{left:16px}
|
|
169
|
+
[data-arya-settings] input[type=checkbox][role=switch]:disabled{opacity:.5;cursor:default}
|
|
170
|
+
[data-arya-settings] summary{cursor:pointer;color:var(--arya-fg);font-size:13px;font-weight:500;list-style:none;display:flex;align-items:center;gap:7px;padding:11px 0;width:fit-content}
|
|
171
|
+
[data-arya-settings] summary::-webkit-details-marker{display:none}
|
|
172
|
+
[data-arya-settings] summary:before{content:'';width:0;height:0;border-style:solid;border-width:4.5px 0 4.5px 6px;border-color:transparent transparent transparent currentColor;opacity:.55;transition:transform .15s}
|
|
173
|
+
[data-arya-settings] details[open]>summary:before{transform:rotate(90deg)}
|
|
174
|
+
[data-arya-settings] select,[data-arya-settings] input[type=number],[data-arya-settings] input[type=text]{background:var(--arya-control);color:var(--arya-fg);border:.5px solid var(--arya-line-strong);border-radius:var(--dsw-radius-sm,8px);padding:5px 8px;font:inherit;min-width:0;width:100%}
|
|
175
|
+
[data-arya-settings] .arya-number{display:flex;align-items:center;gap:6px}
|
|
176
|
+
[data-arya-settings] .arya-number input{width:96px}
|
|
177
|
+
[data-arya-settings] .arya-segment{display:inline-flex;border:.5px solid var(--arya-line-strong);border-radius:var(--dsw-radius-sm,8px);overflow:hidden}
|
|
178
|
+
[data-arya-settings] .arya-segment label{display:flex;align-items:center;gap:6px;padding:5px 12px;font-size:13px;cursor:pointer;border-left:.5px solid var(--arya-line)}
|
|
179
|
+
[data-arya-settings] .arya-segment label:first-child{border-left:0}
|
|
180
|
+
[data-arya-settings] .arya-segment label:has(input:checked){background:var(--arya-hover)}
|
|
181
|
+
[data-arya-settings] .arya-segment input{position:absolute;opacity:0;pointer-events:none}
|
|
182
|
+
[data-arya-settings] button{font:inherit;color:var(--arya-fg);background:var(--arya-control);border:.5px solid var(--arya-line-strong);border-radius:var(--dsw-radius-sm,8px);padding:5px 12px;cursor:pointer;transition:background .15s}
|
|
183
|
+
[data-arya-settings] button:hover:enabled{background:var(--arya-hover)}
|
|
184
|
+
[data-arya-settings] button:disabled{opacity:.5;cursor:default}
|
|
185
|
+
[data-arya-settings] button.arya-small{font-size:12.5px;padding:3px 9px}
|
|
186
|
+
[data-arya-settings] button.arya-primary{background:var(--arya-primary);color:var(--arya-primary-fg);border-color:transparent}
|
|
187
|
+
[data-arya-settings] .arya-footer{display:flex;align-items:center;gap:10px;border-top:.5px solid var(--arya-line);padding-top:12px}
|
|
188
|
+
[data-arya-settings] .arya-status{flex:1;font-size:13px;color:var(--arya-muted)}
|
|
189
|
+
[data-arya-settings] .arya-status[data-tone=success]{color:var(--arya-accent)}
|
|
190
|
+
[data-arya-settings] .arya-status[data-tone=error]{color:#e05656}
|
|
191
|
+
[data-arya-settings] .arya-actions{display:flex;gap:8px}
|
|
192
|
+
[data-arya-settings] .arya-info{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:8px;color:var(--arya-subtle);cursor:help;vertical-align:baseline;margin-left:3px}
|
|
193
|
+
[data-arya-settings] .arya-info:hover,[data-arya-settings] .arya-info:focus-visible{color:var(--arya-fg);background:var(--arya-hover);outline:none}
|
|
194
|
+
[data-arya-settings] .arya-info svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.5}
|
|
195
|
+
[data-arya-settings] .arya-tip{position:fixed;z-index:1000;max-width:280px;padding:8px 11px;border-radius:var(--dsw-radius-sm,8px);background:var(--arya-tip-bg);color:var(--arya-tip-fg);font-size:12.5px;line-height:1.5;box-shadow:0 4px 16px rgba(0,0,0,.28);visibility:hidden}
|
|
196
|
+
[data-arya-settings] .arya-tip[data-open]{visibility:visible}
|
|
197
|
+
[data-arya-settings] .arya-caption{font-size:12px;color:var(--arya-subtle)}
|
|
198
|
+
[data-arya-settings] .arya-shortcut-value{display:flex;flex-wrap:wrap;align-items:center;gap:3px;min-height:24px;font-size:12.5px;color:var(--arya-fg)}
|
|
199
|
+
[data-arya-settings] .arya-shortcut-value kbd{font:inherit;font-size:12px;padding:2px 7px;border:.5px solid var(--arya-line-strong);border-bottom-width:1.5px;border-radius:5px;background:var(--arya-sunken)}
|
|
200
|
+
[data-arya-settings] .arya-shortcut-value small{color:var(--arya-subtle);font-size:11.5px;margin-left:4px}
|
|
201
|
+
[data-arya-settings] .arya-shortcut-value[data-recording]{outline:2px solid var(--arya-accent);outline-offset:2px;border-radius:6px}
|
|
202
|
+
[data-arya-settings] .arya-hint{font-size:12px;color:var(--arya-subtle);margin:2px 0 0}
|
|
203
|
+
[data-arya-settings] .arya-nowrap{white-space:nowrap}
|
|
204
|
+
@media (max-width:640px){[data-arya-settings] .arya-grid,[data-arya-settings] .arya-fields{grid-template-columns:minmax(0,1fr)}}
|
|
205
|
+
`;
|
|
206
|
+
function Info({ id, label, text }) {
|
|
207
|
+
const anchor = React.useRef(null), tip = React.useRef(null), timer = React.useRef(0);
|
|
208
|
+
const [open, setOpen] = React.useState(false), [pinned, setPinned] = React.useState(false);
|
|
209
|
+
const show = (next, delay) => { clearTimeout(timer.current); timer.current = setTimeout(() => { setOpen(next); if (!next) setPinned(false); }, delay); };
|
|
210
|
+
const toggle = () => { if (pinned) show(false, 0); else { clearTimeout(timer.current); setOpen(true); setPinned(true); } };
|
|
211
|
+
React.useEffect(() => () => clearTimeout(timer.current), []);
|
|
212
|
+
React.useLayoutEffect(() => {
|
|
213
|
+
const icon = anchor.current, el = tip.current;
|
|
214
|
+
if (!open || !icon || !el) return;
|
|
215
|
+
try { el.showPopover(); } catch {}
|
|
216
|
+
const r = icon.getBoundingClientRect(), width = el.offsetWidth, height = el.offsetHeight, view = document.documentElement, gap = 8, edge = 8;
|
|
217
|
+
let side = 'top', left = r.left + r.width / 2 - width / 2, top = r.top - gap - height;
|
|
218
|
+
if (top < edge) { if (r.left - gap - width >= edge) { side = 'left'; left = r.left - gap - width; top = r.top + r.height / 2 - height / 2; } else { side = 'bottom'; top = r.bottom + gap; } }
|
|
219
|
+
left = Math.max(edge, Math.min(left, view.clientWidth - edge - width)); top = Math.max(edge, Math.min(top, view.clientHeight - edge - height));
|
|
220
|
+
Object.assign(el.style, { left: left + 'px', top: top + 'px' }); el.dataset.side = side;
|
|
221
|
+
const close = () => { clearTimeout(timer.current); setOpen(false); setPinned(false); };
|
|
222
|
+
const key = (event) => { if (event.key === 'Escape') { event.stopPropagation(); close(); } };
|
|
223
|
+
const press = (event) => { if (!icon.contains(event.target)) close(); };
|
|
224
|
+
window.addEventListener('scroll', close, true); window.addEventListener('resize', close); window.addEventListener('keydown', key, true); window.addEventListener('pointerdown', press, true);
|
|
225
|
+
return () => { window.removeEventListener('scroll', close, true); window.removeEventListener('resize', close); window.removeEventListener('keydown', key, true); window.removeEventListener('pointerdown', press, true); try { el.hidePopover(); } catch {} };
|
|
226
|
+
}, [open]);
|
|
227
|
+
return h('span', { className: 'arya-info', ref: anchor, tabIndex: 0, role: 'button', 'aria-label': label, 'aria-describedby': id, 'data-open': open || undefined,
|
|
228
|
+
onPointerEnter: () => show(true, 120), onPointerLeave: () => { if (!pinned) show(false, 250); }, onFocus: () => show(true, 0), onBlur: () => { if (!pinned) show(false, 0); },
|
|
229
|
+
onClick: (event) => { event.preventDefault(); if (!tip.current?.contains(event.target)) toggle(); }, onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); toggle(); } } },
|
|
230
|
+
h('svg', { viewBox: '0 0 16 16', 'aria-hidden': true, focusable: false }, h('circle', { cx: 8, cy: 8, r: 6.4 }), h('path', { d: 'M8 7.3v3.9M8 4.9v.2' })),
|
|
231
|
+
h('span', { className: 'arya-tip', id, role: 'tooltip', ref: tip, popover: 'manual', 'data-open': open || undefined }, text));
|
|
232
|
+
}
|
|
233
|
+
const drawing = (...children) => h('svg', { viewBox: '0 0 112 70', 'aria-hidden': true, focusable: false }, ...children);
|
|
234
|
+
const box = (className, x, y, width, height, rx = 0) => h('rect', { className, x, y, width, height, rx });
|
|
235
|
+
const arrow = (x, y) => h('path', { className: 'a-ptr', d: `M${x} ${y}v15.5l3.8-3.6 2.7 6.1 2.6-1.1-2.7-6h5.4z` });
|
|
236
|
+
const monitor = () => [box('a-desk', 9, 5, 94, 52, 4), h('path', { className: 'a-bar', d: 'M9.4 50h93.2v3.6a3.4 3.4 0 0 1-3.4 3.4H12.8a3.4 3.4 0 0 1-3.4-3.4z' }), box('a-ink', 54.5, 52, 3, 3, .8), h('path', { className: 'a-ink', d: 'M50 57h12l1.5 6h-15z' }), box('a-ink', 41, 63, 30, 2, 1)];
|
|
237
|
+
const banner = (y) => [box('a-pill', 33, y, 46, 9, 4.5), h('circle', { className: 'a-dot', cx: 38.5, cy: y + 4.5, r: 1.8 }), box('a-text', 42.5, y + 3.55, 23, 1.9, .95), box('a-key', 68, y + 2, 8, 5, 1.2)];
|
|
238
|
+
const arts = {
|
|
239
|
+
indicator: (on, bottom) => drawing(...monitor(), box('a-win', 22, 15, 48, 27, 2.5), h('path', { className: 'a-bar', d: 'M22 17.5a2.5 2.5 0 0 1 2.5-2.5h43a2.5 2.5 0 0 1 2.5 2.5v2.5H22z' }), box('a-ink', 26, 25, 30, 2.2, 1.1), box('a-ink', 26, 30, 22, 2.2, 1.1), box('a-frame', 22, 15, 48, 27, 2.5),
|
|
240
|
+
...(on ? [box('a-glow a-soft', 12, 8, 88, 46, 2.5), box('a-glow', 9.8, 5.8, 92.4, 50.4, 3.6), ...banner(bottom ? 40 : 9.5)] : [])),
|
|
241
|
+
cursor: (on) => drawing(box('a-ink', 14, 13, 42, 3, 1.5), box('a-ink', 14, 20, 30, 3, 1.5), box('a-ink', 14, 27, 36, 3, 1.5), arrow(48, 25),
|
|
242
|
+
...(on ? [box('a-acc', 60, 44, 26, 11, 5.5), h('text', { className: 'a-tag', x: 73, y: 51.9, textAnchor: 'middle' }, 'DSH')] : [])),
|
|
243
|
+
esc: () => drawing(box('a-ink2', 33, 19, 46, 34, 7), box('a-win', 33, 16, 46, 34, 7), box('a-frame', 33, 16, 46, 34, 7), h('text', { className: 'a-cap', x: 56, y: 37.2, textAnchor: 'middle' }, 'Esc')),
|
|
244
|
+
progress: (on) => drawing(...monitor(), box('a-win', 22, 15, 48, 27, 2.5), box('a-ink', 26, 25, 30, 2.2, 1.1), box('a-frame', 22, 15, 48, 27, 2.5),
|
|
245
|
+
...(on ? [box('a-acc', 58, 12, 40, 26, 3), box('a-text', 62, 17, 26, 2, 1), box('a-text', 62, 22, 30, 2, 1), box('a-text', 62, 27, 20, 2, 1)] : [])),
|
|
246
|
+
keyboard: (on) => drawing(box('a-win', 14, 10, 84, 34, 4), box('a-ink', 20, 16, 72, 2.4, 1.2), box('a-ink', 20, 22, 60, 2.4, 1.2), box('a-frame', 14, 10, 84, 34, 4),
|
|
247
|
+
box(on ? 'a-acc' : 'a-ink2', 30, 50, 52, 12, 3), ...(on ? [box('a-text', 34, 53, 6, 2, 1), box('a-text', 42, 53, 6, 2, 1), box('a-text', 50, 53, 6, 2, 1), box('a-text', 58, 53, 6, 2, 1), box('a-text', 66, 53, 6, 2, 1)] : []), arrow(84, 46)),
|
|
248
|
+
lock: (on) => drawing(box('a-acc', 40, 26, 32, 24, 4), h('path', { className: on ? 'a-acc' : 'a-ink2', d: 'M47 26v-6a9 9 0 0 1 18 0v6' }), box('a-text', 47, 36, 18, 2.4, 1.2)),
|
|
249
|
+
shield: (on) => drawing(h('path', { className: on ? 'a-acc' : 'a-ink2', d: 'M56 8l30 10v20c0 18-12 26-30 32-18-6-30-14-30-32V18z' }), h('path', { className: 'a-text', d: 'M44 38l9 9 17-18' })),
|
|
250
|
+
};
|
|
251
|
+
function Card({ scope, t, savePatch, as = 'li', idPrefix = 'arya', view }) {
|
|
252
|
+
if (view === 'summary') return h('span', { 'data-arya-summary': true }, t('pluginDescription'), ' ', h('span', { style: { color: 'var(--dsw-alias-label-link,#71a7ff)' } }, t('openSettings')));
|
|
253
|
+
return h(SettingsGate, { scope, t, savePatch, as, idPrefix });
|
|
254
|
+
}
|
|
255
|
+
function SettingsGate(props) {
|
|
256
|
+
const state = React.useSyncExternalStore((fn) => props.scope.subscribe(fn), () => props.scope.getSnapshot());
|
|
257
|
+
if ((state.status !== undefined && state.status !== 'ready') || !state.value) return h(props.as, { 'data-arya-unavailable': true }, h('p', { role: 'status' }, props.t(state.status === 'loading' ? 'loadingSettings' : 'notLoaded')), h('button', { type: 'button', onClick: () => window.location.reload() }, props.t('reloadSettings')));
|
|
258
|
+
return h(SettingsCard, props);
|
|
259
|
+
}
|
|
260
|
+
function SettingsCard({ scope, t, savePatch, as = 'li', idPrefix = 'arya' }) {
|
|
261
|
+
const state = React.useSyncExternalStore((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
262
|
+
const available = state.status === undefined ? state.value !== undefined : state.status === 'ready';
|
|
263
|
+
const writable = available && state.writable;
|
|
264
|
+
const saved = { ...defaults, ...state.value };
|
|
265
|
+
const [draft, setDraft] = React.useState(saved), [dirty, setDirty] = React.useState(false);
|
|
266
|
+
const [saving, setSaving] = React.useState(false), [message, setMessage] = React.useState(''), [tone, setTone] = React.useState('');
|
|
267
|
+
const [recording, setRecording] = React.useState(false), [recordMessage, setRecordMessage] = React.useState('');
|
|
268
|
+
const savedKey = JSON.stringify(Object.fromEntries(Object.keys(defaults).map((key) => [key, saved[key]])));
|
|
269
|
+
React.useEffect(() => { if (!dirty) setDraft(saved); }, [savedKey, dirty]);
|
|
270
|
+
const change = (field, value) => { const next = { ...draft, [field]: value }; setDraft(next); setDirty(Object.keys(defaults).some((key) => !equal(next[key], saved[key]))); setMessage(''); };
|
|
271
|
+
React.useEffect(() => {
|
|
272
|
+
if (!recording) return;
|
|
273
|
+
if (!writable || saving) { setRecording(false); return; }
|
|
274
|
+
const cancel = () => { setRecording(false); setRecordMessage('recordCancelled'); };
|
|
275
|
+
const keydown = (event) => {
|
|
276
|
+
if (event.isComposing) return;
|
|
277
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
278
|
+
if (event.key === 'Escape' || event.code === 'Escape') { cancel(); return; }
|
|
279
|
+
if (event.repeat || ['Control', 'Alt', 'Shift', 'Meta', 'OS'].includes(event.key)) return;
|
|
280
|
+
const shortcut = shortcutFromEvent(event);
|
|
281
|
+
if (!shortcut) { setRecordMessage('unsupportedKey'); return; }
|
|
282
|
+
change('stopShortcut', shortcut); setRecordMessage(''); setRecording(false);
|
|
283
|
+
};
|
|
284
|
+
const timer = setTimeout(() => { setRecording(false); setRecordMessage('recordTimedOut'); }, 15000);
|
|
285
|
+
window.addEventListener('keydown', keydown, true); window.addEventListener('blur', cancel);
|
|
286
|
+
return () => { clearTimeout(timer); window.removeEventListener('keydown', keydown, true); window.removeEventListener('blur', cancel); };
|
|
287
|
+
}, [recording, writable, saving, savedKey]);
|
|
288
|
+
const run = async (patch, success) => {
|
|
289
|
+
setSaving(true); setMessage('');
|
|
290
|
+
try { await savePatch(patch); setMessage(t(success)); setTone('success'); return true; }
|
|
291
|
+
catch (error) { setMessage(t('failed') + ': ' + String(error.message || error)); setTone('error'); return false; }
|
|
292
|
+
finally { setSaving(false); }
|
|
293
|
+
};
|
|
294
|
+
const overlayKeys = Object.keys(defaults).filter((key) => key.startsWith('progress'));
|
|
295
|
+
const applyOverlayPatch = async (patch) => {
|
|
296
|
+
const next = { ...draft, ...patch }; setDraft(next);
|
|
297
|
+
if (await run(patch, 'saved')) {
|
|
298
|
+
const base = { ...saved, ...patch };
|
|
299
|
+
setDirty(Object.keys(defaults).some((key) => !equal(next[key], base[key])));
|
|
300
|
+
} else setDirty(true);
|
|
301
|
+
};
|
|
302
|
+
const applyOverlaySwitch = (field, value) => applyOverlayPatch({ [field]: value });
|
|
303
|
+
const openProgress = () => applyOverlayPatch({ ...Object.fromEntries(overlayKeys.map((key) => [key, draft[key]])), progressOverlayEnabled: true, progressPreviewToken: Date.now() });
|
|
304
|
+
const save = async () => {
|
|
305
|
+
if (!draft.indicatorText.trim() || draft.indicatorText.length > 120 || /[\r\n\x00-\x1f]/.test(draft.indicatorText) || !draft.pointerLabel.trim() || draft.pointerLabel.trim().length > 16 || /[\r\n\x00-\x1f]/.test(draft.pointerLabel) || !Number.isInteger(draft.indicatorIdleMs) || draft.indicatorIdleMs < 200 || draft.indicatorIdleMs > 3000) { setMessage(t('invalid')); setTone('error'); return; }
|
|
306
|
+
if ((draft.browserSession && !/^[\p{L}\p{N}_-]+$/u.test(draft.browserSession)) || (draft.browserCdp && !/^(?:\d{1,5}|https?:\/\/\S+|wss?:\/\/\S+)$/.test(draft.browserCdp)) || (draft.browserStateFile && !/^(?:[A-Za-z]:[\\/]|\/)/.test(draft.browserStateFile))) { setMessage(t('invalidBrowser')); setTone('error'); return; }
|
|
307
|
+
const patch = Object.fromEntries(Object.keys(defaults).filter((key) => !equal(draft[key], saved[key])).map((key) => [key, draft[key]]));
|
|
308
|
+
if (await run(patch, 'saved')) setDirty(false);
|
|
309
|
+
};
|
|
310
|
+
const disabled = saving || !writable || recording, indicatorDisabled = disabled || !draft.indicatorEnabled;
|
|
311
|
+
const radio = (field, value, label) => h('label', { key: value }, h('input', { type: 'radio', name: idPrefix + '-' + field, id: idPrefix + '-' + field + '-' + value, value, checked: draft[field] === value, disabled: indicatorDisabled, onChange: () => change(field, value) }), label);
|
|
312
|
+
const button = (label, props) => h('button', { type: 'button', ...props }, t(label));
|
|
313
|
+
const info = (key) => h(Info, { id: idPrefix + '-tip-' + key, label: t('moreInfo'), text: t(key) });
|
|
314
|
+
const head = (id, title, hint, ...end) => h('div', { className: 'arya-section-head' }, h('h3', { id }, t(title)), hint && info(hint), end.length > 0 && h('div', { className: 'arya-end' }, ...end));
|
|
315
|
+
const item = (art, ...main) => h('div', { className: 'arya-item' }, art, h('div', { className: 'arya-item-main' }, ...main));
|
|
316
|
+
const title = (text, hint, props) => { const cut = /[\u2e80-\u9fff\uff00-\uffef]$/.test(text) ? text.length - 2 : text.lastIndexOf(' ') + 1; return h(props?.htmlFor ? 'label' : 'span', { className: 'arya-item-title', ...props }, text.slice(0, cut), h('span', { className: 'arya-nowrap' }, text.slice(cut), hint && info(hint))); };
|
|
317
|
+
const toggle = (field, label, locked) => h('input', { id: idPrefix + '-' + field, type: 'checkbox', role: 'switch', 'aria-label': t(label), checked: draft[field], disabled: disabled || locked, onChange: (e) => ['progressOverlayEnabled', 'progressOverlayLocked'].includes(field) ? applyOverlaySwitch(field, e.target.checked) : change(field, e.target.checked) });
|
|
318
|
+
const switchItem = (field, label, hint, art, locked) => item(h('label', { className: 'arya-art', htmlFor: idPrefix + '-' + field, 'aria-hidden': true }, art), title(t(label), hint, { htmlFor: idPrefix + '-' + field }), toggle(field, label, locked));
|
|
319
|
+
const field = (label, control) => h('div', { className: 'arya-field' }, label, control);
|
|
320
|
+
const keycap = (key, label) => h('kbd', { className: 'arya-key', key }, label);
|
|
321
|
+
const shortcutKeys = recording ? [h('span', { key: 'listen' }, t('listening')), h('small', { key: 'cancel' }, t('listeningCancel'))]
|
|
322
|
+
: draft.stopShortcut ? draft.stopShortcut.split('+').flatMap((key, i) => i ? [h('span', { className: 'arya-plus', key: '+' + i }, ' + '), keycap(i, key)] : [keycap(i, key)])
|
|
323
|
+
: [keycap('esc', 'Esc'), h('small', { key: 'default' }, t('noShortcut').slice(3))];
|
|
324
|
+
return h(as, { 'data-arya-settings': 'true' }, h('style', null, css),
|
|
325
|
+
h('header', { className: 'arya-header' }, h('h2', null, t('title')), h('p', null, t('intro'))),
|
|
326
|
+
h('section', { className: 'arya-section', 'aria-labelledby': idPrefix + '-indicator-title' },
|
|
327
|
+
head(idPrefix + '-indicator-title', 'indicatorTitle', 'indicatorHint', button('preview', { className: 'arya-small', disabled: disabled || dirty || (!draft.indicatorEnabled && !draft.cursorIndicatorEnabled), onClick: () => run({ previewToken: Date.now() }, 'previewed'), title: dirty ? t('previewHint') : undefined })),
|
|
328
|
+
h('div', { className: 'arya-grid' },
|
|
329
|
+
switchItem('indicatorEnabled', 'indicator', 'indicatorHint', arts.indicator(draft.indicatorEnabled, draft.indicatorPosition === 'bottom')),
|
|
330
|
+
switchItem('cursorIndicatorEnabled', 'cursorIndicator', 'cursorIndicatorHint', arts.cursor(draft.cursorIndicatorEnabled))),
|
|
331
|
+
h('details', { className: 'arya-detail' }, h('summary', null, t('appearance')),
|
|
332
|
+
h('div', { className: 'arya-fields' },
|
|
333
|
+
field(title(t('position'), null, { id: idPrefix + '-position-label' }), h('div', { className: 'arya-segment', role: 'radiogroup', 'aria-labelledby': idPrefix + '-position-label' }, radio('indicatorPosition', 'top', t('top')), radio('indicatorPosition', 'bottom', t('bottom')))),
|
|
334
|
+
field(title(t('delay'), 'delayHint', { htmlFor: idPrefix + '-delay' }), h('div', { className: 'arya-number' }, h('input', { id: idPrefix + '-delay', type: 'number', min: 200, max: 3000, step: 100, value: draft.indicatorIdleMs, disabled: indicatorDisabled, onChange: (e) => change('indicatorIdleMs', Number(e.target.value)) }), h('span', null, t('ms')))),
|
|
335
|
+
field(title(t('text'), null, { htmlFor: idPrefix + '-text' }), h('input', { id: idPrefix + '-text', type: 'text', maxLength: 120, value: draft.indicatorText, disabled: indicatorDisabled, onChange: (e) => change('indicatorText', e.target.value) })),
|
|
336
|
+
field(title(t('cursorLabel'), 'cursorLabelHint', { htmlFor: idPrefix + '-pointerLabel' }), h('input', { id: idPrefix + '-pointerLabel', type: 'text', maxLength: 16, value: draft.pointerLabel, disabled: disabled || !draft.cursorIndicatorEnabled, onChange: (e) => change('pointerLabel', e.target.value) })),
|
|
337
|
+
field(title(t('remoteVisible'), 'remoteVisibleHint', { htmlFor: idPrefix + '-remoteIndicatorVisible' }), toggle('remoteIndicatorVisible', 'remoteVisible'))))),
|
|
338
|
+
h('section', { className: 'arya-section', 'data-arya-progress-settings': true, 'aria-labelledby': idPrefix + '-progress-title' },
|
|
339
|
+
head(idPrefix + '-progress-title', 'progressTitle', null, button('openProgress', { 'data-arya-open-progress': true, className: 'arya-small', disabled, onClick: openProgress })),
|
|
340
|
+
h('div', { className: 'arya-grid' }, switchItem('progressOverlayEnabled', 'progressOverlay', 'progressOverlayHint', arts.progress(draft.progressOverlayEnabled)), switchItem('progressOverlayLocked', 'progressLocked', 'progressLockedHint', arts.lock(draft.progressOverlayLocked))),
|
|
341
|
+
h('details', { className: 'arya-detail', 'data-arya-progress-details': true }, h('summary', null, t('appearance')),
|
|
342
|
+
h('p', { className: 'arya-hint' }, t('progressSync')),
|
|
343
|
+
h('div', { className: 'arya-fields' },
|
|
344
|
+
...[['progressOverlayOpacity', 'progressOpacity', 20, 100], ['progressOverlayWidth', 'progressWidth', 240, 1600], ['progressOverlayHeight', 'progressHeight', 160, 1600]].map(([key, label, min, max]) => field(title(t(label), null, { htmlFor: idPrefix + '-' + key }), h('input', { id: idPrefix + '-' + key, type: 'number', min, max, value: draft[key], disabled, onChange: (e) => change(key, Number(e.target.value)) }))),
|
|
345
|
+
field(title(t('progressPosition'), null, { htmlFor: idPrefix + '-progressOverlayPosition' }), h('select', { id: idPrefix + '-progressOverlayPosition', value: draft.progressOverlayPosition, disabled, onChange: (e) => change('progressOverlayPosition', e.target.value) }, ...['top-right', 'top-left', 'bottom-right', 'bottom-left', 'custom'].map((key, index) => h('option', { key, value: key }, t(['progress_topRight', 'progress_topLeft', 'progress_bottomRight', 'progress_bottomLeft', 'progress_custom'][index]))))),
|
|
346
|
+
...[['progressOverlayX', 'progressX'], ['progressOverlayY', 'progressY']].map(([key, label]) => field(title(t(label), null, { htmlFor: idPrefix + '-' + key }), h('input', { id: idPrefix + '-' + key, type: 'number', min: -32768, max: 32767, value: draft[key], disabled: disabled || draft.progressOverlayPosition !== 'custom', onChange: (e) => change(key, Number(e.target.value)) }))),
|
|
347
|
+
field(title(t('progressShortcut'), 'progressShortcutHint', { htmlFor: idPrefix + '-progressLockShortcut' }), h('input', { id: idPrefix + '-progressLockShortcut', type: 'text', value: draft.progressLockShortcut, placeholder: 'Ctrl+Alt+P', maxLength: 50, disabled, onChange: (e) => change('progressLockShortcut', e.target.value) }))))),
|
|
348
|
+
h('section', { className: 'arya-section', 'aria-labelledby': idPrefix + '-behavior-title' },
|
|
349
|
+
head(idPrefix + '-behavior-title', 'behaviorTitle'),
|
|
350
|
+
h('div', { className: 'arya-grid' },
|
|
351
|
+
switchItem('headedDefault', 'headedDefault', 'headedDefaultHint', arts.keyboard(draft.headedDefault)),
|
|
352
|
+
switchItem('overlayDefault', 'overlayDefault', 'overlayDefaultHint', arts.cursor(draft.overlayDefault)),
|
|
353
|
+
switchItem('driverLockEnabled', 'driverLock', 'driverLockHint', arts.shield(draft.driverLockEnabled)),
|
|
354
|
+
field(title(t('toolPreset'), 'toolPresetHint', { htmlFor: idPrefix + '-toolPreset' }), h('select', { id: idPrefix + '-toolPreset', value: draft.toolPreset, disabled, onChange: (e) => change('toolPreset', e.target.value) }, h('option', { value: 'lite' }, t('toolPresetLite')), h('option', { value: 'lazy' }, t('toolPresetLazy')))))),
|
|
355
|
+
h('section', { className: 'arya-section', 'aria-labelledby': idPrefix + '-browser-title' },
|
|
356
|
+
head(idPrefix + '-browser-title', 'browserTitle'),
|
|
357
|
+
h('div', { className: 'arya-grid' },
|
|
358
|
+
field(title(t('browserTools'), 'browserToolsHint', { htmlFor: idPrefix + '-browserTools' }), h('select', { id: idPrefix + '-browserTools', value: draft.browserTools, disabled, onChange: (e) => change('browserTools', e.target.value) }, ...['core', 'network', 'state', 'debug', 'tabs', 'react', 'mobile', 'all'].map((key) => h('option', { key, value: key }, key)))),
|
|
359
|
+
switchItem('browserHeaded', 'browserHeaded', 'browserHeadedHint', arts.monitor && arts.monitor(draft.browserHeaded) || arts.cursor(draft.browserHeaded))),
|
|
360
|
+
h('div', { className: 'arya-fields' },
|
|
361
|
+
field(title(t('browserProfile'), 'browserProfileHint', { htmlFor: idPrefix + '-browserProfile' }), h('input', { id: idPrefix + '-browserProfile', type: 'text', maxLength: 200, placeholder: 'Default', value: draft.browserProfile, disabled, onChange: (e) => change('browserProfile', e.target.value) })),
|
|
362
|
+
field(title(t('browserSession'), 'browserSessionHint', { htmlFor: idPrefix + '-browserSession' }), h('input', { id: idPrefix + '-browserSession', type: 'text', maxLength: 64, placeholder: 'arya', value: draft.browserSession, disabled, onChange: (e) => change('browserSession', e.target.value) })),
|
|
363
|
+
field(title(t('browserCdp'), 'browserCdpHint', { htmlFor: idPrefix + '-browserCdp' }), h('input', { id: idPrefix + '-browserCdp', type: 'text', maxLength: 200, placeholder: 'http://127.0.0.1:9222', value: draft.browserCdp, disabled, onChange: (e) => change('browserCdp', e.target.value) })),
|
|
364
|
+
field(title(t('browserStateFile'), 'browserStateFileHint', { htmlFor: idPrefix + '-browserStateFile' }), h('input', { id: idPrefix + '-browserStateFile', type: 'text', maxLength: 400, value: draft.browserStateFile, disabled, onChange: (e) => change('browserStateFile', e.target.value) }))),
|
|
365
|
+
h('p', { className: 'arya-hint' }, t('browserRestart'))),
|
|
366
|
+
h('section', { className: 'arya-section', 'aria-labelledby': idPrefix + '-stop-title' },
|
|
367
|
+
head(idPrefix + '-stop-title', 'stop', null, button('clearShortcut', { className: 'arya-small', disabled: disabled || !draft.stopShortcut, onClick: () => { change('stopShortcut', ''); setRecordMessage(''); } })),
|
|
368
|
+
h('div', { className: 'arya-grid' },
|
|
369
|
+
item(h('div', { className: 'arya-art', 'aria-hidden': true }, arts.esc()), title(t('escTitle'), 'stopHint'), h('span', { className: 'arya-caption' }, t('escAlways'))),
|
|
370
|
+
h('div', { className: 'arya-item', 'data-arya-shortcut': 'true' },
|
|
371
|
+
h('output', { className: 'arya-art arya-shortcut-value', 'data-recording': recording, 'aria-live': 'polite', 'aria-label': t('extraStop') }, ...shortcutKeys),
|
|
372
|
+
h('div', { className: 'arya-item-main' }, title(t('extraStop'), 'shortcutHint'),
|
|
373
|
+
button(recording ? 'cancelRecord' : draft.stopShortcut ? 'rerecord' : 'record', { className: 'arya-small', disabled: saving || !writable, onClick: () => { setRecordMessage(''); setRecording(!recording); } })))),
|
|
374
|
+
recordMessage && h('p', { role: 'status', className: 'arya-hint' }, t(recordMessage))),
|
|
375
|
+
!writable && h('p', { role: 'status', 'data-tone': 'error' }, t(available ? 'unavailable' : 'notLoaded')),
|
|
376
|
+
h('footer', { className: 'arya-footer' }, h('span', { className: 'arya-status', role: 'status', 'aria-live': 'polite', 'data-tone': message ? tone : undefined }, message || t(dirty ? 'dirty' : 'clean')),
|
|
377
|
+
h('div', { className: 'arya-actions' }, button('discard', { disabled: !dirty || saving || recording, onClick: () => { setDraft(saved); setDirty(false); setMessage(''); setRecordMessage(''); } }), button('save', { className: 'arya-primary', disabled: !dirty || disabled, onClick: save }))));
|
|
378
|
+
}
|
|
379
|
+
const actionKeys = { snapshot: 'see', screenshot: 'see', list_windows: 'windows', find: 'findControl', get: 'read', is: 'read', click: 'clickAction', set_value: 'valueAction', type: 'typeAction', press: 'keyAction', scroll: 'scrollAction', wait: 'waitAction', ocr: 'ocrAction', launch: 'launchAction', batch: 'seriesTitle', tools: 'toolsAction' };
|
|
380
|
+
const browserActionKeys = { open: 'openPage', read: 'readPage', snapshot: 'lookPage', click: 'clickPage', dblclick: 'clickPage', fill: 'typePage', type: 'typePage', press: 'keyPage', screenshot: 'shotPage', eval: 'evalPage', tab_new: 'tabsPage', tab_list: 'tabsPage', tab_switch: 'tabsPage', tab_close: 'tabsPage', close: 'closePage', wait_ms: 'waitPage', wait_for_selector: 'waitPage', wait_for_text: 'waitPage', wait_for_load: 'waitPage', get_text: 'readPage', get_url: 'infoPage', get_title: 'infoPage', scroll: 'scrollPage', back: 'navPage', forward: 'navPage', reload: 'navPage', select: 'selectPage', check: 'togglePage', uncheck: 'togglePage' };
|
|
381
|
+
const isBrowserTool = (name) => /^mcp__arya_browser__/.test(name || '');
|
|
382
|
+
const shortTool = (name) => String(name || '').replace(/^mcp__arya_desktop__/, '').replace(/^mcp__arya_browser__agent_browser_/, '');
|
|
383
|
+
const desktopTool = (name) => /^mcp__arya_desktop__(?!tools$)[a-z_]+$/.test(name || '') || (/^mcp__arya_browser__agent_browser_[a-z_]+$/.test(name || '') && name !== 'mcp__arya_browser__agent_browser_close');
|
|
384
|
+
const objectArgs = (value) => { if (typeof value === 'string') try { return JSON.parse(value); } catch { return {}; } return value && typeof value === 'object' ? value : {}; };
|
|
385
|
+
function receipt(message) {
|
|
386
|
+
for (const part of message?.content || []) if (part.type === 'text' && typeof part.text === 'string') try { return JSON.parse(part.text); } catch {}
|
|
387
|
+
return {};
|
|
388
|
+
}
|
|
389
|
+
const resultStatus = (message) => message?.isError ? ((message.content || []).some((part) => part.type === 'text' && /COMPUTER_USE_STOPPED_BY_USER|COMPUTER_USE_STOPPED_IN_DSH/.test(part.text)) ? 'stopped' : 'failed') : 'completed';
|
|
390
|
+
const mouseIcon = () => h('svg', { viewBox: '0 0 24 24', width: 17, height: 17, fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, 'aria-hidden': true, style: { verticalAlign: 'middle', flexShrink: 0 } }, h('rect', { x: 6, y: 2, width: 12, height: 20, rx: 6 }), h('path', { d: 'M12 2v7M6 10h12' }));
|
|
391
|
+
const activityCss = `.arya-activity{color:var(--dsw-alias-label-secondary,inherit);font-size:13px;line-height:1.6;margin:7px 0}.arya-activity>summary{cursor:pointer;display:flex;align-items:center;gap:8px;list-style:none}.arya-activity>summary::-webkit-details-marker{display:none}.arya-activity .arya-activity-label{color:var(--dsw-alias-label-primary,inherit)}.arya-activity .arya-activity-state{font-size:12px;opacity:.72;margin-left:4px}.arya-activity[open]{padding-bottom:8px}.arya-activity ol{margin:10px 0 4px 22px;padding-left:16px}.arya-activity li{padding:3px 0}.arya-activity .arya-activity-note{font-size:12px;opacity:.75}.arya-activity button{color:inherit;background:transparent;border:1px solid var(--dsw-alias-border-l2,#8885);border-radius:6px;padding:3px 8px;cursor:pointer}`;
|
|
392
|
+
function actionText(call, t) {
|
|
393
|
+
const args = objectArgs(call.args), action = shortTool(call.name);
|
|
394
|
+
let label = t((isBrowserTool(call.name) ? browserActionKeys[action] : actionKeys[action]) || 'seriesTitle');
|
|
395
|
+
if (action === 'find') { const query = args.name || args.text || args.value; if (query) label += '「' + String(query).slice(0, 60) + '」'; }
|
|
396
|
+
if (action === 'launch' && args.app) label += '「' + String(args.app).slice(0, 60) + '」';
|
|
397
|
+
return label;
|
|
398
|
+
}
|
|
399
|
+
function seriesText(calls, t) {
|
|
400
|
+
const actions = [...new Set(calls.filter((c) => c.status === 'completed').map((c) => actionText(c, t)))];
|
|
401
|
+
return actions.slice(-3).join(t('and')) || t('seriesTitle');
|
|
402
|
+
}
|
|
403
|
+
function activityBody(call, renderFactorySlot, loadImage, t) {
|
|
404
|
+
const data = receipt(call.message), images = (call.message?.content || []).filter((x) => x.type === 'image' && x.attachment);
|
|
405
|
+
const disposition = data?.data?.disposition || {};
|
|
406
|
+
return h(React.Fragment, null,
|
|
407
|
+
disposition.delivery === 'delivered_unverified' && h('div', { className: 'arya-activity-note' }, t('sentOnly')),
|
|
408
|
+
['failed', 'stopped'].includes(call.status) && h('div', { role: 'status' }, t(call.status === 'stopped' ? 'stoppedAction' : 'stepError')),
|
|
409
|
+
images.length > 0 && renderFactorySlot && loadImage && renderFactorySlot('conversation.images.content', { images, loadImage, align: 'start' }));
|
|
410
|
+
}
|
|
411
|
+
function MouseToolRow({ toolName, phase, block, renderFactorySlot, loadImage, inspect, t }) {
|
|
412
|
+
const message = phase === 'result' ? block.message : undefined;
|
|
413
|
+
const status = phase === 'result' ? resultStatus(message) : 'working';
|
|
414
|
+
const call = { name: toolName, args: block.call?.arguments ?? block.arguments, message, status };
|
|
415
|
+
const stateKey = status === 'failed' ? 'failedAction' : status === 'stopped' ? 'stoppedAction' : status;
|
|
416
|
+
return h('details', { className: 'arya-activity', 'data-arya-action': true },
|
|
417
|
+
h('style', null, activityCss),
|
|
418
|
+
h('summary', null,
|
|
419
|
+
mouseIcon(),
|
|
420
|
+
h('span', { className: 'arya-activity-label' }, status === 'working' ? t('working') : actionText(call, t)),
|
|
421
|
+
h('span', { className: 'arya-activity-state' }, t(stateKey)),
|
|
422
|
+
activityBody(call, renderFactorySlot, loadImage, t),
|
|
423
|
+
inspect && h('button', { type: 'button', onClick: inspect }, t('showDetails'))));
|
|
424
|
+
}
|
|
425
|
+
function reduceSeries(state, event) {
|
|
426
|
+
const data = event.data || {};
|
|
427
|
+
if (event.type === 'tool/call' && desktopTool(data.name)) return { ...state, calls: [...state.calls, { id: data.callId, name: data.name, args: data.arguments, seq: event.seq, status: 'working' }] };
|
|
428
|
+
if (event.type === 'tool/result') return { ...state, calls: state.calls.map((call) => call.id === data.message?.callId ? { ...call, message: data.message, status: resultStatus(data.message) } : call) };
|
|
429
|
+
if (event.type === 'turn/end') return { ...state, ended: true, reason: data.reason?.kind || 'completed' };
|
|
430
|
+
return state;
|
|
431
|
+
}
|
|
432
|
+
const seriesDefinition = {
|
|
433
|
+
kind: 'arya-series', target: 'chat',
|
|
434
|
+
match: (event) => ['turn/start', 'turn/end', 'tool/call', 'tool/result'].includes(event.type) && Number.isInteger(event.data?.turn) ? { id: String(event.data.turn), role: 'start' } : null,
|
|
435
|
+
start: (_context, match) => reduceSeries({ calls: [], seq: match.event.seq, ended: false }, match.event),
|
|
436
|
+
update: (context, match) => reduceSeries(context.state, match.event),
|
|
437
|
+
buildViewNode: (context) => !context.state?.ended || !context.state.calls.length ? null : { key: context.key, kind: 'arya-series', id: context.id, target: 'chat', anchorSeq: context.state.calls[0].seq, location: context.start?.location ?? { kind: 'unresolved' }, visibility: 'visible', data: context.state },
|
|
438
|
+
};
|
|
439
|
+
function SeriesRow({ node, t, renderFactorySlot, loadImage }) {
|
|
440
|
+
const state = node.data, status = state.reason === 'aborted' || state.calls.some((c) => c.status === 'stopped') ? 'stoppedAction' : state.reason === 'error' || state.calls.some((c) => c.status !== 'completed') ? 'failedAction' : 'completed';
|
|
441
|
+
return h('details', { className: 'arya-activity', 'data-arya-series': true }, h('style', null, activityCss), h('summary', null, mouseIcon(), h('span', { className: 'arya-activity-label' }, seriesText(state.calls, t)), h('span', { className: 'arya-activity-state' }, t(status) + ' · ' + state.calls.length + ' ' + t('seriesCount'))), h('ol', null, ...state.calls.map((call) => h('li', { key: call.id }, actionText(call, t), activityBody(call, renderFactorySlot, loadImage, t)))));
|
|
442
|
+
}
|
|
443
|
+
function registerActivity(ctx) {
|
|
444
|
+
const names = [...Object.keys(actionKeys), ...Object.keys(browserActionKeys)];
|
|
445
|
+
ctx.slots.inject('tool.call.toolview', function* () { for (const name of names) for (const prefix of ['mcp__arya_desktop__', 'mcp__arya_browser__agent_browser_', '']) yield ctx.slots.register({ name: 'tool.call.toolview', key: prefix + name, locale: NS }, MouseToolRow); });
|
|
446
|
+
ctx.inject(['uiConversation'], (owner) => {
|
|
447
|
+
owner.uiConversation.events.register(seriesDefinition);
|
|
448
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'arya-series', locale: NS }, SeriesRow));
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function apply(ctx) {
|
|
452
|
+
ctx.effect(() => ctx.locale.register(NS, dictionaries));
|
|
453
|
+
registerActivity(ctx);
|
|
454
|
+
const t = ctx.locale.bind ? ctx.locale.bind(NS) : (key) => key;
|
|
455
|
+
let mounted = false;
|
|
456
|
+
let modernRemote;
|
|
457
|
+
ctx.inject(['remote', 'remote.settings'], (owner) => { modernRemote = owner.remote.settings; return () => { modernRemote = undefined; }; });
|
|
458
|
+
const mount = (scope, owner, namespace) => {
|
|
459
|
+
if (mounted) return; mounted = true;
|
|
460
|
+
const connection = typeof scope.mutate === 'function' ? undefined : owner.connection;
|
|
461
|
+
const savePatch = async (patch) => {
|
|
462
|
+
const current = scope.getSnapshot();
|
|
463
|
+
if (current.status !== undefined && current.status !== 'ready') throw Error(t('notLoaded'));
|
|
464
|
+
if (modernRemote && owner.configForms) {
|
|
465
|
+
const response = await modernRemote.mutate(namespace, Object.entries(patch).map(([field, value]) => ({ op: 'set', path: [field], value })), current.revision);
|
|
466
|
+
if (response.ok) owner.configForms.describe().acceptView(response.value);
|
|
467
|
+
else await owner.configForms.describe().load();
|
|
468
|
+
if (!response.ok) throw Error(response.error?.message || response.error?.code || t('persistFailed'));
|
|
469
|
+
} else if (typeof scope.mutate === 'function') {
|
|
470
|
+
const accepted = await scope.mutate(Object.entries(patch).map(([field, value]) => ({ op: 'set', path: [field], value })), scope.getSnapshot().revision);
|
|
471
|
+
if (!accepted) throw Error(t('persistFailed'));
|
|
472
|
+
} else if (connection?.api?.settings?.mutate) {
|
|
473
|
+
const snapshot = scope.getSnapshot();
|
|
474
|
+
const response = await connection.api.settings.mutate({ ns: namespace, ops: Object.entries(patch).map(([field, value]) => ({ op: 'set', path: [field], value })), ...(snapshot.revision === undefined ? {} : { expectedRevision: snapshot.revision }) });
|
|
475
|
+
if (!response.result?.ok) throw Error(response.result?.message || response.result?.error || t('persistFailed'));
|
|
476
|
+
await scope.load();
|
|
477
|
+
} else {
|
|
478
|
+
for (const [field, value] of Object.entries(patch)) await scope.set(field, value);
|
|
479
|
+
}
|
|
480
|
+
const value = scope.getSnapshot().value ?? {};
|
|
481
|
+
if (Object.entries(patch).some(([key, next]) => !equal(value[key], next))) throw Error(t('persistFailed'));
|
|
482
|
+
};
|
|
483
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'computer-use', order: 16, label: () => t('nav'), locale: NS, inject: () => ({ scope, savePatch, as: 'section', idPrefix: 'arya-page' }) }, Card));
|
|
484
|
+
ctx.slots.inject('plugins.item', () => ctx.slots.register({ name: 'plugins.item', id: 'arya-computer-use', order: 30, label: () => t('nav'), locale: NS, inject: () => ({ scope, savePatch, as: 'section', idPrefix: 'arya-plugin' }) }, Card));
|
|
485
|
+
ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', id: 'arya-computer-use', order: 30, locale: NS, inject: () => ({ scope, savePatch }) }, Card));
|
|
486
|
+
};
|
|
487
|
+
ctx.inject(['configForms'], (owner) => mount(owner.configForms.get('arya-computer-use'), owner, 'arya-computer-use'));
|
|
488
|
+
ctx.inject(['settingsScope', 'connection', 'remote'], (owner) => mount(owner.settingsScope.bind({ namespace: 'arya-computer-use' }), owner, 'arya-computer-use'));
|
|
489
|
+
}
|
|
490
|
+
return { apply, inject: ['slots', 'locale'] };
|
|
491
|
+
},
|
|
492
|
+
});
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Arya Computer Use bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# One row: the Host plugin. It registers the skill, the settings page, the chat
|
|
4
|
+
# rows, the driver lock, the takeover helper and the status endpoint — and it
|
|
5
|
+
# mounts both MCP lanes in-process through the harness's own mcp-client plugin.
|
|
6
|
+
#
|
|
7
|
+
# Mounting in-process is what makes this bundle installable from a registry:
|
|
8
|
+
# no row names an absolute path, and `npx` is never used as an MCP stdio child
|
|
9
|
+
# (on Windows that is a .cmd shim, which the client cannot spawn). The lanes are
|
|
10
|
+
# arya_desktop node <package>/mcp/adapter-server.mjs
|
|
11
|
+
# arya_browser node <package>/mcp/browser-launcher.mjs
|
|
12
|
+
# with both paths derived from the installed package at runtime.
|
|
13
|
+
- insert:
|
|
14
|
+
- id: arya-computer-use
|
|
15
|
+
name: '@aryacode/dsh-plugin-arya-computer-use'
|
|
16
|
+
config: {}
|