Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
49f20b6
feat(opencode): add experimental code mode execute tool
rekram1-node Jun 29, 2026
b0aa6bf
feat(opencode): namespace code mode tools by MCP server
rekram1-node Jun 29, 2026
cad83ab
feat(opencode): progressive tool discovery for code mode
rekram1-node Jun 29, 2026
caa5f28
refactor(opencode): simplify code mode namespace listing
rekram1-node Jun 29, 2026
3a6621c
chore(opencode): vendor rune interpreter for code mode
rekram1-node Jun 30, 2026
14527d2
feat(opencode): code mode result+attachments envelope and typed describe
rekram1-node Jun 30, 2026
71ffc73
feat(opencode): run code mode on the vendored rune interpreter
rekram1-node Jun 30, 2026
1448f24
feat(opencode): budgeted tool preview in code mode description
rekram1-node Jun 30, 2026
acc1742
test(opencode): end-to-end code mode test over a real MCP server
rekram1-node Jun 30, 2026
ba12049
feat(opencode): tokenized, ranked tool search in code mode
rekram1-node Jun 30, 2026
a3bfba8
feat(opencode): unify code mode discovery under tools.$rune
rekram1-node Jun 30, 2026
cbcc67b
refactor(opencode): single source of discovery docs, clearer attachme…
rekram1-node Jun 30, 2026
79275e6
fix(opencode): describe attachments as routable values, not opaque
rekram1-node Jun 30, 2026
394c370
docs(opencode): add rune.md (how it works, what's missing)
rekram1-node Jun 30, 2026
55aa8cc
feat(opencode): inline budgeted call signatures in code mode preview
rekram1-node Jun 30, 2026
dc75ea0
feat(opencode): state whether the code mode tool list is complete or …
rekram1-node Jun 30, 2026
bc427b1
feat: live code-mode execute UI in the TUI
rekram1-node Jun 30, 2026
2726a74
fix(tui): normalize execute tool styling
rekram1-node Jun 30, 2026
05b9346
fix(tui): surface execute child call details
rekram1-node Jul 1, 2026
5085a13
feat(opencode): render code-mode types as TypeScript, not JSON Schema
rekram1-node Jul 1, 2026
c16bba8
feat(opencode): JSDoc tags, Result<T> return hints, and opaque attach…
rekram1-node Jul 1, 2026
2d9015c
fix(tui): simplify execute running state
rekram1-node Jul 1, 2026
064c34b
feat(opencode): capture console output and surface it to the model
rekram1-node Jul 1, 2026
cc437b9
fix(opencode): make Rune tolerate idiomatic defensive JavaScript
rekram1-node Jul 1, 2026
e06a099
fix(opencode): let NaN/Infinity flow in Rune, normalize to null at th…
rekram1-node Jul 1, 2026
51ea0ac
feat(codemode): add @opencode-ai/codemode confined execution package
rekram1-node Jul 2, 2026
90b4af6
feat(opencode): rebuild code mode as an MCP adapter over @opencode-ai…
rekram1-node Jul 2, 2026
2a13900
feat(codemode): simplify limits, enrich search, condense instructions
rekram1-node Jul 2, 2026
cafcec4
feat(opencode): run code mode without execution limits
rekram1-node Jul 2, 2026
560fc7f
feat(codemode): expand JS parity and make output truncation opt-in
rekram1-node Jul 3, 2026
27fbf7c
feat(opencode): rely on native tool-output truncation for code mode
rekram1-node Jul 3, 2026
231144f
docs(codemode): fix stale claims, log wiring-review findings
rekram1-node Jul 3, 2026
9a6fdc4
fix(opencode): interrupt code mode execution on cancel
rekram1-node Jul 3, 2026
9be249a
refactor(opencode): align code mode module with session conventions
rekram1-node Jul 3, 2026
8f330de
feat(codemode): render bracket notation for non-identifier tool names
rekram1-node Jul 3, 2026
9431ba3
refactor(opencode): promote code mode to a registry tool service
rekram1-node Jul 3, 2026
3b3c6d6
Merge remote-tracking branch 'origin/dev' into codemode-v2
rekram1-node Jul 3, 2026
fedae57
fix(codemode): quote non-identifier keys in signatures; unify compoun…
rekram1-node Jul 3, 2026
1b7f565
refactor(opencode): share the MCP invocation middle between direct an…
rekram1-node Jul 3, 2026
8ca700f
fix(opencode): isolate the code-mode integration suite from bun modul…
rekram1-node Jul 3, 2026
dca428c
fix(ci): pin node-gyp so native install scripts stop resolving node-g…
rekram1-node Jul 3, 2026
16f3c8e
fix(codemode): align code-mode catalog filtering and schema union ren…
rekram1-node Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(codemode): expand JS parity and make output truncation opt-in
Interpreter-surface parity pass:

- Real error values: the seven standard error constructors are callable
  globals; instanceof works for Error types (a specific type matches
  itself and Error, never a sibling) plus Date/RegExp/Map/Set/Array/
  Object/Promise. Caught failures carry the name the equivalent real-JS
  failure would have — JSON.parse and invalid regex patterns are
  SyntaxError, unknown identifiers and TDZ access ReferenceError,
  assignment to a constant TypeError, a bad normalize form RangeError;
  anything without a specific analogue stays plain Error and internal
  class names never leak. JSON.parse failures now include the engine's
  position detail. Error values remain plain { name, message } data;
  the brand rides on a non-enumerable symbol, so serialization is
  unchanged and spread loses it like JS loses the prototype.
- Array splice/fill/copyWithin and keys/values/entries; string
  localeCompare/normalize/trimLeft/trimRight; regex failures now name
  the offending pattern or flags, the engine reason, and how to fix it.
- Sandbox values survive intra-sandbox checkpoints: Object.* helpers,
  coercions, and spread pass Date/RegExp/Map/Set through by reference
  (copyIn gained a preserving mode), so Object.values({d}).getTime()
  works. The host boundary — results, tool arguments, JSON.stringify —
  still serializes JSON forms exactly as documented.
- Console formatting is total and deep: NaN/Infinity print literally,
  nested sandbox values render their friendly forms, opaque references
  become in-place markers, cycles render [Circular]; console can no
  longer fail a program.
- maxOutputBytes lost its 32,000 default, completing uniform no-default
  limits: absent now means no truncation, for hosts that bound tool
  output themselves. Explicit values validate and truncate as before.
  • Loading branch information
rekram1-node committed Jul 3, 2026
commit 560fc7fcbfe4e6398f3d370783c1bead0d73f481
16 changes: 8 additions & 8 deletions packages/codemode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ tools.github.list_issues(input: {

Result paths carry the `tools.` prefix (`tools.orders.lookup`), so each `path` is directly usable as the call site. An empty query browses the catalog alphabetically by path; combined with `namespace` (`{ query: "", namespace: "orders" }`) it lists everything in that namespace. A query that names one tool path exactly (with or without the `tools.` prefix) is treated as a lookup and returns that tool alone.

The instructions are structured markdown, ordered so the workflow sits at the top and the catalog at the bottom: a `## Workflow` section with numbered steps (find a tool via search when the catalog is partial, or pick from the inlined list when it is complete; call the exact path as-is; `JSON.parse` string results; return only the needed fields), a `## Rules` section holding only guidance the workflow does not already cover (filter and aggregate collections in code; treat `Promise<unknown>` results as shapeless until verified; run independent calls through `Promise.all`; enumerate `tools` with `Object.keys`/`for...in`; browse a namespace via search when it is advertised), a short `## Syntax` section that assumes standard JavaScript and names only what is unusual (TypeScript annotations stripped; the data-boundary serialization of Date/Map/Set/RegExp) or missing (classes, generators, `for await...of`, `.then`/`.catch`/`.finally`, `x instanceof Error`, `splice`), and the budgeted `## Available tools` catalog. Every call form uses explicit `<namespace>.<tool>`/`<field>` placeholders — never a real or fabricated tool name.
The instructions are structured markdown, ordered so the workflow sits at the top and the catalog at the bottom: a `## Workflow` section with numbered steps (find a tool via search when the catalog is partial, or pick from the inlined list when it is complete; call the exact path as-is; `JSON.parse` string results; return only the needed fields), a `## Rules` section holding only guidance the workflow does not already cover (filter and aggregate collections in code; treat `Promise<unknown>` results as shapeless until verified; run independent calls through `Promise.all`; enumerate `tools` with `Object.keys`/`for...in`; browse a namespace via search when it is advertised), a short `## Syntax` section that assumes standard JavaScript and names only what is unusual (TypeScript annotations stripped; the data-boundary serialization of Date/Map/Set/RegExp) or missing (classes, generators, `for await...of`, `.then`/`.catch`/`.finally`), and the budgeted `## Available tools` catalog. Every call form uses explicit `<namespace>.<tool>`/`<field>` placeholders — never a real or fabricated tool name.

A host cannot define its own `$codemode` top-level namespace.

Expand All @@ -205,14 +205,14 @@ CodeMode executes a deliberately bounded JavaScript subset. It supports:
- `if`, conditional expressions, `switch`, `for`, `for...of` (arrays, strings, Maps, Sets), `for...in` (own keys of plain objects, index strings of arrays, and namespace/tool names of `tools` references — anything else is an error suggesting `for...of` or `Object.keys`, rather than real JS's surprising behavior of indices for strings and zero iterations for Maps/Sets), `while`, and `do...while`.
- Arrow functions and function declarations with closures, defaults, rest parameters, and destructuring.
- Optional chaining, nullish coalescing, templates, spread (arrays, strings, Maps, Sets), and `try`/`catch`.
- Common array, string, number, `Object`, `Math`, and `JSON` operations. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces and `Object.keys(tools.ns)` the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`.
- Common array, string, number, `Object`, `Math`, and `JSON` operations. Mutating array methods include `push`/`pop`/`shift`/`unshift`/`splice` (removes in place and returns the removed elements)/`fill`/`copyWithin`; array `keys`/`values`/`entries` return **arrays** (matching the Map/Set convention) and work with `for...of` and spread. String methods include `localeCompare` (locale/options arguments ignored), `normalize`, and the `trimLeft`/`trimRight` aliases. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces and `Object.keys(tools.ns)` the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`.
- `Date` — `Date.now()`/`Date.parse()`/`Date.UTC()`, `new Date(...)`, the getter methods, and date arithmetic/comparison via the time value. Dates stringify as ISO (`toString` included, for determinism across host timezones).
- Regular expressions — `/literals/` and `new RegExp(...)` with `test`/`exec` (stateful `lastIndex` for `g`), plus string `match`/`matchAll`/`replace`/`replaceAll`/`split`/`search` with patterns. Match results are arrays carrying `index` and named `groups` as own properties (`input` is omitted). Patterns run on the host engine, so pathological backtracking is bounded only by the execution timeout. Function replacers are not supported.
- Regular expressions — `/literals/` and `new RegExp(...)` with `test`/`exec` (stateful `lastIndex` for `g`), plus string `match`/`matchAll`/`replace`/`replaceAll`/`split`/`search` with patterns. Match results are arrays carrying `index` and named `groups` as own properties (`input` is omitted). Invalid patterns, invalid flags, and missing-`g` calls fail with catchable errors that say what was wrong and how to fix it (escaping hints, the exact `/pattern/g` to write). Patterns run on the host engine, so pathological backtracking is bounded only by the execution timeout. Function replacers are not supported.
- `Map` and `Set` — construction from entries/arrays/strings, `get`/`set`/`add`/`has`/`delete`/`clear`/`size`/`forEach`, and `keys`/`values`/`entries` returning **arrays** (not iterators).
- First-class promises — an un-awaited `tools.ns.tool(...)` is a promise value whose call starts immediately on a supervised fiber; `await` resolves it (awaiting a non-promise value is a no-op, and `return tools.ns.tool(...)` resolves like an async-function return). `Promise.all`, `Promise.allSettled`, and `Promise.race` accept any array mixing promises and plain values (built inline, beforehand, or via spread); `Promise.resolve`/`Promise.reject` construct settled promises. `Promise.allSettled` rejection reasons are the same plain `{ name?, message }` data a `catch` binding sees, and `Promise.race` interrupts its losing in-flight calls. At most 8 tool calls run concurrently. When a program completes, still-running un-awaited calls are awaited before the execution ends; a failure from a call that was never awaited surfaces as an unhandled-rejection diagnostic.
- `throw value` and `throw new Error(message)` for explicit program failure.
- `throw value` and `throw new Error(message)` for explicit program failure. `Error` (and `TypeError`/`RangeError`/`SyntaxError`/`ReferenceError`/`EvalError`/`URIError`) are real constructors, callable with or without `new`; error values are plain `{ name, message }` data that additionally satisfy `instanceof Error` (a specific type matches itself and `Error`, as in JS). Every caught failure — thrown errors, interpreter runtime errors, and tool failures — is `instanceof Error` in a `catch` block; a thrown non-error value (`throw "text"`) is not, matching JS. Caught failures carry the `name` the equivalent real-JS failure would have — `JSON.parse` and invalid regex patterns produce a `SyntaxError` (satisfying `instanceof SyntaxError`), an unknown identifier a `ReferenceError`, assigning to a constant a `TypeError`, a bad `normalize` form a `RangeError`; failures with no specific analogue (including tool failures) are named `"Error"`. `instanceof` also recognizes `Date`, `RegExp`, `Map`, `Set`, `Array`, `Object`, and `Promise`; any other right-hand side is a catchable error.

At every data boundary (final result, tool arguments, `JSON.stringify`, and the internal data checkpoints of `Object.*`/coercion helpers) the four value types serialize exactly as `JSON.stringify` would: a Date becomes its ISO string (`null` when invalid) and RegExp/Map/Set become `{}`. Promise values never cross a data boundary: an un-awaited promise in a result or tool argument produces a diagnostic that says to await it, instead of serializing to `{}`.
Inside a program, Date/RegExp/Map/Set values stay live everywhere: the internal data checkpoints (`Object.*` helpers, spread, coercion inputs) preserve the instances, so `Object.values({ d: date })[0].getTime()` and a spread copy of an object holding a Map keep working. Only at the host boundary (final result, tool arguments, `JSON.stringify`) do the four value types serialize exactly as `JSON.stringify` would: a Date becomes its ISO string (`null` when invalid) and RegExp/Map/Set become `{}`. Promise values never cross a data boundary: an un-awaited promise in a result or tool argument produces a diagnostic that says to await it, instead of serializing to `{}`.

It does not expose `eval`, dynamic imports, modules, classes, generators, timers, host globals, prototype mutation, custom promise constructors (`new Promise`), promise chaining (`.then`/`.catch`/`.finally` — `await` with `try`/`catch` is the supported style), or arbitrary method calls. Unsupported syntax returns an `UnsupportedSyntax` diagnostic with a source location when available.

Expand All @@ -226,9 +226,9 @@ The limits are exactly three knobs:
| --- | ---: | --- |
| `timeoutMs` | none — no timeout | Wall-clock execution time. |
| `maxToolCalls` | none — unlimited | Tool calls admitted during the execution. |
| `maxOutputBytes` | 32,000 | Model-facing output: the serialized result value plus captured logs. |
| `maxOutputBytes` | none — no truncation | Model-facing output: the serialized result value plus captured logs. |

`timeoutMs` and `maxToolCalls` have no defaults on purpose: execution budgets are host policy, not library policy — a host that wants a bound sets one; a host that can interrupt the execution fiber (as OpenCode does on user cancel) may set none. `maxOutputBytes` keeps a default because truncation never breaks correctness, while its absence would silently flood model context.
No limit has a default, on purpose: execution budgets are host policy, not library policy — a host that wants a bound sets one; a host that can interrupt the execution fiber (as OpenCode does on user cancel) may set no timeout, and a host with its own tool-output truncation (as OpenCode has) may leave `maxOutputBytes` unset. A host with neither should set `maxOutputBytes`, or oversized results silently flood model context.

Pass only the overrides you need:

Expand All @@ -244,7 +244,7 @@ const runtime = CodeMode.make({

Limits are safe integers. `timeoutMs` must be at least `1`; the others may be `0`. Invalid configuration throws a `RangeError` when `CodeMode.make` or `CodeMode.execute` is called. An explicitly `undefined` value is the same as leaving the limit unset.

Exceeding `maxOutputBytes` never fails the execution. An oversized result value is replaced by its truncated serialized text plus an explanatory marker, logs are kept from the start until the remaining budget is exhausted (with a final marker line noting the cut), and the result carries `truncated: true`.
Exceeding a configured `maxOutputBytes` never fails the execution. An oversized result value is replaced by its truncated serialized text plus an explanatory marker, logs are kept from the start until the remaining budget is exhausted (with a final marker line noting the cut), and the result carries `truncated: true`.

When configured, the timeout interrupts in-flight tool Effects, including eagerly started calls the program has not awaited (their fibers are supervised by the execution). The interpreter yields cooperatively between steps, so the timeout also interrupts pure busy loops (`while (true) {}`) — no separate work budget exists. Tool implementations remain responsible for making their external operations interruptible or independently bounded.

Expand Down
Loading