@mirasim/computer-use 0.1.0-rc.6 → 0.1.0-rc.8

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 (45) hide show
  1. package/README.md +3 -1
  2. package/dist/platform/browser/bridge/model-commands.js +1 -1
  3. package/dist/platform/browser/bridge/model-core.d.ts +7 -0
  4. package/dist/platform/browser/bridge/model-core.js +1 -1
  5. package/dist/platform/browser/bridge/server.js +1 -1
  6. package/dist/platform/browser/bridge-tabs.d.ts +3 -0
  7. package/dist/platform/browser/bridge-tabs.js +1 -1
  8. package/dist/platform/browser/driver-connect.d.ts +1 -0
  9. package/dist/platform/browser/driver-connect.js +1 -1
  10. package/dist/platform/browser/driver-verbs.js +2 -2
  11. package/dist/platform/browser/driver.d.ts +2 -0
  12. package/dist/platform/browser/driver.js +3 -2
  13. package/dist/platform/browser/messages.d.ts +6 -0
  14. package/dist/platform/browser/messages.js +2 -2
  15. package/dist/platform/browser/state.js +2 -2
  16. package/dist/platform/browser/tabs.js +2 -2
  17. package/dist/platform/chromium/actions.js +2 -2
  18. package/dist/platform/chromium/run-js.d.ts +5 -1
  19. package/dist/platform/chromium/run-js.js +2 -2
  20. package/dist/platform/electron/driver.js +1 -1
  21. package/dist/platform/electron/verbs.js +1 -1
  22. package/dist/platform/macos/type-verb.js +1 -1
  23. package/dist/platform/tools/api-contract.d.ts +1 -0
  24. package/dist/platform/tools/api.js +1 -1
  25. package/dist/platform/tools/repl-docs.js +1 -1
  26. package/dist/platform/tools/repl.js +12 -12
  27. package/extension/_locales/en/messages.json +179 -0
  28. package/extension/_locales/zh_CN/messages.json +179 -0
  29. package/extension/access.js +245 -0
  30. package/extension/ask.html +26 -0
  31. package/extension/ask.js +20 -0
  32. package/extension/background.js +79 -3
  33. package/extension/group.js +335 -79
  34. package/extension/icons/icon-128.png +0 -0
  35. package/extension/icons/icon-16.png +0 -0
  36. package/extension/icons/icon-32.png +0 -0
  37. package/extension/icons/icon-48.png +0 -0
  38. package/extension/manifest.json +8 -2
  39. package/extension/panel.css +86 -0
  40. package/extension/panel.html +86 -0
  41. package/extension/panel.js +192 -0
  42. package/extension/relay.js +280 -5
  43. package/extension/toolbar.js +172 -0
  44. package/package.json +5 -5
  45. package/scripts/extension.mjs +4 -2
