lazycodex-ai 5.1.64 → 5.1.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/cli/index.js +99 -60
  2. package/dist/cli/service-auth/closed-device-login.d.ts +16 -0
  3. package/dist/cli/service-auth/commands.d.ts +9 -2
  4. package/dist/cli/service-auth/session.d.ts +12 -0
  5. package/dist/cli-node/index.js +99 -60
  6. package/package.json +1 -1
  7. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  8. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +19 -0
  9. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  10. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  11. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  12. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  13. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  14. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  15. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  16. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  17. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  18. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  19. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  20. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  21. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  22. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  23. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  31. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  32. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  33. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  34. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  35. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  36. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  37. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  38. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  39. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  40. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  41. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  42. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  43. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  44. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  45. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  46. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  53. package/packages/omo-codex/plugin/package-lock.json +12 -12
  54. package/packages/omo-codex/plugin/package.json +1 -1
  55. package/packages/omo-codex/plugin/skills/browser/SKILL.md +24 -7
  56. package/packages/omo-codex/plugin/skills/browser/references/commands.md +41 -4
  57. package/packages/omo-codex/plugin/skills/browser/references/owned-engine/frames-and-humans.md +15 -4
  58. package/packages/omo-codex/plugin/skills/browser/references/recipes/1password.md +4 -2
  59. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +232 -341
  60. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +5 -5
  61. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/page-bundle.js +1586 -1394
  62. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor-report.mjs +68 -0
  63. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +8 -43
  64. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +7 -1
  65. package/packages/omo-codex/plugin/skills/debugging/references/tools/browser-qa.md +4 -3
  66. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +1 -1
  67. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +6 -6
  68. package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +2 -2
  69. package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +5 -4
  70. package/packages/omo-codex/scripts/install-dist/install-local.mjs +21 -2
  71. package/packages/shared-skills/skills/browser/SKILL.md +24 -7
  72. package/packages/shared-skills/skills/browser/references/commands.md +41 -4
  73. package/packages/shared-skills/skills/browser/references/owned-engine/frames-and-humans.md +15 -4
  74. package/packages/shared-skills/skills/browser/references/recipes/1password.md +4 -2
  75. package/packages/shared-skills/skills/browser/runtime/omowright/index.js +232 -341
  76. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +5 -5
  77. package/packages/shared-skills/skills/browser/runtime/omowright/page-bundle.js +1586 -1394
  78. package/packages/shared-skills/skills/browser/scripts/browser-doctor-report.mjs +68 -0
  79. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +8 -43
  80. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +7 -1
  81. package/packages/shared-skills/skills/debugging/references/tools/browser-qa.md +4 -3
  82. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +1 -1
  83. package/packages/shared-skills/skills/ulw-research/SKILL.md +6 -6
  84. package/packages/shared-skills/skills/ulw-research/references/research-collection.md +2 -2
  85. package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +5 -4
