@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
@@ -4,4 +4,4 @@ This driver attaches to a running app and never starts one, and it needs that ap
4
4
  An INSTALLED app opens no such port and cannot be driven here: the port is unauthenticated, so shipping one would hand every local process the app. To operate an installed app, use its own authenticated route (a CLI or API it ships).`}import{RUN_JS_VERB as x,UPLOAD_TO_REF_VERB as I}from"./verbs.js";class j extends u{previewTarget(){return!this.active||this.active.isClosed()?Promise.resolve(null):p(this.active,this.cdpUrl,"electron")}platform="electron";browser=null;context=null;active=null;windowIds=new WeakMap;nextWindowId=1;cdpUrl;constructor(t={}){super();const e=t.env??process.env;this.cdpUrl=t.cdpUrl?.trim()||e.COMPUTER_USE_ELECTRON_CDP_URL?.trim()||R}async snapshot(){const t=await super.snapshot(),e=await this.page().evaluate(()=>{const n=/\(KHTML, like Gecko\) (.+?)\/(\S+) Chrome\/.* Electron\//.exec(navigator.userAgent);return n?{name:n[1],version:n[2]}:void 0}).catch(()=>{});return t.target={transport:"cdp",endpoint:this.cdpUrl,windowId:this.windowId(this.page()),...e?{application:e}:{}},t}extraVerbs(){return[w,v,b,E,I,x]}staleRefCause(){return"it predates a navigation, a reload or a window switch"}promptFragment(){return"You are operating a DESKTOP APPLICATION, not a web page: there is no address bar, no back button and no reload \u2014 reach everything through the app's own UI. computer.surfaces() lists WINDOWS, not tabs. run_js is the app's DevTools console \u2014 a DEBUG channel for inspecting state; operating the product goes through the interaction verbs. Grounding: observations are text lists of interactive elements with refs, for the CURRENT viewport only. Act by ref from the latest observation. When what you want is NOT in the list: if you can name it, w.find('<name>') searches EVERY control in the window and hands back a handle \u2014 act on it directly, the click scrolls it into view, so do not scroll first. Scroll only to discover what you cannot name. Observations may show only changes since the previous one; refs stay valid across observations. Use text() to read the full window text."}async ensure(){if(this.browser?.isConnected()&&this.active&&!this.active.isClosed())return;if(this.browser?.isConnected()){if(!this.surfaces().length)throw new h(this.goneMessage());this.resetObservationState();return}this.browser=null,this.context=null,this.active=null;const t=await C(this.cdpUrl);if(t===null)throw new Error(l(this.cdpUrl,"nothing answers at /json/list"));const e=t?await U(this.cdpUrl,t):[];let n;try{n=await d.connectOverCDP(this.cdpUrl,{timeout:O}),await y(n)}catch(r){const a=r instanceof Error?r.message.split(`
5
5
  `)[0]??"":String(r);throw t?new Error(`mirasim-cu: ${this.cdpUrl} answers \u2014 ${t.length} target(s): ${t.map(c=>c.url).join(", ")} \u2014 but attaching to it failed.
6
6
  (${a})