@@ -0,0 +1,86 @@
1
+ /* The panel and the ask window. Mirasim's paper, ink and cobalt (mirasim scripts/brand-mark.mjs). */
2
+ :root {
3
+ color-scheme: light dark;
4
+ --paper: #f8f6f2;
5
+ --surface: #ffffff;
6
+ --ink: #16181a;
7
+ --muted: #6b6e72;
8
+ --line: #e4e1da;
9
+ --accent: #2a58c0;
10
+ --accent-ink: #ffffff;
11
+ --warn: #b45309;
12
+ --warn-bg: #fdf3e6;
13
+ }
14
+ @media (prefers-color-scheme: dark) {
15
+ :root {
16
+ --paper: #141617;
17
+ --surface: #1d2021;
18
+ --ink: #f1efea;
19
+ --muted: #9a9c9f;
20
+ --line: #2d3133;
21
+ --accent: #7499f2;
22
+ --accent-ink: #0e1220;
23
+ --warn: #f0a356;
24
+ --warn-bg: #2a2117;
25
+ }
26
+ }
27
+ * { box-sizing: border-box; }
28
+ html, body { margin: 0; background: var(--paper); color: var(--ink); }
29
+ body { font: 13px/1.45 system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
30
+ main { width: 360px; padding: 14px 16px 12px; }
31
+ body.page main { width: auto; max-width: 440px; margin: 24px auto; }
32
+ header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
33
+ header img { border-radius: 6px; }
34
+ .brand { flex: 1; display: flex; flex-direction: column; min-width: 0; }
35
+ .brand strong { font-size: 14px; }
36
+ .status { color: var(--muted); font-size: 12px; }
37
+ .status.on::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #2f9e6a; margin-right: 6px; vertical-align: 1px; }
38
+ .status.off::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--muted); margin-right: 6px; vertical-align: 1px; }
39
+ section { margin-top: 14px; }
40
+ h2 { font-size: 12px; font-weight: 600; color: var(--muted); margin: 0 0 6px; }
41
+ .card { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; }
42
+ .tab-line { display: flex; align-items: center; gap: 10px; }
43
+ .tab-text { flex: 1; min-width: 0; }
44
+ .tab-title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
45
+ .tab-host { color: var(--muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
46
+ .tab-state { margin-top: 6px; font-size: 12px; color: var(--muted); }
47
+ .tab-state.good { color: var(--accent); }
48
+ .tab-state.bad { color: var(--warn); }
49
+ button {
50
+ font: inherit; font-weight: 600; border-radius: 8px; border: 1px solid var(--accent);
51
+ background: var(--accent); color: var(--accent-ink); padding: 6px 12px; cursor: pointer; white-space: nowrap;
52
+ }
53
+ button.ghost { background: transparent; color: var(--ink); border-color: var(--line); font-weight: 500; }
54
+ button.ghost.active { background: var(--warn-bg); border-color: var(--warn); color: var(--warn); }
55
+ button.secondary { background: transparent; color: var(--accent); }
56
+ button:disabled { opacity: 0.5; cursor: default; }
57
+ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
58
+ .note { margin: 0; padding: 8px 10px; border-radius: 8px; font-size: 12px; }
59
+ .note.warn { background: var(--warn-bg); color: var(--warn); }
60
+ .list { list-style: none; margin: 0; padding: 0; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; }
61
+ .list li { padding: 7px 12px; border-top: 1px solid var(--line); display: flex; gap: 8px; align-items: baseline; min-width: 0; }
62
+ .list li:first-child { border-top: 0; }
63
+ .list li.empty { color: var(--muted); }
64
+ .list a { color: inherit; text-decoration: none; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
65
+ .list a:hover { color: var(--accent); }
66
+ .log li { font-size: 12px; }
67
+ .log time { color: var(--muted); font-variant-numeric: tabular-nums; flex: none; }
68
+ .log .what { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
69
+ .choices { display: flex; flex-direction: column; gap: 6px; }
70
+ .choice { display: flex; gap: 8px; align-items: flex-start; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; cursor: pointer; }
71
+ .choice:has(input:checked) { border-color: var(--accent); }
72
+ .choice input { margin: 3px 0 0; accent-color: var(--accent); }
73
+ .choice span { display: flex; flex-direction: column; }
74
+ .choice small { color: var(--muted); font-size: 12px; }
75
+ .field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
76
+ .field > span { font-size: 12px; }
77
+ textarea {
78
+ font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--ink); background: var(--surface);
79
+ border: 1px solid var(--line); border-radius: 8px; padding: 6px 8px; resize: vertical; width: 100%;
80
+ }
81
+ .check { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; font-size: 12px; cursor: pointer; }
82
+ .check input { accent-color: var(--accent); margin: 0; }
83
+ .row { display: flex; align-items: center; gap: 10px; }
84
+ .quiet { color: var(--muted); font-size: 12px; }
85
+ footer { margin-top: 14px; }
86
+ [hidden] { display: none !important; }
@@ -0,0 +1,86 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Mirasim</title>
7
+ <link rel="stylesheet" href="panel.css" />
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <header>
12
+ <img src="icons/icon-32.png" alt="" width="24" height="24" />
13
+ <div class="brand">
14
+ <strong>Mirasim</strong>
15
+ <span id="status" class="status"></span>
16
+ </div>
17
+ <button id="pause" class="ghost" type="button"></button>
18
+ </header>
19
+ <p id="paused-note" class="note warn" hidden data-i18n="pausedNote"></p>
20
+
21
+ <section id="this-tab">
22
+ <h2 data-i18n="sectionThisTab"></h2>
23
+ <div class="card">
24
+ <div class="tab-line">
25
+ <div class="tab-text">
26
+ <div id="tab-title" class="tab-title"></div>
27
+ <div id="tab-host" class="tab-host"></div>
28
+ </div>
29
+ <button id="tab-action" type="button" hidden></button>
30
+ </div>
31
+ <div id="tab-state" class="tab-state"></div>
32
+ </div>
33
+ </section>
34
+
35
+ <section>
36
+ <h2 data-i18n="sectionAgentTabs"></h2>
37
+ <ul id="using" class="list"></ul>
38
+ </section>
39
+
40
+ <section>
41
+ <h2 data-i18n="sectionScope"></h2>
42
+ <div class="choices" id="scope">
43
+ <label class="choice">
44
+ <input type="radio" name="scope" value="group" />
45
+ <span><b data-i18n="scopeGroup"></b><small data-i18n="scopeGroupHint"></small></span>
46
+ </label>
47
+ <label class="choice">
48
+ <input type="radio" name="scope" value="ask" />
49
+ <span><b data-i18n="scopeAsk"></b><small data-i18n="scopeAskHint"></small></span>
50
+ </label>
51
+ <label class="choice">
52
+ <input type="radio" name="scope" value="all" />
53
+ <span><b data-i18n="scopeAll"></b><small data-i18n="scopeAllHint"></small></span>
54
+ </label>
55
+ </div>
56
+ </section>
57
+
58
+ <section>
59
+ <h2 data-i18n="sectionSites"></h2>
60
+ <label class="field">
61
+ <span data-i18n="blockedLabel"></span>
62
+ <textarea id="blocked" rows="3" spellcheck="false" data-i18n-placeholder="sitesPlaceholder"></textarea>
63
+ </label>
64
+ <label class="check">
65
+ <input type="checkbox" id="allow-only" />
66
+ <span data-i18n="allowOnlyLabel"></span>
67
+ </label>
68
+ <label class="field" id="allowed-field" hidden>
69
+ <textarea id="allowed" rows="3" spellcheck="false" data-i18n-placeholder="sitesPlaceholder"></textarea>
70
+ </label>
71
+ <div class="row">
72
+ <button id="save" type="button" data-i18n="save"></button>
73
+ <span id="saved" class="quiet" hidden data-i18n="saved"></span>
74
+ </div>
75
+ </section>
76
+
77
+ <section>
78
+ <h2 data-i18n="sectionLog"></h2>
79
+ <ul id="log" class="list log"></ul>
80
+ </section>
81
+
82
+ <footer id="footer" class="quiet"></footer>
83
+ </main>
84
+ <script src="panel.js" type="module"></script>
85
+ </body>
86
+ </html>
@@ -0,0 +1,192 @@
1
+ /**
2
+ * The panel: the toolbar popup on a tab already handed over (or one Mirasim cannot use), and
3
+ * the options page (right-click the icon → Options). Shows where the connection and this tab
4
+ * stand, and edits the access settings — which the worker enforces (access.js); this page only
5
+ * asks it (background.js `_onPageMessage`).
6
+ */
7
+
8
+ const t = (key, ...subs) => chrome.i18n.getMessage(key, subs) || key;
9
+ const $ = (id) => document.getElementById(id);
10
+
11
+ function send(type, extra = {}) {
12
+ return new Promise((resolve, reject) => {
13
+ chrome.runtime.sendMessage({ type, ...extra }, (response) => {
14
+ if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
15
+ if (!response?.ok) return reject(new Error(response?.error ?? "no answer"));
16
+ resolve(response.result);
17
+ });
18
+ });
19
+ }
20
+
21
+ function hostOf(url) {
22
+ try {
23
+ const u = new URL(url);
24
+ return u.protocol.startsWith("http") ? u.host : url;
25
+ } catch {
26
+ return url ?? "";
27
+ }
28
+ }
29
+
30
+ for (const el of document.querySelectorAll("[data-i18n]")) el.textContent = t(el.dataset.i18n);
31
+ for (const el of document.querySelectorAll("[data-i18n-placeholder]")) el.placeholder = t(el.dataset.i18nPlaceholder);
32
+ document.documentElement.lang = chrome.i18n.getUILanguage();
33
+
34
+ let tabId = null;
35
+ let state = null;
36
+ /** The settings form is the user's while they edit it: a refresh does not overwrite it. */
37
+ let dirty = false;
38
+
39
+ async function findTab() {
40
+ const [active] = await chrome.tabs.query({ active: true, currentWindow: true });
41
+ // Opened as the options page, the active tab is this page: there is no "this tab" to show.
42
+ if (!active || active.url?.startsWith(chrome.runtime.getURL(""))) {
43
+ document.body.classList.add("page");
44
+ return null;
45
+ }
46
+ return active.id ?? null;
47
+ }
48
+
49
+ function render() {
50
+ const s = state;
51
+ const status = $("status");
52
+ status.textContent = s.connected ? t("statusConnected") : t("statusOffline");
53
+ status.className = `status ${s.connected ? "on" : "off"}`;
54
+ const pause = $("pause");
55
+ pause.textContent = s.paused ? t("resume") : t("pause");
56
+ pause.classList.toggle("active", s.paused);
57
+ $("paused-note").hidden = !s.paused;
58
+
59
+ renderTab(s);
60
+
61
+ const using = $("using");
62
+ using.replaceChildren();
63
+ if (!s.using.length) using.append(item(t("agentTabsNone"), "empty"));
64
+ for (const tab of s.using) {
65
+ const li = document.createElement("li");
66
+ const a = document.createElement("a");
67
+ a.textContent = tab.title || hostOf(tab.url);
68
+ a.title = tab.url;
69
+ a.addEventListener("click", () => void send("panel.focusTab", { tabId: tab.id }).then(() => window.close()));
70
+ li.append(a);
71
+ using.append(li);
72
+ }
73
+
74
+ if (!dirty) {
75
+ for (const input of document.querySelectorAll('input[name="scope"]')) input.checked = input.value === s.settings.scope;
76
+ $("blocked").value = s.settings.blocked.join("\n");
77
+ $("allow-only").checked = s.settings.allowOnly;
78
+ $("allowed").value = s.settings.allowed.join("\n");
79
+ $("allowed-field").hidden = !s.settings.allowOnly;
80
+ }
81
+
82
+ const log = $("log");
83
+ log.replaceChildren();
84
+ if (!s.log.length) log.append(item(t("logNone"), "empty"));
85
+ for (const entry of [...s.log].reverse()) {
86
+ const li = document.createElement("li");
87
+ const time = document.createElement("time");
88
+ time.textContent = new Date(entry.at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
89
+ const what = document.createElement("span");
90
+ what.className = "what";
91
+ const where = entry.url ? hostOf(entry.url) : entry.tabId !== undefined ? t("logTab", String(entry.tabId)) : "";
92
+ what.textContent = [t(`reason_${entry.reason.replace(/-/g, "_")}`), where].filter(Boolean).join(" · ");
93
+ what.title = entry.url ?? "";
94
+ li.append(time, what);
95
+ log.append(li);
96
+ }
97
+
98
+ const bridge = s.bridge ? t("footerBridge", `${s.bridge.name} ${s.bridge.version}`.trim()) : s.connected ? t("footerBridgeOld") : "";
99
+ $("footer").textContent = [t("footerExtension", s.extension), bridge].filter(Boolean).join(" · ");
100
+ }
101
+
102
+ function renderTab(s) {
103
+ const tab = s.tab;
104
+ const title = $("tab-title");
105
+ const host = $("tab-host");
106
+ const line = $("tab-state");
107
+ const action = $("tab-action");
108
+ action.hidden = true;
109
+ line.className = "tab-state";
110
+ if (!tab) {
111
+ title.textContent = "";
112
+ host.textContent = "";
113
+ line.textContent = t("tabStateNone");
114
+ return;
115
+ }
116
+ title.textContent = tab.title || hostOf(tab.url);
117
+ host.textContent = hostOf(tab.url);
118
+ const scope = s.settings.scope;
119
+ let text;
120
+ if (tab.restricted) text = t("tabStateRestricted");
121
+ else if (tab.siteVerdict) (text = t("tabStateBlocked")), (line.className = "tab-state bad");
122
+ else if (tab.using) (text = t("tabStateUsing")), (line.className = "tab-state good");
123
+ else if (tab.handedOver) (text = t("tabStateHanded")), (line.className = "tab-state good");
124
+ else if (tab.granted) text = t("tabStateGranted");
125
+ else if (scope === "all") text = t("tabStateOutsideAll");
126
+ else if (scope === "ask") text = t("tabStateOutsideAsk");
127
+ else text = t("tabStateOutside");
128
+ line.textContent = text;
129
+ if (tab.restricted || tab.siteVerdict) return;
130
+ action.hidden = false;
131
+ const giveBack = tab.handedOver || tab.using || tab.granted;
132
+ action.textContent = giveBack ? t("takeBack") : t("handOver");
133
+ action.className = giveBack ? "secondary" : "";
134
+ action.onclick = async () => {
135
+ action.disabled = true;
136
+ try {
137
+ await send(giveBack ? "panel.takeBack" : "panel.handOver", { tabId: tab.id });
138
+ await refresh();
139
+ } finally {
140
+ action.disabled = false;
141
+ }
142
+ };
143
+ }
144
+
145
+ function item(text, cls) {
146
+ const li = document.createElement("li");
147
+ li.textContent = text;
148
+ if (cls) li.className = cls;
149
+ return li;
150
+ }
151
+
152
+ async function refresh() {
153
+ state = await send("panel.state", { tabId });
154
+ render();
155
+ }
156
+
157
+ function lines(id) {
158
+ return $(id).value.split(/[\n,\s]+/).map((s) => s.trim()).filter(Boolean);
159
+ }
160
+
161
+ async function save() {
162
+ const scope = document.querySelector('input[name="scope"]:checked')?.value ?? "group";
163
+ const settings = await send("panel.settings", {
164
+ settings: { scope, blocked: lines("blocked"), allowOnly: $("allow-only").checked, allowed: lines("allowed") },
165
+ });
166
+ dirty = false;
167
+ state.settings = settings;
168
+ render();
169
+ $("saved").hidden = false;
170
+ setTimeout(() => ($("saved").hidden = true), 1500);
171
+ }
172
+
173
+ $("pause").addEventListener("click", async () => {
174
+ await send("panel.pause", { paused: !state.paused });
175
+ await refresh();
176
+ });
177
+ // The scope applies at once: one choice, nothing else to fill in.
178
+ for (const input of document.querySelectorAll('input[name="scope"]')) {
179
+ input.addEventListener("change", () => void save());
180
+ }
181
+ for (const id of ["blocked", "allowed"]) $(id).addEventListener("input", () => (dirty = true));
182
+ $("allow-only").addEventListener("change", () => {
183
+ dirty = true;
184
+ $("allowed-field").hidden = !$("allow-only").checked;
185
+ });
186
+ $("save").addEventListener("click", () => void save());
187
+
188
+ tabId = await findTab();
189
+ await send("panel.seen");
190
+ await refresh();
191
+ // The agent works while the panel is open: keep the lists current.
192
+ setInterval(() => void refresh().catch(() => {}), 1500);