lazycodex-ai 5.0.0-beta.89 → 5.0.0-beta.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.zh-cn.md +1 -1
- package/dist/cli/config-manager/refresh-opencode-plugin-sandbox.d.ts +31 -0
- package/dist/cli/doctor/checks/tui-plugin-config.d.ts +11 -0
- package/dist/cli/index.js +1661 -859
- package/dist/cli/install-native/index.d.ts +9 -1
- package/dist/cli/install-native/legacy-omo-bin.d.ts +27 -0
- package/dist/cli/install-native/offer-native-setup.d.ts +31 -0
- package/dist/cli/install-native/omo-bin-test-fixtures.d.ts +35 -0
- package/dist/cli/install-native/plan.d.ts +8 -0
- package/dist/cli/install-native/repair-legacy-omo-bin.d.ts +26 -0
- package/dist/cli/install-native/run-native-install.d.ts +11 -1
- package/dist/cli/install-native/verify-omo-command.d.ts +22 -0
- package/dist/cli/native-edition-hint.d.ts +2 -2
- package/dist/cli-node/index.js +1661 -859
- package/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +5 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +7 -3
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +7 -3
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -2
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +8 -1
- package/packages/omo-codex/plugin/skills/browser/references/install.md +34 -5
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +2 -2
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +3 -3
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +14 -3
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +13 -1
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +7 -3
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +63 -0
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +37 -25
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +187 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +102 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +162 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +204 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/design-spec.mjs +161 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +39 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/entry-guard.mjs +21 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/format-extract-css.mjs +122 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/format-extract.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-figures.mjs +173 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-layout.mjs +146 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +167 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +118 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +190 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +239 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/layout-probe.mjs +83 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +250 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/repair-tracker.mjs +171 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +214 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +61 -0
- package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +3 -1
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +11 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -2
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +20 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/prompts-core/prompts/ultrawork/codex.md +7 -3
- package/packages/shared-skills/skills/browser/SKILL.md +8 -1
- package/packages/shared-skills/skills/browser/references/install.md +34 -5
- package/packages/shared-skills/skills/browser/runtime/omowright/index.js +2 -2
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +3 -3
- package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +14 -3
- package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +13 -1
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +63 -0
- package/packages/shared-skills/skills/ulw-research/SKILL.md +38 -26
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +187 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +102 -0
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +88 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +162 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +177 -0
- package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +204 -0
- package/packages/shared-skills/skills/ulw-research/scripts/css-lite.test.ts +159 -0
- package/packages/shared-skills/skills/ulw-research/scripts/design-spec.mjs +161 -0
- package/packages/shared-skills/skills/ulw-research/scripts/design-spec.test.ts +162 -0
- package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +39 -0
- package/packages/shared-skills/skills/ulw-research/scripts/entry-guard.mjs +21 -0
- package/packages/shared-skills/skills/ulw-research/scripts/format-extract-css.mjs +122 -0
- package/packages/shared-skills/skills/ulw-research/scripts/format-extract.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/format-extract.test.ts +243 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-figures.mjs +173 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-layout.mjs +146 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-layout.test.ts +236 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +167 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.test.ts +265 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +118 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +190 -0
- package/packages/shared-skills/skills/ulw-research/scripts/glossary-vocabulary.test.ts +43 -0
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +239 -0
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.test.ts +191 -0
- package/packages/shared-skills/skills/ulw-research/scripts/layout-probe.mjs +83 -0
- package/packages/shared-skills/skills/ulw-research/scripts/layout-probe.test.ts +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +250 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +281 -0
- package/packages/shared-skills/skills/ulw-research/scripts/repair-tracker.mjs +171 -0
- package/packages/shared-skills/skills/ulw-research/scripts/repair-tracker.test.ts +246 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +214 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +61 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +213 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/assets/figure-1.svg +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/bad-report.html +44 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/boxes.json +90 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/design-spec.md +29 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/good-report.html +57 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/reference-report.html +86 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/reference.md +23 -0
- package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +3 -1
|
@@ -1530,5 +1530,5 @@ ${snapshot.tree}`}}var DEFAULT_TIMEOUT_MS=30000;var JSON_MIME=/^application\/jso
|
|
|
1530
1530
|
const css = {};
|
|
1531
1531
|
for (const ref of Object.keys(result.refs || {})) css[ref] = cssPath(api.elementRegistry.get(ref));
|
|
1532
1532
|
return { tree: result.tree, refs: result.refs || {}, css, error: result.error };
|
|
1533
|
-
})()`}async function bskSnapshot(session,{tabId,timeoutMs,...snapshotOptions}={}){const reply=await session.evaluate(buildSnapshotExpression(snapshotOptions),{tabId,timeoutMs,awaitPromise:false,returnByValue:true});if(!reply||reply.ok===false){const text=reply?.error?.text??"unknown error";throw new Error(`bskSnapshot failed inside the page: ${text}`)}const value=reply.value;if(!value||typeof value.tree!=="string")throw new Error("bskSnapshot: page returned no tree");if(value.error)throw new Error(`bskSnapshot: ${value.error}`);return{tree:value.tree,refs:value.refs,css:value.css}}import{existsSync as existsSync4,mkdirSync as mkdirSync2,readFileSync as readFileSync4,rmSync as rmSync2,writeFileSync as writeFileSync2}from"node:fs";import{spawn as spawn4}from"node:child_process";import path11 from"node:path";import{existsSync as existsSync3}from"node:fs";import{homedir as homedir3}from"node:os";import path10 from"node:path";var BROWSERSKILL_EXTENSION_IDS=Object.freeze({chrome:"hhcmgoofomhgciiibhipgmgkgnoenaoi",edge:"emacgiaaaiojkkpkddmmdfhmokgmnikg"});var STORE_UPDATE_URLS=Object.freeze({chrome:"https://clients2.google.com/service/update2/crx",edge:"https://edge.microsoft.com/extensionwebstorebase/v1/crx"});var STORE_PAGE_URLS=Object.freeze({chrome:`https://chromewebstore.google.com/detail/${BROWSERSKILL_EXTENSION_IDS.chrome}`,edge:`https://microsoftedge.microsoft.com/addons/detail/browserskill/${BROWSERSKILL_EXTENSION_IDS.edge}`});var MAC=[{id:"chrome",store:"chrome",userData:"Library/Application Support/Google/Chrome",binary:"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",bundleId:"com.google.Chrome"},{id:"edge",store:"edge",userData:"Library/Application Support/Microsoft Edge",binary:"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",bundleId:"com.microsoft.edgemac"},{id:"brave",store:"chrome",userData:"Library/Application Support/BraveSoftware/Brave-Browser",binary:"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",bundleId:"com.brave.Browser"},{id:"chromium",store:"chrome",userData:"Library/Application Support/Chromium",binary:"/Applications/Chromium.app/Contents/MacOS/Chromium",bundleId:"org.chromium.Chromium"}];var LINUX=[{id:"chrome",store:"chrome",userData:".config/google-chrome",binaries:["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/opt/google/chrome/chrome"],externalDir:"/opt/google/chrome/extensions",userExternalDir:false},{id:"chromium",store:"chrome",userData:".config/chromium",binaries:["/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium"],externalDir:"/usr/share/chromium/extensions",userExternalDir:true},{id:"edge",store:"edge",userData:".config/microsoft-edge",binaries:["/usr/bin/microsoft-edge","/opt/microsoft/msedge/msedge"],externalDir:"/usr/share/microsoft-edge/extensions",userExternalDir:false},{id:"brave",store:"chrome",userData:".config/BraveSoftware/Brave-Browser",binaries:["/usr/bin/brave-browser","/opt/brave.com/brave/brave"],externalDir:"/usr/share/brave/extensions",userExternalDir:false}];var WINDOWS=[{id:"chrome",store:"chrome",userData:"Google/Chrome/User Data",registryKey:"HKCU\\Software\\Google\\Chrome\\Extensions",binaryRel:"Google/Chrome/Application/chrome.exe"},{id:"edge",store:"edge",userData:"Microsoft/Edge/User Data",registryKey:"HKCU\\Software\\Microsoft\\Edge\\Extensions",binaryRel:"Microsoft/Edge/Application/msedge.exe"},{id:"brave",store:"chrome",userData:"BraveSoftware/Brave-Browser/User Data",registryKey:"HKCU\\Software\\BraveSoftware\\Brave-Browser\\Extensions",binaryRel:"BraveSoftware/Brave-Browser/Application/brave.exe"}];function detectBrowsers({platform=process.platform,home=homedir3(),env=process.env,exists=existsSync3}={}){if(platform==="darwin"){return MAC.filter((b)=>exists(path10.join(home,b.userData))).map((b)=>({id:b.id,store:b.store,userDataDir:path10.join(home,b.userData),binary:b.binary,bundleId:b.bundleId}))}if(platform==="linux"){return LINUX.filter((b)=>exists(path10.join(home,b.userData))).map((b)=>({id:b.id,store:b.store,userDataDir:path10.join(home,b.userData),binary:b.binaries.find((p)=>exists(p))??null,externalDir:b.externalDir,userExternalDir:b.userExternalDir}))}if(platform==="win32"){const localAppData=env.LOCALAPPDATA??path10.join(home,"AppData","Local");const programFiles=[env.PROGRAMFILES,env["PROGRAMFILES(X86)"],"C:\\Program Files","C:\\Program Files (x86)"].filter(Boolean);return WINDOWS.filter((b)=>exists(path10.join(localAppData,b.userData))).map((b)=>({id:b.id,store:b.store,userDataDir:path10.join(localAppData,b.userData),registryKey:b.registryKey,binary:programFiles.map((root)=>path10.join(root,b.binaryRel)).find((p)=>exists(p))??null}))}return[]}var ENABLE_STEP=(browser)=>`Quit ${browser} completely and open it again; a dialog offers to enable "BrowserSkill" — click Enable.`;var BADGE_STEP=(browser)=>`In ${browser}, open the menu (the badge on the toolbar) and enable "BrowserSkill" when prompted; no restart is needed.`;function externalExtensionEntry({platform=process.platform,browser}){const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const updateUrl=STORE_UPDATE_URLS[browser.store];const label=browser.id.charAt(0).toUpperCase()+browser.id.slice(1);if(platform==="win32"){const key=`${browser.registryKey}\\${id}`;return{kind:"registry",key,needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],regAddArgs:["add",key,"/v","update_url","/t","REG_SZ","/d",updateUrl,"/f"],regDeleteArgs:["delete",key,"/f"],humanStep:BADGE_STEP(label)}}const content=JSON.stringify({external_update_url:updateUrl},null,2)+`
|
|
1534
|
-
`;if(platform==="linux"&&!browser.userExternalDir){return{kind:"file",path:path11.join(browser.externalDir,`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:`Quit ${label} completely and open it again; the extension installs without a prompt.`}}return{kind:"file",path:path11.join(browser.userDataDir,"External Extensions",`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:platform==="linux"?`Quit ${label} completely and open it again; the extension installs without a prompt.`:ENABLE_STEP(label)}}function isBlocklisted(browser,id,{readFile=readFileSync4}={}){if(!browser.userDataDir)return false;for(const profile of["Default","Profile 1","Profile 2","Profile 3"]){const file=path11.join(browser.userDataDir,profile,"Preferences");let prefs;try{prefs=JSON.parse(readFile(file,"utf8"))}catch{continue}if(Array.isArray(prefs?.extensions?.external_uninstalls)&&prefs.extensions.external_uninstalls.includes(id))return true}return false}function runRegDefault(args){return new Promise((resolve)=>{const child=spawn4("reg",args,{stdio:["ignore","ignore","pipe"],windowsHide:true});let stderr="";child.stderr.on("data",(c)=>{stderr+=c});child.on("error",(error)=>resolve({code:-1,stderr:error.message}));child.on("exit",(code)=>resolve({code,stderr}))})}async function registerExternalExtension({platform=process.platform,browser,writeFile=writeFileSync2,readFile=readFileSync4,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const storeStep=`Open ${entry.storeUrl} in ${browser.id} and click "Add to ${browser.store==="edge"?"Edge":"Chrome"}".`;if(isBlocklisted(browser,id,{readFile})){return{...entry,registered:false,alreadyPresent:false,reason:"blocklisted",humanStep:`The extension was removed from ${browser.id} before, so external registration is ignored. ${storeStep}`}}if(entry.kind==="registry"){const result=await runReg(entry.regAddArgs);if(result.code!==0)return{...entry,registered:false,alreadyPresent:false,reason:"reg_failed",detail:result.stderr,humanStep:storeStep};return{...entry,registered:true,alreadyPresent:false}}let alreadyPresent=false;try{alreadyPresent=readFile(entry.path,"utf8")===entry.content}catch{}if(alreadyPresent)return{...entry,registered:true,alreadyPresent:true};try{mkdirSync2(path11.dirname(entry.path),{recursive:true});writeFile(entry.path,entry.content)}catch(error){if(error.code==="EACCES"||error.code==="EPERM"||error.code==="EROFS"){return{...entry,registered:false,alreadyPresent:false,reason:"not_writable",humanStep:`${entry.path} is not writable without administrator rights. ${storeStep}`}}throw error}return{...entry,registered:true,alreadyPresent:false}}async function unregisterExternalExtension({platform=process.platform,browser,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});if(entry.kind==="registry"){const result=await runReg(entry.regDeleteArgs);return{...entry,removed:result.code===0,detail:result.stderr}}const present=existsSync4(entry.path);if(present)rmSync2(entry.path,{force:true});return{...entry,removed:present}}import{existsSync as existsSync5,readFileSync as readFileSync5,rmSync as rmSync3,writeFileSync as writeFileSync3}from"node:fs";import{homedir as homedir4}from"node:os";import path12 from"node:path";import{spawn as spawn5}from"node:child_process";var INSTALL_SH="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.sh";var INSTALL_PS1="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.ps1";var RC_FILES=[".zshrc",".bashrc",".profile"];function cliInstallCommand({platform=process.platform,installDir,version}={}){const env={BSK_INSTALL_DIR:installDir};if(version)env.BSK_VERSION=version;if(platform==="win32"){return{command:"powershell",args:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`irm ${INSTALL_PS1} | iex`],env}}return{command:"sh",args:["-c",`curl -fsSL ${INSTALL_SH} | sh`],env}}function runDefault(command,args,env){return new Promise((resolve)=>{const child=spawn5(command,args,{env:{...process.env,...env},stdio:["ignore","pipe","pipe"],windowsHide:true});let stdout="",stderr="";child.stdout.on("data",(c)=>{stdout=(stdout+c).slice(-8192)});child.stderr.on("data",(c)=>{stderr=(stderr+c).slice(-8192)});child.on("error",(error)=>resolve({code:-1,stdout,stderr:error.message}));child.on("exit",(code)=>resolve({code,stdout,stderr}))})}function snapshotRcFiles(home){const snapshot=new Map;for(const name of RC_FILES){const file=path12.join(home,name);snapshot.set(file,existsSync5(file)?readFileSync5(file):null)}return snapshot}function restoreRcFiles(snapshot){const restored=[];for(const[file,before]of snapshot){const after=existsSync5(file)?readFileSync5(file):null;if(before===null&&after===null)continue;if(before!==null&&after!==null&&before.equals(after))continue;if(before===null)rmSync3(file,{force:true});else writeFileSync3(file,before);restored.push(file)}return restored}async function installBskCli({platform=process.platform,home=homedir4(),installDir=path12.join(home,".local","bin"),version,run=runDefault}={}){const spec=cliInstallCommand({platform,installDir,version});const rcBefore=platform==="win32"?new Map:snapshotRcFiles(home);const result=await run(spec.command,spec.args,spec.env);const restoredRcFiles=restoreRcFiles(rcBefore);const bskBin=path12.join(installDir,platform==="win32"?"bsk.exe":"bsk");const installed=result.code===0&&existsSync5(bskBin);return{installed,bskBin:installed?bskBin:null,code:result.code,stdout:result.stdout,stderr:result.stderr,restoredRcFiles}}function cliVersionDefault(bskBin){return new Promise((resolve)=>{const child=spawn5(bskBin,["--version"],{stdio:["ignore","pipe","ignore"],windowsHide:true});let out="";child.stdout.on("data",(c)=>{out+=c});child.on("error",()=>resolve(null));child.on("exit",(code)=>resolve(code===0?out.trim():null))})}import{homedir as homedir5}from"node:os";import{existsSync as existsSync6,readFileSync as readFileSync6}from"node:fs";var DEFAULT_WAIT_TOTAL_MS=180000;function resolveBskBin(bskBin,env){if(bskBin===null)return null;return bskBin??findBskBinary(env)}async function cliReport({bskBin,env,cliVersion}){const bin=resolveBskBin(bskBin,env);if(!bin)return{installed:false,bskBin:null,version:null};const raw=await cliVersion(bin);return raw?{installed:true,bskBin:bin,version:raw.replace(/^bsk\s+/,"")}:{installed:false,bskBin:bin,version:null}}async function daemonReport(client,waitForBrowserMs){try{const status=await client.call("system.status",waitForBrowserMs>0?{wait_for_browser_ms:waitForBrowserMs}:{},{idPrefix:"status"});return{running:true,version:status.daemon_version,protocolVersion:status.protocol_version,browsers:status.browsers??[],sessions:status.sessions??[]}}catch(error){return{running:false,error:error.message,code:error.code,browsers:[],sessions:[]}}}function browserRows({platform,home,env}){return detectBrowsers({platform,home,env}).map((browser)=>{const entry=externalExtensionEntry({platform,browser});const extensionRegistered=entry.kind==="file"&&existsSync6(entry.path)&&readFileSync6(entry.path,"utf8")===entry.content;return{...browser,registration:entry,extensionRegistered}})}function nextStepFor({cli,daemon,browsers,browsersConnected}){if(browsersConnected.length>0)return null;if(!cli.installed)return"Install the bsk CLI (bskOnboard() runs the official installer).";if(!daemon.running)return"Start the BrowserSkill daemon: run `bsk status` once, or `bsk daemon start` in the environment that owns it.";if(browsers.length===0)return`No Chromium-family browser profile was found; install Chrome or Edge and add the extension from ${STORE_PAGE_URLS.chrome}.`;const unregistered=browsers.find((b)=>!b.extensionRegistered);if(unregistered)return`Register the BrowserSkill extension for ${unregistered.id} (bskOnboard() does this), then let the user enable it.`;return browsers[0].registration.humanStep}async function bskDoctor({platform=process.platform,home=homedir5(),env=process.env,client,bskBin,cliVersion=cliVersionDefault,waitForBrowserMs=0}={}){const cli=await cliReport({bskBin,env,cliVersion});const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:false});const daemon=await daemonReport(ipc,waitForBrowserMs);const browsers=browserRows({platform,home,env});const browsersConnected=daemon.browsers;const report={cli,daemon,browsers,browsersConnected,ready:browsersConnected.length>0};report.nextStep=nextStepFor(report);return report}async function bskOnboard({platform=process.platform,home=homedir5(),env=process.env,client,bskBin,installDir,installCli=installBskCli,cliVersion=cliVersionDefault,onHumanStep=()=>{},waitForBrowserMs=15000,waitTotalMs=DEFAULT_WAIT_TOTAL_MS}={}){let cli=await cliReport({bskBin,env,cliVersion});let install=null;if(!cli.installed){install=await installCli({platform,home,installDir});cli=install.installed?await cliReport({bskBin:install.bskBin,env,cliVersion}):cli}const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:true});let daemon=await daemonReport(ipc,0);const browsers=browserRows({platform,home,env});const registrations=[];let humanStep=null;if(daemon.browsers.length===0){for(const browser of browsers){const registration=await registerExternalExtension({platform,browser});registrations.push({browser:browser.id,...registration});if(!humanStep)humanStep=registration.humanStep;onHumanStep({browser:browser.id,step:registration.humanStep,registered:registration.registered,needsRestart:registration.needsRestart})}if(browsers.length===0)humanStep=`No Chromium-family browser profile was found. Install Chrome or Edge, then add the extension from ${STORE_PAGE_URLS.chrome}.`}const deadline=Date.now()+waitTotalMs;let browsersConnected=daemon.browsers;while(browsersConnected.length===0&&daemon.running&&Date.now()<deadline){browsersConnected=await listBrowsers(ipc,{waitForBrowserMs});if(waitForBrowserMs===0)break}daemon={...daemon,browsers:browsersConnected};const ready=browsersConnected.length>0;return{cli,install,daemon,browsers,registrations,humanStep:ready?null:humanStep,browsersConnected,ready}}export{AgentTabManager,BROWSERSKILL_EXTENSION_IDS,BROWSER_EVENT_NAMES,BridgeProtocolError,BridgeUnavailableError,BrowserCdpCommandError,BrowserConnection,BskIpcClient,BskRpcError,BskSession,CdpClient,CommandSchema,DEFAULT_AGENT_VIEWPORT,DEVICE_PRESETS,ElementHandle,EventSchema,ExtensionHelloSchema,FrameLocator,FrameManager,HostHelloSchema,Locator,ModifierState,OmOKeyboard,OmOMouse,OmOPage,PipeCdpClient,ResponseSchema,STORE_PAGE_URLS,SessionManager,TabRepository,UnsupportedOperationError,bskDoctor,bskOnboard,bskSnapshot,buildCloakBrowserArgs,buildSnapshotExpression,collectWhileScrolling,compactSnapshot,connect,connectBrowserSkill,connectCloakProfile,connectPipe,createAgentTabs,createBridgeClient,createCaptcha,createChromeApi2 as createChromeApi,createCua,createEvents,createNativeMessagingHost,createNetworkSnoop,createRoutes,createTrace,decodeFrames,describeLayers,detectBrowsers,emulate,encodeFrame,externalExtensionEntry,findCloakBrowserPath,injectCookies,installBskCli,installNativeMessagingHost,layersHeader,listBrowsers,macOSVisionOcr,normalizeDialogPolicy,pageBundle,readDaemonInfo,reconcileFrames,registerExternalExtension,repinViewport,requestHuman,resolveBskHome,resolveCloakProfile,resolveDialogAction,sanitizeCookies,snapshotTokens,snapshotWithFrames,snapshotWithLayers,takeSnapshot,toHar,toolSchemas,unregisterExternalExtension};
|
|
1533
|
+
})()`}async function bskSnapshot(session,{tabId,timeoutMs,...snapshotOptions}={}){const reply=await session.evaluate(buildSnapshotExpression(snapshotOptions),{tabId,timeoutMs,awaitPromise:false,returnByValue:true});if(!reply||reply.ok===false){const text=reply?.error?.text??"unknown error";throw new Error(`bskSnapshot failed inside the page: ${text}`)}const value=reply.value;if(!value||typeof value.tree!=="string")throw new Error("bskSnapshot: page returned no tree");if(value.error)throw new Error(`bskSnapshot: ${value.error}`);return{tree:value.tree,refs:value.refs,css:value.css}}import{existsSync as existsSync4,mkdirSync as mkdirSync2,readFileSync as readFileSync4,rmSync as rmSync2,writeFileSync as writeFileSync2}from"node:fs";import{spawn as spawn4}from"node:child_process";import path11 from"node:path";import{existsSync as existsSync3}from"node:fs";import{homedir as homedir3}from"node:os";import path10 from"node:path";var BROWSERSKILL_EXTENSION_IDS=Object.freeze({chrome:"hhcmgoofomhgciiibhipgmgkgnoenaoi",edge:"emacgiaaaiojkkpkddmmdfhmokgmnikg"});var STORE_UPDATE_URLS=Object.freeze({chrome:"https://clients2.google.com/service/update2/crx",edge:"https://edge.microsoft.com/extensionwebstorebase/v1/crx"});var STORE_PAGE_URLS=Object.freeze({chrome:`https://chromewebstore.google.com/detail/${BROWSERSKILL_EXTENSION_IDS.chrome}`,edge:`https://microsoftedge.microsoft.com/addons/detail/browserskill/${BROWSERSKILL_EXTENSION_IDS.edge}`});var NON_CHROMIUM_DEFAULTS=Object.freeze([{id:"safari",label:"Safari",match:["com.apple.safari","safarihtml"]},{id:"firefox",label:"Firefox",match:["org.mozilla.firefox","firefox.desktop","firefoxurl","firefoxhtml"]},{id:"zen",label:"Zen",match:["app.zen-browser.zen","zen.desktop","zenhtml"]},{id:"orion",label:"Orion",match:["com.kagi.kagimacos"]}]);var MAC=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Library/Application Support/Google/Chrome",app:"Google Chrome",bundleId:"com.google.Chrome"},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Library/Application Support/Microsoft Edge",app:"Microsoft Edge",bundleId:"com.microsoft.edgemac"},{id:"brave",label:"Brave",store:"chrome",userData:"Library/Application Support/BraveSoftware/Brave-Browser",app:"Brave Browser",bundleId:"com.brave.Browser"},{id:"chromium",label:"Chromium",store:"chrome",userData:"Library/Application Support/Chromium",app:"Chromium",bundleId:"org.chromium.Chromium"},{id:"arc",label:"Arc",store:"chrome",userData:"Library/Application Support/Arc/User Data",app:"Arc",bundleId:"company.thebrowser.Browser"},{id:"dia",label:"Dia",store:"chrome",userData:"Library/Application Support/Dia/User Data",app:"Dia",bundleId:"company.thebrowser.dia"},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Library/Application Support/Vivaldi",app:"Vivaldi",bundleId:"com.vivaldi.Vivaldi"},{id:"opera",label:"Opera",store:"chrome",userData:"Library/Application Support/com.operasoftware.Opera",app:"Opera",bundleId:"com.operasoftware.Opera",externalExtensions:false},{id:"comet",label:"Comet",store:"chrome",userData:"Library/Application Support/Comet",app:"Comet",bundleId:"ai.perplexity.comet"},{id:"aside",label:"Aside",store:"chrome",userData:"Library/Application Support/Aside",app:"Aside",bundleId:"at.studio.AsideBrowser"},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Library/Application Support/Naver/Whale",app:"Naver Whale",bundleId:"com.naver.Whale"}];var LINUX=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:".config/google-chrome",binaries:["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/opt/google/chrome/chrome"],externalDir:"/opt/google/chrome/extensions",userExternalDir:false,desktopIds:["google-chrome.desktop","google-chrome-stable.desktop","com.google.Chrome.desktop"],processNames:["chrome","google-chrome"]},{id:"chromium",label:"Chromium",store:"chrome",userData:".config/chromium",binaries:["/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium"],externalDir:"/usr/share/chromium/extensions",userExternalDir:true,desktopIds:["chromium.desktop","chromium-browser.desktop","chromium_chromium.desktop","org.chromium.Chromium.desktop"],processNames:["chromium","chromium-browse"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:".config/microsoft-edge",binaries:["/usr/bin/microsoft-edge","/opt/microsoft/msedge/msedge"],externalDir:"/usr/share/microsoft-edge/extensions",userExternalDir:false,desktopIds:["microsoft-edge.desktop","microsoft-edge-stable.desktop","com.microsoft.Edge.desktop"],processNames:["msedge"]},{id:"brave",label:"Brave",store:"chrome",userData:".config/BraveSoftware/Brave-Browser",binaries:["/usr/bin/brave-browser","/opt/brave.com/brave/brave"],externalDir:"/usr/share/brave/extensions",userExternalDir:false,desktopIds:["brave-browser.desktop","com.brave.Browser.desktop"],processNames:["brave"]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:".config/vivaldi",binaries:["/usr/bin/vivaldi","/opt/vivaldi/vivaldi"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["vivaldi-stable.desktop","vivaldi.desktop"],processNames:["vivaldi-bin"]},{id:"opera",label:"Opera",store:"chrome",userData:".config/opera",binaries:["/usr/bin/opera"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["opera.desktop"],processNames:["opera"]}];var WINDOWS=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Google/Chrome/User Data",registryKey:"HKCU\\Software\\Google\\Chrome\\Extensions",binaryRel:"Google/Chrome/Application/chrome.exe",progIds:["chromehtml"],processNames:["chrome.exe"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Microsoft/Edge/User Data",registryKey:"HKCU\\Software\\Microsoft\\Edge\\Extensions",binaryRel:"Microsoft/Edge/Application/msedge.exe",progIds:["msedgehtm"],processNames:["msedge.exe"]},{id:"brave",label:"Brave",store:"chrome",userData:"BraveSoftware/Brave-Browser/User Data",registryKey:"HKCU\\Software\\BraveSoftware\\Brave-Browser\\Extensions",binaryRel:"BraveSoftware/Brave-Browser/Application/brave.exe",progIds:["bravehtml"],processNames:["brave.exe"]},{id:"chromium",label:"Chromium",store:"chrome",userData:"Chromium/User Data",registryKey:"HKCU\\Software\\Chromium\\Extensions",binaryRel:"Chromium/Application/chrome.exe",progIds:["chromiumhtm"],processNames:[]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Vivaldi/User Data",registryKey:null,externalExtensions:false,binaryRel:"Vivaldi/Application/vivaldi.exe",progIds:["vivaldihtm"],processNames:["vivaldi.exe"]},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Naver/Naver Whale/User Data",registryKey:null,externalExtensions:false,binaryRel:"Naver/Naver Whale/Application/whale.exe",progIds:["whalehtml"],processNames:["whale.exe"]}];var BROWSER_IDS=Object.freeze([...new Set([...MAC,...LINUX,...WINDOWS].map((b)=>b.id))]);function macRow(b,home,exists){const apps=[path10.join("/Applications",`${b.app}.app`),path10.join(home,"Applications",`${b.app}.app`)];const appPath=apps.find((p)=>exists(p))??null;const userDataDir=path10.join(home,b.userData);return{id:b.id,label:b.label,store:b.store,userDataDir,binary:path10.join(appPath??apps[0],"Contents","MacOS",b.app),bundleId:b.bundleId,processNames:apps.map((a)=>path10.join(a,"Contents","MacOS",b.app)),defaultIds:[b.bundleId.toLowerCase()],externalExtensions:b.externalExtensions!==false,installed:appPath!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function linuxRow(b,home,exists){const userDataDir=path10.join(home,b.userData);const binary=b.binaries.find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,binary,externalDir:b.externalDir,userExternalDir:b.userExternalDir,processNames:b.processNames,defaultIds:b.desktopIds.map((d)=>d.toLowerCase()),externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function windowsRow(b,env,home,exists){const localAppData=env.LOCALAPPDATA??path10.join(home,"AppData","Local");const programFiles=[localAppData,env.PROGRAMFILES,env["PROGRAMFILES(X86)"],"C:\\Program Files","C:\\Program Files (x86)"].filter(Boolean);const userDataDir=path10.join(localAppData,b.userData);const binary=programFiles.map((root)=>path10.join(root,b.binaryRel)).find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,registryKey:b.registryKey,binary,processNames:b.processNames,defaultIds:b.progIds,externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function catalogBrowsers({platform=process.platform,home=homedir3(),env=process.env,exists=existsSync3}={}){if(platform==="darwin")return MAC.map((b)=>macRow(b,home,exists));if(platform==="linux")return LINUX.map((b)=>linuxRow(b,home,exists));if(platform==="win32")return WINDOWS.map((b)=>windowsRow(b,env,home,exists));return[]}function detectBrowsers(options={}){return catalogBrowsers(options).filter((b)=>b.installed)}var ENABLE_STEP=(browser)=>`Quit ${browser} completely and open it again; a dialog offers to enable "BrowserSkill" — click Enable.`;var BADGE_STEP=(browser)=>`In ${browser}, open the menu (the badge on the toolbar) and enable "BrowserSkill" when prompted; no restart is needed.`;var storeStep=(browser,label)=>`Open ${STORE_PAGE_URLS[browser.store]} in ${label} and click the "Add" button.`;function externalExtensionEntry({platform=process.platform,browser}){const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const updateUrl=STORE_UPDATE_URLS[browser.store];const label=browser.label??browser.id.charAt(0).toUpperCase()+browser.id.slice(1);if(browser.externalExtensions===false){return{kind:"store",needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:storeStep(browser,label)}}if(platform==="win32"){const key=`${browser.registryKey}\\${id}`;return{kind:"registry",key,needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],regAddArgs:["add",key,"/v","update_url","/t","REG_SZ","/d",updateUrl,"/f"],regDeleteArgs:["delete",key,"/f"],humanStep:BADGE_STEP(label)}}const content=JSON.stringify({external_update_url:updateUrl},null,2)+`
|
|
1534
|
+
`;if(platform==="linux"&&!browser.userExternalDir){return{kind:"file",path:path11.join(browser.externalDir,`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:`Quit ${label} completely and open it again; the extension installs without a prompt.`}}return{kind:"file",path:path11.join(browser.userDataDir,"External Extensions",`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:platform==="linux"?`Quit ${label} completely and open it again; the extension installs without a prompt.`:ENABLE_STEP(label)}}function isBlocklisted(browser,id,{readFile=readFileSync4}={}){if(!browser.userDataDir)return false;for(const profile of["Default","Profile 1","Profile 2","Profile 3"]){const file=path11.join(browser.userDataDir,profile,"Preferences");let prefs;try{prefs=JSON.parse(readFile(file,"utf8"))}catch{continue}if(Array.isArray(prefs?.extensions?.external_uninstalls)&&prefs.extensions.external_uninstalls.includes(id))return true}return false}function runRegDefault(args){return new Promise((resolve)=>{const child=spawn4("reg",args,{stdio:["ignore","ignore","pipe"],windowsHide:true});let stderr="";child.stderr.on("data",(c)=>{stderr+=c});child.on("error",(error)=>resolve({code:-1,stderr:error.message}));child.on("exit",(code)=>resolve({code,stderr}))})}async function registerExternalExtension({platform=process.platform,browser,writeFile=writeFileSync2,readFile=readFileSync4,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const label=browser.label??browser.id;const storeStep=`Open ${entry.storeUrl} in ${label} and click the "Add" button.`;if(entry.kind==="store")return{...entry,registered:false,alreadyPresent:false,reason:"store_only"};if(isBlocklisted(browser,id,{readFile})){return{...entry,registered:false,alreadyPresent:false,reason:"blocklisted",humanStep:`The extension was removed from ${label} before, so external registration is ignored. ${storeStep}`}}if(entry.kind==="registry"){const result=await runReg(entry.regAddArgs);if(result.code!==0)return{...entry,registered:false,alreadyPresent:false,reason:"reg_failed",detail:result.stderr,humanStep:storeStep};return{...entry,registered:true,alreadyPresent:false}}let alreadyPresent=false;try{alreadyPresent=readFile(entry.path,"utf8")===entry.content}catch{}if(alreadyPresent)return{...entry,registered:true,alreadyPresent:true};try{mkdirSync2(path11.dirname(entry.path),{recursive:true});writeFile(entry.path,entry.content)}catch(error){if(error.code==="EACCES"||error.code==="EPERM"||error.code==="EROFS"){return{...entry,registered:false,alreadyPresent:false,reason:"not_writable",humanStep:`${entry.path} is not writable without administrator rights. ${storeStep}`}}throw error}return{...entry,registered:true,alreadyPresent:false}}async function unregisterExternalExtension({platform=process.platform,browser,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});if(entry.kind==="store")return{...entry,removed:false};if(entry.kind==="registry"){const result=await runReg(entry.regDeleteArgs);return{...entry,removed:result.code===0,detail:result.stderr}}const present=existsSync4(entry.path);if(present)rmSync2(entry.path,{force:true});return{...entry,removed:present}}import{execFile}from"node:child_process";import{statSync}from"node:fs";import{homedir as homedir4}from"node:os";import path12 from"node:path";var PROBE_TIMEOUT_MS=3000;function runDefault(command,args){return new Promise((resolve)=>{execFile(command,args,{timeout:PROBE_TIMEOUT_MS,windowsHide:true,maxBuffer:8*1024*1024},(error,stdout)=>{resolve(error?null:String(stdout))})})}function parseMacDefaultBrowser(plistJson){let handlers;try{handlers=JSON.parse(plistJson)?.LSHandlers}catch{return null}if(!Array.isArray(handlers))return null;const pick=(scheme)=>handlers.find((h)=>h?.LSHandlerURLScheme===scheme&&typeof h.LSHandlerRoleAll==="string");const handler=pick("https")??pick("http");return handler?handler.LSHandlerRoleAll.toLowerCase():"com.apple.safari"}function parseWindowsProgId(regOutput){const match=/ProgId\s+REG_SZ\s+(\S+)/i.exec(regOutput??"");return match?match[1].toLowerCase():null}function parseProcessNames(platform,output){const names=new Set;for(const raw of String(output??"").split(/\r?\n/)){const line=raw.trim();if(!line)continue;if(platform==="win32"){const image=/^"([^"]+)"/.exec(line)?.[1];if(image)names.add(image.toLowerCase())}else if(platform==="darwin"){if(/\.app\/Contents\/MacOS\/[^/]+$/.test(line))names.add(line)}else{names.add(path12.basename(line))}}return[...names]}async function probeDefault({platform,home,run}){if(platform==="darwin"){const plist=path12.join(home,"Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist");const json=await run("plutil",["-convert","json","-o","-",plist]);return json===null?null:parseMacDefaultBrowser(json)}if(platform==="linux"){const out=await run("xdg-settings",["get","default-web-browser"]);return out?.trim()?out.trim().toLowerCase():null}if(platform==="win32"){const key="HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice";return parseWindowsProgId(await run("reg",["query",key,"/v","ProgId"]))}return null}async function probeRunning({platform,run}){const out=platform==="win32"?await run("tasklist",["/fo","csv","/nh"]):await run("ps",platform==="darwin"?["-axo","comm="]:["-eo","comm="]);return out===null?null:parseProcessNames(platform,out)}function lastUsedAt(browser,stat){let latest=null;for(const rel of["Local State",path12.join("Default","History"),path12.join("Default","Preferences")]){try{const mtime=stat(path12.join(browser.userDataDir,rel)).mtimeMs;if(latest===null||mtime>latest)latest=mtime}catch{}}return latest}async function probeBrowserSignals({platform=process.platform,home=homedir4(),browsers=[],run=runDefault,stat=statSync}={}){const[defaultBrowser,running]=await Promise.all([probeDefault({platform,home,run}),probeRunning({platform,run})]);const lastUsed=Object.fromEntries(browsers.map((b)=>[b.id,lastUsedAt(b,stat)]));return{defaultBrowser,running,lastUsed}}var RECENT_USE_MS=7*24*60*60*1000;function matchesDefault(browser,defaultId){if(!defaultId)return false;return browser.defaultIds.some((id)=>defaultId===id||defaultId.startsWith(id))}function describeDefault(defaultId,catalog){if(defaultId===null||defaultId===undefined)return{raw:null,id:null,label:null,supported:null};const hit=catalog.find((b)=>matchesDefault(b,defaultId));if(hit)return{raw:defaultId,id:hit.id,label:hit.label,supported:true};const other=NON_CHROMIUM_DEFAULTS.find((b)=>b.match.some((m)=>defaultId===m||defaultId.startsWith(m)));return other?{raw:defaultId,id:other.id,label:other.label,supported:false}:{raw:defaultId,id:null,label:defaultId,supported:false}}function candidateRow(browser,{defaultBrowser,running,lastUsed},now){const isDefault=defaultBrowser.id===browser.id;const isRunning=Array.isArray(running)?browser.processNames.some((n)=>running.includes(n)||running.includes(n.toLowerCase())):null;const last=lastUsed?.[browser.id]??null;const recentlyUsed=last!==null&&now-last<=RECENT_USE_MS;const signals=[isDefault&&"default browser",isRunning&&"running now",recentlyUsed&&"used in the last 7 days"].filter(Boolean);return{id:browser.id,label:browser.label,installed:browser.installed,isDefault,running:isRunning,lastUsedAt:last===null?null:new Date(last).toISOString(),recentlyUsed,signals,browser}}function choose(candidates,primary,confidence,reason,defaultBrowser){return{primary:primary.browser,primaryId:primary.id,confidence,needsChoice:false,reason,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function ask(candidates,reason,defaultBrowser,suggested=null){return{primary:null,primaryId:null,confidence:null,needsChoice:true,reason,suggested,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function identifyBrowser({catalog,signals={},explicit=null,now=Date.now()}){const defaultBrowser=describeDefault(signals.defaultBrowser??null,catalog);const installed=catalog.filter((b)=>b.installed||b.id===explicit);const candidates=installed.map((b)=>candidateRow(b,{defaultBrowser,running:signals.running??null,lastUsed:signals.lastUsed},now));if(explicit){const hit=candidates.find((c)=>c.id===explicit);if(hit)return choose(candidates,hit,"explicit",`${hit.label} was chosen explicitly`,defaultBrowser);return ask(candidates,`"${explicit}" is not a Chromium-family browser this library knows; pick one of the candidates`,defaultBrowser)}if(candidates.length===0)return ask(candidates,"no Chromium-family browser is installed",defaultBrowser);const inUse=candidates.filter((c)=>c.running||c.recentlyUsed);const def=candidates.find((c)=>c.isDefault);if(def&&(def.running||def.recentlyUsed))return choose(candidates,def,"default-and-in-use",`${def.label} is the default browser and ${def.signals.slice(1).join(", ")}`,defaultBrowser);if(def&&inUse.length===0)return choose(candidates,def,"default",`${def.label} is the default browser`,defaultBrowser);if(def)return ask(candidates,`${def.label} is the default browser, but ${inUse.map((c)=>c.label).join(" and ")} ${inUse.length===1?"is":"are"} the one in use`,defaultBrowser);if(defaultBrowser.supported===false){const suggested=inUse.length===1?inUse[0].id:null;return ask(candidates,`the default browser is ${defaultBrowser.label}, which BrowserSkill cannot drive; the user has to name the Chromium browser to use`,defaultBrowser,suggested)}const runningNow=inUse.filter((c)=>c.running);if(inUse.length===1)return choose(candidates,inUse[0],"only-in-use",`${inUse[0].label} is the only browser ${inUse[0].signals.join(", ")}`,defaultBrowser);if(runningNow.length===1)return choose(candidates,runningNow[0],"only-running",`${runningNow[0].label} is the only browser running now`,defaultBrowser);if(inUse.length===0&&candidates.length===1)return choose(candidates,candidates[0],"only-installed",`${candidates[0].label} is the only Chromium-family browser installed`,defaultBrowser);return ask(candidates,inUse.length>1?`several browsers are in use (${inUse.map((c)=>c.label).join(", ")})`:"no browser shows recent use",defaultBrowser)}import{existsSync as existsSync5,readFileSync as readFileSync5,rmSync as rmSync3,writeFileSync as writeFileSync3}from"node:fs";import{homedir as homedir5}from"node:os";import path13 from"node:path";import{spawn as spawn5}from"node:child_process";var INSTALL_SH="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.sh";var INSTALL_PS1="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.ps1";var RC_FILES=[".zshrc",".bashrc",".profile"];function cliInstallCommand({platform=process.platform,installDir,version}={}){const env={BSK_INSTALL_DIR:installDir};if(version)env.BSK_VERSION=version;if(platform==="win32"){return{command:"powershell",args:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`irm ${INSTALL_PS1} | iex`],env}}return{command:"sh",args:["-c",`curl -fsSL ${INSTALL_SH} | sh`],env}}function runDefault2(command,args,env){return new Promise((resolve)=>{const child=spawn5(command,args,{env:{...process.env,...env},stdio:["ignore","pipe","pipe"],windowsHide:true});let stdout="",stderr="";child.stdout.on("data",(c)=>{stdout=(stdout+c).slice(-8192)});child.stderr.on("data",(c)=>{stderr=(stderr+c).slice(-8192)});child.on("error",(error)=>resolve({code:-1,stdout,stderr:error.message}));child.on("exit",(code)=>resolve({code,stdout,stderr}))})}function snapshotRcFiles(home){const snapshot=new Map;for(const name of RC_FILES){const file=path13.join(home,name);snapshot.set(file,existsSync5(file)?readFileSync5(file):null)}return snapshot}function restoreRcFiles(snapshot){const restored=[];for(const[file,before]of snapshot){const after=existsSync5(file)?readFileSync5(file):null;if(before===null&&after===null)continue;if(before!==null&&after!==null&&before.equals(after))continue;if(before===null)rmSync3(file,{force:true});else writeFileSync3(file,before);restored.push(file)}return restored}async function installBskCli({platform=process.platform,home=homedir5(),installDir=path13.join(home,".local","bin"),version,run=runDefault2}={}){const spec=cliInstallCommand({platform,installDir,version});const rcBefore=platform==="win32"?new Map:snapshotRcFiles(home);const result=await run(spec.command,spec.args,spec.env);const restoredRcFiles=restoreRcFiles(rcBefore);const bskBin=path13.join(installDir,platform==="win32"?"bsk.exe":"bsk");const installed=result.code===0&&existsSync5(bskBin);return{installed,bskBin:installed?bskBin:null,code:result.code,stdout:result.stdout,stderr:result.stderr,restoredRcFiles}}function cliVersionDefault(bskBin){return new Promise((resolve)=>{const child=spawn5(bskBin,["--version"],{stdio:["ignore","pipe","ignore"],windowsHide:true});let out="";child.stdout.on("data",(c)=>{out+=c});child.on("error",()=>resolve(null));child.on("exit",(code)=>resolve(code===0?out.trim():null))})}import{homedir as homedir6}from"node:os";import{existsSync as existsSync6,readFileSync as readFileSync6}from"node:fs";var DEFAULT_WAIT_TOTAL_MS=180000;function resolveBskBin(bskBin,env){if(bskBin===null)return null;return bskBin??findBskBinary(env)}async function cliReport({bskBin,env,cliVersion}){const bin=resolveBskBin(bskBin,env);if(!bin)return{installed:false,bskBin:null,version:null};const raw=await cliVersion(bin);return raw?{installed:true,bskBin:bin,version:raw.replace(/^bsk\s+/,"")}:{installed:false,bskBin:bin,version:null}}async function daemonReport(client,waitForBrowserMs){try{const status=await client.call("system.status",waitForBrowserMs>0?{wait_for_browser_ms:waitForBrowserMs}:{},{idPrefix:"status"});return{running:true,version:status.daemon_version,protocolVersion:status.protocol_version,browsers:status.browsers??[],sessions:status.sessions??[]}}catch(error){return{running:false,error:error.message,code:error.code,browsers:[],sessions:[]}}}var NO_BROWSER_STEP=`No Chromium-family browser was found. Install one (Chrome, Edge, Brave, Arc, ...), then add the extension from ${STORE_PAGE_URLS.chrome}.`;function withRegistration(platform,browser){const entry=externalExtensionEntry({platform,browser});const extensionRegistered=entry.kind==="file"&&existsSync6(entry.path)&&readFileSync6(entry.path,"utf8")===entry.content;return{...browser,registration:entry,extensionRegistered}}async function identify({platform,home,env,exists,browser,signals,probeSignals,now}){const catalog=catalogBrowsers({platform,home,env,exists}).map((b)=>withRegistration(platform,b));const observed=signals??await probeSignals({platform,home,browsers:catalog.filter((b)=>b.hasProfile)});const explicit=browser??env.OMOWRIGHT_BROWSER??null;const{primary,...identification}=identifyBrowser({catalog,signals:observed,explicit:explicit||null,now});const browsers=catalog.filter((b)=>b.installed);const registeredElsewhere=browsers.filter((b)=>b.extensionRegistered&&b.id!==identification.primaryId).map((b)=>b.id);return{browsers,identification:{...identification,registeredElsewhere},primary}}function choiceStep(identification){const listed=identification.candidates.map((c)=>`${c.label} [${c.id}]${c.signals.length?` — ${c.signals.join(", ")}`:""}`).join("; ");const hint=identification.suggested?` Likely ${identification.suggested}, but confirm first.`:"";return`Ask the user which browser they actually use (${identification.reason}). Candidates: ${listed}.${hint} Then re-run with browser: "<id>"; register nothing until they answer.`}function nextStepFor({cli,daemon,browsers,browsersConnected,identification,primary}){if(browsersConnected.length>0)return null;if(!cli.installed)return"Install the bsk CLI (bskOnboard() runs the official installer).";if(!daemon.running)return"Start the BrowserSkill daemon: run `bsk status` once, or `bsk daemon start` in the environment that owns it.";if(browsers.length===0&&!primary)return NO_BROWSER_STEP;if(identification.needsChoice)return choiceStep(identification);if(!primary.extensionRegistered&&primary.registration.kind!=="store")return`Register the BrowserSkill extension for ${primary.label} (bskOnboard() does this), then let the user enable it.`;return primary.registration.humanStep}async function bskDoctor({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,cliVersion=cliVersionDefault,waitForBrowserMs=0,exists=existsSync6,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){const cli=await cliReport({bskBin,env,cliVersion});const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:false});const daemon=await daemonReport(ipc,waitForBrowserMs);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const browsersConnected=daemon.browsers;const report={cli,daemon,browsers,primary:primary??null,identification,browsersConnected,ready:browsersConnected.length>0};report.nextStep=nextStepFor(report);return report}async function bskOnboard({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,installDir,installCli=installBskCli,cliVersion=cliVersionDefault,onHumanStep=()=>{},waitForBrowserMs=15000,waitTotalMs=DEFAULT_WAIT_TOTAL_MS,exists=existsSync6,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){let cli=await cliReport({bskBin,env,cliVersion});let install=null;if(!cli.installed){install=await installCli({platform,home,installDir});cli=install.installed?await cliReport({bskBin:install.bskBin,env,cliVersion}):cli}const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:true});let daemon=await daemonReport(ipc,0);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const registrations=[];let humanStep=null;let needsChoice=false;if(daemon.browsers.length===0){if(browsers.length===0&&!primary){humanStep=NO_BROWSER_STEP}else if(identification.needsChoice){needsChoice=true;humanStep=choiceStep(identification)}else{const registration=await registerExternalExtension({platform,browser:primary});registrations.push({browser:primary.id,...registration});humanStep=registration.humanStep;onHumanStep({browser:primary.id,step:registration.humanStep,registered:registration.registered,needsRestart:registration.needsRestart})}}const deadline=Date.now()+waitTotalMs;let browsersConnected=daemon.browsers;while(!needsChoice&&browsersConnected.length===0&&daemon.running&&Date.now()<deadline){browsersConnected=await listBrowsers(ipc,{waitForBrowserMs});if(waitForBrowserMs===0)break}daemon={...daemon,browsers:browsersConnected};const ready=browsersConnected.length>0;return{cli,install,daemon,browsers,primary:primary??null,identification,needsChoice,registrations,humanStep:ready?null:humanStep,browsersConnected,ready}}export{AgentTabManager,BROWSERSKILL_EXTENSION_IDS,BROWSER_EVENT_NAMES,BridgeProtocolError,BridgeUnavailableError,BrowserCdpCommandError,BrowserConnection,BskIpcClient,BskRpcError,BskSession,CdpClient,CommandSchema,DEFAULT_AGENT_VIEWPORT,DEVICE_PRESETS,ElementHandle,EventSchema,ExtensionHelloSchema,FrameLocator,FrameManager,HostHelloSchema,Locator,ModifierState,OmOKeyboard,OmOMouse,OmOPage,PipeCdpClient,ResponseSchema,STORE_PAGE_URLS,SessionManager,TabRepository,UnsupportedOperationError,bskDoctor,bskOnboard,bskSnapshot,buildCloakBrowserArgs,buildSnapshotExpression,catalogBrowsers,collectWhileScrolling,compactSnapshot,connect,connectBrowserSkill,connectCloakProfile,connectPipe,createAgentTabs,createBridgeClient,createCaptcha,createChromeApi2 as createChromeApi,createCua,createEvents,createNativeMessagingHost,createNetworkSnoop,createRoutes,createTrace,decodeFrames,describeLayers,detectBrowsers,emulate,encodeFrame,externalExtensionEntry,findCloakBrowserPath,identifyBrowser,injectCookies,installBskCli,installNativeMessagingHost,layersHeader,listBrowsers,macOSVisionOcr,normalizeDialogPolicy,pageBundle,probeBrowserSignals,readDaemonInfo,reconcileFrames,registerExternalExtension,repinViewport,requestHuman,resolveBskHome,resolveCloakProfile,resolveDialogAction,sanitizeCookies,snapshotTokens,snapshotWithFrames,snapshotWithLayers,takeSnapshot,toHar,toolSchemas,unregisterExternalExtension};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.0.1",
|
|
3
|
-
"sourceDigest": "
|
|
3
|
+
"sourceDigest": "167dcc331041e909a54c6e9695ea8d09bbb96bc351d62333d7ac628f780b83f6",
|
|
4
4
|
"files": {
|
|
5
|
-
"index.js": "
|
|
5
|
+
"index.js": "b6b064fab277031c6c51f5a58b84b31040f9960bf767bf646adc0540c1d25c1b",
|
|
6
6
|
"page-bundle.js": "bfd51e8383cf47200d57387bbbed258bce5a3a3d00c38fc108034d2888e23f0c"
|
|
7
7
|
},
|
|
8
|
-
"stagedAtUtc": "2026-09-
|
|
8
|
+
"stagedAtUtc": "2026-09-24T23:43:38.269Z"
|
|
9
9
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { loadOmowright } from "./omowright.mjs"
|
|
3
3
|
|
|
4
4
|
const { entry, omowright } = await loadOmowright()
|
|
5
|
-
const
|
|
5
|
+
const browserArg = process.argv.find((arg) => arg.startsWith("--browser="))
|
|
6
|
+
const report = await omowright.bskDoctor({ waitForBrowserMs: 0, browser: browserArg ? browserArg.slice("--browser=".length) : undefined })
|
|
6
7
|
|
|
7
8
|
const state = report.ready
|
|
8
9
|
? "ready"
|
|
@@ -10,15 +11,18 @@ const state = report.ready
|
|
|
10
11
|
? "no-cli"
|
|
11
12
|
: !report.daemon.running
|
|
12
13
|
? "no-daemon"
|
|
13
|
-
: report.browsers.length === 0
|
|
14
|
+
: report.browsers.length === 0 && !report.primary
|
|
14
15
|
? "no-browser-support"
|
|
15
|
-
:
|
|
16
|
+
: report.identification.needsChoice
|
|
17
|
+
? "choose-browser"
|
|
18
|
+
: "no-extension"
|
|
16
19
|
|
|
17
20
|
const REMEDIES = {
|
|
18
21
|
ready: "Attached engine is live: const { connectBrowserSkill } = await import(omowrightEntry); const session = await connectBrowserSkill({ name: \"<task>\", focused: false })",
|
|
19
22
|
"no-cli": "Run node \"<skill-root>/scripts/browser-install.mjs\" — it installs the CLI, starts the daemon and registers the extension; then tell the user the single step it prints.",
|
|
20
23
|
"no-daemon": "Run node \"<skill-root>/scripts/browser-install.mjs\" (it starts the daemon through `bsk status`), then re-run this doctor.",
|
|
21
24
|
"no-extension": report.nextStep ?? "Register the extension with browser-install.mjs, then relay its human step to the user and re-run this doctor.",
|
|
25
|
+
"choose-browser": `Do not install anything yet. First check your memory for the browser this user works in; if it names one of the candidates, re-run with --browser=<id>. Otherwise ask the user which browser they actually use (${report.identification.reason}), record the answer in memory, then run node "<skill-root>/scripts/browser-install.mjs" --browser=<id>.`,
|
|
22
26
|
"no-browser-support": "No Chromium-family browser profile was found on this machine. Say so and stop; do not substitute another engine.",
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -27,6 +31,10 @@ const summary = {
|
|
|
27
31
|
omowright: entry,
|
|
28
32
|
cli: report.cli,
|
|
29
33
|
daemon: { running: report.daemon.running, version: report.daemon.version ?? null, protocolVersion: report.daemon.protocolVersion ?? null, error: report.daemon.error ?? null },
|
|
34
|
+
browser: report.primary ? { id: report.primary.id, label: report.primary.label, confidence: report.identification.confidence, reason: report.identification.reason } : null,
|
|
35
|
+
candidates: report.identification.candidates.map((c) => ({ id: c.id, label: c.label, signals: c.signals })),
|
|
36
|
+
defaultBrowser: report.identification.defaultBrowser,
|
|
37
|
+
registeredElsewhere: report.identification.registeredElsewhere,
|
|
30
38
|
browsersDetected: report.browsers.map((browser) => ({ id: browser.id, userDataDir: browser.userDataDir, extensionRegistered: browser.extensionRegistered })),
|
|
31
39
|
browsersConnected: report.browsersConnected.map((browser) => ({ instanceId: browser.instance_id, name: browser.browser_name, version: browser.browser_version })),
|
|
32
40
|
nextStep: report.nextStep,
|
|
@@ -40,6 +48,9 @@ if (process.argv.includes("--json")) {
|
|
|
40
48
|
console.log(`omowright: ${entry}`)
|
|
41
49
|
console.log(`cli: ${report.cli.installed ? `${report.cli.bskBin} (${report.cli.version})` : "not installed"}`)
|
|
42
50
|
console.log(`daemon: ${report.daemon.running ? `running (${report.daemon.version}, protocol ${report.daemon.protocolVersion})` : `not running${report.daemon.error ? ` — ${report.daemon.error}` : ""}`}`)
|
|
51
|
+
console.log(`browser the user uses: ${summary.browser ? `${summary.browser.label} [${summary.browser.id}] (${summary.browser.reason})` : `not identified (${report.identification.reason})`}`)
|
|
52
|
+
console.log(`candidates: ${summary.candidates.map((c) => `${c.id}${c.signals.length ? ` (${c.signals.join(", ")})` : ""}`).join(", ") || "none"}`)
|
|
53
|
+
if (summary.registeredElsewhere.length > 0) console.log(`extension entries left in other browsers: ${summary.registeredElsewhere.join(", ")} (not removed automatically)`)
|
|
43
54
|
console.log(`browsers detected: ${summary.browsersDetected.map((b) => `${b.id}${b.extensionRegistered ? " (extension registered)" : ""}`).join(", ") || "none"}`)
|
|
44
55
|
console.log(`browsers connected: ${summary.browsersConnected.map((b) => `${b.name} ${b.version} [${b.instanceId}]`).join(", ") || "none"}`)
|
|
45
56
|
console.log(`\n${REMEDIES[state]}`)
|
|
@@ -4,10 +4,13 @@ import { loadOmowright } from "./omowright.mjs"
|
|
|
4
4
|
const json = process.argv.includes("--json")
|
|
5
5
|
const waitArg = process.argv.find((arg) => arg.startsWith("--wait-ms="))
|
|
6
6
|
const waitTotalMs = waitArg ? Number(waitArg.slice("--wait-ms=".length)) : 0
|
|
7
|
+
const browserArg = process.argv.find((arg) => arg.startsWith("--browser="))
|
|
8
|
+
const browser = browserArg ? browserArg.slice("--browser=".length) : undefined
|
|
7
9
|
|
|
8
10
|
const { omowright } = await loadOmowright()
|
|
9
11
|
const steps = []
|
|
10
12
|
const result = await omowright.bskOnboard({
|
|
13
|
+
browser,
|
|
11
14
|
onHumanStep: (step) => steps.push(step),
|
|
12
15
|
waitForBrowserMs: waitTotalMs > 0 ? Math.min(15_000, waitTotalMs) : 0,
|
|
13
16
|
waitTotalMs,
|
|
@@ -18,6 +21,10 @@ const summary = {
|
|
|
18
21
|
cli: result.cli,
|
|
19
22
|
installerRan: result.install !== null,
|
|
20
23
|
daemon: { running: result.daemon.running, version: result.daemon.version ?? null, error: result.daemon.error ?? null },
|
|
24
|
+
browser: result.primary ? { id: result.primary.id, label: result.primary.label, confidence: result.identification.confidence, reason: result.identification.reason } : null,
|
|
25
|
+
needsChoice: result.needsChoice,
|
|
26
|
+
candidates: result.identification.candidates.map((c) => ({ id: c.id, label: c.label, signals: c.signals })),
|
|
27
|
+
registeredElsewhere: result.identification.registeredElsewhere,
|
|
21
28
|
registrations: result.registrations.map((r) => ({ browser: r.browser, registered: r.registered, alreadyPresent: r.alreadyPresent ?? false, reason: r.reason ?? null, needsRestart: r.needsRestart ?? null, humanStep: r.humanStep ?? null })),
|
|
22
29
|
browsersConnected: result.browsersConnected.map((b) => ({ instanceId: b.instance_id, name: b.browser_name, version: b.browser_version })),
|
|
23
30
|
humanStep: result.humanStep,
|
|
@@ -30,9 +37,14 @@ if (json) {
|
|
|
30
37
|
} else {
|
|
31
38
|
console.log(`cli: ${result.cli.installed ? `${result.cli.bskBin} (${result.cli.version})` : "install failed"}`)
|
|
32
39
|
console.log(`daemon: ${result.daemon.running ? "running" : `not running${result.daemon.error ? ` — ${result.daemon.error}` : ""}`}`)
|
|
40
|
+
console.log(summary.browser ? `browser: ${summary.browser.label} [${summary.browser.id}] (${summary.browser.reason})` : `browser: not identified (${result.identification.reason})`)
|
|
33
41
|
for (const r of summary.registrations) {
|
|
34
42
|
console.log(`extension for ${r.browser}: ${r.registered ? (r.alreadyPresent ? "already registered" : "registered") : `not registered (${r.reason})`}`)
|
|
35
43
|
}
|
|
36
|
-
|
|
44
|
+
if (result.needsChoice) {
|
|
45
|
+
console.log(`\nNothing was registered. ${result.humanStep}\nRe-run: node "<skill-root>/scripts/browser-install.mjs" --browser=<id>`)
|
|
46
|
+
} else {
|
|
47
|
+
console.log(`\nTell the user exactly this, then re-run browser-doctor.mjs:\n ${result.humanStep}`)
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
process.exitCode = result.ready ? 0 : 2
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ulw-research — Research Orchestration + report-tools Runtime
|
|
2
|
+
|
|
3
|
+
## OVERVIEW
|
|
4
|
+
|
|
5
|
+
`SKILL.md` is the research orchestration contract (team-first saturation, claim graph, convergence, delivery). `scripts/` is the deliverable runtime that contract calls: `report-tools.mjs`, a zero-dependency Node CLI for the deliverable phase (outcome manifest, static and layout gates, bounded repair, format extraction). `references/` holds the edition-neutral deliverable contract and the defect glossary. Earned this file: the only shared skill whose runtime is overlaid into a second, separately authored edition (the senpi native skill) at sync time.
|
|
6
|
+
|
|
7
|
+
## STRUCTURE
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ulw-research/
|
|
11
|
+
├── SKILL.md # shared-edition prose contract (router to the references)
|
|
12
|
+
├── references/
|
|
13
|
+
│ ├── deliverable-phase.md # lanes, state, destination defaults, interview, memory episodes, design spec, gates, repair, manifest, command reference
|
|
14
|
+
│ ├── report-gates.md # one row per defect code: severity, integrity, what it detects, how to fix
|
|
15
|
+
│ └── latex-report.md
|
|
16
|
+
└── scripts/
|
|
17
|
+
├── report-tools.mjs # CLI entry + dispatcher (COMMANDS, run, main)
|
|
18
|
+
├── report-tools-commands.mjs # one function per subcommand
|
|
19
|
+
├── cli-support.mjs / entry-guard.mjs # flags, CliError, exit codes, atomic JSON, symlink-safe entry check
|
|
20
|
+
├── contracts.mjs # frozen vocabulary: DEFECT_CODES (severity lives only here), enums, manifest + repair-state validators
|
|
21
|
+
├── outcome.mjs # outcome.json manifest, deliverable state, verify, closing briefing
|
|
22
|
+
├── repair-tracker.mjs # bounded repair decisions (attempts, plateau, oscillation, wall clock, integrity codes)
|
|
23
|
+
├── html-lite.mjs / entities.mjs # HTML tokenizer, selector paths, prose text
|
|
24
|
+
├── css-lite.mjs / design-spec.mjs # CSS scanning, palette + lineage parsing, design-spec rendering
|
|
25
|
+
├── gates-static.mjs + gates-text.mjs / gates-figures.mjs / gates-structure.mjs # static gates G1-G15
|
|
26
|
+
├── layout-probe.mjs / gates-layout.mjs # in-page probe source + layout gates L1-L5 over its output
|
|
27
|
+
├── format-extract.mjs / format-extract-css.mjs # design-spec draft from a pointed-at HTML or Markdown document
|
|
28
|
+
├── *.test.ts # co-located bun tests, one per module (unshipped)
|
|
29
|
+
└── tests/fixtures/ # good/bad reports, boxes.json, reference docs (unshipped)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## SURFACE
|
|
33
|
+
|
|
34
|
+
`node "$SKILL_DIR/scripts/report-tools.mjs" <command>`: `check`, `layout-probe`, `repair decide`, `outcome init`, `outcome set`, `outcome gate`, `outcome render`, `outcome state`, `outcome verify`, `outcome finish`, `outcome briefing`, `format-extract`, `--help --json`. Exit codes: `0` pass, `1` semantic failure (blockers, a failed verify, a blocked repair), `2` usage or IO. JSON on stdout, one summary line on stderr. Flags per command: `references/deliverable-phase.md` section 10.
|
|
35
|
+
|
|
36
|
+
## CONVENTIONS
|
|
37
|
+
|
|
38
|
+
- Zero runtime dependencies: plain ESM `.mjs`, `node:` builtins and sibling imports only, no `Bun.*`. Runs under Node >= 20 or Bun 1.4 (global `fetch` for `format-extract --from-url`).
|
|
39
|
+
- Each `.mjs` stays at or under 250 lines; split by responsibility, never by size.
|
|
40
|
+
- The CLI never launches a browser. The layout probe is printed as one async expression; the orchestrator evaluates it through the browser skill's owned headless engine and passes the result back with `check --layout`.
|
|
41
|
+
- Time is always injected (`--now`, `startedAt`); no module reads the clock inside a decision.
|
|
42
|
+
- Tests are co-located given/when/then bun tests; CLI tests spawn `node` with bun removed from `PATH`. Fixtures live only under `scripts/tests/fixtures/`, are depersonalized, and never ship.
|
|
43
|
+
- The senpi source dir (`packages/omo-senpi/skills/ulw-research/`) intentionally has no `scripts/`, `references/report-gates.md`, or `references/deliverable-phase.md`: the senpi sync overlays them from here byte-for-byte (`sharedAssets` in `packages/omo-senpi/plugin/scripts/native-skill-sources.mjs`), and both editions' sync suites assert the shipped copies equal these sources.
|
|
44
|
+
- References and scripts carry no Hangul (Korean match strings are `\u` escapes) and never three consecutive newlines (the senpi sync normalizes those and would break byte equality).
|
|
45
|
+
|
|
46
|
+
## ANTI-PATTERNS
|
|
47
|
+
|
|
48
|
+
- NEVER hand-edit a shipped copy under `packages/omo-senpi/plugin/skills/` or the packaged plugin tree; edit here and re-sync.
|
|
49
|
+
- NEVER add a severity override or a caller-supplied severity. The only sanctioned exception is `unsourced_number` following the design spec's `Lineage:` mode.
|
|
50
|
+
- Never let a gate or the repair loop silently skip: an unrunnable gate is recorded `not_run`, and an integrity code blocks delivery.
|
|
51
|
+
- Never name a browser library in the references or `SKILL.md`; route through the browser skill.
|
|
52
|
+
- Never test prose: pin machine-consumed values (CLI JSON, exit codes, extractor output, tracker decisions, defect-code vocabulary) and shipped-copy equality only.
|
|
53
|
+
|
|
54
|
+
## COMMANDS
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# from the repository root
|
|
58
|
+
bun test --timeout 20000 packages/shared-skills/skills/ulw-research/scripts
|
|
59
|
+
node packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs --help --json
|
|
60
|
+
node packages/omo-senpi/plugin/scripts/sync-skills.mjs && bun test packages/omo-senpi/src/skills-sync.test.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- Parent: [`packages/shared-skills/AGENTS.md`](../../AGENTS.md).
|
|
@@ -52,10 +52,10 @@ The research is done when all of these hold:
|
|
|
52
52
|
- Claims that were contested, undocumented, or performance-shaped were proven or refuted by executed code.
|
|
53
53
|
- Every claim in the deliverable cites a source or a verification artifact.
|
|
54
54
|
- Every asserted claim is represented in the claim graph, tied to an intent-vs-reality diff when an expected truth exists, and backed by observation manifest entries from independent observation groups or a documented single-source exception; convergence or exception status is explicit.
|
|
55
|
-
- The format
|
|
56
|
-
- The delivered artifact passed
|
|
55
|
+
- The deliverable lane and format were derived from the destination or asked through the empty-info interview without blocking collection, recorded in `brief.md` with `answered_by`, and the final materials match that record.
|
|
56
|
+
- The delivered artifact passed the delivery gates in order (static gates, layout gates, visual QA, and the proofread pass where the harness provides one), each status is in `outcome.json`, and `outcome verify` passed.
|
|
57
57
|
- Every excursion opened during the run was closed by an EXIT rule, folded back into the claim or axis that triggered it, and recorded in both `excursion-log.md` and the ulw-loop ledger.
|
|
58
|
-
- The delivery message carries the closing briefing
|
|
58
|
+
- The delivery message carries the closing briefing printed by `outcome briefing`: sources (total + unique domains), elapsed minutes, every promised deliverable with its status, and any residual defects.
|
|
59
59
|
- The session journal reconstructs what was searched, found, and expanded, wave by wave, and it was written in real time rather than reconstructed at the end.
|
|
60
60
|
|
|
61
61
|
## Epistemic instrumentation
|
|
@@ -132,6 +132,8 @@ mkdir -p .omo/ulw-research/$(date +%Y%m%d-%H%M%S)
|
|
|
132
132
|
|
|
133
133
|
This is `$SESSION_DIR`. The orchestrator owns the journal: you write every file in it; workers never do. Maintain:
|
|
134
134
|
|
|
135
|
+
- `brief.md` — the analysis block, the axis list, the expected truths, and the `## Deliverable` record from the interview below (lane, state, formats, each field with `answered_by`).
|
|
136
|
+
- `sources-ledger.md` — one line per source the moment it is read: `[S<n>] <url> — <what it is>`. The closing briefing counts sources and domains from this file.
|
|
135
137
|
- `wave-<N>-<kind>-<axis>.md` — your digest of each worker return: key findings, sources with URLs, and the worker's EXPAND markers verbatim.
|
|
136
138
|
- `expansion-log.md` — per wave: workers spawned, markers gained, leads opened and closed.
|
|
137
139
|
- `excursion-log.md` — one ENTER row and one EXIT row per excursion: `excursion_id`, parent claim or axis, ENTER trigger, depth, workers spent, the EXIT rule that closed it, what it changed in the top-level answer (`none` is a valid, required answer), and the ulw-loop steer/evidence id it was mirrored into.
|
|
@@ -148,20 +150,20 @@ Append each digest the moment its worker returns, not in a batch at the end —
|
|
|
148
150
|
|
|
149
151
|
ulw-loop is ON by default for this mode: when the `ulw-loop` skill is available, register the research axes as loop goals so the run has durable state and survives a compaction. The session directory's timestamp is the run's start clock — the closing briefing is computed from it, so create it once and never rename it. From that point every finding, source, quote, number, and lead is written into `$SESSION_DIR` **the instant it lands** — never held in the conversation for an end-of-run dump. After any context loss, re-read the brief and the journal before doing anything else, then resume from the open wave.
|
|
150
152
|
|
|
151
|
-
###
|
|
153
|
+
### Deliverable lane, format, and the empty-info interview
|
|
152
154
|
|
|
153
|
-
Never
|
|
155
|
+
Never block collection on the shape of the deliverable, and never guess it either. Read [references/deliverable-phase.md](references/deliverable-phase.md) sections 1-6 before writing the brief, then:
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
1. **Derive first.** Name the lane (`template-strict`, `template-vibe`, `no-format`, `edit-existing`) and the promised formats from the request and its destination (the reference's section 3). When the request refers to an existing deliverable, decide its state with `node "$SKILL_DIR/scripts/report-tools.mjs" outcome state --deliverable <path> --session-dir "$SESSION_DIR"`: `partial` resumes the skeleton on disk, `complete` means edit-existing and never a regenerated report. `$SKILL_DIR` is this skill's own directory, the folder containing this SKILL.md.
|
|
158
|
+
2. **Read the requester's format memory** with the memory tool when the harness exposes one: the pointer `system/human/report-style.md`, then `reference/human-report-style.md`; take the choice recorded for the most similar context (same destination kind and audience) as the first option.
|
|
159
|
+
3. **Ask only what is still missing**: at most the reference's three questions (destination and format, audience and length, template lineage) in one call to the harness question tool (`ask_user_question` or `request_user_input`), non-blocking where the harness supports it, each with its default first, a free-text "describe the format" path, and "don't care, you decide". Collection starts without waiting; a late answer is folded in until the assembly worker starts, and after that it becomes a re-render request.
|
|
160
|
+
4. **Record it.** Write `## Deliverable` into `brief.md` (lane, state, formats, destination, audience, template, format description, each with `answered_by: user|default|request`) and open the manifest in the same step: `node "$SKILL_DIR/scripts/report-tools.mjs" outcome init --promised <formats> --lane <lane> --session-dir "$SESSION_DIR"`.
|
|
159
161
|
|
|
160
|
-
|
|
162
|
+
Phase 5 opens by turning the recorded fields into `design-spec.md`.
|
|
161
163
|
|
|
162
164
|
## Phase 1 — Saturation wave
|
|
163
165
|
|
|
164
|
-
**When the user asked for MASS research, the wave is sized by the topic's angles, not by the roster ceiling.** "mass ulw research", "mulw research", "ulw mass research" — in any language — order over-collection that a team of 8 cannot produce. Where the harness has a dependency-graph surface (the `mass-ulw` skill and its `references/planning.md`), run collection through it instead: a 60+ node opening wave covering every angle the topic has, routed across the whole difficulty ladder in one graph — mechanical sweeps and per-item harvest batches on the cheapest tier, judgment-shaped angles a step up, cross-territory angles a step above that, and the deepest tier reserved for genuinely hairy contradictions. Each wave's EXPAND leads define the next wave's nodes, and the synthesis reduces through several parallel architect-tier nodes into ONE architect-tier reducer (the strongest reasoning tier substitutes wherever no architect tier exists). Absent that surface, hold the same shape with background workers in batched waves. Everything else in this skill still binds: the
|
|
166
|
+
**When the user asked for MASS research, the wave is sized by the topic's angles, not by the roster ceiling.** "mass ulw research", "mulw research", "ulw mass research" — in any language — order over-collection that a team of 8 cannot produce. Where the harness has a dependency-graph surface (the `mass-ulw` skill and its `references/planning.md`), run collection through it instead: a 60+ node opening wave covering every angle the topic has, routed across the whole difficulty ladder in one graph — mechanical sweeps and per-item harvest batches on the cheapest tier, judgment-shaped angles a step up, cross-territory angles a step above that, and the deepest tier reserved for genuinely hairy contradictions. Each wave's EXPAND leads define the next wave's nodes, and the synthesis reduces through several parallel architect-tier nodes into ONE architect-tier reducer (the strongest reasoning tier substitutes wherever no architect tier exists). Absent that surface, hold the same shape with background workers in batched waves. Everything else in this skill still binds: the deliverable interview, the journal, the claim graph, the convergence rules, and the delivery gates.
|
|
165
167
|
|
|
166
168
|
Otherwise launch the entire first wave in one turn — every axis at once, as team members if you formed a team, else as background workers. Sequential launches and "start with one and see" defeat the mode.
|
|
167
169
|
|
|
@@ -303,7 +305,7 @@ Workers: <total> · Waves: <count> · Excursions: <count> · Sources: <count> (<
|
|
|
303
305
|
|
|
304
306
|
`SYNTHESIS.md` is the citation source of truth for final materials: every claim carries inline `[Source N]` citations, and every high-risk non-code claim you assert must be a verified-claims row from Phase 3b. Assert nothing the gate left in the unresolved/refuted annex.
|
|
305
307
|
|
|
306
|
-
**Write the skeleton early and fill it as claims lock.** The moment the
|
|
308
|
+
**Write the skeleton early and fill it as claims lock.** The moment the brief records the deliverable, create the deliverable file with its section headings and a `STATUS: draft — <n> sections open` line as line 2 (an HTML comment in HTML); that line is the partial-state marker `outcome state` reads, removed only when the deliverable is complete. An interrupted run must leave a partial report on disk, never an empty directory and a lost conversation.
|
|
307
309
|
|
|
308
310
|
**Keep sourced numbers, assumptions, and derived results visibly apart.** Every quantitative claim carries its lineage: `MEASURED` (a number a source states, cited), `ASSUMED` (a coefficient, distribution, or scope you chose — say why), `DERIVED` (computed from those, showing the formula), plus a sensitivity line whenever the assumption moves the answer. Presenting a derived estimate with the confidence of a measured one is the most damaging thing this mode can ship.
|
|
309
311
|
|
|
@@ -311,7 +313,7 @@ Workers: <total> · Waves: <count> · Excursions: <count> · Sources: <count> (<
|
|
|
311
313
|
|
|
312
314
|
## Phase 5 — Final materials
|
|
313
315
|
|
|
314
|
-
The
|
|
316
|
+
The promised formats recorded in `brief.md` and `outcome.json` are binding; `pdf` + `docx` is the default pair only when a document was asked for with no format word and no destination:
|
|
315
317
|
|
|
316
318
|
| Target | How |
|
|
317
319
|
|---|---|
|
|
@@ -320,7 +322,7 @@ The format answered at the Phase 0 gate is binding. Absent an explicit user over
|
|
|
320
322
|
| Slides / deck | `uv run --with python-pptx python` — one claim per slide, a chart or diagram per claim. |
|
|
321
323
|
| Standalone HTML / Markdown | The authored source itself. |
|
|
322
324
|
|
|
323
|
-
**Write `design-spec.md` the moment the
|
|
325
|
+
**Write `design-spec.md` the moment the deliverable is recorded — before any asset worker spawns.** When a document was pointed at, extract it instead of retyping its stylesheet: `node "$SKILL_DIR/scripts/report-tools.mjs" format-extract <reference> --out "$SESSION_DIR/design-spec.md"` (`--from-url` for a URL), then fill every `TODO: ask` from the interview answers, never by guessing. It is the one design contract every asset and assembly worker receives: template family (absent a reference, the clean analyst-report register — one accent over neutral tones, generous margins, styled section headings, no emoji, no clipart), palette tokens, body/heading fonts (a real gothic CJK webfont — Pretendard, Noto Sans KR — when the report language needs one, with `word-break: keep-all` for Korean), responsive breakpoints and HTML/CSS charts for web deliverables, the figure standard below, and the lineage mode on its own line: `Lineage: inline` (analyst default: every unit-bearing number tagged MEASURED / ASSUMED / DERIVED or cited in the same element) or `Lineage: section` (web and blog register: every section cited, lineage in the figure captions). The reference's section 6 lists the full defaults. One font family and one palette govern prose, charts, Mermaid, and generated images alike; a diagram rendering in a random default font inside a styled report is a defect, not a style choice.
|
|
324
326
|
|
|
325
327
|
**The figure standard — binding for every image, chart, and diagram.** Each figure sits in a fixed-size container styled from the spec (border, background, caption); the image scales to fit entirely inside it with its original aspect ratio preserved — object-fit: contain semantics — never stretched, never cropped, never spilling out. Every chart carries a title, axis labels, units, and value labels in the report's language; a bare number the reader cannot name is a defect.
|
|
326
328
|
|
|
@@ -335,24 +337,29 @@ Asset workers (background, parallel, each fed `design-spec.md`) — visuals are
|
|
|
335
337
|
|
|
336
338
|
Assembly worker — `task(category="deep-low", load_skills=["frontend", "visual-qa", "open-design", "data-scientist", "imagegen", "ulw-loop"], run_in_background=true, ...)`: before writing, read every available design and visualization skill and apply it — the report is a designed artifact, not a text dump; the worker's prompt carries `design-spec.md`. Use the template the user approved; absent a stronger house style the default skeleton is executive summary → key findings by theme → detailed analysis (quotes under 20 words with attribution, charts, Mermaid graphs, generated visuals, SHA-pinned permalinks, verification results) → comparative analysis when options compete → numbered sources with access dates → methodology appendix (workers, waves, searches, verifications, debate rounds) → correction log naming what verification overturned. Write it long and specific: every claim cites `[Source N]`, and the sources section lists every source the run actually used rather than a curated few.
|
|
337
339
|
|
|
338
|
-
### The delivery gates — every gate must PASS, in order
|
|
340
|
+
### The delivery gates — every gate for the QA tier must PASS, in order
|
|
339
341
|
|
|
340
|
-
Nothing reaches the user until the gates pass
|
|
342
|
+
Nothing reaches the user until the gates pass. Record each result with `node "$SKILL_DIR/scripts/report-tools.mjs" outcome gate <static|layout|visual|proofread> <pass|fail|not_run> --session-dir "$SESSION_DIR"`; a gate this harness does not provide is recorded `not_run`. A chat or post answer (light tier) runs the static gates and one render; every paginated or web deliverable runs them all (the reference's sections 3 and 7).
|
|
341
343
|
|
|
342
|
-
1. **
|
|
343
|
-
2. **
|
|
344
|
+
1. **Static gates (always, before anyone looks at pixels).** `node "$SKILL_DIR/scripts/report-tools.mjs" check "$SESSION_DIR/report.html" --design-spec "$SESSION_DIR/design-spec.md" > "$SESSION_DIR/defects.json"`: keep-all, palette tokens, emoji, em dashes, heading length, unsourced numbers, figure containers, chart text, gothic-only fonts, citations, the closing and sources sections, broken assets. Codes and fixes: [references/report-gates.md](references/report-gates.md).
|
|
345
|
+
2. **Layout gates.** Print the probe with `node "$SKILL_DIR/scripts/report-tools.mjs" layout-probe --json`, evaluate its `source` in the rendered page through the `browser` skill's owned headless engine, save the result as `$SESSION_DIR/boxes.json`, and re-run `check` with `--layout "$SESSION_DIR/boxes.json"` (overflow, clipped text, distorted images, overlapping siblings).
|
|
346
|
+
3. **Visual QA.** Render the produced artifact back to images — PDF pages to PNG, the HTML through the browser skill at desktop and phone widths — and look at them: missing or broken figures, images stretched or spilling their containers, diagram or chart text rendered off the spec's font or palette, clipped tables, overflowing CJK text, blank pages, unlabeled chart values, wrong page breaks. Reading the source markup is not visual QA; inspect the pixels.
|
|
347
|
+
4. **Proofread gate — `task(category="writing", ...)`.** Hand the final text to a dedicated `writing` worker whose only job is language: grammar, spelling, punctuation, terminology consistency, and whether the prose reads NATIVELY in the report's own language. It proofreads only and never composes. It returns a defect list; fix every item and re-run the gate on the delta. Deliver only on a clean pass — this gate runs BEFORE the first delivery, not after the user finds the typo.
|
|
344
348
|
|
|
345
|
-
|
|
349
|
+
**Repair is bounded.** After every gate run that found defects, ask `node "$SKILL_DIR/scripts/report-tools.mjs" repair decide --state "$SESSION_DIR/repair-state.json" --defects "$SESSION_DIR/defects.json" --artifact-bytes <bytes> --renders <pages> --session-dir "$SESSION_DIR"` and obey it: `repair` means fix and re-run; `deliver` means stop and ship with the residual defects the manifest now lists; `block` means a content-integrity defect remains (or there is no usable artifact), so do not deliver: tell the user what blocks it and record the row as `failed` with the reason. Never repair past a stop decision (3 attempts, a 2-attempt plateau, an oscillation, or 15 minutes; the reference's section 8).
|
|
350
|
+
|
|
351
|
+
Then resolve every manifest row (`outcome set <format> delivered --path <file>` or `blocked_capability` / `skipped` / `failed` with `--reason`) and deliver: the artifact plus a compact chat-readable summary of what it says — the answer in a few sentences, the numbers that matter, and what to look at first. The document is the deliverable; the summary is what gets it read.
|
|
346
352
|
|
|
347
353
|
### The closing briefing — every run ends with it
|
|
348
354
|
|
|
349
355
|
The last thing the user reads states, in one compact block, what the answer is made of:
|
|
350
356
|
|
|
351
|
-
- **
|
|
352
|
-
- **Effort.** Workers, waves, excursions, and verifications — the same counters as the `SYNTHESIS.md` header.
|
|
353
|
-
|
|
357
|
+
- **The printed block.** `node "$SKILL_DIR/scripts/report-tools.mjs" outcome verify --session-dir "$SESSION_DIR" && node "$SKILL_DIR/scripts/report-tools.mjs" outcome finish --session-dir "$SESSION_DIR" && node "$SKILL_DIR/scripts/report-tools.mjs" outcome briefing --session-dir "$SESSION_DIR"`: sources and distinct domains counted from `sources-ledger.md`, elapsed minutes from the session directory's own timestamp, every promised deliverable with its status, the gate results, the residual defects, and the repair summary. A failing `verify` means the run is not done.
|
|
358
|
+
- **Effort.** Workers, waves, excursions, and verifications — the same counters as the `SYNTHESIS.md` header — plus how many sources were primary and how many claims went to the unresolved/refuted annex.
|
|
359
|
+
|
|
360
|
+
Never ship the artifact without this block, and never fill it from memory — paste the printed block and read the rest off the journal.
|
|
354
361
|
|
|
355
|
-
|
|
362
|
+
**Record the format choice.** After delivery, when the harness exposes a memory tool and the run qualifies (the user declared a preference, or made the same unsolicited choice on 2+ runs), append one episode line to `reference/human-report-style.md` in the grammar of the reference's section 5, and update the `system/human/report-style.md` pointer when a generalization changed.
|
|
356
363
|
If you stood up a team, disband it and confirm every worker is terminal before the final answer.
|
|
357
364
|
|
|
358
365
|
## Search craft
|
|
@@ -386,7 +393,8 @@ High-yield combinations: official docs (`site:<docs domain>`), GitHub implementa
|
|
|
386
393
|
| A `Browsing: yes` run whose roster carries no browsing worker, or a browsing worker spawned without `ultimate-browsing` | The browsing column is binding — name the angle in the brief and spawn it, armed with the skill, in the first wave before any lead is chased |
|
|
387
394
|
| Contested claim settled by judgment | Phase 3 — run code, capture output, verdict |
|
|
388
395
|
| Deliverable claims without citations | Every claim cites a source or a verification artifact |
|
|
389
|
-
| Guessing the deliverable format
|
|
396
|
+
| Guessing the deliverable format | Derive it from the request and destination; ask the empty-info interview only for what is still missing, and record `answered_by` for every field |
|
|
397
|
+
| Blocking the collection wave on the format question | Ask non-blocking and start collecting; unanswered fields run on the shown defaults |
|
|
390
398
|
| A roster smaller than the harness ceiling | Fill every member slot; split the broadest axis until the team is full |
|
|
391
399
|
| One tier across the whole roster | Mixed tiers by design — cheap breadth, premium attack |
|
|
392
400
|
| Silently re-routing a "quick"/"fast" instruction | Routing words are literal; journal requested -> spawned -> fallback per slot |
|
|
@@ -395,10 +403,14 @@ High-yield combinations: official docs (`site:<docs domain>`), GitHub implementa
|
|
|
395
403
|
| Batching findings into an end-of-run journal dump | Journal each return as it lands; the journal is what survives a compaction |
|
|
396
404
|
| Ending the run because every worker finished | Reserve the final fifth of the run for synthesis and materials |
|
|
397
405
|
| A derived estimate presented as a measured number | MEASURED / ASSUMED / DERIVED lineage on every quantitative claim, plus a sensitivity line |
|
|
398
|
-
| Delivering before the delivery gates pass |
|
|
406
|
+
| Delivering before the delivery gates pass | Static and layout gates, visual QA on rendered pages, plus the harness's proofread gate — a typo the user finds means a gate did not run |
|
|
407
|
+
| Skipping the static gates | `report-tools check` runs before any pixel review; its defects are cheaper than a visual pass |
|
|
408
|
+
| Delivering with a pending manifest row | `outcome verify` must pass; every promised format ends `delivered`, `blocked_capability`, `skipped`, or `failed` with a reason |
|
|
409
|
+
| Repairing past the tracker's stop decision | `repair decide` owns the budget; `deliver` ships with the residual list, `block` stops delivery |
|
|
410
|
+
| Guessing a design-spec value the extractor marked `TODO: ask` | Fill it from the interview answers or the reference's defaults, never by guessing |
|
|
399
411
|
| Referencing an asset that is not on disk | Verify the asset manifest before rendering; re-render whatever is missing |
|
|
400
412
|
| A figure stretched, cropped, or styled off the report's design language | `design-spec.md` binds every asset: fixed containers, contain-fit with aspect preserved, spec fonts and palette in charts and Mermaid |
|
|
401
413
|
| Chasing an interesting find with no ENTER trigger | Excursions need a named trigger; everything else stays a queued lead |
|
|
402
414
|
| An excursion that never came back, or drifted into a new mission | EXIT rules are unconditional; depth 3 means promote it to an axis or record it as a gap |
|
|
403
415
|
| An excursion whose result was never folded back | Every EXIT writes what it changed in the top-level answer, `none` included, and mirrors into the loop ledger |
|
|
404
|
-
| Delivering without the closing briefing |
|
|
416
|
+
| Delivering without the closing briefing | Paste the block `outcome briefing` prints; never compute or recall its numbers by hand |
|