# `@opengeni/codemode`

Opengeni's canonical attempt-frozen programmatic tool surface. It compiles one
catalog from the exact tools admitted to an execution attempt and dispatches
both model MCP calls and sandbox Codemode calls through the same opaque tool
identities and executors.

The package owns deterministic catalog projection, normalized unique JavaScript
paths with path-prefix collisions rejected during catalog creation, catalog
digests, stale-catalog fencing, approval classification, and result-shape
preservation. It does not discover MCP servers, resolve
credentials, persist attempts, or bypass host authorization; callers provide
the already-admitted definitions and one authorization hook.

`modelName` and `codemodePath` are projections only. Execution authority is
always the exact `{ serverId, toolName }` identity in the frozen catalog.

Inside an Opengeni sandbox the worker supplies `OPENGENI_CODEMODE_URL` and a
renewed bearer file. The package exposes one lazy namespace over that exact
attempt catalog:

```ts
import { tools } from "@opengeni/codemode";

const sessions = await tools.opengeni.sessions_list({ status: "running" });
const hits = await tools.slack.search({ query: "release blocker" });
```

Generate digest-pinned project declarations from the live attempt catalog with
`ogtool declarations opengeni-codemode.d.ts`. Runtime schema validation remains
authoritative when a script outlives a catalog generation.

When submission receives the structured `codemode_catalog_stale` response, the
client refreshes the catalog once, re-resolves the requested identity or
namespace path, and retries with the same caller-owned operation id. The API
emits that code only before creating an operation. Ambiguous transport failures
may be reconciled by operation id only when the recovered row exactly matches
the requested attempt scope, catalog digest, tool identity, and canonical
arguments. Deterministic HTTP conflicts are returned directly and never adopt an
existing operation during initial admission. Once the API has returned the exact
operation, a later wake-notification failure resumes from an exact journal read;
it still rejects any mismatched operation. If that recovery read is unavailable,
the client returns `outcomeUnknown: true` with the exact operation id so the
caller can reconcile without generating a second identity. The API also treats
its post-dispatch journal refresh as best-effort and returns the already-admitted
operation if that refresh is unavailable. The database serializes concurrent
first submissions of one operation id, so identical races converge to one
creation plus one replay rather than a unique-constraint failure. No response
after operation creation triggers a catalog retry. While a journaled operation
remains queued or running, the client periodically re-notifies the dispatcher
with the same operation id. A live claim is not replayed; an expired claim is
either reclaimed before execution or durably settled outcome-unknown after the
execution marker. Public
`CodemodeTransportError` identity and its `codemode_transport_error`
compatibility code remain unchanged; the stable API detail is exposed as
`remoteCode`.

The bearer is reread for every request. Namespace paths are resolved against
the signed catalog and never parsed into authority from flattened model names.
For Browser/Computer work, the authored facade wraps those same atomic entries:

```ts
import { openGeni } from "@opengeni/codemode";

const browser = await openGeni.browsers.open({ initialUrl: "http://127.0.0.1:3000" });
const tab = await browser.tabs.selected();
const page = await tab.observe(); // Compact refs and explicit omission counts.
const buttons = await tab.read({ role: "button", nameContains: "Save", limit: 5 });
const email = await tab.read({ mode: "dom", dom: { kind: "element", locator: { kind: "css", selector: "#email" }, attributes: ["placeholder"] } });
await tab.getByRole("button", { name: "Save" }).click();
const still = await tab.screenshot({ quality: 40 });
console.log(still.path); // Open this local PNG/JPEG/WebP with view_image.

// Bulk captures: wait for client-rendered data, then keep each still as a file.
for (const width of [1440, 390]) {
  await tab.act({ type: "viewport", width, height: 900 }); // Desktop unless mobile: true.
  await tab.navigate("http://127.0.0.1:3000/reports", { waitUntil: "network_idle" });
  await tab.screenshot({ saveTo: `shots/reports-${width}.jpg` });
}

const { downloads } = await browser.downloads.list();
const completed = downloads.find((download) => download.status === "completed");
if (completed) {
  const operationId = crypto.randomUUID(); // Retain for any reconciliation.
  const saved = await browser.downloads.download(completed.id).saveToWorkspace(
    "exports/report.csv",
    { overwrite: false },
    { operationId },
  );
  console.log(saved.destinationPath); // Read exact bytes with workspace file tools.
}

const computer = await openGeni.computers.open();
const app = await computer.apps.focused();
await app.getByRole("button", { name: "1" }).invoke();
```

`tab.observeFull()` retrieves the complete accessibility snapshot when compact
refs or a focused `tab.read()` query are insufficient. The default focused read
searches accessibility; `mode: "dom"` returns bounded text, editable values,
safe attributes, or counts. Sensitive fields are redacted. DOM CSS reads accept
simple tag, class, id, descendant, and child selectors; attribute and pseudo
selectors are rejected to prevent secret-value probing. Browser image blocks
are bounded below the Code Mode journal limit; for an oversized screenshot,
capture the viewport or lower JPEG quality.

`CodemodeCallOptions.expectedDefinitionDigest` optionally pins one complete
public entry. The client checks it before submission and again after the one
permitted pre-execution stale-catalog refresh. Local Site previews always pass
this pin, so a retry cannot execute a changed definition while reporting old
metadata. The pin grants no authority and does not alter durable operation
reconciliation or frozen approval decisions.