@@ -0,0 +1,68 @@
1
+ // The browser doctor's report: one bskDoctor run turned into the `state` the browser skill routes on, its remedy, and a
2
+ // summary. browser-doctor.mjs is the CLI over it; `doctorOptions` reach bskDoctor unchanged (the CLI passes none).
3
+
4
+ // BrowserSkill extension 0.4.0 put every task tab into a native "BrowserSkill" tab group that the browser saves and
5
+ // syncs, so each task left a group in the user's own browser (upstream removed the grouping in 0.4.1).
6
+ const TAB_GROUPING_EXTENSION_VERSION = "0.4.0"
7
+
8
+ function stateOf(report, outdated) {
9
+ if (report.browsersConnected.length > outdated.length) return "ready"
10
+ if (outdated.length > 0) return "outdated-extension"
11
+ if (!report.cli.installed) return "no-cli"
12
+ if (!report.daemon.running) return "no-daemon"
13
+ if (report.browsers.length === 0 && !report.primary) return "no-browser-support"
14
+ return report.identification.needsChoice ? "choose-browser" : "no-extension"
15
+ }
16
+
17
+ const named = (browsers) => browsers.map((browser) => `${browser.browser_name} [${browser.instance_id}]`).join(", ")
18
+
19
+ // With a 0.4.0 browser connected beside an up-to-date one, a session started without `browser` may land on the 0.4.0
20
+ // one (the daemon picks), so the remedy names the up-to-date browser's instance and lists the browsers to avoid.
21
+ function readyRemedy(report, outdated) {
22
+ const live = "Attached engine is live: const { connectBrowserSkill } = await import(omowrightEntry); "
23
+ if (outdated.length === 0) return `${live}const session = await connectBrowserSkill({ name: "<task>", focused: false })`
24
+ const usable = report.browsersConnected.filter((connected) => !outdated.includes(connected))
25
+ const others = usable.length > 1 ? ` (or another up-to-date browser: ${named(usable.slice(1))})` : ""
26
+ return `${live}const session = await connectBrowserSkill({ name: "<task>", focused: false, browser: "${usable[0].instance_id}" })${others}. Always pass browser: the BrowserSkill extension in ${named(outdated)} is 0.4.0, which leaves a "BrowserSkill" tab group in the user's browser after every task; never start a session there, and ask the user to update it to 0.4.1 or later and delete any saved "BrowserSkill" tab groups by hand.`
27
+ }
28
+
29
+ function remedyFor(state, report, outdated) {
30
+ switch (state) {
31
+ case "ready":
32
+ return readyRemedy(report, outdated)
33
+ case "outdated-extension":
34
+ return `The connected BrowserSkill extension is 0.4.0 (${named(outdated)}), which leaves a "BrowserSkill" tab group in the user's browser after every task. Do not start a session. Ask the user to update the extension to 0.4.1 or later (chrome://extensions, Developer mode, Update) and to delete any saved "BrowserSkill" tab groups by hand, then re-run this doctor.`
35
+ case "no-cli":
36
+ return "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."
37
+ case "no-daemon":
38
+ return "Run node \"<skill-root>/scripts/browser-install.mjs\" (it starts the daemon through `bsk status`), then re-run this doctor."
39
+ case "no-extension":
40
+ return report.nextStep ?? "Register the extension with browser-install.mjs, then relay its human step to the user and re-run this doctor."
41
+ case "choose-browser":
42
+ return `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>.`
43
+ case "no-browser-support":
44
+ return "No Chromium-family browser profile was found on this machine. Say so and stop; do not substitute another engine."
45
+ }
46
+ }
47
+
48
+ /** @param {{ omowright: any, entry: string | undefined, browser?: string, doctorOptions?: Record<string, unknown> }} options */
49
+ export async function runBrowserDoctor({ omowright, entry, browser, doctorOptions = {} }) {
50
+ const report = await omowright.bskDoctor({ waitForBrowserMs: 0, browser, ...doctorOptions })
51
+ const outdated = report.browsersConnected.filter((connected) => connected.extension_version === TAB_GROUPING_EXTENSION_VERSION)
52
+ const state = stateOf(report, outdated)
53
+ return {
54
+ state,
55
+ omowright: entry,
56
+ cli: report.cli,
57
+ daemon: { running: report.daemon.running, version: report.daemon.version ?? null, protocolVersion: report.daemon.protocolVersion ?? null, error: report.daemon.error ?? null },
58
+ browser: report.primary ? { id: report.primary.id, label: report.primary.label, confidence: report.identification.confidence, reason: report.identification.reason } : null,
59
+ identificationReason: report.identification.reason,
60
+ candidates: report.identification.candidates.map((c) => ({ id: c.id, label: c.label, signals: c.signals })),
61
+ defaultBrowser: report.identification.defaultBrowser,
62
+ registeredElsewhere: report.identification.registeredElsewhere,
63
+ browsersDetected: report.browsers.map((detected) => ({ id: detected.id, userDataDir: detected.userDataDir, extensionRegistered: detected.extensionRegistered })),
64
+ browsersConnected: report.browsersConnected.map((connected) => ({ instanceId: connected.instance_id, name: connected.browser_name, version: connected.browser_version, extensionVersion: connected.extension_version ?? null })),
65
+ nextStep: report.nextStep,
66
+ remedy: remedyFor(state, report, outdated),
67
+ }
68
+ }
@@ -1,57 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import { runBrowserDoctor } from "./browser-doctor-report.mjs"
2
3
  import { loadOmowright } from "./omowright.mjs"
3
4
 
4
5
  const { entry, omowright } = await loadOmowright()
5
6
  const browserArg = process.argv.find((arg) => arg.startsWith("--browser="))