7
- Attaching initialises every target, so one renderer that never answers hangs the whole connection; a DevTools window is the usual one`+(e.length?` (${e.length} unresponsive one(s) were just closed: ${e.join(", ")})`:"")+". Close the app's DevTools windows and try again."):new Error(l(this.cdpUrl,a))}const o=n.contexts()[0];if(!o)throw await n.close().catch(()=>{}),new Error(`mirasim-cu: ${this.cdpUrl} answered but exposes no browser context.`);this.browser=n,this.context=o;const s=this.surfaces();if(!s.length)throw new Error(`mirasim-cu: attached to ${this.cdpUrl} but the app has no renderer window to drive (only its DevTools or an about:blank surface). Open the app's window and try again.`);this.active=s[s.length-1],this.resetObservationState()}surfaces(){return(this.context?.pages()??[]).filter(S)}goneMessage(){return"The application you were driving has closed \u2014 every window is gone. This driver attaches to a running app and never starts one, so there is nothing to reopen and nothing to re-observe. Stop and report what you had done when it closed."}page(){if(this.active&&!this.active.isClosed())return this.active;throw new h("The bound Electron window closed. List surfaces and explicitly bind the intended remaining window; no input was redirected.")}async listTargets(){return await this.ensure(),await Promise.all(this.surfaces().map(async t=>({id:this.windowId(t),kind:"window",title:await t.title().catch(()=>"")||t.url(),active:t===this.active})))}async focusTarget(t){await this.ensure();for(const e of this.surfaces())if(this.windowId(e)===t)return this.active=e,this.resetObservationState(),{ok:!0};return{ok:!1,note:`no window with id ${t}; call computer.surfaces() for current ids`}}windowId(t){let e=this.windowIds.get(t);return e||(e=`w${this.nextWindowId++}`,this.windowIds.set(t,e)),e}async perform(t,e){if(await this.ensure(),t==="upload"){const n=Array.isArray(e.paths)?e.paths:[],o=m(n);if(o)return o;const s=String(e.ref),r=e.name,a=await this.locate(s,r,!1);if(!a)return await this.missing(s,r);const c=await f(a);return c?await g(n,s,async()=>c,()=>this.missing(s,r)):{ok:!1,note:`${s} is not a file input and the UI around it holds no single one \u2014 pass the attach control itself; if that button opens the app's OWN file dialog, the window is native and cannot be answered`}}return t==="run_js"?await T(()=>this.page(),e.code):await this.dispatchPage(t,e)??{ok:!1,note:`unknown action kind ${t}`}}async close(){await this.browser?.close().catch(()=>{}),this.browser=null,this.context=null,this.active=null,this.resetObservationState(),this.resetPageState(),await this.disconnected()}}export{j as ElectronDriver};
7
+ Attaching initialises every target, so one renderer that never answers hangs the whole connection; a DevTools window is the usual one`+(e.length?` (${e.length} unresponsive one(s) were just closed: ${e.join(", ")})`:"")+". Close the app's DevTools windows and try again."):new Error(l(this.cdpUrl,a))}const o=n.contexts()[0];if(!o)throw await n.close().catch(()=>{}),new Error(`mirasim-cu: ${this.cdpUrl} answered but exposes no browser context.`);this.browser=n,this.context=o;const s=this.surfaces();if(!s.length)throw new Error(`mirasim-cu: attached to ${this.cdpUrl} but the app has no renderer window to drive (only its DevTools or an about:blank surface). Open the app's window and try again.`);this.active=s[s.length-1],this.resetObservationState()}surfaces(){return(this.context?.pages()??[]).filter(S)}goneMessage(){return"The application you were driving has closed \u2014 every window is gone. This driver attaches to a running app and never starts one, so there is nothing to reopen and nothing to re-observe. Stop and report what you had done when it closed."}page(){if(this.active&&!this.active.isClosed())return this.active;throw new h("The bound Electron window closed. List surfaces and explicitly bind the intended remaining window; no input was redirected.")}async listTargets(){return await this.ensure(),await Promise.all(this.surfaces().map(async t=>({id:this.windowId(t),kind:"window",title:await t.title().catch(()=>"")||t.url(),active:t===this.active})))}async focusTarget(t){await this.ensure();for(const e of this.surfaces())if(this.windowId(e)===t)return this.active=e,this.resetObservationState(),{ok:!0};return{ok:!1,note:`no window with id ${t}; call computer.surfaces() for current ids`}}windowId(t){let e=this.windowIds.get(t);return e||(e=`w${this.nextWindowId++}`,this.windowIds.set(t,e)),e}async perform(t,e){if(await this.ensure(),t==="upload"){const n=Array.isArray(e.paths)?e.paths:[],o=m(n);if(o)return o;const s=String(e.ref),r=e.name,a=await this.locate(s,r,!1);if(!a)return await this.missing(s,r);const c=await f(a);return c?await g(n,s,async()=>c,()=>this.missing(s,r)):{ok:!1,note:`${s} is not a file input and the UI around it holds no single one \u2014 pass the attach control itself; if that button opens the app's OWN file dialog, the window is native and cannot be answered`}}return t==="run_js"?await T(()=>this.page(),e.code,e.ms,e.budgetMs):await this.dispatchPage(t,e)??{ok:!1,note:`unknown action kind ${t}`}}async close(){await this.browser?.close().catch(()=>{}),this.browser=null,this.context=null,this.active=null,this.resetObservationState(),this.resetPageState(),await this.disconnected()}}export{j as ElectronDriver};
@@ -1 +1 @@
1
- import{Type as e}from"typebox";import{RUN_JS_CODE_DESC as t}from"../chromium/run-js.js";import"../types.js";const o={kind:"upload",schema:e.Object({kind:e.Literal("upload"),paths:e.Array(e.String(),{description:"Absolute path(s) of the file(s) to upload. They must already exist on this machine.",minItems:1}),ref:e.String({description:"REQUIRED here: the attach control you can SEE \u2014 the paperclip/add button, or a `file` element if the list shows one. The input behind such a button is hidden and never appears in an observation, so it is found from this ref instead. If the button opens the app's OWN file dialog rather than a hidden input, that window is native and cannot be answered \u2014 use another route (many apps also accept a typed or pasted path)."}),name:e.Optional(e.String({description:"Visible name of the element \u2014 identity check + fallback locator"}))})},r={kind:"run_js",readOnly:!0,schema:e.Object({kind:e.Literal("run_js"),code:e.String({description:`${t} This is YOUR OWN app under development, so the console is an ordinary part of debugging it \u2014 including the command bridges it exposes.`})})};export{r as RUN_JS_VERB,o as UPLOAD_TO_REF_VERB};
1
+ import{Type as e}from"typebox";import{RUN_JS_CODE_DESC as t,RUN_JS_MAX_MS as i,RUN_JS_MIN_MS as n,RUN_JS_MS_DESC as a}from"../chromium/run-js.js";import"../types.js";const d={kind:"upload",schema:e.Object({kind:e.Literal("upload"),paths:e.Array(e.String(),{description:"Absolute path(s) of the file(s) to upload. They must already exist on this machine.",minItems:1}),ref:e.String({description:"REQUIRED here: the attach control you can SEE \u2014 the paperclip/add button, or a `file` element if the list shows one. The input behind such a button is hidden and never appears in an observation, so it is found from this ref instead. If the button opens the app's OWN file dialog rather than a hidden input, that window is native and cannot be answered \u2014 use another route (many apps also accept a typed or pasted path)."}),name:e.Optional(e.String({description:"Visible name of the element \u2014 identity check + fallback locator"}))})},p={kind:"run_js",readOnly:!0,schema:e.Object({kind:e.Literal("run_js"),code:e.String({description:`${t} This is YOUR OWN app under development, so the console is an ordinary part of debugging it \u2014 including the command bridges it exposes.`}),ms:e.Optional(e.Integer({minimum:n,maximum:i,description:a}))})};export{p as RUN_JS_VERB,d as UPLOAD_TO_REF_VERB};
@@ -1 +1 @@
1
- import{Type as h}from"typebox";const g={kind:"type",schema:h.Object({kind:h.Literal("type"),text:h.String({description:"Text to type, character by character"})},{description:"Type text with key events at the app's keyboard focus \u2014 for a field that takes typed input only (a file being renamed, a search field that filters as you type). set_text fills a field by ref; type is for where that does not stick. Focus the field first (click, or select_text with caret)."})},l=250;async function u(r,d,f){const{typedKey:i,typedValue:t,typedRange:o}=f;if(!i||typeof t!="string"||!o)return{note:"unverified \u2014 the target field, original text or selection could not be read before input"};const{start:e,length:n}=o;if(!Number.isSafeInteger(e)||!Number.isSafeInteger(n)||e<0||n<0||e>t.length||n>t.length-e)return{note:"unverified \u2014 the original selection was outside the readable text"};const a=t.slice(0,e)+d+t.slice(e+n);try{const s=Date.now()+200;for(;;){const{value:c}=await r("textvalue",{key:i});if(typeof c!="string")break;if(Date.now()>=s)return c===a&&a===t?{note:"unverified \u2014 the expected text was already present before input, so read-back cannot establish whether the keys took effect"}:{verified:c===a,note:c===a?"matched \u2014 the same field reads back exactly the expected text after inserting or replacing the original selection":"content differs \u2014 the same field does not read back the expected text; automatic correction or formatting may change it. This does not establish where the keys landed; inspect it with text() before editing further, and do not automatically replay the input"};await new Promise(p=>setTimeout(p,Math.min(50,s-Date.now())))}}catch{}return{note:"unverified \u2014 the original target field could not be read after input; inspect it before editing further, and do not automatically replay the input"}}async function m(r,d,f,i,t=0){if(!i)return{ok:!1,note:"type needs text"};const o=t?Math.max(0,Date.now()-t):Number.POSITIVE_INFINITY,e=o<l?Math.ceil(l-o):0;e&&await new Promise(s=>setTimeout(s,e));const n=await r("type",{pid:d,text:i}),a=await u(r,i,n??{});return{ok:!0,...a,note:`posted ${n?.postedCount??i.length} characters at ${f}'s keyboard focus with key events`+(e?`, after ${e} ms for the key before it to finish moving the focus \u2014 a field that commits on Enter hands it on asynchronously, and a character posted into that gap is lost with no error`:"")+`; ${a.note}`}}export{l as TYPE_AFTER_KEY_MS,g as TYPE_VERB,m as typeVerb};
1
+ import{Type as h}from"typebox";const m={kind:"type",schema:h.Object({kind:h.Literal("type"),text:h.String({description:"Text to type, character by character"})},{description:"Type text with key events at the app's keyboard focus \u2014 for a field that takes typed input only (a file being renamed, a search field that filters as you type). set_text fills a field by ref; type is for where that does not stick. Focus the field first (click, or select_text with caret)."})},l=250;async function u(s,c,f){const{typedKey:a,typedValue:e,typedRange:o}=f;if(!a||typeof e!="string"||!o)return{note:"unverified \u2014 the target field, original text or selection could not be read before input"};const{start:t,length:r}=o;if(!Number.isSafeInteger(t)||!Number.isSafeInteger(r)||t<0||r<0||t>e.length||r>e.length-t)return{note:"unverified \u2014 the original selection was outside the readable text"};const i=e.slice(0,t)+c+e.slice(t+r);try{const d=Date.now()+200;for(;;){const{value:n}=await s("textvalue",{key:a});if(typeof n!="string")break;if(Date.now()>=d)return n!==i&&n===c&&e.trim()&&!n.includes(e.trim())?{verified:!0,note:`matched \u2014 the field reads back exactly the typed text; what it read before input (${JSON.stringify(e.trim().slice(0,40))}) was an empty field's placeholder, which some web fields report as their value`}:n===i&&i===e?{note:"unverified \u2014 the expected text was already present before input, so read-back cannot establish whether the keys took effect"}:{verified:n===i,note:n===i?"matched \u2014 the same field reads back exactly the expected text after inserting or replacing the original selection":"content differs \u2014 the same field does not read back the expected text; automatic correction or formatting may change it. This does not establish where the keys landed; inspect it with text() before editing further, and do not automatically replay the input"};await new Promise(p=>setTimeout(p,Math.min(50,d-Date.now())))}}catch{}return{note:"unverified \u2014 the original target field could not be read after input; inspect it before editing further, and do not automatically replay the input"}}async function b(s,c,f,a,e=0){if(!a)return{ok:!1,note:"type needs text"};const o=e?Math.max(0,Date.now()-e):Number.POSITIVE_INFINITY,t=o<l?Math.ceil(l-o):0;t&&await new Promise(d=>setTimeout(d,t));const r=await s("type",{pid:c,text:a}),i=await u(s,a,r??{});return{ok:!0,...i,note:`posted ${r?.postedCount??a.length} characters at ${f}'s keyboard focus with key events`+(t?`, after ${t} ms for the key before it to finish moving the focus \u2014 a field that commits on Enter hands it on asynchronously, and a character posted into that gap is lost with no error`:"")+`; ${i.note}`}}export{l as TYPE_AFTER_KEY_MS,m as TYPE_VERB,b as typeVerb};
@@ -15,6 +15,7 @@ export interface RunLedger {
15
15
  push(kind: string, target: string, ok: boolean, note: string, read?: boolean, outcome?: ActionOutcome): void;
16
16
  attach(part: ToolResult["content"][number]): void;
17
17
  round?: WriteRound;
18
+ remainingMs?(): number;
18
19
  }