Both surfaces return the same durable tool receipts. Human approval, catalog
generation, operation idempotency, and outcome-unknown behavior remain enforced
by the shared attempt executor. Catalog, approval, authorization, input
validation, and argument-sensitive connector-policy prepare complete before the
durable execution-start marker. The prepared call performs connector begin at
the executor boundary and completion afterward, so model MCP and Codemode share
one lifecycle. Invalid, blocked, or unavailable-policy calls settle before
provider execution. With the `durableApproval` protocol capability, Ask becomes
`waiting_for_approval` and returns `CodemodeApprovalPendingError` with a compact
operation handle. It releases the claim and pauses the owning turn for review.
Human approval resumes the stored operation once; it does not restore a
JavaScript stack. Use `environmentCodemodeClient().status(id)` or `.resume(id)`
from the current authorized attempt of the same turn to observe results.
`ogtool read <id>` and `ogtool resume <id>` expose the same behavior. Older
clients receive an upgrade-required failure before an approval can execute. Human-gated model calls additionally require the
attempt host's exact approved SDK invocation context; calling the environment
directly cannot bypass approval. The dispatcher keeps its claim alive during
gateway preparation and reuses a deterministic durable tool-created event if a
pre-execution claim is reclaimed.

`tab.screenshot({ fullPage?: boolean, saveTo?: string })` calls the same atomic
`interaction.browser.screenshot` tool and writes its image block to a private
local file. It returns the absolute path, MIME type, byte count, and small frame
metadata, without printing base64 pixels. Use the agent's `view_image` tool on
that path to inspect the still. `saveTo` writes to an exact caller-chosen path
and refuses to overwrite an existing file. `CodemodeClient.callPath` remains
available when code needs the complete MCP result, including image blocks.
Default image paths are private OS scratch files; later image calls remove this
client's scratch directories older than 24 hours. Use `saveTo` when a file must
remain available longer; that path is caller-owned and never pruned by Code Mode.
The generated `tools.*` namespace normally returns structured output directly.
When a typed tool returns image blocks, it instead returns
`{ structuredContent, images, otherContent }`: `images` contains local paths,
and `otherContent` preserves text and other non-image blocks. Generated
declarations express this union. Open `images[n].path` with `view_image`.
The facade and typed wrapper keep base64 out of program output; raw `callPath`
retains it and should not be printed wholesale.

`CodemodeCallOptions.signal` cancels only the caller's HTTP/polling observation.
It does not request server cancellation and cannot prove that an operation
stopped. The attempt/turn lifecycle remains the only cancellation authority; a
caller that aborts after submission must reconcile with the same operation id.

`browser.downloads.list()` and `browser.downloads.download(id).get()` use the
read-only `browser_downloads` tool. `saveToWorkspace(path, options, callOptions)`
uses `browser_download_save`, requiring both `sessions:control` and `files:upload`.
It publishes and materializes the exact completed bytes into the browser's
source session workspace, retaining size/SHA-256 verification and the durable
operation id. Downloads stay controller-private until this explicit save.
Lightpanda browsers have no downloads to export.

Editable artifacts use the same path. The object remains in Opengeni; files are
only explicit import/export boundaries:

```ts
import { openGeni } from "@opengeni/codemode";

const workbook = await openGeni.artifacts.create("spreadsheet", "Forecast");
const sheetId = openGeni.artifacts.ids.stable();
await workbook.apply([
  { kind: "sheet.create", sheetId, name: "Inputs", after: null },
  {
    kind: "cells.set",
    sheet: { kind: "created-in-batch", sheetId, createCommandIndex: 0 },
    anchor: { row: 0, column: 0 },
    rows: 2,
    columns: 2,
    cells: ["Metric", "Value", "Revenue", 120],
  },
]);
```

### Frozen paginated selections

`collectIdPages(load, { maxItems, maxPages, signal })` reads all pages, removes
repeated IDs and refuses incomplete selections (repeated cursor, empty continued
page, cap, provider error or cancellation). Defaults: 10,000 IDs and 200 pages;
hard bounds: 50,000 IDs and 1,000 pages. `planIdBatches(ids, 1000)` freezes a
selection digest and distinct operation IDs for chunks of at most 1,000 IDs.
It performs no writes. Save the plan in the agent workspace before submitting
chunks; keep the arrays out of model output. Never continue searching after a
mutation changes the search membership.

For Gmail, use `search_messages` with `messageFormat: "IDS_ONLY"` (or
`search_threads` with `view: "IDS_ONLY"`). These return one list page without
hydration requests; existing rich views keep their defaults. Adapt that page's
`messages.map(message => message.id)` and `nextPageToken` to `collectIdPages`.
Each planned chunk is an independently authorized durable call. An Ask returns
a waiting handle; catch that typed result and retain its operation ID rather
than copying arguments into another call. Original JavaScript locals are not
restored when a turn resumes. Inspect saved handles on the next attempt.

`batch_modify_messages` returns `status: "acknowledged"`, `submittedCount` and
`reconciliation: "not_checked"`. Gmail's empty success response establishes
batch acceptance, not a per-message verification. The former `modified: true`
claim and echoed ID array are removed. `summarizeIdBatches(plan, receipts)`
counts acknowledged, failed-before-effect, unknown, waiting and unstarted IDs
separately. Only mark a receipt acknowledged after that provider response;
transport uncertainty is unknown and must not be retried automatically.