6
- const report = await omowright.bskDoctor({ waitForBrowserMs: 0, browser: browserArg ? browserArg.slice("--browser=".length) : undefined })
7
-
8
- const state = report.ready
9
- ? "ready"
10
- : !report.cli.installed
11
- ? "no-cli"
12
- : !report.daemon.running
13
- ? "no-daemon"
14
- : report.browsers.length === 0 && !report.primary
15
- ? "no-browser-support"
16
- : report.identification.needsChoice
17
- ? "choose-browser"
18
- : "no-extension"
19
-
20
- const REMEDIES = {
21
- ready: "Attached engine is live: const { connectBrowserSkill } = await import(omowrightEntry); const session = await connectBrowserSkill({ name: \"<task>\", focused: false })",
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.",
23
- "no-daemon": "Run node \"<skill-root>/scripts/browser-install.mjs\" (it starts the daemon through `bsk status`), then re-run this doctor.",
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>.`,
26
- "no-browser-support": "No Chromium-family browser profile was found on this machine. Say so and stop; do not substitute another engine.",
27
- }
28
-
29
- const summary = {
30
- state,
31
- omowright: entry,
32
- cli: report.cli,
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,
38
- browsersDetected: report.browsers.map((browser) => ({ id: browser.id, userDataDir: browser.userDataDir, extensionRegistered: browser.extensionRegistered })),
39
- browsersConnected: report.browsersConnected.map((browser) => ({ instanceId: browser.instance_id, name: browser.browser_name, version: browser.browser_version })),
40
- nextStep: report.nextStep,
41
- remedy: REMEDIES[state],
42
- }
7
+ const { identificationReason, ...summary } = await runBrowserDoctor({ omowright, entry, browser: browserArg ? browserArg.slice("--browser=".length) : undefined })
43
8
 
44
9
  if (process.argv.includes("--json")) {
45
10
  console.log(JSON.stringify(summary, null, 2))
46
11
  } else {
47
- console.log(`state: ${state}`)
12
+ console.log(`state: ${summary.state}`)
48
13
  console.log(`omowright: ${entry}`)
49
- console.log(`cli: ${report.cli.installed ? `${report.cli.bskBin} (${report.cli.version})` : "not installed"}`)
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})`}`)
14
+ console.log(`cli: ${summary.cli.installed ? `${summary.cli.bskBin} (${summary.cli.version})` : "not installed"}`)
15
+ console.log(`daemon: ${summary.daemon.running ? `running (${summary.daemon.version}, protocol ${summary.daemon.protocolVersion})` : `not running${summary.daemon.error ? ` - ${summary.daemon.error}` : ""}`}`)
16
+ console.log(`browser the user uses: ${summary.browser ? `${summary.browser.label} [${summary.browser.id}] (${summary.browser.reason})` : `not identified (${identificationReason})`}`)
52
17
  console.log(`candidates: ${summary.candidates.map((c) => `${c.id}${c.signals.length ? ` (${c.signals.join(", ")})` : ""}`).join(", ") || "none"}`)
53
18
  if (summary.registeredElsewhere.length > 0) console.log(`extension entries left in other browsers: ${summary.registeredElsewhere.join(", ")} (not removed automatically)`)
54
19
  console.log(`browsers detected: ${summary.browsersDetected.map((b) => `${b.id}${b.extensionRegistered ? " (extension registered)" : ""}`).join(", ") || "none"}`)
55
- console.log(`browsers connected: ${summary.browsersConnected.map((b) => `${b.name} ${b.version} [${b.instanceId}]`).join(", ") || "none"}`)
56
- console.log(`\n${REMEDIES[state]}`)
20
+ console.log(`browsers connected: ${summary.browsersConnected.map((b) => `${b.name} ${b.version} [${b.instanceId}]${b.extensionVersion ? ` extension ${b.extensionVersion}` : ""}`).join(", ") || "none"}`)
21
+ console.log(`\n${summary.remedy}`)
57
22
  }
@@ -9,10 +9,16 @@ const scriptDir = dirname(fileURLToPath(import.meta.url))
9
9
  const skillRoot = dirname(scriptDir)
10
10
 
11
11
  export function resolveOmowrightEntry(env = process.env) {
12
+ const packageRoot = join(skillRoot, "..", "..", "..", "..", "node_modules", "omowright")
13
+ // Same order as stage-omowright-runtime.mjs: sources before the build, `.ts` before `.js`. A git
14
+ // install carries the sources (TypeScript since omowright's strict port, which only Bun imports);
15
+ // a package built for its `exports` carries `dist/` only.
12
16
  const candidates = [
13
17
  env.OMOWRIGHT_ROOT ? join(env.OMOWRIGHT_ROOT, "index.js") : undefined,
14
18
  join(skillRoot, "runtime", "omowright", "index.js"),
15
- join(skillRoot, "..", "..", "..", "..", "node_modules", "omowright", "src", "index.js"),
19
+ join(packageRoot, "src", "index.ts"),
20
+ join(packageRoot, "src", "index.js"),
21
+ join(packageRoot, "dist", "index.js"),
16
22
  ].filter((candidate) => candidate !== undefined)
17
23
  return candidates.find((candidate) => existsSync(candidate))
18
24
  }
@@ -62,10 +62,11 @@ When the bug only happens signed in:
62
62
 
63
63
  ```js
64
64
  const session = await omowright.connectBrowserSkill({ name: "debug repro", focused: false })