19
20
  export interface WriteRound {
20
21
  fields: number;
@@ -1 +1 @@
1
- import{STATE_CHANGING_ACTIONS as B}from"../driver.js";import{withoutBlankOptionals as G}from"./normalize.js";import{ambiguityNote as L,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 O,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])),C=[...V.keys()],{findWithMiss:v,asPoint:N,resolve:g,requireFocus:M}=F(r,c,w,x);let T=null;const k=(e,t)=>{const n=V.get(e);if(!n)return null;const o=G({kind:e,...t},n);return q(n,E(e),o)},R=async(e,t,n,o=!1)=>{c().charge(e);const s=k(e,t);if(s)throw c().push(e,n,!1,s,!1,"blocked"),new Error(s);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},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},_=(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&&B.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?L(o):s),o},text:_("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 O(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 O(await y("diagnostics",t.id??"",x,async()=>r.readDiagnostics(t))())},targets:_("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)=>A(e,t,!0,n),setTextBestEffort:(e,t,n)=>A(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 M(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 C)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
+ 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 ve,text as L}from"../types.js";import{MAX_ACTIONS as be,MAX_MS as ye,SYNC_MAX_MS as $e,RETURN_CAP as oe,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(B,D={}){if(!B.length)throw new Error("createReplTools needs at least one platform");let M=null;const E=D.ownership??new tt,m=new WeakMap,W=new WeakMap;let j=null,P=!1,ae=Promise.resolve(),q=!1,I=null,G=null;const Te=D.idleReleaseMs??nt;let K=null,N=null,k,Z=!!D.docsDelivered,ee=!!D.docsDelivered,C=null;const T=()=>{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 B)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=B.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=B.map(e=>({...e,id:e.driver.platform,api:Ke(e.driver,T,{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:!D.docsDelivered,lastMiss:null}));if(k=d[0],U)for(const e of d)e.driver.surfaceIdPrefix=`${e.id}/`;const le=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}T().charge("focus");const t=await n.slice.driver.focusTarget(n.inner);if(T().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 f=await i.focusTarget(s);if(!f.ok)throw new H(`Surface ${e.id} disappeared while binding; list surfaces and re-acquire it.`);if(T().push("focus",e.id,!0,Se(f)||`\u2192 ${e.title}`),a=await i.ownershipResources(),!await i.isActiveTarget(s))throw new Error("The target kept changing while binding; observe before continuing.")}const u=a[0]?.key;if(!u)throw new Error("The driver did not identify a resource to claim.");const O=!!l&&l!==u;if(O&&!a.some(f=>W.get(e)?.has(f.key)))throw $.delete(e.id),m.delete(e),W.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(u,a)}catch(f){throw f instanceof Ee&&(j=f.message),f}O&&await E.release(l),m.set(e,u),W.set(e,new Set(a.map(f=>f.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),W.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 X({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},X=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 u=await a(...l);return await Ie(t),u}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(u){if(i==="wait"&&u 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 u instanceof H&&await S.get(t).slice.driver.heldNothing()?new H(`${u.message} This session holds no tab now: openUrl({url}) or newTab({url}) on this handle opens a fresh one.`):u}return await a(...l)}finally{b=null}});return s.focus=async()=>{b=t;try{await ne(t)}finally{b=null}},s.release=async()=>{T().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 T().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(`
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 u=a.toLowerCase(),O=e.filter(c=>c.title.toLowerCase()===u),f=O.length?O:e.filter(c=>c.title.toLowerCase().includes(u));if(f.length===1)return f[0];const Y=f.filter(c=>c.primary);if(Y.length===1)return Y[0];if(!f.length){const c=[...new Set(d.map(p=>p.driver.explainMiss(a)).filter(Boolean))].join(`
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(`${f.length} surfaces match ${JSON.stringify(a)} \u2014 name one exactly:
7
- ${i(f)}`)},Ae=async e=>{try{return await ce(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}},ce=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)},De=te({surfaces:async()=>{T().charge("targets");const e=await F();return T().push("targets","",!0,`${e.length} surfaces`),e.map(X)},surface:async e=>(e===void 0?await Ne():null)??X(await ce(e)),log:le.log,help:()=>K=d.map(e=>Re(e.driver)).join(`
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(le)),de=(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:De,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`]:[]),z=ve({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,K=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=[],u=[],O=Date.now(),f=()=>d.reduce((r,o)=>r+o.driver.waitedOnUserMs,0),Y=f();let c=0,p=null,_=!1,Q=null;const ue={fields:0,verified:0,ended:null};C={round:ue,charge(r){if(Q??=Date.now(),P)throw new Error("The runtime was reset; this run stops before its next operation.");if(++c>be)throw new Error(`step cap reached (${be} actions) at ${r} \u2014 split the work across calls, or narrow the loop`);if(Date.now()-O-(f()-Y)>ye)throw new Error(`time cap reached (${ye/1e3}s) at ${r} \u2014 the surface may be slower than the code assumes`)},push(r,o,y,x,we,ge){!we&&y&&!ke.has(r)&&!rt.has(r)&&(p=r),!we&&!y&&!ke.has(r)&&(_=!0);const me=Q===null?void 0:Date.now()-Q;Q=null,l.push({i:l.length+1,kind:r,target:o,ok:y,note:x,...ge?{outcome:ge}:{},...me!==void 0?{ms:me}:{}})},attach(r){u.push(r)}};const fe=!M;M??=je();let V,w=null;try{const r=String(e.target??"").trim();r&&await ne(X(await Ae(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=de(r instanceof Error?`${r.name==="Error"?"":`${r.name}: `}${r.message}`:String(r),fe)}finally{try{await te.drain()}catch(r){w??=de(r instanceof Error?r.message:String(r),fe)}C=null}const he=Date.now()-O,pe=(he/1e3).toFixed(1),J=r=>({runMs:he,...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 (${pe}s): ${w}`:`Run completed ${l.length} steps in ${pe}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>oe&&V!==K?`${r.slice(0,oe)}\u2026 (cut at ${oe} of ${r.length} chars \u2014 return a slice, a count or a filtered list instead of the whole thing)`:r}`)}const A=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,A.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(ue):_&&k.driver.noteContentRead();const Je=p?{afterAction:p}:{},se=d.find(r=>r.driver.permissionRefusal)?.driver.permissionRefusal;if(K&&l.length===0){const r=L(g.join(`
12
- `),{verdict:"unknown",ledgerLines:g.length,timing:J(),...w?{error:"run_stopped"}:{}});return r.content.push(...A),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:J(),...w?{error:"run_stopped"}:{},...se?{permissionRefused:!0}:{ownershipRefused:!0}});return o.content.push(...u,...A),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(`
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:J(),...w?{error:"run_stopped"}:{}});return x.content.push(...u,...A),x}const r=Date.now(),o=await k.driver.observeElements({prefix:g.join(`
17
- `),...Je});return o.details.ledgerLines=g.length,o.details.timing=J(Date.now()-r),Ge(o,a),o.content.push(...u,...A),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(`
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:J(),...w?{error:"run_stopped"}:{}});return x.content.push(...u,...A),x}}}),Ue=z.execute.bind(z),ie=()=>{I&&clearTimeout(I),I=null},Fe=()=>{I=null,!q&&(G=(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(()=>{G=null}))};z.execute=async e=>{ie(),G&&await G,q=!0;let n;ae=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{q=!1,n(),ie(),I=setTimeout(Fe,Te),I.unref?.()}}};const He=ve({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=ae;if(ie(),q)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[z,He]}export{ht as createReplTools};
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
+ }