@mirasim/computer-use 0.1.0-rc.6 → 0.1.0-rc.7
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/README.md +3 -1
- package/dist/platform/browser/bridge/model-commands.js +1 -1
- package/dist/platform/browser/bridge/model-core.d.ts +7 -0
- package/dist/platform/browser/bridge/model-core.js +1 -1
- package/dist/platform/browser/bridge/server.js +1 -1
- package/dist/platform/browser/bridge-tabs.d.ts +3 -0
- package/dist/platform/browser/bridge-tabs.js +1 -1
- package/dist/platform/browser/driver-verbs.js +2 -2
- package/dist/platform/browser/driver.d.ts +2 -0
- package/dist/platform/browser/driver.js +3 -2
- package/dist/platform/browser/messages.d.ts +5 -0
- package/dist/platform/browser/messages.js +2 -2
- package/dist/platform/chromium/actions.js +2 -2
- package/dist/platform/chromium/run-js.d.ts +5 -1
- package/dist/platform/chromium/run-js.js +2 -2
- package/dist/platform/electron/driver.js +1 -1
- package/dist/platform/electron/verbs.js +1 -1
- package/dist/platform/macos/type-verb.js +1 -1
- package/dist/platform/tools/api-contract.d.ts +1 -0
- package/dist/platform/tools/api.js +1 -1
- package/dist/platform/tools/repl-docs.js +1 -1
- package/dist/platform/tools/repl.js +12 -12
- package/extension/_locales/en/messages.json +179 -0
- package/extension/_locales/zh_CN/messages.json +179 -0
- package/extension/access.js +245 -0
- package/extension/ask.html +26 -0
- package/extension/ask.js +20 -0
- package/extension/background.js +79 -3
- package/extension/group.js +335 -79
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-32.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/manifest.json +8 -2
- package/extension/panel.css +86 -0
- package/extension/panel.html +86 -0
- package/extension/panel.js +192 -0
- package/extension/relay.js +264 -5
- package/extension/toolbar.js +172 -0
- package/package.json +5 -5
- package/scripts/extension.mjs +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{STATE_CHANGING_ACTIONS as
|
|
1
|
+
import{STATE_CHANGING_ACTIONS as G}from"../driver.js";import{withoutBlankOptionals as L}from"./normalize.js";import{ambiguityNote as k,findScope as D,scopeLabel as U}from"./find-target.js";import{argComplaint as q}from"./validate.js";import{createTargeting as F}from"./api-target.js";import{waitVerb as H}from"./api-wait.js";import{expectVerb as W}from"./api-expect.js";import{checkShotPath as J,saveShot as Y}from"./shot-file.js";import{fnName as E,firstLine as x,firstText as M,noteOf as S,options as d}from"./api-contract.js";const z=600,j=150;function K(r,c,w){return r==="menu"&&!c.path||w.some($=>$.kind===r&&$.readOnly)}function ie(r,c,w={}){const $=r.extraVerbs(),V=new Map($.map(e=>[e.kind,e.schema])),O=[...V.keys()],{findWithMiss:v,asPoint:N,resolve:g,requireFocus:C}=F(r,c,w,x);let T=null;const B=(e,t)=>{const n=V.get(e);if(!n)return null;const o=L({kind:e,...t},n);return q(n,E(e),o)},R=async(e,t,n,o=!1)=>{c().charge(e);const s=B(e,t);if(s)throw c().push(e,n,!1,s,!1,"blocked"),new Error(s);if(e==="run_js"){const u=c().remainingMs?.();u!==void 0&&(t={...t,budgetMs:Math.floor(u)})}const i=K(e,t,$);if(!i)try{await w.beforeAction?.(e,t)}catch(u){throw c().push(e,n,!1,u.message,!1,"blocked"),u}i||r.noteAction(e,t),i?r.noteContentRead():T=e,i||await r.ensureSettleBaseline(e);let a;try{a=e==="focus"?await r.focusTarget(String(t.target??"")):await r.perform(e,t)}catch(u){throw c().push(e,n,!1,`${u.message}; outcome unknown \u2014 observe before retrying`,i,i?void 0:"uncertain"),u}a.ok&&r.publishPreview(i?void 0:e,t);const l=(o||e==="type")&&a.ok&&a.verified===!1,f=e==="set_text"&&a.ok&&a.verified===void 0?`${S(a)||"write dispatched"}; read-back unavailable \u2014 the value was not verified`:S(a),m=a.mayHaveActed||a.verified===!1?"uncertain":a.ok?a.verified===!0?"verified":"dispatched":"blocked";c().push(e,n,a.ok&&!l,f,i,i?void 0:m);const p=c().round;if(p&&a.ok&&(e==="set_text"?(p.fields+=1,a.verified===!0&&(p.verified+=1)):p.ended=`${e}${t.chord?` ${String(t.chord)}`:""}${n?` ${n}`:""}`),!a.ok)throw new Error(`${e}${n?` ${n}`:""} failed: ${a.note??"no reason given"}`);if(l){const u=new Error(`${e==="type"?"type":"setText"} ${n}: ${a.note??"the field did not retain the requested text"}. The write was dispatched, but verification failed; subsequent actions were not run. `+(e==="type"?"Observe the focused field before continuing.":"Use setTextBestEffort only when you intend to handle a mismatched value yourself."));throw u.name="WriteVerificationError",u}return a},_=async(e,t,n,o)=>{const s=d("setText",o,"setText(target, text, {ifValue:previousValue})");if(Object.keys(s).some(f=>f!=="ifValue")||s.ifValue!==void 0&&typeof s.ifValue!="string")throw new Error("setText's only option is ifValue:string");const{ref:i,name:a}=await g(e,{write:!0});if(s.ifValue!==void 0&&(c().charge("precondition"),!r.readValue||!r.valueMatches(await r.readValue(i),s.ifValue))){const f="The field no longer matches ifValue, or cannot be read. Nothing was written; observe and reconcile the edit.";throw c().push("set_text",i,!1,f,!1,"blocked"),new Error(f)}return(await R("set_text",{ref:i,...a?{name:a}:{},text:String(t??"")},i,n)).verified},h=async(e,t,n)=>{const o=await R(e,t,n);return"value"in o?o.value:o.note||(n?`${e} ${n}`:e)},y=(e,t,n,o)=>async()=>{c().charge(e),r.noteContentRead();const s=await o();return r.publishPreview(),c().push(e,t,!0,n(s)),s},A=(e,t,n)=>async(...o)=>{if(o.length)throw new Error(`${e}() takes no arguments \u2014 ${t}`);return n()},I={observe:async(e={})=>{const t=d("observe",e,"observe() lists every element; observe({screenshot: true}) also attaches the picture"),n=Object.keys(t).filter(b=>b!=="screenshot");if(n.length)throw new Error(`observe() does not take ${n.join(", ")} \u2014 it already answers with EVERY element, on screen and beyond it (each one off-viewport carries \`offscreen: true\`); narrow it by filtering the array it returns. Its one option is screenshot: true.`);c().charge("observe");const{url:o,title:s,elements:i,groups:a,headings:l,partial:f,coverage:m,target:p}=await r.readElements();w.onRefs?.(i.map(b=>b.ref));let u="";if(t.screenshot){c().charge("screenshot");const b=await r.observeImage();for(const P of b.content)P.type==="image"&&(c().attach(P),u=" + image");u||(u=` (no image \u2014 ${x(b)})`)}return c().push("observe","",!0,`${i.length} elements${u}`),{url:o,title:s,elements:i,coverage:m,...p?{target:p}:{},...a?.length?{groups:a}:{},...l?.length?{headings:l}:{},...f?{partial:f}:{}}},find:async(e,t={})=>{c().charge("find");const n=D(t,r.readsOffscreen);let{h:o,miss:s}=await v(String(e??""),n);if(!o&&G.has(T??"")){T=null;for(let i=0;!o&&i<z;i+=j)await new Promise(a=>setTimeout(a,j)),{h:o,miss:s}=await v(String(e??""),n)}return c().push("find",`${String(e??"")}${U(n)}`,!0,o?k(o):s),o},text:A("text","it returns the surface's whole readable text",y("text","",e=>`${e.length} chars`,()=>r.readText())),value:async e=>{if(!r.readValue)throw new Error("value() is not supported on this platform \u2014 text() reads the whole surface instead");const t=await g(e);return y("value",t.ref,n=>`${n.length} chars`,()=>r.readValue(t.ref))()},page:async(e={})=>{const t=d("page",e,"page() reads the whole page and page({ref}) one node \u2014 to NAVIGATE, openUrl({url})");if(!r.readPage)throw new Error("page reading is not supported on this platform \u2014 use text()");if(t.ref&&!r.knownRef(t.ref))throw new Error(r.staleRefNote(t.ref));const n=y("page",t.ref??t.node??"",o=>x(o),()=>r.readPage(t));return M(await n())},screenshot:async(e={})=>{const{path:t,...n}=d("screenshot",e,"screenshot() takes the viewport; screenshot({ref}) / ({region}) / ({fullPage:true}) narrow it");if(n.ref&&!r.knownRef(n.ref))throw new Error(r.staleRefNote(n.ref));if(t!==void 0&&typeof t!="string")throw new Error("screenshot path must be a string");t&&J(t,r.screenshotMimeType);const o=!!(n.ref||n.region||n.fullPage||n.padding!==void 0);let s=null;const i=f=>s?`saved ${s.path}`:`no image \u2014 ${x(f)}`,l=await y("screenshot",n.ref??(n.fullPage?"full page":n.region?"region":""),i,async()=>{const f=await r.observeImage(o?{scope:n}:{}),m=f.content.find(p=>p.type==="image");return m&&m.type==="image"&&(s=Y(m.data,m.mimeType,t||void 0)),f})();for(const f of l.content)f.type==="image"&&c().attach(f);return s?{...s,note:x(l),...l.details.capture?{capture:l.details.capture}:{}}:{path:null,note:i(l)}},diagnostics:async(e={})=>{const t=d("diagnostics",e,"diagnostics() reads every channel, diagnostics({id}) one of them");return M(await y("diagnostics",t.id??"",x,async()=>r.readDiagnostics(t))())},targets:A("targets","it lists every surface there is",y("targets","",e=>`${e.length} targets`,()=>r.listTargets())),click:async(e,t={})=>{const n=N(e);if(n)return h("click",{...n,...t},`${n.x},${n.y}`);const{ref:o,name:s}=await g(e,{write:!0});return h("click",{ref:o,...s?{name:s}:{},...t},o)},hover:async e=>{const t=N(e);if(t)return h("hover",{...t},`${t.x},${t.y}`);const{ref:n,name:o}=await g(e);return h("hover",{ref:n,...o?{name:o}:{}},n)},setText:(e,t,n)=>_(e,t,!0,n),setTextBestEffort:(e,t,n)=>_(e,t,!1,n),keys:async(e,t,n)=>{const o=typeof t=="object"&&t!==null?t:n,s=typeof t=="number"?t:void 0;return o?.focus!==void 0&&await C(o.focus),h("keys",{chord:String(e??""),...s?{count:s}:{}},String(e??""))},scroll:async(e={})=>{const t=d("scroll",e,"scroll({direction:'down'}) or scroll({to:'bottom'})");if(t.into!==void 0){const o=typeof t.into=="string"?t.into:t.into?.ref;if(typeof o!="string")throw new Error("scroll({into}) takes a ref string or a find() handle");if(!r.scrollsInto)throw new Error("scroll({into}) is not available on this platform \u2014 scroll by direction, count or to:");const s=["direction","count","to","ref"].filter(a=>t[a]!==void 0);if(s.length)throw new Error(`scroll({into}) moves to one control and takes nothing else \u2014 drop ${s.join(", ")}`);w.onTarget?.(o);const i=await h("scroll",{into:o},o);return await r.readElements().catch(()=>{}),i}let n=t.ref;return n!==void 0&&typeof n!="string"?n=(await g(n)).ref:typeof n=="string"&&w.onTarget?.(n),h("scroll",{...t,...n===void 0?{}:{ref:n}},String(n??t.direction??t.to??""))},wait:H(r,c),expect:W(r,c,async e=>{if(!r.readValue)throw new Error("This platform cannot read field values");const{ref:t}=await g(e);return r.readValue(t)}),focus:async e=>h("focus",{target:String(e??"")},String(e??"")),log:(...e)=>{c().charge("log"),c().push("log","",!0,e.map(Q).join(" ").slice(0,200))},act:async(e,t={})=>{if(typeof t.ref=="string"&&(w.onTarget?.(t.ref),!r.knownRef(t.ref)))throw new Error(r.staleRefNote(t.ref));return h(String(e??""),{...t},String(t.ref??""))}};for(const e of O)I[E(e)]=async(t={})=>{const n=d(E(e),t,`pass its arguments as an object, e.g. ${E(e)}({\u2026})`),o=n.ref!==void 0?await g(n.ref,{write:!0}):null;return h(e,{...n,...o?{ref:o.ref}:{}},String(n.ref??""))};return I}import{renderJournal as le,stringify as he,verbGate as pe}from"./api-run.js";import{stringify as Q}from"./api-run.js";export{ie as createComputerUseApi,E as fnName,K as isReadVerb,S as noteOf,le as renderJournal,he as stringify,pe as verbGate};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{fnName as u}from"./api.js";import{IDLE_RELEASE_MS as p}from"../ownership.js";const g="Run JavaScript in this session's PERSISTENT runtime to look at and act on the GUI \u2014 web pages in a browser, Electron apps and native macOS / Windows apps, whichever this server mounts \u2014 the one tool for both. The code is an async function body: `await` every verb, `return` a value to get it back as JSON, and top-level const/let/function declarations SURVIVE to later calls \u2014 keep handles and collected data in them instead of re-reading. Surfaces (tabs / windows / apps) are HANDLES and EVERY verb lives on one \u2014 there are no top-level verbs. Bind first: computer.surface('<id or title>') names one, computer.surface() alone takes the only surface, or the one this session opened when other tabs are open too, computer.surfaces() lists them. Then w.observe() w.find(name) w.click(t) w.setText(t, text) w.keys(chord, n) w.scroll({\u2026}) w.wait({\u2026}) w.text() w.page({\u2026}) w.screenshot({\u2026}) and this platform's own verbs, each switching to that surface as needed; a ref read on one surface is refused on another. Bind ONCE \u2014 keep the handle in a top-level variable and later calls reuse it. The first bound operation claims the target across calls; await w.release() when its task is done. Another runtime's claim refuses input before dispatch. The first result carries the full API reference (computer.help() returns it again) \u2014 use only what it documents. One verb at a time (one focus, one caret): a failed action or expect() throws and stops the run; wait() instead returns false on timeout, so check it or use expect() for a required condition. The result is the step journal plus one observation of the surface it acted on. Per call: 100 actions, 60 s. No require/import/fetch/process/timers. Before using an operation that may affect the user's foreground, real pointer, keyboard focus or system clipboard (including automatic fallback), explain its target and effects and obtain permission unless existing permission covers them. Do not silently escalate a background operation; inspect computer.help() for platform-specific effects. Before sending a message or publishing a post, let the user confirm its content and recipients or destination, unless they already approved those exact details.",v=`Reset computer_repl's persistent JavaScript runtime: every binding \u2014 handles, variables, functions \u2014 is discarded, and the next computer_repl call starts fresh and returns the API reference again. Nothing on the surfaces changes: no tab, window or app is closed. This runtime's target ownership is released after any active call stops. Use it only when the runtime is wedged or its state is wrong, never as a way to "start over" on the screen.`;function f(e,a){const o=a,s=Object.keys(o.properties??{}).filter(t=>t!=="kind"),n=new Set(o.required??[]),r=t=>n.has(t)?t:`${t}?`,h=s.filter(t=>t!=="ref"&&t!=="name").map(t=>o.properties?.[t]?.description).find(Boolean),i=o.description??h??"",c=/^.*?[.!?](?=\s+[A-Z(`]|$)/s.exec(i)?.[0]??i,d=s.filter(t=>t==="ref"||t==="name").flatMap(t=>o.properties?.[t]?.description?[` ${r(t)}: ${o.properties[t].description}`]:[]);return[` w.${u(e)}({${s.map(r).join(", ")}})${c?` \u2014 ${c.trim()}`:""}`,...d,...n.has("ref")&&s.includes("name")?[" name does not replace the required ref."]:[]].join(`
|
|
2
|
-
`)}function l(e){const a=e.extraVerbs();return{head:["API reference \u2014 computer_repl (this text comes once per session; computer.help() returns it again)","","Everything is async \u2014 the verbs AND computer.surfaces() / computer.surface(\u2026): await each. A verb that","fails THROWS with the reason, which stops the run; the journal in the result says what ran. Refs (e12)","come from observe()/find() and stay valid until the surface changes; never invent one, and never carry one in from","another session \u2014 a fresh session re-mints them, so the same e12 names a different element. A target `t` is "+"a find() handle, a ref string, or a point {x, y} read off a screenshot"+".","","Bind a surface first \u2014 every verb below is a method on the handle it returns, never a name of its own:",' await computer.surfaces() \u2192 [handle] \u2014 every surface (tab / window / app) as {id, kind, title, active}; `id` is an opaque key, what the platform knows the surface by lives in `title` (macOS: "TextEdit (com.apple.TextEdit) \u2014 Untitled"; Windows: the window title, then the program name \u2014 "Untitled - Notepad \u2014 Notepad")'," await computer.surface(name) \u2192 handle \u2014 by id, by whole title, or by a piece of the title; several matches or none is a refusal that lists the candidates, never a guess",' await computer.surface() \u2192 handle \u2014 the ONE surface, when there is only one. Not "whatever is in front": with more than one open it refuses and lists them, so a script always says which it meant'," Bind once per surface and keep the handle in a top-level declaration \u2014 it stays valid for later calls, and re-binding costs a read each time."," A browser tab marked unloaded:true is still discoverable. Using its handle restores that exact tab in the background after acquiring ownership; merely listing tabs never reloads it.",""],reads:[...e.interactionNotes(),"Reads (return data to your code; none of them is shown to you except through `return` or computer.log):",e.readsOffscreen?" w.observe({screenshot?}) \u2192 {url, title, elements:[{ref, role, name, state, rect, offscreen?}], groups?, headings?, partial?, coverage, target?} \u2014 every control, viewport and beyond; filter the array rather than asking it to narrow. `rect` {x, y, w, h} is the control's box \u2014 compare boxes to answer layout questions (which row a control sits on, whether it moved) without a screenshot. `groups` is the page's OWN labelling of those controls \u2014 [{label, members:[ref]}] over that same set, off-viewport members included \u2014 present where the author wrote one, and the only place some facts exist (a feed labels each post's button row with its counts, bookmarks included, which no control name and no page text carries). `headings` (web surfaces) is [{name, level, rect}] for the page's headings, never listed as controls \u2014 the fixed point to measure a control's box against, since a click can scroll the container under every control in a card. `partial` (a reason) says the read stopped before covering the surface, so what it did not list may still be there. screenshot:true also attaches the picture of the same screen (one call, two reads), for when you need to SEE where something is":" w.observe({screenshot?}) \u2192 {url, title, elements:[{ref, role, name, state, rect}], coverage} \u2014 every control the window SHOWS (`rect` {x, y, w, h} is its box \u2014 compare boxes to answer layout questions without a screenshot); one scrolled out of view is counted in the observation and never listed, so no ref names it until a scroll brings it in; filter the array rather than asking it to narrow. screenshot:true also attaches the picture of the same screen"," coverage reports exact {offscreen, covered, capped, modal?} counts (null when unreported) even when repeated context notes are omitted. Electron target reports the CDP endpoint, windowId and, when its user agent carries them, the app's own name/version; never infer the app version from the tool or helper path."," w.find(name, {role?, within?, "+(e.readsOffscreen?"visible?, ":"")+"near?}) \u2192 {ref, name, matches, others, incidental?} | null \u2014 by visible name, "+(e.readsOffscreen?"whole surface":"everything the window shows \u2014 NOT what is scrolled out of it, which this platform's read does not contain")+" (an exact name beats a partial one, a partial the query is most of beats one it is only a fragment of, and a control beats a text label); a handle that re-resolves itself when used, so a loop survives a re-render; `matches` says how many controls carried the name and `others` names the runners-up, so an ambiguous name is visible BEFORE you act on the pick; `incidental: true` says NOTHING here is NAMED that and the pick is only a long name that happens to contain the word \u2014 the one `matches: 1` not to trust; narrow it with role (button / textbox / \u2026), within (a ref or handle \u2014 matches inside that element's box, e.g. one sheet or one message)"+(e.readsOffscreen?" visible (on screen only)":"")+" and near (a labelled group restricts candidates to its members; otherwise prefer the control closest to the named element). Duplicate group labels or same-named controls within a group still need disambiguation"," w.text() \u2192 the surface's full readable text",...e.readValue?[" w.value(t) \u2192 ONE control's whole value as a string \u2014 a long field, a clipped label, the text you are about to edit. The listing shows a value cut to what a line holds; this is the rest of it, for one element, where text() re-reads the entire surface"]:[],...e.readPage?[" w.page({ref?, depth?, node?}) \u2192 the content tree as text, with a completeness receipt on its first line"]:[]," w.screenshot({ref?, padding?|region:{x,y,width,height}?|fullPage?, path?}) \u2192 {path, host, width, height, note, capture?} \u2014 the image is attached to this result for you to see AND written to a file the code can keep: to `path` (absolute, never overwrites an existing file, extension must match the surface's format \u2014 .jpg on web surfaces, .png on macOS and Windows) or else to a rolling temp directory; `host` names the machine the path is on; no image \u2192 {path: null, note: why}. ref is a ref string and crops to a visible element's box; padding keeps its surroundings. fullPage is web-only. On macOS and Windows, ref/region preserve native detail; region uses FULL screenshot pixels, so take a full screenshot first. Windows padding and crop bounds use physical pixels; macOS padding uses full-screenshot pixels when available, otherwise window points. capture reports the window, crop, units, pointsPerPixel, and toFullScreenshot (null without a full screenshot or when window geometry changed). Windows refuses a region based on old window geometry; take a new full screenshot. Convert crop pixel p to full screenshot coordinates with toFullScreenshot.origin + p * toFullScreenshot.scale on each axis. A crop never changes point-action coordinates; use a current mapping or act by ref.",...e.supportsDiagnostics?[` w.diagnostics({id?}) \u2192 ${e.diagnosticsAbout.empty} \u2014 the newest 20, with the ones new since your last read marked "new"`]:[]," computer.log(\u2026) \u2192 a line in the journal; the one member that is not on a surface",""],actions:["Actions (every one is w.verb(\u2026) on a bound handle, which switches to that surface first):"," w.click(t, {button?, count?}) w.hover(t)"," w.setText(t, text) \u2014 replace the target field's ENTIRE value, even after selectText; never a selection/caret edit. Returns true when read-back matches; throws WriteVerificationError on mismatch, stopping the run unless caught."," w.setTextBestEffort(t, text) \u2014 the same whole-field replacement, returning true/false without throwing on mismatch. Both return undefined when read-back is unavailable; that is not verification."," click and hover also take a POINT \u2014 w.click({x, y}) \u2014 in the coordinates of the last screenshot, for a control you"," can see and cannot name. It checks no identity (no stale-ref gate, no name, no surface check) and so acts on"," whatever is at that spot NOW: take observe({screenshot: true}) and read the coordinates off that picture."," click / hover / keys / scroll / focus answer with what they did, as text \u2014 the same note the journal shows, e.g. which"," rung a click ended on. A verb that FAILS throws instead, so a returned string always describes something that happened."," w.keys(chord, count?, {focus?}) e.g. w.keys('Enter'), w.keys('Backspace', 3), w.keys('Enter', {focus: field}) \u2014 one key or chord, never a text string. setText replaces a whole field; use the platform's paste/type verb to edit at a selection or caret."," keys go wherever the surface has the keyboard focus. Pass focus: <target> to REQUIRE it be there: if it is not,"," nothing is pressed, including when focus is unknown or ambiguous. Omit focus only to send without that check. Focusing stays explicit (click / setText / selectText with a caret)."," w.scroll({direction?: up|down|left|right, count?: screens (default 1), to?: top|bottom, ref?}) w.wait({} | {ms} | {text} | {text_gone} | {control} | {control_gone} | {url} | {url_gone} | {loaded}) \u2192 ok"," count covers that many screens in ONE call \u2014 the note says how far it actually got, which is less once the end is reached;"," count and to: are refused together, since to: already goes as far as the surface scrolls."," Browser/Electron list scrollable regions with refs. With several regions, pass ref:<region or a control inside it \u2014 a ref or a find() handle>; a bare scroll refuses and lists the choices. ref:'window' explicitly scrolls the page. Reaching an edge never switches to another region.",...e.scrollsInto?[" w.scroll({into: t}) instead brings ONE control (a ref or a find() handle) to the middle of the viewport and says whether anything still covers it. It takes a covered control's ref too \u2014 the way out from under a sticky header or a bottom strip that a screen of scrolling overshoots."]:[]," a wait polls until the condition holds or it times out, and says which: {text\u2026} searches the readable text,",' {control\u2026} the CONTROLS by name ("until the Stop button is gone" \u2014 the honest way to wait out a long answer),'," {url}/{url_gone} where the surface IS (substring, so a query string needs no spelling out), and {loaded:true} that"," the DOCUMENT finished. Reach for it before reporting that a page has nothing on it: a document that never finishes"," reads as an empty page and no content condition can tell you which it was. It is NOT rendered \u2014 a single-page app"," reaches loaded with nothing drawn yet (measured), so follow it with the CONTENT wait, which is w.wait({}):",' it polls until the readable text stops changing, so it is the one "the page is ready" condition that needs no word'," from the page and no knowledge of the site. Use it after a navigation, a tab switch inside a page, or a search \u2014 a"," single-page app mounts its rows well after {loaded}, and an observation taken before that reads as an empty page."," Also after a click that REDRAWS content (a chart, a list) and before you screenshot or read it: the control's own state"," updates at once, the content a beat later, so a read straight after the click can pair the new button with the old view."," Every condition takes ms as its OWN polling budget (default 16s): w.wait({text:'Done', ms:3000}) gives up in three seconds,"," give or take the one read already in flight when it runs out \u2014 on a heavy surface a single read is seconds, and the result says so when that happened."," wait() returns true/false; awaiting it alone does NOT stop on timeout. Use expect() for a required postcondition, or check the result: if (!await w.wait({...})) throw new Error('condition not met');"," which is what a GUESS at the wording is worth \u2014 an unguarded guess that no page ever matches costs the full 16s."," w.focus() \u2014 select w as the target for this runtime; target selection is not an OS-activation guarantee (see platform interaction notes)",` w.release() \u2014 release this target and its input resources for other sessions. Claims span calls until release, reset, turn end, process exit or ${p/6e4} minutes with no call (the next call takes the target back unless another session has it by then). Observe again before acting after handoff; old refs are invalidated. Separate windows can still share app/account/document state; ownership does not isolate those or a human editor.`," w.expect({text, control, url, value, equals, ms}) \u2014 verify exactly ONE condition; value takes a ref/find handle and equals:string. Read-only polling, default 3000 ms, maximum 16000 ms. Returns true when verified; otherwise throws before subsequent steps. It never repeats the preceding action. Use it to verify save/submit results, not screen-change verdicts."," w.setText(t, text, {ifValue:previousValue}) \u2014 optional precondition: refuse before writing if the field changed or cannot be read. This is a checked precondition, not an atomic transaction with a human or another app.","Action journal evidence: [verified] confirms the stated value/condition; [dispatched] only confirms delivery; [blocked] means no dispatch; [uncertain] may already have acted. Never replay an uncertain send/submit/delete; inspect its actual result first."],extras:[...a.length?[" This platform's verbs \u2014 each returns what it did as text (the journal keeps only its first line, so a listing is read from the return value):"," In these signatures, ? marks an optional property; every unmarked property is required. Omit the ? in JavaScript calls.",...a.map(s=>f(s.kind,s.schema))]:[],...a.some(s=>s.kind==="select_option")?[" selectOption example: const choice = await w.find('Priority', {role:'combobox'}); if (!choice || choice.matches !== 1 || choice.incidental) throw new Error('Priority is missing or ambiguous'); await w.selectOption({ref:choice.ref, option:'High'});"]:[],...a.some(s=>s.kind==="run_js")?[' runJs is the exception: it returns the VALUE the code produced, whole (the journal shows a short preview); an exception thrown in the page comes back as the string "threw: \u2026" instead of stopping the run.']:[],...a.some(s=>s.kind==="download")?[` download also returns a VALUE: const watch = await w.download(); await w.click(await w.find('Download')); const result = await w.download({id:watch.id}); return result; result.downloads carries the actual path, filename, host and state. Read/upload only complete receipts. Query the same ID after a timeout; several candidates mean ambiguous, never the newest file. Re-arming this tab ends its previous collection; stop:true explicitly ends collection without canceling any download. On this session's own tabs a page's save dialog (showSaveFilePicker) never opens: its file arrives as a download marked source "save-picker".`]:[]],tail:[" w.act(kind, {\u2026}) \u2014 any verb by its wire name, arguments forwarded whole; returns its text like the platform verbs","","Observation changes use + for added controls, - for removed controls, and ~ for a surviving ref whose state or label changed. Scope and grouping explanations repeat only when their context changes; observe() still returns the complete available data.","The changed/frozen verdict compares this run's end with the previous run's end: a run whose last action puts the","surface back where it started reads as unchanged \u2014 return your own intermediate observe() results when you need proof.","","Rules that are enforced, not advised:"," \xB7 one verb in flight \u2014 a second one before the first resolves is refused by name (one focus, one caret)"," \xB7 a ref read on surface A is refused on surface B \u2014 act on it through A's handle"," \xB7 a handle whose surface is gone throws: re-acquire with computer.surfaces()"," \xB7 100 actions and 60 s per call; reads, find(), waits, verification and computer.log() also spend this budget"," \xB7 nothing outside this API exists but URL and URLSearchParams: no require, import, fetch, process, timers, console","","Persistence: top-level const/let/var/function/class declarations live until computer_repl_reset. Keep a","handle in one and reuse it; collect results in a variable across calls; return only what you need.","Batch at verified task boundaries, with room for observation and recovery. A helper's nested verbs also count: a loop of 35 find/setText/expect sequences exceeds 100 actions. Split it into smaller calls.","Keep a top-level checkpoint after each confirmed stage. After an uncertain save/submit, inspect or expect its result before resuming; replaying the whole batch can submit twice. A dispatched checkpoint is not proof of success. Reset or runtime restart loses checkpoints, so recover from surface facts then.","Returned values are capped at 12000 characters and a cut JSON value is not parseable. Read a large result once into a top-level variable, return a small slice per call, and check the final count/identity; do not re-read or replay the task to retrieve the next slice."]}}function b(e){const a=l(e);return[...a.head,...a.reads,...a.actions,...a.extras,...a.tail].join(`
|
|
2
|
+
`)}function l(e){const a=e.extraVerbs();return{head:["API reference \u2014 computer_repl (this text comes once per session; computer.help() returns it again)","","Everything is async \u2014 the verbs AND computer.surfaces() / computer.surface(\u2026): await each. A verb that","fails THROWS with the reason, which stops the run; the journal in the result says what ran. Refs (e12)","come from observe()/find() and stay valid until the surface changes; never invent one, and never carry one in from","another session \u2014 a fresh session re-mints them, so the same e12 names a different element. A target `t` is "+"a find() handle, a ref string, or a point {x, y} read off a screenshot"+".","","Bind a surface first \u2014 every verb below is a method on the handle it returns, never a name of its own:",' await computer.surfaces() \u2192 [handle] \u2014 every surface (tab / window / app) as {id, kind, title, active}; `id` is an opaque key, what the platform knows the surface by lives in `title` (macOS: "TextEdit (com.apple.TextEdit) \u2014 Untitled"; Windows: the window title, then the program name \u2014 "Untitled - Notepad \u2014 Notepad")'," await computer.surface(name) \u2192 handle \u2014 by id, by whole title, or by a piece of the title; several matches or none is a refusal that lists the candidates, never a guess",' await computer.surface() \u2192 handle \u2014 the ONE surface, when there is only one. Not "whatever is in front": with more than one open it refuses and lists them, so a script always says which it meant'," Bind once per surface and keep the handle in a top-level declaration \u2014 it stays valid for later calls, and re-binding costs a read each time."," A browser tab marked unloaded:true is still discoverable. Using its handle restores that exact tab in the background after acquiring ownership; merely listing tabs never reloads it.",""],reads:[...e.interactionNotes(),"Reads (return data to your code; none of them is shown to you except through `return` or computer.log):",e.readsOffscreen?" w.observe({screenshot?}) \u2192 {url, title, elements:[{ref, role, name, state, rect, offscreen?}], groups?, headings?, partial?, coverage, target?} \u2014 every control, viewport and beyond; filter the array rather than asking it to narrow. `rect` {x, y, w, h} is the control's box \u2014 compare boxes to answer layout questions (which row a control sits on, whether it moved) without a screenshot. `groups` is the page's OWN labelling of those controls \u2014 [{label, members:[ref]}] over that same set, off-viewport members included \u2014 present where the author wrote one, and the only place some facts exist (a feed labels each post's button row with its counts, bookmarks included, which no control name and no page text carries). `headings` (web surfaces) is [{name, level, rect}] for the page's headings, never listed as controls \u2014 the fixed point to measure a control's box against, since a click can scroll the container under every control in a card. `partial` (a reason) says the read stopped before covering the surface, so what it did not list may still be there. screenshot:true also attaches the picture of the same screen (one call, two reads), for when you need to SEE where something is":" w.observe({screenshot?}) \u2192 {url, title, elements:[{ref, role, name, state, rect}], coverage} \u2014 every control the window SHOWS (`rect` {x, y, w, h} is its box \u2014 compare boxes to answer layout questions without a screenshot); one scrolled out of view is counted in the observation and never listed, so no ref names it until a scroll brings it in; filter the array rather than asking it to narrow. screenshot:true also attaches the picture of the same screen"," coverage reports exact {offscreen, covered, capped, modal?} counts (null when unreported) even when repeated context notes are omitted. Electron target reports the CDP endpoint, windowId and, when its user agent carries them, the app's own name/version; never infer the app version from the tool or helper path."," w.find(name, {role?, within?, "+(e.readsOffscreen?"visible?, ":"")+"near?}) \u2192 {ref, name, matches, others, incidental?} | null \u2014 by visible name, "+(e.readsOffscreen?"whole surface":"everything the window shows \u2014 NOT what is scrolled out of it, which this platform's read does not contain")+" (an exact name beats a partial one, a partial the query is most of beats one it is only a fragment of, and a control beats a text label); a handle that re-resolves itself when used, so a loop survives a re-render; `matches` says how many controls carried the name and `others` names the runners-up, so an ambiguous name is visible BEFORE you act on the pick; `incidental: true` says NOTHING here is NAMED that and the pick is only a long name that happens to contain the word \u2014 the one `matches: 1` not to trust; narrow it with role (button / textbox / \u2026), within (a ref or handle \u2014 matches inside that element's box, e.g. one sheet or one message)"+(e.readsOffscreen?" visible (on screen only)":"")+" and near (a labelled group restricts candidates to its members; otherwise prefer the control closest to the named element). Duplicate group labels or same-named controls within a group still need disambiguation"," w.text() \u2192 the surface's full readable text",...e.readValue?[" w.value(t) \u2192 ONE control's whole value as a string \u2014 a long field, a clipped label, the text you are about to edit. The listing shows a value cut to what a line holds; this is the rest of it, for one element, where text() re-reads the entire surface"]:[],...e.readPage?[" w.page({ref?, depth?, node?}) \u2192 the content tree as text, with a completeness receipt on its first line"]:[]," w.screenshot({ref?, padding?|region:{x,y,width,height}?|fullPage?, path?}) \u2192 {path, host, width, height, note, capture?} \u2014 the image is attached to this result for you to see AND written to a file the code can keep: to `path` (absolute, never overwrites an existing file, extension must match the surface's format \u2014 .jpg on web surfaces, .png on macOS and Windows) or else to a rolling temp directory; `host` names the machine the path is on; no image \u2192 {path: null, note: why}. ref is a ref string and crops to a visible element's box; padding keeps its surroundings. fullPage is web-only. On macOS and Windows, ref/region preserve native detail; region uses FULL screenshot pixels, so take a full screenshot first. Windows padding and crop bounds use physical pixels; macOS padding uses full-screenshot pixels when available, otherwise window points. capture reports the window, crop, units, pointsPerPixel, and toFullScreenshot (null without a full screenshot or when window geometry changed). Windows refuses a region based on old window geometry; take a new full screenshot. Convert crop pixel p to full screenshot coordinates with toFullScreenshot.origin + p * toFullScreenshot.scale on each axis. A crop never changes point-action coordinates; use a current mapping or act by ref.",...e.supportsDiagnostics?[` w.diagnostics({id?}) \u2192 ${e.diagnosticsAbout.empty} \u2014 the newest 20, with the ones new since your last read marked "new"`]:[]," computer.log(\u2026) \u2192 a line in the journal; the one member that is not on a surface",""],actions:["Actions (every one is w.verb(\u2026) on a bound handle, which switches to that surface first):"," w.click(t, {button?, count?}) w.hover(t)"," w.setText(t, text) \u2014 replace the target field's ENTIRE value, even after selectText; never a selection/caret edit. Returns true when read-back matches; throws WriteVerificationError on mismatch, stopping the run unless caught."," w.setTextBestEffort(t, text) \u2014 the same whole-field replacement, returning true/false without throwing on mismatch. Both return undefined when read-back is unavailable; that is not verification."," click and hover also take a POINT \u2014 w.click({x, y}) \u2014 in the coordinates of the last screenshot, for a control you"," can see and cannot name. It checks no identity (no stale-ref gate, no name, no surface check) and so acts on"," whatever is at that spot NOW: take observe({screenshot: true}) and read the coordinates off that picture."," click / hover / keys / scroll / focus answer with what they did, as text \u2014 the same note the journal shows, e.g. which"," rung a click ended on. A verb that FAILS throws instead, so a returned string always describes something that happened."," w.keys(chord, count?, {focus?}) e.g. w.keys('Enter'), w.keys('Backspace', 3), w.keys('Enter', {focus: field}) \u2014 one key or chord, never a text string. setText replaces a whole field; use the platform's paste/type verb to edit at a selection or caret."," keys go wherever the surface has the keyboard focus. Pass focus: <target> to REQUIRE it be there: if it is not,"," nothing is pressed, including when focus is unknown or ambiguous. Omit focus only to send without that check. Focusing stays explicit (click / setText / selectText with a caret)."," w.scroll({direction?: up|down|left|right, count?: screens (default 1), to?: top|bottom, ref?}) w.wait({} | {ms} | {text} | {text_gone} | {control} | {control_gone} | {url} | {url_gone} | {loaded}) \u2192 ok"," count covers that many screens in ONE call \u2014 the note says how far it actually got, which is less once the end is reached;"," count and to: are refused together, since to: already goes as far as the surface scrolls."," Browser/Electron list scrollable regions with refs. With several regions, pass ref:<region or a control inside it \u2014 a ref or a find() handle>; a bare scroll refuses and lists the choices. ref:'window' explicitly scrolls the page. Reaching an edge never switches to another region.",...e.scrollsInto?[" w.scroll({into: t}) instead brings ONE control (a ref or a find() handle) to the middle of the viewport and says whether anything still covers it. It takes a covered control's ref too \u2014 the way out from under a sticky header or a bottom strip that a screen of scrolling overshoots."]:[]," a wait polls until the condition holds or it times out, and says which: {text\u2026} searches the readable text,",' {control\u2026} the CONTROLS by name ("until the Stop button is gone" \u2014 the honest way to wait out a long answer),'," {url}/{url_gone} where the surface IS (substring, so a query string needs no spelling out), and {loaded:true} that"," the DOCUMENT finished. Reach for it before reporting that a page has nothing on it: a document that never finishes"," reads as an empty page and no content condition can tell you which it was. It is NOT rendered \u2014 a single-page app"," reaches loaded with nothing drawn yet (measured), so follow it with the CONTENT wait, which is w.wait({}):",' it polls until the readable text stops changing, so it is the one "the page is ready" condition that needs no word'," from the page and no knowledge of the site. Use it after a navigation, a tab switch inside a page, or a search \u2014 a"," single-page app mounts its rows well after {loaded}, and an observation taken before that reads as an empty page."," Also after a click that REDRAWS content (a chart, a list) and before you screenshot or read it: the control's own state"," updates at once, the content a beat later, so a read straight after the click can pair the new button with the old view."," Every condition takes ms as its OWN polling budget (default 16s): w.wait({text:'Done', ms:3000}) gives up in three seconds,"," give or take the one read already in flight when it runs out \u2014 on a heavy surface a single read is seconds, and the result says so when that happened."," wait() returns true/false; awaiting it alone does NOT stop on timeout. Use expect() for a required postcondition, or check the result: if (!await w.wait({...})) throw new Error('condition not met');"," which is what a GUESS at the wording is worth \u2014 an unguarded guess that no page ever matches costs the full 16s."," w.focus() \u2014 select w as the target for this runtime; target selection is not an OS-activation guarantee (see platform interaction notes)",` w.release() \u2014 release this target and its input resources for other sessions. Claims span calls until release, reset, turn end, process exit or ${p/6e4} minutes with no call (the next call takes the target back unless another session has it by then). Observe again before acting after handoff; old refs are invalidated. Separate windows can still share app/account/document state; ownership does not isolate those or a human editor.`," w.expect({text, control, url, value, equals, ms}) \u2014 verify exactly ONE condition; value takes a ref/find handle and equals:string. Read-only polling, default 3000 ms, maximum 16000 ms. Returns true when verified; otherwise throws before subsequent steps. It never repeats the preceding action. Use it to verify save/submit results, not screen-change verdicts."," w.setText(t, text, {ifValue:previousValue}) \u2014 optional precondition: refuse before writing if the field changed or cannot be read. This is a checked precondition, not an atomic transaction with a human or another app.","Action journal evidence: [verified] confirms the stated value/condition; [dispatched] only confirms delivery; [blocked] means no dispatch; [uncertain] may already have acted. Never replay an uncertain send/submit/delete; inspect its actual result first."],extras:[...a.length?[" This platform's verbs \u2014 each returns what it did as text (the journal keeps only its first line, so a listing is read from the return value):"," In these signatures, ? marks an optional property; every unmarked property is required. Omit the ? in JavaScript calls.",...a.map(s=>f(s.kind,s.schema))]:[],...a.some(s=>s.kind==="select_option")?[" selectOption example: const choice = await w.find('Priority', {role:'combobox'}); if (!choice || choice.matches !== 1 || choice.incidental) throw new Error('Priority is missing or ambiguous'); await w.selectOption({ref:choice.ref, option:'High'});"]:[],...a.some(s=>s.kind==="run_js")?[` runJs is the exception: it returns the VALUE the code produced, whole (the journal shows a short preview); an exception thrown in the page comes back as the string "threw: \u2026" instead of stopping the run. It waits 10s for that value; runJs({code, ms}) waits up to 45s for code meant to run longer, within the call's 60s.`]:[],...a.some(s=>s.kind==="download")?[` download also returns a VALUE: const watch = await w.download(); await w.click(await w.find('Download')); const result = await w.download({id:watch.id}); return result; result.downloads carries the actual path, filename, host and state. Read/upload only complete receipts. Query the same ID after a timeout; several candidates mean ambiguous, never the newest file. Re-arming this tab ends its previous collection; stop:true explicitly ends collection without canceling any download. On this session's own tabs a page's save dialog (showSaveFilePicker) never opens: its file arrives as a download marked source "save-picker".`]:[]],tail:[" w.act(kind, {\u2026}) \u2014 any verb by its wire name, arguments forwarded whole; returns its text like the platform verbs","","Observation changes use + for added controls, - for removed controls, and ~ for a surviving ref whose state or label changed. Scope and grouping explanations repeat only when their context changes; observe() still returns the complete available data.","The changed/frozen verdict compares this run's end with the previous run's end: a run whose last action puts the","surface back where it started reads as unchanged \u2014 return your own intermediate observe() results when you need proof.","","Rules that are enforced, not advised:"," \xB7 one verb in flight \u2014 a second one before the first resolves is refused by name (one focus, one caret)"," \xB7 a ref read on surface A is refused on surface B \u2014 act on it through A's handle"," \xB7 a handle whose surface is gone throws: re-acquire with computer.surfaces()"," \xB7 100 actions and 60 s per call; reads, find(), waits, verification and computer.log() also spend this budget"," \xB7 nothing outside this API exists but URL and URLSearchParams: no require, import, fetch, process, timers, console","","Persistence: top-level const/let/var/function/class declarations live until computer_repl_reset. Keep a","handle in one and reuse it; collect results in a variable across calls; return only what you need.","Batch at verified task boundaries, with room for observation and recovery. A helper's nested verbs also count: a loop of 35 find/setText/expect sequences exceeds 100 actions. Split it into smaller calls.","Keep a top-level checkpoint after each confirmed stage. After an uncertain save/submit, inspect or expect its result before resuming; replaying the whole batch can submit twice. A dispatched checkpoint is not proof of success. Reset or runtime restart loses checkpoints, so recover from surface facts then.","Returned values are capped at 12000 characters and a cut JSON value is not parseable. Read a large result once into a top-level variable, return a small slice per call, and check the final count/identity; do not re-read or replay the task to retrieve the next slice."]}}function b(e){const a=l(e);return[...a.head,...a.reads,...a.actions,...a.extras,...a.tail].join(`
|
|
3
3
|
`)}function y(e){const a=l(e);return[`Reaching a second platform: ${e.platform} \u2014 everything else in the reference above still holds, and these are the reads and verbs that are its own.`,"",...a.reads,...a.extras].join(`
|
|
4
4
|
`)}export{g as REPL_DESCRIPTION,v as RESET_DESCRIPTION,b as apiReference,y as platformReference};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import{createContext as Be,runInContext as We}from"node:vm";import{Type as h}from"typebox";import{spec as
|
|
1
|
+
import{createContext as Be,runInContext as We}from"node:vm";import{Type as h}from"typebox";import{spec as be,text as L}from"../types.js";import{MAX_ACTIONS as ye,MAX_MS as oe,SYNC_MAX_MS as $e,RETURN_CAP as ae,MAX_REF_OWNERS as qe,trimObservation as Ge}from"./repl-budget.js";import{createComputerUseApi as Ke,noteOf as Se,renderJournal as Xe,stringify as ze,verbGate as Ye}from"./api.js";import{prepareCode as Qe}from"./repl-code.js";import{apiReference as Re,platformReference as Ve,REPL_DESCRIPTION as Ze,RESET_DESCRIPTION as et}from"./repl-docs.js";import{ComputerUseOwnership as tt,IDLE_RELEASE_MS as nt,OwnershipBusyError as Ee}from"../ownership.js";const ke=new Set(["observe","find","text","value","page","screenshot","diagnostics","targets","log","verb","release"]),rt=new Set(["dialog","close_tab"]);function ht(W,A={}){if(!W.length)throw new Error("createReplTools needs at least one platform");let M=null;const E=A.ownership??new tt,m=new WeakMap,q=new WeakMap;let j=null,P=!1,le=Promise.resolve(),G=!1,I=null,K=null;const Te=A.idleReleaseMs??nt;let X=null,N=null,k,Z=!!A.docsDelivered,ee=!!A.docsDelivered,C=null;const O=()=>{if(!C)throw new Error("no computer_repl call is in progress \u2014 every verb must be awaited inside the call that started it");return C},v=new Map,$=new Map,S=new WeakMap;for(const{driver:e}of W)e.onDisconnect(async()=>{for(const n of $.values()){if(S.get(n)?.slice.driver!==e)continue;const t=m.get(n);t&&await E.release(t),m.delete(n);for(const[s,i]of v)i===n.id&&v.delete(s)}});const U=W.length>1;let b=null,R=null;const Oe=e=>{const n=b?.id;if(n){for(const t of e)v.delete(t),v.set(t,n);for(const t of v.keys()){if(v.size<=qe)break;v.delete(t)}}},_e=e=>{if(!b)return;const n=v.get(e);if(n&&n!==b.id)throw new Error(`ref ${e} was read on surface ${n}, not on ${b.id} (${b.title}) \u2014 act on it through computer.surface('${n}'), or observe ${b.id} and use one of its refs`)},te=Ye(e=>C?.push("verb","",!1,e)),d=W.map(e=>({...e,id:e.driver.platform,api:Ke(e.driver,O,{onRefs:Oe,onTarget:_e,beforeAction:async(n,t)=>{if(!b)throw new Error("An action needs a bound surface.");const s=m.get(b);if(!s)throw new Error("The target was released; observe it before acting again.");try{await E.claim(s,await e.driver.actionResources(n,t))}catch(i){throw i instanceof Ee&&(j=i.message),i}}}),docsDue:!A.docsDelivered,lastMiss:null}));if(k=d[0],U)for(const e of d)e.driver.surfaceIdPrefix=`${e.id}/`;const ce=d[0].api,F=async()=>{const e=[],n=R?[R]:d;for(const t of n){let s;try{s=await t.driver.listTargets()}catch(i){if(n.length===1)throw i;const a=i instanceof Error?i.message:String(i);t.lastMiss!==a&&C?.push("targets",t.id,!1,`${t.id} is not reachable: ${a}`),t.lastMiss=a;continue}t.lastMiss=null;for(const i of s)e.push({...i,id:U?`${t.id}/${i.id}`:i.id,slice:t,inner:i.id})}return e};class H extends Error{}const ne=async e=>{const n=S.get(e);if(k=n.slice,N=e,await n.slice.driver.isActiveTarget(n.inner)){await re(e,!0);return}O().charge("focus");const t=await n.slice.driver.focusTarget(n.inner);if(O().push("focus",e.id,t.ok,Se(t)||`\u2192 ${e.title}`),!t.ok)throw $.delete(e.id),new H(`surface ${e.id} (${e.title}) is gone from this session \u2014 closed, replaced or out of reach; when that is known, a NOTE in this result or the one before it names which (it is said once, when the loss is seen). Call computer.surfaces() and re-acquire what you need.`);await re(e,!1)},re=async(e,n)=>{const{slice:t,inner:s}=S.get(e),i=t.driver;let a=await i.ownershipResources();const l=m.get(e);if((!l||l!==a[0]?.key)&&!await i.isActiveTarget(s)){const u=await i.focusTarget(s);if(!u.ok)throw new H(`Surface ${e.id} disappeared while binding; list surfaces and re-acquire it.`);if(O().push("focus",e.id,!0,Se(u)||`\u2192 ${e.title}`),a=await i.ownershipResources(),!await i.isActiveTarget(s))throw new Error("The target kept changing while binding; observe before continuing.")}const f=a[0]?.key;if(!f)throw new Error("The driver did not identify a resource to claim.");const T=!!l&&l!==f;if(T&&!a.some(u=>q.get(e)?.has(u.key)))throw $.delete(e.id),m.delete(e),q.delete(e),await E.release(l),new Error("This handle now resolves to a different target. List surfaces and bind the intended one again.");try{await E.claim(f,a)}catch(u){throw u instanceof Ee&&(j=u.message),u}T&&await E.release(l),m.set(e,f),q.set(e,new Set(a.map(u=>u.key))),!l&&!n&&i.invalidateObservation(),await i.prepareTarget()},xe=new Set(["openUrl","newTab"]),Le=async e=>{const{slice:n,inner:t}=S.get(e);return await n.driver.heldNothing()?t?!(await n.driver.listTargets()).some(s=>s.id===t):!0:!1},Ie=async e=>{const n=S.get(e),t=(await n.slice.driver.listTargets()).find(a=>a.active);if(!t)throw new Error("the tab was opened, but none is active afterwards \u2014 computer.surfaces() lists what is open");const s=m.get(e);s&&await E.release(s),m.delete(e),q.delete(e);for(const[a,l]of v)l===e.id&&v.delete(a);$.get(e.id)===e&&$.delete(e.id);const i=U?`${n.slice.id}/${t.id}`:t.id;Object.assign(e,{id:i,kind:t.kind,title:t.title,active:!0,own:t.own,mine:t.mine}),S.set(e,{slice:n.slice,inner:t.id}),$.set(i,e),k=n.slice,N=e,await re(e,!0)},Ne=async()=>{const e=await F();if(e.some(n=>n.mine))return null;for(const n of R?[R]:d){const t=await n.driver.heldNothing();if(t==="closed"||t==="not-yet"&&!e.length)return z({id:U?`${n.id}/new`:"new",kind:"tab",title:"no tab open \u2014 openUrl({url}) or newTab({url}) on this handle opens one",active:!1,own:!0,mine:!0,slice:n,inner:""})}return null},z=e=>{const n=$.get(e.id);if(n)return n.title=e.title,n.kind=e.kind,n.active=e.active,n.own=e.own,n.mine=e.mine,n.unloaded=e.unloaded,n;const t={id:e.id,kind:e.kind,title:e.title,active:e.active,...e.own===void 0?{}:{own:e.own},...e.mine===void 0?{}:{mine:e.mine},...e.unloaded?{unloaded:!0}:{}};S.set(t,{slice:e.slice,inner:e.inner});const s={};for(const[i,a]of Object.entries(e.slice.api))typeof a!="function"||i==="log"||i==="focus"||i==="targets"||(s[i]=async(...l)=>{b=t;try{if(xe.has(i)&&await Le(t)){m.has(t)||m.set(t,`opening:${t.id}`);const f=await a(...l);return await Ie(t),f}if(!S.get(t).inner)throw new Error(`${t.id} holds no tab yet \u2014 openUrl({url}) or newTab({url}) on it opens one; nothing else can act until then`);try{await ne(t)}catch(f){if(i==="wait"&&f instanceof H)return`${t.id} is gone \u2014 closed or replaced, so there is nothing left on it to wait for. computer.surfaces() lists what remains`;throw f instanceof H&&await S.get(t).slice.driver.heldNothing()?new H(`${f.message} This session holds no tab now: openUrl({url}) or newTab({url}) on this handle opens a fresh one.`):f}return await a(...l)}finally{b=null}});return s.focus=async()=>{b=t;try{await ne(t)}finally{b=null}},s.release=async()=>{O().charge("release");const i=m.get(t);await e.slice.driver.releaseInputResources(),i&&await E.release(i),m.delete(t),e.slice.driver.invalidateObservation();for(const[a,l]of v)l===t.id&&v.delete(a);return O().push("release",t.id,!0,"ownership released; observe again before continuing"),"Ownership released. Other sessions may now use this target; observe before continuing."},Object.assign(t,te(s)),$.set(e.id,t),t},Ce=(e,n)=>{const t=c=>c.mine?" (yours \u2014 opened by this session)":c.own&&c.mine===!1?" (left open by an earlier session)":"",s=c=>c.mine?0:c.own?1:2,i=c=>[...c].sort((p,_)=>s(p)-s(_)).map(p=>` ${p.id} ${p.title}${t(p)}`).join(`
|
|
2
2
|
`);if(!e.length)throw new Error("this driver has no surface right now");if(n===void 0){if(e.length===1)return e[0];const c=e.filter(_=>_.mine),p=c.filter(_=>_.active);if(p.length===1)return p[0];if(c.length===1)return c[0];throw new Error(`${e.length} surfaces are open \u2014 name one, computer.surface('<id or title>'):
|
|
3
|
-
${i(e)}`)}const a=String(n).trim(),l=e.filter(c=>c.id===a);if(l.length===1)return l[0];const
|
|
3
|
+
${i(e)}`)}const a=String(n).trim(),l=e.filter(c=>c.id===a);if(l.length===1)return l[0];const f=a.toLowerCase(),T=e.filter(c=>c.title.toLowerCase()===f),u=T.length?T:e.filter(c=>c.title.toLowerCase().includes(f));if(u.length===1)return u[0];const J=u.filter(c=>c.primary);if(J.length===1)return J[0];if(!u.length){const c=[...new Set(d.map(p=>p.driver.explainMiss(a)).filter(Boolean))].join(`
|
|
4
4
|
`);throw new Error(`no surface matches ${JSON.stringify(a)} \u2014 the open ones are:
|
|
5
5
|
${i(e)}${c?`
|
|
6
|
-
${c}`:""}`)}throw new Error(`${
|
|
7
|
-
${i(
|
|
6
|
+
${c}`:""}`)}throw new Error(`${u.length} surfaces match ${JSON.stringify(a)} \u2014 name one exactly:
|
|
7
|
+
${i(u)}`)},De=async e=>{try{return await de(e)}catch(n){const t=e.toLowerCase(),s=(await F()).filter(i=>i.active&&(i.id===e||i.title.toLowerCase().includes(t)));if(s.length===1)return s[0];throw n}},de=async e=>{const n=await F(),t=e===void 0?"":String(e).trim();if(t&&!n.some(s=>s.id===t||s.title.toLowerCase().includes(t.toLowerCase())))for(const s of R?[R]:d){const i=await s.driver.unlistedTarget(t);if(i)return{...i,id:U?`${s.id}/${i.id}`:i.id,slice:s,inner:i.id}}return Ce(n,e)},Ae=te({surfaces:async()=>{O().charge("targets");const e=await F();return O().push("targets","",!0,`${e.length} surfaces`),e.map(z)},surface:async e=>(e===void 0?await Ne():null)??z(await de(e)),log:ce.log,help:()=>X=d.map(e=>Re(e.driver)).join(`
|
|
8
8
|
|
|
9
|
-
`)}),Me=new Set(Object.keys(
|
|
10
|
-
${o?Re(r.driver):Ve(r.driver)}`,pinned:!0}});ee&&(ee=!1,
|
|
11
|
-
This computer_repl runtime was restarted (idle), so top-level bindings and refs from before are gone; the API reference you already have still applies \u2014 computer.help() returns it again.`,pinned:!0})),p?k.driver.noteWriteRound(
|
|
12
|
-
`),{verdict:"unknown",ledgerLines:g.length,timing:
|
|
13
|
-
`),{verdict:"unknown",ledgerLines:g.length,timing:
|
|
9
|
+
`)}),Me=new Set(Object.keys(ce)),ue=(e,n)=>{const t=/^(?:ReferenceError: )?(\w+) is not defined$/.exec(e);return t?Me.has(t[1])?`${e} \u2014 verbs live on a surface handle, not on the top level: const w = await computer.surface('<id or title>'); await w.${t[1]}(\u2026). computer.surface() with no argument binds the only surface, or the one this session opened among several; computer.surfaces() lists them.`:`${e} \u2014 `+(n?"this call started a FRESH runtime, so nothing declared in an earlier call survives (the server was restarted, or its host reaped it while idle). ":"no call on this runtime declared it at the top level, and nothing is predefined but computer, URL and URLSearchParams. ")+"If it is a surface handle, bind it: const w = await computer.surface('<id or title>').":e},je=()=>Be({computer:Ae,console:void 0,URL,URLSearchParams}),Pe=e=>Object.entries(e).flatMap(([n,t])=>t&&typeof t=="object"&&S.has(t)&&$.get(t.id)===t?[`${n} \u2192 ${t.id} \xAB${t.title}\xBB`]:[]),Y=be({name:"computer_repl",label:"Computer REPL",description:Ze,promptSnippet:"computer_repl runs JavaScript over the GUI in a persistent session: verbs, surface handles, one observation per call.",promptGuidelines:["Plan several actions per computer_repl call \u2014 one call costs one step and one observation whatever it does inside.","Keep surface handles and collected data in top-level declarations; they persist across calls.","Re-find by name inside a loop rather than reusing a ref you read earlier: find() handles re-resolve, raw refs do not survive a re-render."],parameters:h.Object({code:h.String({description:"JavaScript, run as the body of an async function in the persistent runtime. `await` every verb; `return` what you want back. Bind once \u2014 first call: `const w = await computer.surface();` \u2014 binding opens nothing (a browser page is opened with `await w.openUrl({url})` once bound), then later calls use `w` directly: `for (const e of (await w.observe()).elements.filter(e => e.role === 'checkbox')) await w.click(e.ref);`"}),purpose:h.Optional(h.String({maxLength:500,description:"Briefly describe the intended task when requesting app access. Shown to the user as the agent's stated purpose; it does not narrow the app permission. Do not include secrets."})),interaction:h.Optional(h.Union([h.Literal("background"),h.Literal("desktop")],{description:"Native apps default to background \u2014 macOS: driver mouse/activation/clipboard routes are blocked, including fallback; Windows: limited UIA setText/click/scroll by ref, with automatic focus and real-input fallback disabled; unsupported routes are refused. Use desktop only after explaining effects and obtaining permission covering this call. This option is not user consent."})),platform:h.Optional(h.String({description:'For a HOST that mounts several platforms in one session: the platform this call is about ("browser", "electron", "macos", "windows"). computer.surfaces() and computer.surface() then see only its surfaces, and the observation is of it; the other platforms stay mounted and untouched.'})),target:h.Optional(h.String({description:"Bind this surface before the code runs \u2014 the same string computer.surface() takes (an id, a whole title, or a piece of one). For a HOST that decides which surface a call is about: the code can still bind whatever it likes, and this only settles where a run that binds nothing lands."})),observation:h.Optional(h.Union([h.Literal("full"),h.Literal("brief"),h.Literal("none")],{description:'How much of the end-of-run observation to render. "full" (default) is the element list and the text excerpt; "brief" keeps the head (title, targets, verdict) and drops the listing; "none" leaves only the action journal. The observation is always TAKEN either way \u2014 the change verdict and the stale-ref bookkeeping are unaffected \u2014 and a NOTE line always survives, so a swallowed click is never hidden by this. Reach for it when the window holds content you do not want in the transcript, or when your own `return` value is the answer.'}))}),async execute(e){j=null,X=null;const n=String(e.code??"");if(!n.trim())return L("computer_repl needs code to run.",{error:"bad_args"});const t=String(e.platform??"").trim();if(R=t?d.find(r=>r.id===t)??null:null,t&&!R)return L(`platform ${JSON.stringify(t)} is not mounted in this session (mounted: ${d.map(r=>r.id).join(", ")}).`,{error:"bad_args"});R&&(k=R);const s=e.purpose;for(const r of d)r.driver.interactionMode=e.interaction==="desktop"?"desktop":"background",r.driver.consentPurpose=typeof s=="string"&&s.trim().slice(0,500)||null,r.driver.permissionRefusal=null;const i=e.observation,a=i==="brief"||i==="none"?i:"full",l=[],f=[],T=Date.now(),u=()=>d.reduce((r,o)=>r+o.driver.waitedOnUserMs,0),J=u();let c=0,p=null,_=!1,Q=null;const fe={fields:0,verified:0,ended:null};C={round:fe,charge(r){if(Q??=Date.now(),P)throw new Error("The runtime was reset; this run stops before its next operation.");if(++c>ye)throw new Error(`step cap reached (${ye} actions) at ${r} \u2014 split the work across calls, or narrow the loop`);if(Date.now()-T-(u()-J)>oe)throw new Error(`time cap reached (${oe/1e3}s) at ${r} \u2014 the surface may be slower than the code assumes`)},push(r,o,y,x,ge,me){!ge&&y&&!ke.has(r)&&!rt.has(r)&&(p=r),!ge&&!y&&!ke.has(r)&&(_=!0);const ve=Q===null?void 0:Date.now()-Q;Q=null,l.push({i:l.length+1,kind:r,target:o,ok:y,note:x,...me?{outcome:me}:{},...ve!==void 0?{ms:ve}:{}})},attach(r){f.push(r)},remainingMs:()=>oe-(Date.now()-T-(u()-J))};const he=!M;M??=je();let V,w=null;try{const r=String(e.target??"").trim();r&&await ne(z(await De(r))),V=await We(Qe(n),M,{filename:"computer_repl",timeout:$e})}catch(r){r.code==="ERR_SCRIPT_EXECUTION_TIMEOUT"&&(r=new Error(`the code ran for ${$e/1e3}s without awaiting anything and was stopped \u2014 a loop that never awaits blocks the whole runtime; await a verb (or w.wait) inside it`)),w=ue(r instanceof Error?`${r.name==="Error"?"":`${r.name}: `}${r.message}`:String(r),he)}finally{try{await te.drain()}catch(r){w??=ue(r instanceof Error?r.message:String(r),he)}C=null}const pe=Date.now()-T,we=(pe/1e3).toFixed(1),B=r=>({runMs:pe,...r!==void 0?{observeMs:r}:{},steps:l.map(o=>({kind:o.kind,target:o.target,ok:o.ok,...o.outcome?{outcome:o.outcome}:{},...o.ms!==void 0?{ms:o.ms}:{},note:o.note.slice(0,160)}))}),g=[w?`Run STOPPED after ${l.length} steps (${we}s): ${w}`:`Run completed ${l.length} steps in ${we}s.`,...Xe(l)];if(w&&l.length>0&&g.push("Nothing was returned: the throw ended the call before `return`. What survives a stop \u2014 top-level bindings (read them in your next call) and anything already passed to log() (in the journal above)."),w){for(const o of await F().catch(()=>[])){const y=$.get(o.id);y&&(y.title=o.title)}const r=Pe(M);r.length&&g.push(`Still bound: ${r.join(", ")} \u2014 use ${r.length===1?"it":"them"} directly in the next call.`)}if(V!==void 0){const r=ze(V);g.push(`Returned: ${r.length>ae&&V!==X?`${r.slice(0,ae)}\u2026 (cut at ${ae} of ${r.length} chars \u2014 return a slice, a count or a filtered list instead of the whole thing)`:r}`)}const D=d.filter(r=>r.docsDue&&(r===k||d.length===1)).map(r=>{r.docsDue=!1;const o=!Z;return Z=!0,{type:"text",text:`
|
|
10
|
+
${o?Re(r.driver):Ve(r.driver)}`,pinned:!0}});ee&&(ee=!1,D.push({type:"text",text:`
|
|
11
|
+
This computer_repl runtime was restarted (idle), so top-level bindings and refs from before are gone; the API reference you already have still applies \u2014 computer.help() returns it again.`,pinned:!0})),p?k.driver.noteWriteRound(fe):_&&k.driver.noteContentRead();const Je=p?{afterAction:p}:{},se=d.find(r=>r.driver.permissionRefusal)?.driver.permissionRefusal;if(X&&l.length===0){const r=L(g.join(`
|
|
12
|
+
`),{verdict:"unknown",ledgerLines:g.length,timing:B(),...w?{error:"run_stopped"}:{}});return r.content.push(...D),r}if(se||j){const r=se||j;w?.includes(r)||g.push(`Observation skipped: ${r}`);const o=L(g.join(`
|
|
13
|
+
`),{verdict:"unknown",ledgerLines:g.length,timing:B(),...w?{error:"run_stopped"}:{},...se?{permissionRefused:!0}:{ownershipRefused:!0}});return o.content.push(...f,...D),o}try{if(P||N&&(!m.has(N)||!await k.driver.isActiveTarget(S.get(N).inner))){const y=k.driver.takeNoteForResult(),x=L(`${g.join(`
|
|
14
14
|
`)}${y?`
|
|
15
15
|
NOTE: ${y}.`:""}
|
|
16
|
-
Observation skipped: the bound target was released, closed or replaced. List surfaces and bind the intended target before continuing.`,{verdict:"unknown",ledgerLines:g.length,timing:
|
|
17
|
-
`),...Je});return o.details.ledgerLines=g.length,o.details.timing=
|
|
16
|
+
Observation skipped: the bound target was released, closed or replaced. List surfaces and bind the intended target before continuing.`,{verdict:"unknown",ledgerLines:g.length,timing:B(),...w?{error:"run_stopped"}:{}});return x.content.push(...f,...D),x}const r=Date.now(),o=await k.driver.observeElements({prefix:g.join(`
|
|
17
|
+
`),...Je});return o.details.ledgerLines=g.length,o.details.timing=B(Date.now()-r),Ge(o,a),o.content.push(...f,...D),w&&(o.details.error="run_stopped"),o}catch(r){if(r.surfaceGone)throw r;const o=r.message,y=w&&w.includes(o)?"for the same reason":`${o} \u2014 observe again before the next action`,x=L(`${g.join(`
|
|
18
18
|
`)}
|
|
19
|
-
(re-observation failed: ${y}.)`,{verdict:"unknown",ledgerLines:g.length,timing:
|
|
19
|
+
(re-observation failed: ${y}.)`,{verdict:"unknown",ledgerLines:g.length,timing:B(),...w?{error:"run_stopped"}:{}});return x.content.push(...f,...D),x}}}),Ue=Y.execute.bind(Y),ie=()=>{I&&clearTimeout(I),I=null},Fe=()=>{I=null,!G&&(K=(async()=>{try{for(const e of d)await e.driver.releaseInputResources();await E.releaseAll();for(const e of d)await e.driver.releaseIdle().catch(()=>{})}catch{}})().finally(()=>{K=null}))};Y.execute=async e=>{ie(),K&&await K,G=!0;let n;le=new Promise(t=>{n=t});try{return await Ue(e)}finally{try{if(P){for(const t of d)await t.driver.releaseInputResources();await E.releaseAll(),P=!1}R=null;for(const t of d)t.driver.endCall(),t.driver.consentPurpose=null,t.driver.permissionRefusal=null}finally{G=!1,n(),ie(),I=setTimeout(Fe,Te),I.unref?.()}}};const He=be({name:"computer_repl_reset",label:"Computer REPL reset",description:et,promptSnippet:"computer_repl_reset discards the persistent JavaScript bindings; surfaces are untouched.",parameters:h.Object({}),async execute(e){const n=le;if(ie(),G)P=!0;else{for(const t of d)await t.driver.releaseInputResources();await E.releaseAll()}M=null,N=null,$.clear(),v.clear();for(const t of d)t.docsDue=!0;return Z=!1,ee=!1,e?.waitForIdle&&await n,L("The JavaScript runtime was reset: every binding is gone. The next computer_repl call starts fresh and returns the API reference. No surface was touched \u2014 tabs, windows and apps are as they were.",{reset:!0})}});return[Y,He]}export{ht as createReplTools};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tipUsing": {
|
|
3
|
+
"message": "The Mirasim agent is using this tab. Click for the panel."
|
|
4
|
+
},
|
|
5
|
+
"tipHandedOver": {
|
|
6
|
+
"message": "Handed to the Mirasim agent. Click for the panel."
|
|
7
|
+
},
|
|
8
|
+
"tipJoin": {
|
|
9
|
+
"message": "Click to hand this tab to the Mirasim agent."
|
|
10
|
+
},
|
|
11
|
+
"tipBlocked": {
|
|
12
|
+
"message": "Blocked by your Mirasim site rules. Click for the panel."
|
|
13
|
+
},
|
|
14
|
+
"tipRestricted": {
|
|
15
|
+
"message": "Mirasim cannot use this page. Click for the panel."
|
|
16
|
+
},
|
|
17
|
+
"tipPausedPrefix": {
|
|
18
|
+
"message": "Agent paused. "
|
|
19
|
+
},
|
|
20
|
+
"tipOfflinePrefix": {
|
|
21
|
+
"message": "Mirasim is not connected. "
|
|
22
|
+
},
|
|
23
|
+
"handOverFailed": {
|
|
24
|
+
"message": "Could not hand this tab over:"
|
|
25
|
+
},
|
|
26
|
+
"statusConnected": {
|
|
27
|
+
"message": "Connected"
|
|
28
|
+
},
|
|
29
|
+
"statusOffline": {
|
|
30
|
+
"message": "Not connected. Start Mirasim to use the agent."
|
|
31
|
+
},
|
|
32
|
+
"pause": {
|
|
33
|
+
"message": "Pause agent"
|
|
34
|
+
},
|
|
35
|
+
"resume": {
|
|
36
|
+
"message": "Resume agent"
|
|
37
|
+
},
|
|
38
|
+
"pausedNote": {
|
|
39
|
+
"message": "The agent is paused: every browser command is refused until you resume it."
|
|
40
|
+
},
|
|
41
|
+
"sectionThisTab": {
|
|
42
|
+
"message": "This tab"
|
|
43
|
+
},
|
|
44
|
+
"tabStateNone": {
|
|
45
|
+
"message": "Open the panel from the toolbar icon on a tab to manage that tab."
|
|
46
|
+
},
|
|
47
|
+
"tabStateRestricted": {
|
|
48
|
+
"message": "Browser pages cannot be handed to Mirasim."
|
|
49
|
+
},
|
|
50
|
+
"tabStateBlocked": {
|
|
51
|
+
"message": "Blocked by your site rules: the agent cannot use it."
|
|
52
|
+
},
|
|
53
|
+
"tabStateUsing": {
|
|
54
|
+
"message": "The agent is using this tab."
|
|
55
|
+
},
|
|
56
|
+
"tabStateHanded": {
|
|
57
|
+
"message": "Handed to the agent: it can use this tab."
|
|
58
|
+
},
|
|
59
|
+
"tabStateGranted": {
|
|
60
|
+
"message": "You allowed the agent to use this tab."
|
|
61
|
+
},
|
|
62
|
+
"tabStateOutsideAll": {
|
|
63
|
+
"message": "Not in the Mirasim group, but the agent can use it: all tabs are allowed."
|
|
64
|
+
},
|
|
65
|
+
"tabStateOutsideAsk": {
|
|
66
|
+
"message": "Not handed over: the agent asks you before using it."
|
|
67
|
+
},
|
|
68
|
+
"tabStateOutside": {
|
|
69
|
+
"message": "Not handed over: the agent cannot see this tab."
|
|
70
|
+
},
|
|
71
|
+
"handOver": {
|
|
72
|
+
"message": "Hand to agent"
|
|
73
|
+
},
|
|
74
|
+
"takeBack": {
|
|
75
|
+
"message": "Take back"
|
|
76
|
+
},
|
|
77
|
+
"sectionAgentTabs": {
|
|
78
|
+
"message": "Tabs the agent is using"
|
|
79
|
+
},
|
|
80
|
+
"agentTabsNone": {
|
|
81
|
+
"message": "None right now"
|
|
82
|
+
},
|
|
83
|
+
"sectionScope": {
|
|
84
|
+
"message": "What the agent can reach"
|
|
85
|
+
},
|
|
86
|
+
"scopeGroup": {
|
|
87
|
+
"message": "Only the Mirasim group"
|
|
88
|
+
},
|
|
89
|
+
"scopeGroupHint": {
|
|
90
|
+
"message": "Tabs you hand over, the agent's own tabs and their popups. It cannot see any other tab."
|
|
91
|
+
},
|
|
92
|
+
"scopeAsk": {
|
|
93
|
+
"message": "Ask me"
|
|
94
|
+
},
|
|
95
|
+
"scopeAskHint": {
|
|
96
|
+
"message": "The agent sees every tab's title and address, and asks you before using one outside the group."
|
|
97
|
+
},
|
|
98
|
+
"scopeAll": {
|
|
99
|
+
"message": "All tabs"
|
|
100
|
+
},
|
|
101
|
+
"scopeAllHint": {
|
|
102
|
+
"message": "The agent can see and use every tab."
|
|
103
|
+
},
|
|
104
|
+
"sectionSites": {
|
|
105
|
+
"message": "Site rules"
|
|
106
|
+
},
|
|
107
|
+
"blockedLabel": {
|
|
108
|
+
"message": "Never let the agent use these sites"
|
|
109
|
+
},
|
|
110
|
+
"sitesPlaceholder": {
|
|
111
|
+
"message": "One site per line, e.g. bank.com (covers its subdomains)"
|
|
112
|
+
},
|
|
113
|
+
"allowOnlyLabel": {
|
|
114
|
+
"message": "Only let the agent use these sites"
|
|
115
|
+
},
|
|
116
|
+
"save": {
|
|
117
|
+
"message": "Save"
|
|
118
|
+
},
|
|
119
|
+
"saved": {
|
|
120
|
+
"message": "Saved"
|
|
121
|
+
},
|
|
122
|
+
"sectionLog": {
|
|
123
|
+
"message": "Recently refused"
|
|
124
|
+
},
|
|
125
|
+
"logNone": {
|
|
126
|
+
"message": "Nothing refused yet"
|
|
127
|
+
},
|
|
128
|
+
"logTab": {
|
|
129
|
+
"message": "tab $1"
|
|
130
|
+
},
|
|
131
|
+
"reason_site_blocked": {
|
|
132
|
+
"message": "Blocked site"
|
|
133
|
+
},
|
|
134
|
+
"reason_site_not_allowed": {
|
|
135
|
+
"message": "Not on the allow list"
|
|
136
|
+
},
|
|
137
|
+
"reason_out_of_scope": {
|
|
138
|
+
"message": "Tab not handed over"
|
|
139
|
+
},
|
|
140
|
+
"reason_ask_pending": {
|
|
141
|
+
"message": "No answer in time"
|
|
142
|
+
},
|
|
143
|
+
"reason_declined": {
|
|
144
|
+
"message": "You declined"
|
|
145
|
+
},
|
|
146
|
+
"reason_paused": {
|
|
147
|
+
"message": "Agent paused"
|
|
148
|
+
},
|
|
149
|
+
"reason_clear_cookies": {
|
|
150
|
+
"message": "Clearing every cookie"
|
|
151
|
+
},
|
|
152
|
+
"reason_cookie_site": {
|
|
153
|
+
"message": "Cookies of a restricted site"
|
|
154
|
+
},
|
|
155
|
+
"footerExtension": {
|
|
156
|
+
"message": "Extension $1"
|
|
157
|
+
},
|
|
158
|
+
"footerBridge": {
|
|
159
|
+
"message": "bridge: $1"
|
|
160
|
+
},
|
|
161
|
+
"footerBridgeOld": {
|
|
162
|
+
"message": "bridge: an older version"
|
|
163
|
+
},
|
|
164
|
+
"askTitle": {
|
|
165
|
+
"message": "Let the Mirasim agent use this tab?"
|
|
166
|
+
},
|
|
167
|
+
"askBody": {
|
|
168
|
+
"message": "If you allow it, the agent can read and operate this page until the tab is closed."
|
|
169
|
+
},
|
|
170
|
+
"allow": {
|
|
171
|
+
"message": "Allow"
|
|
172
|
+
},
|
|
173
|
+
"deny": {
|
|
174
|
+
"message": "Don't allow"
|
|
175
|
+
},
|
|
176
|
+
"askGone": {
|
|
177
|
+
"message": "This tab has been closed."
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tipUsing": {
|
|
3
|
+
"message": "Mirasim Agent 正在使用这个标签页。点击打开面板。"
|
|
4
|
+
},
|
|
5
|
+
"tipHandedOver": {
|
|
6
|
+
"message": "已交给 Mirasim Agent。点击打开面板。"
|
|
7
|
+
},
|
|
8
|
+
"tipJoin": {
|
|
9
|
+
"message": "点击把这个标签页交给 Mirasim Agent。"
|
|
10
|
+
},
|
|
11
|
+
"tipBlocked": {
|
|
12
|
+
"message": "被 Mirasim 站点规则阻止。点击打开面板。"
|
|
13
|
+
},
|
|
14
|
+
"tipRestricted": {
|
|
15
|
+
"message": "Mirasim 无法使用这个页面。点击打开面板。"
|
|
16
|
+
},
|
|
17
|
+
"tipPausedPrefix": {
|
|
18
|
+
"message": "Agent 已暂停。"
|
|
19
|
+
},
|
|
20
|
+
"tipOfflinePrefix": {
|
|
21
|
+
"message": "Mirasim 未连接。"
|
|
22
|
+
},
|
|
23
|
+
"handOverFailed": {
|
|
24
|
+
"message": "没能交出这个标签页:"
|
|
25
|
+
},
|
|
26
|
+
"statusConnected": {
|
|
27
|
+
"message": "已连接"
|
|
28
|
+
},
|
|
29
|
+
"statusOffline": {
|
|
30
|
+
"message": "未连接,启动 Mirasim 后 Agent 才能使用浏览器"
|
|
31
|
+
},
|
|
32
|
+
"pause": {
|
|
33
|
+
"message": "暂停 Agent"
|
|
34
|
+
},
|
|
35
|
+
"resume": {
|
|
36
|
+
"message": "恢复 Agent"
|
|
37
|
+
},
|
|
38
|
+
"pausedNote": {
|
|
39
|
+
"message": "Agent 已暂停:恢复之前,它发来的浏览器命令都会被拒绝。"
|
|
40
|
+
},
|
|
41
|
+
"sectionThisTab": {
|
|
42
|
+
"message": "当前标签页"
|
|
43
|
+
},
|
|
44
|
+
"tabStateNone": {
|
|
45
|
+
"message": "在标签页上点工具栏图标打开面板,才能管理那个标签页。"
|
|
46
|
+
},
|
|
47
|
+
"tabStateRestricted": {
|
|
48
|
+
"message": "浏览器自带的页面无法交给 Mirasim。"
|
|
49
|
+
},
|
|
50
|
+
"tabStateBlocked": {
|
|
51
|
+
"message": "被站点规则阻止,Agent 无法使用。"
|
|
52
|
+
},
|
|
53
|
+
"tabStateUsing": {
|
|
54
|
+
"message": "Agent 正在使用这个标签页。"
|
|
55
|
+
},
|
|
56
|
+
"tabStateHanded": {
|
|
57
|
+
"message": "已交给 Agent,它可以使用这个标签页。"
|
|
58
|
+
},
|
|
59
|
+
"tabStateGranted": {
|
|
60
|
+
"message": "你已允许 Agent 使用这个标签页。"
|
|
61
|
+
},
|
|
62
|
+
"tabStateOutsideAll": {
|
|
63
|
+
"message": "不在 Mirasim 分组里,但当前允许所有标签页,Agent 也能使用。"
|
|
64
|
+
},
|
|
65
|
+
"tabStateOutsideAsk": {
|
|
66
|
+
"message": "未交给 Agent,它使用前会先问你。"
|
|
67
|
+
},
|
|
68
|
+
"tabStateOutside": {
|
|
69
|
+
"message": "未交给 Agent,它看不到这个标签页。"
|
|
70
|
+
},
|
|
71
|
+
"handOver": {
|
|
72
|
+
"message": "交给 Agent"
|
|
73
|
+
},
|
|
74
|
+
"takeBack": {
|
|
75
|
+
"message": "收回"
|
|
76
|
+
},
|
|
77
|
+
"sectionAgentTabs": {
|
|
78
|
+
"message": "Agent 正在使用的标签页"
|
|
79
|
+
},
|
|
80
|
+
"agentTabsNone": {
|
|
81
|
+
"message": "目前没有"
|
|
82
|
+
},
|
|
83
|
+
"sectionScope": {
|
|
84
|
+
"message": "Agent 能访问的范围"
|
|
85
|
+
},
|
|
86
|
+
"scopeGroup": {
|
|
87
|
+
"message": "只限 Mirasim 分组"
|
|
88
|
+
},
|
|
89
|
+
"scopeGroupHint": {
|
|
90
|
+
"message": "你交出的标签页、Agent 自己开的标签页和它们的弹窗。其他标签页它看不到。"
|
|
91
|
+
},
|
|
92
|
+
"scopeAsk": {
|
|
93
|
+
"message": "每次问我"
|
|
94
|
+
},
|
|
95
|
+
"scopeAskHint": {
|
|
96
|
+
"message": "Agent 能看到所有标签页的标题和网址,使用分组外的标签页前先问你。"
|
|
97
|
+
},
|
|
98
|
+
"scopeAll": {
|
|
99
|
+
"message": "所有标签页"
|
|
100
|
+
},
|
|
101
|
+
"scopeAllHint": {
|
|
102
|
+
"message": "Agent 能看到并使用所有标签页。"
|
|
103
|
+
},
|
|
104
|
+
"sectionSites": {
|
|
105
|
+
"message": "站点规则"
|
|
106
|
+
},
|
|
107
|
+
"blockedLabel": {
|
|
108
|
+
"message": "不允许 Agent 使用这些网站"
|
|
109
|
+
},
|
|
110
|
+
"sitesPlaceholder": {
|
|
111
|
+
"message": "每行一个网站,例如 bank.com(包括它的子域名)"
|
|
112
|
+
},
|
|
113
|
+
"allowOnlyLabel": {
|
|
114
|
+
"message": "只允许 Agent 使用以下网站"
|
|
115
|
+
},
|
|
116
|
+
"save": {
|
|
117
|
+
"message": "保存"
|
|
118
|
+
},
|
|
119
|
+
"saved": {
|
|
120
|
+
"message": "已保存"
|
|
121
|
+
},
|
|
122
|
+
"sectionLog": {
|
|
123
|
+
"message": "最近被拦下"
|
|
124
|
+
},
|
|
125
|
+
"logNone": {
|
|
126
|
+
"message": "还没有被拦下的请求"
|
|
127
|
+
},
|
|
128
|
+
"logTab": {
|
|
129
|
+
"message": "标签页 $1"
|
|
130
|
+
},
|
|
131
|
+
"reason_site_blocked": {
|
|
132
|
+
"message": "阻止名单里的网站"
|
|
133
|
+
},
|
|
134
|
+
"reason_site_not_allowed": {
|
|
135
|
+
"message": "不在允许名单里"
|
|
136
|
+
},
|
|
137
|
+
"reason_out_of_scope": {
|
|
138
|
+
"message": "未交出的标签页"
|
|
139
|
+
},
|
|
140
|
+
"reason_ask_pending": {
|
|
141
|
+
"message": "没等到你的确认"
|
|
142
|
+
},
|
|
143
|
+
"reason_declined": {
|
|
144
|
+
"message": "你拒绝了"
|
|
145
|
+
},
|
|
146
|
+
"reason_paused": {
|
|
147
|
+
"message": "Agent 已暂停"
|
|
148
|
+
},
|
|
149
|
+
"reason_clear_cookies": {
|
|
150
|
+
"message": "清空所有 cookie"
|
|
151
|
+
},
|
|
152
|
+
"reason_cookie_site": {
|
|
153
|
+
"message": "受限网站的 cookie"
|
|
154
|
+
},
|
|
155
|
+
"footerExtension": {
|
|
156
|
+
"message": "扩展 $1"
|
|
157
|
+
},
|
|
158
|
+
"footerBridge": {
|
|
159
|
+
"message": "桥接:$1"
|
|
160
|
+
},
|
|
161
|
+
"footerBridgeOld": {
|
|
162
|
+
"message": "桥接:旧版本"
|
|
163
|
+
},
|
|
164
|
+
"askTitle": {
|
|
165
|
+
"message": "允许 Mirasim Agent 使用这个标签页吗?"
|
|
166
|
+
},
|
|
167
|
+
"askBody": {
|
|
168
|
+
"message": "允许后,在这个标签页关闭之前,Agent 都能读取和操作它。"
|
|
169
|
+
},
|
|
170
|
+
"allow": {
|
|
171
|
+
"message": "允许"
|
|
172
|
+
},
|
|
173
|
+
"deny": {
|
|
174
|
+
"message": "不允许"
|
|
175
|
+
},
|
|
176
|
+
"askGone": {
|
|
177
|
+
"message": "这个标签页已经关闭。"
|
|
178
|
+
}
|
|
179
|
+
}
|