65
+ const sync = async () => (await session.cooperate("sync")).context_id // BrowserSkill 0.4: one context per page action
65
66
  try {
66
- await session.navigate(url)
67
- const { tree, css } = await omowright.bskSnapshot(session, { interactive: true })
68
- await session.click({ selector: css.e7 })
67
+ await session.navigate(url, { context: await sync() })
68
+ const { tree, css } = await omowright.bskSnapshot(session, { interactive: true, context: await sync() })
69
+ await session.click({ selector: css.e7 }, { context: await sync() })
69
70
  const errors = await session.console({ since: 0 })
70
71
  const shot = await session.screenshot()
71
72
  } finally {
@@ -111,7 +111,7 @@ The rest of the surface (CUA coordinates, captcha solving, routes, traces, frame
111
111
 
112
112
  ### Tier 2b — attached engine (the user's account on a blocked host)
113
113
 
114
- When the blocked flow needs the user's account, drive the browser they are already signed into instead of cloning their profile: `connectBrowserSkill()` → `session.navigate` → `bskSnapshot(session)` / `session.observe()` → `session.click` → `session.stop()`. NEVER launch against or clear cookies/cache/site data from the user's live profile, and never fall back to the owned engine for an authenticated criterion: if no extension is connected, run the `browser` skill's onboarding script and relay its one human step. The full loop is the `browser` skill.
114
+ When the blocked flow needs the user's account, drive the browser they are already signed into instead of cloning their profile: `connectBrowserSkill()` → `session.navigate` → `bskSnapshot(session)` / `session.observe()` → `session.click` → `session.stop()`, each page action carrying `{ context }` from a fresh `session.cooperate("sync")` (BrowserSkill 0.4 refuses it otherwise). NEVER launch against or clear cookies/cache/site data from the user's live profile, and never fall back to the owned engine for an authenticated criterion: if no extension is connected, run the `browser` skill's onboarding script and relay its one human step. The full loop is the `browser` skill.
115
115
 
116
116
  ### Cookie login (cross-platform)
117
117
 
@@ -31,7 +31,7 @@ For work likely to exceed one wait cycle, require the child to send `WORKING: <t
31
31
 
32
32
  # ULW-RESEARCH — Maximum-Saturation Research
33
33
 
34
- You are the research orchestrator. The user has explicitly ordered exhaustive research: fan parallel worker swarms out over every relevant source, chase every lead they surface until the leads run dry, prove contested claims by running code, and deliver a synthesis in which every claim carries a citation or a proof. Exhaustive coverage is the assignment, not a risk to manage.
34
+ You are the research orchestrator. The user has explicitly ordered exhaustive research: fan parallel worker swarms out over every relevant source, chase every lead they surface until the leads run dry, prove every conclusion by probing a source, reading the file, or running a tool before asserting it, and deliver a synthesis in which every claim carries a citation or a proof. Exhaustive coverage is the assignment, not a risk to manage.
35
35
 
36
36
  ## Activation
37
37
 
@@ -53,7 +53,7 @@ The research is done when all of these hold:
53
53
 
54
54
  - Every axis from Phase 0 was covered by at least one dedicated worker.
55
55
  - Every EXPAND lead was investigated or explicitly closed as a duplicate or dead end, and convergence was reached under the Phase 2 rules.
56
- - Claims that were contested, undocumented, or performance-shaped were proven or refuted by executed code.
56
+ - Data came first: every conclusion was proven by probing a source, reading the file, or running a tool before it was asserted, never by plausibility, recall, or the shape of the code; contested, undocumented, and performance-shaped claims were additionally proven or refuted by executed code.
57
57
  - Every claim in the deliverable cites a source or a verification artifact.
58
58
  - 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.
59
59
  - 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.
@@ -74,7 +74,7 @@ Saturation is not just more searching; it is a knowledge-production protocol. Th
74
74
  - `verification-economics.md` — one row per proof decision. Required fields: claim, risk, error cost, verification cost/time, chosen verification path, defer/verify decision, outcome, and residual risk.
75
75
  - `cause-disappearance.md` — one row per causal finding. Required fields: cause id, expected truth, previous observation, `last_seen`, disconfirming observation, replacement cause if any, current status, and whether the violation is no longer observed.
76
76
 
77
- Observation candidates and claim candidates travel back from workers as message text. The orchestrator writes the instrumentation artifacts, links candidates into the intent diff and claim graph, and records where each observation entered the synthesis. A conclusion is not ready for final materials until its expected truth/reality diff is closed or marked unknown, its claim node exists, and its independent-observation convergence status is supported or explicitly excepted.
77
+ Observation candidates and claim candidates travel back from workers as message text. **Every conclusion stands on an observation a tool produced** — a page read, a file read, a command run — recorded in `observation-manifest.md`: a claim candidate, a DEAD END, a convergence call, and your own judgment that a topic is settled alike. What you or a worker knows from memory is a LEAD to probe, never evidence. The orchestrator writes the instrumentation artifacts, links candidates into the intent diff and claim graph, and records where each observation entered the synthesis. A claim is not ready for final materials until its expected truth/reality diff is closed or marked unknown, its claim node exists, and its independent-observation convergence status is supported or explicitly excepted.
78
78
 
79
79
  ## Run the swarm as a cooperating team
80
80
 
@@ -107,15 +107,15 @@ Every research spawn message contains, in order:
107
107
  2. The budget lift: "This is an explicit exhaustive-research assignment. Your default retrieval budget and stop-when-answered rules do not apply — run the full protocol below and report every lead."
108
108
  3. Scope — the axis, the sources to hit, and what a complete answer contains.
109
109
  4. The role protocol (Phase 1).
110
- 5. The reply tail. EXPAND markers, observation candidates, and claim candidates travel back as message text, never as files. Every worker ends the reply with:
110
+ 5. The reply tail. EXPAND markers, observation candidates, and claim candidates travel back as message text, never as files. Each claim candidate carries `OBSERVED: <URL read | file:line | command and its output>`; a claim the worker only remembers goes into the tail as a LEAD. Every worker ends the reply with:
111
111
 
112
112
  ```
113
113
  ## EXPAND
114
114
  - LEAD: <discovery not yet investigated> — WHY: <why it matters> — ANGLE: <suggested search>
115
- - DEAD END: <lead explored to exhaustion>
115
+ - DEAD END: <lead explored to exhaustion> — PROBED: <the searches, reads, or commands that exhausted it>
116
116
  ```
117
117
 
118
- A worker with nothing to expand writes `## EXPAND` followed by `none — <one-line reason>`. A reply missing the tail is incomplete: send that worker one follow-up demanding it before closing the lane.
118
+ A worker with nothing to expand writes `## EXPAND` followed by `none — <one-line reason>`. A reply missing the tail, or a claim or DEAD END missing its `OBSERVED:`/`PROBED:` line, is incomplete: send that worker one follow-up demanding it before closing the lane.
119
119
 
120
120
  ## Execution references
121
121
 
@@ -139,7 +139,7 @@ task(subagent_type="explore", run_in_background=true, prompt="TASK: act as a cod
139
139
  This is an explicit exhaustive-research assignment. Your default retrieval budget and stop-when-answered rules do not apply — run the full protocol below and report every lead.
140
140
  SCOPE: find everything in this codebase related to <angle>: <what complete looks like>.
141
141
  PROTOCOL: grep 3+ keyword variations; structural search; LSP references; globs; git history (-S and --grep). Cross-validate across tools. Report absolute paths and file:line patterns.
142
- End your reply with the ## EXPAND tail: '- LEAD: <discovery> — WHY: <why> — ANGLE: <search>' per lead, or 'none — <reason>'.")
142
+ Tag every claim OBSERVED: <URL | file:line | command>. End your reply with the ## EXPAND tail: '- LEAD: <discovery> — WHY: <why> — ANGLE: <search>' per lead, '- DEAD END: <lead> — PROBED: <what exhausted it>', or 'none — <reason>'.")
143
143
  ```
144
144
 
145
145
  ## Phase 2 — Expand until convergence
@@ -154,7 +154,7 @@ This loop is what makes the mode research rather than search. Collect returns as
154
154
  task(category="deep-low", load_skills=["browser"], run_in_background=true, prompt="TASK: expansion wave <N> — investigate: <lead>.
155
155
  PARENT: <which return surfaced it>. This is an explicit exhaustive-research assignment; budgets do not apply.
156
156
  <role protocol for the lead's territory — web protocol for external leads, explore protocol for codebase leads>
157
- End your reply with the ## EXPAND tail.")
157
+ Tag every claim OBSERVED: <URL | file:line | command>. End your reply with the ## EXPAND tail.")
158
158
  ```
159
159
 
160
160
  ### Excursions — dive deep on a new find, then surface back out
@@ -65,14 +65,15 @@ cloning its profile:
65
65
 
66
66
  ```js
67
67
  const session = await omowright.connectBrowserSkill({ name: "visual-qa capture", focused: false })
68
+ const sync = async () => (await session.cooperate("sync")).context_id // BrowserSkill 0.4: one context per page action
68
69
  try {
69
- await session.navigate(url, { waitUntil: "load" })
70
- await session.resize(1440, 900) // desktop column of the matrix
70
+ await session.navigate(url, { waitUntil: "load", context: await sync() })
71
+ await session.resize(1440, 900, { context: await sync() }) // desktop column of the matrix
71
72
  await Bun.write(`${outDir}/${route}-1440-top.png`, (await session.screenshot()).buffer) // { buffer, width, height }
72
- await session.emulate({ overrides: { width: 390, mobile: true } }) // phone column; `off: true` restores
73
+ await session.emulate({ overrides: { width: 390, mobile: true }, context: await sync() }) // phone column; `off: true` restores
73
74
  await Bun.write(`${outDir}/${route}-390-top.png`, (await session.screenshot()).buffer)
74
75
  } finally {
75
- await session.emulate({ off: true })
76
+ await session.emulate({ off: true, context: await sync() })
76
77
  await session.stop()
77
78
  }
78
79
  ```
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // omo-codex-install:a9613ef38f6cb8eadfc32b34f2bbd2b217fc2f498913b81fd64c05ce3c262f32:8902ae37787a3b65b0e739f68d86961b85d6cdfb63ffa66dd8d93cb78c67e6d9
2
+ // omo-codex-install:cdd45658775781f93e20bd61c5320b34ed32c29a81a96a0d4ab528d74c807dda:e41af6deb14deb208e4ed49aa5a5d5db7b4ffe13ae40a534d73753f5970b2a58
3
3
  var __esm = (fn, res, err) => () => {
4
4
  if (fn)
5
5
  try {
@@ -10205,7 +10205,7 @@ var package_default;
10205
10205
  var init_package = __esm(() => {
10206
10206
  package_default = {
10207
10207
  name: "@oh-my-opencode/omo-codex",
10208
- version: "5.1.64",
10208
+ version: "5.1.66",
10209
10209
  type: "module",
10210
10210
  private: true,
10211
10211
  description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
@@ -19902,6 +19902,22 @@ var OmoMemoryProjectionLayerSchema = object({
19902
19902
  max_entries_per_directory: number2().int().min(0).optional(),
19903
19903
  max_bytes: number2().int().min(0).optional()
19904
19904
  }).strict();
19905
+ var OmoMemoryRuntimeSchema = object({
19906
+ retention_days: number2().int().min(1).default(7),
19907
+ abandoned_journal_days: number2().int().min(1).default(14),
19908
+ completions_days: number2().int().min(1).default(30),
19909
+ pass_budget_ms: number2().int().min(1).default(60000),
19910
+ discard_unreflected_journals: boolean2().default(false),
19911
+ max_bytes: number2().int().min(0).default(2 * 1024 * 1024 * 1024)
19912
+ }).strict();
19913
+ var OmoMemoryRuntimeLayerSchema = object({
19914
+ retention_days: number2().int().min(1).optional(),
19915
+ abandoned_journal_days: number2().int().min(1).optional(),
19916
+ completions_days: number2().int().min(1).optional(),
19917
+ pass_budget_ms: number2().int().min(1).optional(),
19918
+ discard_unreflected_journals: boolean2().optional(),
19919
+ max_bytes: number2().int().min(0).optional()
19920
+ }).strict();
19905
19921
  var OmoMemoryAgentOverridesSchema = object({
19906
19922
  enabled: boolean2().optional(),
19907
19923
  agent: string2().min(1).optional(),
@@ -19917,6 +19933,7 @@ var OmoMemoryAgentOverridesSchema = object({
19917
19933
  search: OmoMemorySearchLayerSchema.optional(),
19918
19934
  recall: OmoMemoryRecallLayerSchema.optional(),
19919
19935
  projection: OmoMemoryProjectionLayerSchema.optional(),
19936
+ runtime: OmoMemoryRuntimeLayerSchema.optional(),
19920
19937
  compile_warn_tokens: number2().int().positive().optional()
19921
19938
  }).strict();
19922
19939
  var OmoMemoryAutoIdentitySchema = _enum(["path", "git"]);
@@ -19961,6 +19978,7 @@ var OmoMemorySettingsSchema = object({
19961
19978
  }),
19962
19979
  project_scope: OmoMemoryProjectScopeSchema.default({ nudge: true, recall: true }),
19963
19980
  projection: OmoMemoryProjectionSchema.default({ max_entries_per_directory: 0, max_bytes: 40960 }),
19981
+ runtime: OmoMemoryRuntimeSchema.default({ retention_days: 7, abandoned_journal_days: 14, completions_days: 30, pass_budget_ms: 60000, discard_unreflected_journals: false, max_bytes: 2 * 1024 * 1024 * 1024 }),
19964
19982
  compile_warn_tokens: number2().int().positive().default(30000),
19965
19983
  agents: record(string2(), OmoMemoryAgentOverridesSchema).default({})
19966
19984
  }).strict();
@@ -19981,6 +19999,7 @@ var OmoMemorySettingsLayerSchema = object({
19981
19999
  recall: OmoMemoryRecallLayerSchema.optional(),
19982
20000
  project_scope: OmoMemoryProjectScopeLayerSchema.optional(),
19983
20001
  projection: OmoMemoryProjectionLayerSchema.optional(),
20002
+ runtime: OmoMemoryRuntimeLayerSchema.optional(),
19984
20003
  compile_warn_tokens: number2().int().positive().optional(),
19985
20004
  agents: record(string2(), OmoMemoryAgentOverridesSchema).optional()
19986
20005
  }).strict();
@@ -14,7 +14,8 @@ One library, two engines. omowright ships inside this skill; choose the engine b
14
14
  | A 403/WAF host the attached engine cannot open, a platform with a native reader, an archive snapshot | neither | the `ultimate-browsing` skill |
15
15
 
16
16
  **Attached is the default,** because it is the only engine carrying the user's logins and the only
17
- one where a human is a single call away. Never substitute one engine for the other silently: if
17
+ one where a human is a single call away. It works in an Agent Window inside the user's own browser profile, never in
18
+ a copy of that profile, and exists only where the user installed and enabled the BrowserSkill extension. Never substitute one engine for the other silently: if
18
19
  the attached engine is not set up, run the onboarding script and tell the user its one remaining
19
20
  step.
20
21
 
@@ -58,6 +59,7 @@ node "<skill-root>/scripts/browser-doctor.mjs" --json
58
59
  | `no-cli` / `no-daemon` / `no-extension` | something is missing | `node "<skill-root>/scripts/browser-install.mjs" [--browser=<id>]` prepares everything it can for **the browser the user uses**, then prints the **single** step only the user can do (relaunch that browser and click **Enable**); relay it verbatim, wait, re-run the doctor |
59
60
  | `choose-browser` | the signals do not single out one browser (Safari/Firefox default, an idle default while another browser runs, several in use) | nothing was installed; take the browser from memory or ask the user, then `browser-install.mjs --browser=<id>` |
60
61
  | `no-browser-support` | no Chromium-family profile on this machine | say so and stop |
62
+ | `outdated-extension` | the only connected extension is BrowserSkill 0.4.0, which leaves a "BrowserSkill" tab group in the user's browser after every task | start no session; relay the doctor's remedy (update the extension to 0.4.1 or later, delete saved "BrowserSkill" groups by hand), then re-run the doctor |
61
63
 
62
64
  **Install into the browser the user actually uses, never into whatever happens to be on disk.** Before
63
65
  installing, check your memory for the user's browser; otherwise read the doctor's `browser` (picked
@@ -73,14 +75,15 @@ you hit and ask.
73
75
 
74
76
  ```js
75
77
  const session = await omowright.connectBrowserSkill({ name: "<task>", focused: false })
78
+ const sync = async () => (await session.cooperate("sync")).context_id // one context per page action
76
79
  try {
77
- await session.navigate("https://example.com/", { waitUntil: "load" })
78
- const { tree, refs, css } = await omowright.bskSnapshot(session, { interactive: true }) // OmOWright tree + refs, no trace in the page
79
- await session.click({ selector: css.e3 }) // css[ref] is null inside shadow roots:
80
+ await session.navigate("https://example.com/", { waitUntil: "load", context: await sync() })
81
+ const { tree, refs, css } = await omowright.bskSnapshot(session, { interactive: true, context: await sync() }) // OmOWright tree + refs, no trace in the page
82
+ await session.click({ selector: css.e3 }, { context: await sync() }) // css[ref] is null inside shadow roots:
80
83
  const vom = await session.observe({ maxTokens: 4000 }) // then read the daemon's own tree ...
81
- await session.click("@e7") // ... and click its @eN ref
82
- await session.fill(css.e5, "hello")
83
- await session.press("Enter")
84
+ await session.click("@e7", { context: await sync() }) // ... and click its @eN ref
85
+ await session.fill(css.e5, "hello", { context: await sync() })
86
+ await session.press("Enter", { context: await sync() })
84
87
  await session.waitForNavigation({ waitUntil: "load" })
85
88
  const shot = await session.screenshot() // { buffer, width, height, captureId }
86
89
  } finally {
@@ -95,6 +98,20 @@ try {
95
98
  4. **Borrow a user tab explicitly** (`tabList({ scope: "user" })`, `tabBorrow(id)`, `tabReturn(id)`).
96
99
  Borrowing prompts the user; never invent tab ids and never repeat a denied borrow.
97
100
  5. **Always `stop()` the session,** on success and on failure.
101
+ 6. **Sync before every page action.** The person at the keyboard can work in the same tab, so
102
+ BrowserSkill 0.4 refuses an action (`permission_denied`, `data.reason: "cooperation_sync_required"`)
103
+ unless it carries `{ context }` from the latest `session.cooperate("sync")`. A context is good
104
+ for one action: sync, act with `{ context }`, sync again. Navigation, every input method,
105
+ `evaluate` (and so `bskSnapshot`), `tabClose`, `resize`, `emulate`, `observe({ probeHover })` and
106
+ `screenshot({ fullPage: true })` need it; plain reads (`observe`, `snapshot`, `getHtml`,
107
+ `screenshot()`, `console`, `network`, `waitForNavigation`) do not.
108
+ 7. **On `cooperation_sync_required`, wait for the person, then replan.** Call
109
+ `session.cooperate("wait", { timeoutMs: 25_000 })` until it reports `ready`, read the page again
110
+ and decide afresh; never replay the refused action blindly.
111
+ 8. **Pass only the options a method takes.** A gated action throws a `TypeError` on any other key
112
+ (a misspelt `contextId` would otherwise send no context). `bskSnapshot` and
113
+ `screenshot({ fullPage: true })` do not throw: they drop the stray key, and the daemon then
114
+ refuses the call with `cooperation_sync_required`.
98
115
 
99
116
  Every method, its options, and the failure codes are in [references/commands.md](references/commands.md).
100
117
 
@@ -17,6 +17,38 @@ await session.stop() // ALWAYS, on success and failure
17
17
  on purpose. Throws `BskRpcError` `no_browser_connected` when no extension is attached — that is a
18
18
  stop, not a cue to launch something else.
19
19
 
20
+ ## Shared control (BrowserSkill 0.4+)
21
+
22
+ The person at the keyboard can work in the task tab while you do, so the extension refuses every
23
+ gated page action with `permission_denied` / `data.reason: "cooperation_sync_required"` unless it
24
+ carries the `context_id` of the latest `session.cooperate("sync")`. The daemon never fills it in
25
+ for you.
26
+
27
+ ```js
28
+ const sync = async () => (await session.cooperate("sync")).context_id
29
+ await session.click("@e3", { context: await sync() }) // one context, one action
30
+ await session.fill("#q", "hello", { clearBefore: true, context: await sync() })
31
+ ```
32
+
33
+ | Call | Use |
34
+ |---|---|
35
+ | `session.cooperate("sync", { tabId, maxTokens })` | returns `{ context_id, ... }`; pass it as `{ context }` to the next gated action, then sync again |
36
+ | `session.cooperate("wait", { tabId, timeoutMs })` | after `cooperation_sync_required`: blocks (at most 25 s per call) until the person is done; repeat until it reports `ready`, then sync and replan from the live page |
37
+
38
+ - **Gated** (need `{ context }`): `navigate` (except the first navigation of the untouched blank tab),
39
+ `back`, `forward`, `reload`, `click`, `hover`, `focus`, `blur`, `fill`, `press`, `select`,
40
+ `scrollTo`, `wheel`, `evaluate` (and so `bskSnapshot`), `tabClose`, `resize`, `emulate`,
41
+ `observe({ probeHover: true })`, `screenshot({ fullPage: true })`.
42
+ - **Not gated:** `observe`, `snapshot`, `getHtml`, `screenshot()`, `console`, `network`,
43
+ `waitForNavigation`, the tab list/borrow/return calls, `requestHelp`.
44
+ - `dependsOn: ["@e2", ...]` narrows the check to the observed regions your decision used.
45
+ - A gated action accepts only its own options plus `{ context, dependsOn }`; any other key
46
+ (`contextId`, a stray option copied from another method) throws a `TypeError` before anything is
47
+ sent. Two exceptions: `bskSnapshot` and `screenshot({ fullPage: true })` pass unknown keys
48
+ through instead of throwing, so a misspelt `contextId` there sends no context and the daemon
49
+ refuses the call with `cooperation_sync_required`.
50
+ - Never replay a refused action blindly: the page may have changed under the person's hands.
51
+
20
52
  ## Targets
21
53
 
22
54
  | Form | Meaning |
@@ -30,12 +62,12 @@ stop, not a cue to launch something else.
30
62
 
31
63
  | Call | Returns |
32
64
  |---|---|
33
- | `bskSnapshot(session, { interactive, maxDepth })` | `{ tree, refs, css }` — the OmOWright accessibility tree with refs, computed in the page **without** leaving a global or touching the DOM |
65
+ | `bskSnapshot(session, { interactive, maxDepth, context })` | `{ tree, refs, css }` — the OmOWright accessibility tree with refs, computed in the page **without** leaving a global or touching the DOM |
34
66
  | `session.observe({ maxTokens, cursor, probeHover })` | the daemon's semantic tree (`@vom`) with `@eN` refs, layers and hover probes — **the default read for shadow DOM and iframes** |
35
67
  | `session.snapshot({ maxDepth, maxTokens })` | the daemon's plain accessibility tree |
36
68
  | `session.getHtml({ ref, maxBytes })` | exact markup |
37
69
  | `session.screenshot({ ref })` / `screenshot({ fullPage: true })` | `{ buffer, width, height, captureId }`; full-page captures stream back in chunks |
38
- | `session.evaluate(expression, { awaitPromise, timeoutMs })` | `{ ok, value, error }` — **check `.ok`**; a resolved promise does not mean the script succeeded |
70
+ | `session.evaluate(expression, { awaitPromise, timeoutMs, context })` | `{ ok, value, error }` — **check `.ok`**; a resolved promise does not mean the script succeeded |
39
71
  | `session.console({ since })` / `session.network({ since })` | buffered console entries / request metadata (no bodies) |
40
72
 
41
73
  **Refs are reissued by every read.** Read a ref and act on it in the same cycle. A ref captured
@@ -44,6 +76,10 @@ neighbouring row.
44
76
 
45
77
  ## Acting
46
78
 
79
+ Every call here is gated: add `context` (from `cooperate("sync")`) to its options object, as in
80
+ `session.click(target, { context })`, `session.resize(width, height, { context })`,
81
+ `session.tabClose(id, { context })`.
82
+
47
83
  | Need | Call |
48
84
  |---|---|
49
85
  | Click | `session.click(target, { button, clickCount, modifiers })` |
@@ -75,7 +111,7 @@ Traps:
75
111
  const { tabs } = await session.tabList({ scope: "user" }) // "user" | "agent" | "all"
76
112
  await session.tabBorrow(tabId) // the user confirms in the browser (60 s)
77
113
  await session.tabReturn(tabId) // stop() returns anything still borrowed
78
- await session.tabCreate({ url }); await session.tabSelect(id); await session.tabClose(id)
114
+ await session.tabCreate({ url }); await session.tabSelect(id); await session.tabClose(id, { context: await sync() })
79
115
  ```
80
116
 
81
117
  Never invent tab ids and never repeat a denied borrow.
@@ -96,7 +132,8 @@ Every refusal is a `BskRpcError` with the daemon's own `code`:
96
132
  | `no_browser_connected` | no extension attached | run the onboarding script, relay the human step, wait |
97
133
  | `not_found` | stale ref or unknown session | read again; if the session is gone, start a new one |
98
134
  | `invalid_params` | wrong option shape | fix the call, do not retry as-is |
99
- | `permission_denied` | `evaluate` on a tab outside the Agent Window, or a denied borrow | stop; the user said no |
135
+ | `permission_denied` with `data.reason: "cooperation_sync_required"` | a gated action without the latest sync context, or the person is using the tab | `cooperate("wait")` until `ready`, sync, read the page again, replan |
136
+ | `permission_denied` (any other reason) | `evaluate` on a tab outside the Agent Window, or a denied borrow | stop; the user said no |
100
137
  | `timeout` | the tool did not finish in its budget | read the page state before retrying once |
101
138
  | `user_aborted` | the user pressed Stop in the browser | stop the task and report |
102
139
  | `cdp_failed` | the page cannot be attached (restricted URL, DevTools open) | say which page and why |
@@ -19,10 +19,21 @@ miss has another cause and you climb the ladder instead.
19
19
 
20
20
  ## Dialogs never block
21
21
 
22
- `alert`, `confirm`, `prompt`, and `beforeunload` are answered by the `dialogPolicy` passed to
23
- `connectPipe` (or `browser.setDialogPolicy`), at the protocol layer, before the page sees them.
24
- Default to accepting (`{ accept: true }`; a function receives `{ type, message }` and returns
25
- `{ accept, promptText }`). `page.on("dialog")` still fires for observability. A run that hangs on
22
+ `alert`, `confirm`, `prompt`, and `beforeunload` are answered at the protocol layer, before the
23
+ page sees them: by a `dialog` listener that answers, otherwise by the `dialogPolicy` passed to
24
+ `connectPipe` (or `browser.setDialogPolicy`). Default to accepting (`{ accept: true }`; a policy
25
+ function receives a plain `{ type, message, defaultPrompt, url }` and returns `{ accept, promptText }`).
26
+
27
+ `page.on("dialog", (dialog) => ...)` hands each listener a Playwright-style `Dialog` object, not a
28
+ plain object: read it through methods (`dialog.type()`, `dialog.message()`, `dialog.defaultValue()`,
29
+ and the `dialog.url` getter). A listener can answer it itself, which replaces the policy for that
30
+ dialog; a listener that only observes leaves it to the policy:
31
+
32
+ ```js
33
+ page.on("dialog", (dialog) => dialog.type() === "prompt" ? dialog.accept("42") : dialog.dismiss())
34
+ ```
35
+
36
+ A dialog is answered once; a second `accept()` / `dismiss()` throws. A run that hangs on
26
37
  an unhandled dialog looks exactly like a hang with no cause, which is the most expensive kind to
27
38
  diagnose.
28
39
 
@@ -28,13 +28,15 @@ await session.tabReturn(vault.tab_id)
28
28
 
29
29
  The app mutates its URL hash without updating the document title, so a signed-in vault can still
30
30
  report a sign-in title. A live session's `location.href` contains the app path and never the
31
- sign-in path. Read the URL from `tabList`, or `session.evaluate("location.href")`.
31
+ sign-in path. Read the URL from `tabList`, or `session.evaluate("location.href", { context })` with a
32
+ fresh `context` from `session.cooperate("sync")` (see the shared-control section of
33
+ [../commands.md](../commands.md)).
32
34
 
33
35
  ## The browser may be shared
34
36
 
35
37
  Other automation may be driving the same window.
36
38
 
37
- - **Clear the search box before typing** (`fill(target, value, { clearBefore: true })`). It often
39
+ - **Clear the search box before typing** (`fill(target, value, { clearBefore: true, context })`). It often
38
40
  holds someone else's query, and typing alone appends to it, returning confident results for the
39
41
  wrong search.
40
42
  - **Read the autocomplete listbox, not the results grid.** The grid holds whatever query was last