Ver Fonte

docs(codemode): track promise status

Aiden Cline há 1 mês atrás
pai
commit
9445497fc5

+ 1 - 1
packages/codemode/README.md

@@ -246,7 +246,7 @@ CodeMode executes a deliberately bounded JavaScript subset. It supports:
 - 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). `replace` and `replaceAll` accept function replacers with captures, offset, input, and named groups; callbacks run sequentially, may await tool calls, and have their results coerced to strings. 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.
 - 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). `replace` and `replaceAll` accept function replacers with captures, offset, input, and named groups; callbacks run sequentially, may await tool calls, and have their results coerced to strings. 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.
 - `Map` and `Set` - construction from entries/arrays/strings, `get`/`set`/`add`/`has`/`delete`/`clear`/`size`/`forEach`, and `keys`/`values`/`entries` returning **arrays** (not iterators).
 - `Map` and `Set` - construction from entries/arrays/strings, `get`/`set`/`add`/`has`/`delete`/`clear`/`size`/`forEach`, and `keys`/`values`/`entries` returning **arrays** (not iterators).
 - URL helpers - `URL` resolution and mutation, linked `URLSearchParams`, `URL.canParse`/`URL.parse`, URI and URI-component encoding/decoding, and query parameter construction, lookup, mutation, sorting, callbacks, and materialization. URLSearchParams iteration methods return arrays, matching the Map/Set convention.
 - URL helpers - `URL` resolution and mutation, linked `URLSearchParams`, `URL.canParse`/`URL.parse`, URI and URI-component encoding/decoding, and query parameter construction, lookup, mutation, sorting, callbacks, and materialization. URLSearchParams iteration methods return arrays, matching the Map/Set convention.
-- 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). Promises support `then`/`catch`/`finally`, including returned-promise flattening and rejection recovery. `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` lets losing promises continue. 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.
+- 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). Promises support `then`/`catch`/`finally`, including returned-promise flattening and rejection recovery. `Promise.all`, `Promise.allSettled`, and `Promise.race` accept any array mixing promises and plain values (built inline, beforehand, or via spread); `Promise.resolve` preserves an existing promise or wraps a plain value, and `Promise.reject` creates a rejected promise. `Promise.allSettled` rejection reasons use the same values a `catch` binding sees, and `Promise.race` lets losing promises continue. 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. `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`, `URL`, `URLSearchParams`, `Array`, `Object`, and `Promise`; any other right-hand side is a catchable error.
 - `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`, `URL`, `URLSearchParams`, `Array`, `Object`, and `Promise`; any other right-hand side is a catchable error.
 
 
 Inside a program, standard-library 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 they serialize exactly as `JSON.stringify` would: Date and URL become strings (an invalid Date becomes `null`), while RegExp, Map, Set, and URLSearchParams 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, standard-library 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 they serialize exactly as `JSON.stringify` would: Date and URL become strings (an invalid Date becomes `null`), while RegExp, Map, Set, and URLSearchParams 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 `{}`.

+ 50 - 5
packages/codemode/codemode.md

@@ -65,7 +65,8 @@ path lookup, namespace browsing, deterministic ranking, and pagination.
 Calling a tool starts its Effect eagerly on a supervised fiber. The returned sandbox promise is run-once and can be
 Calling a tool starts its Effect eagerly on a supervised fiber. The returned sandbox promise is run-once and can be
 awaited directly, chained with `then`/`catch`/`finally`, or passed through the supported `Promise` combinators. At most
 awaited directly, chained with `then`/`catch`/`finally`, or passed through the supported `Promise` combinators. At most
 eight tool calls execute concurrently.
 eight tool calls execute concurrently.
-Unfinished calls are drained before successful program completion, and an unhandled call failure becomes a diagnostic.
+Unfinished tracked promises are drained before successful program completion, and an unhandled rejection becomes a
+diagnostic.
 
 
 The public execution-policy knobs are `timeoutMs`, `maxToolCalls`, and `maxOutputBytes`. The package supplies no
 The public execution-policy knobs are `timeoutMs`, `maxToolCalls`, and `maxOutputBytes`. The package supplies no
 defaults because budgets are host policy. The interpreter also enforces fixed internal boundaries for tool-call
 defaults because budgets are host policy. The interpreter also enforces fixed internal boundaries for tool-call
@@ -110,6 +111,54 @@ MCP tools use this canonical path: they register as grouped tools and are deferr
 output schemas are preserved in generated signatures. Direct Core tools remain direct and are not ambient globals
 output schemas are preserved in generated signatures. Direct Core tools remain direct and are not ambient globals
 inside CodeMode.
 inside CodeMode.
 
 
+## Promise Status
+
+The runtime currently provides eager, run-once promises for tool calls and async functions; `await`;
+`then`/`catch`/`finally`; and chainable `all`/`allSettled`/`race`/`resolve`/`reject`. `Promise.all` rejects promptly while
+siblings continue, and `Promise.race` leaves losers running as JavaScript does. Tracked work remains supervised, at most
+eight tool calls run concurrently, and successful execution drains unfinished work before closing.
+
+### Confirmed defects
+
+- [ ] Keep nested fire-and-forget work alive until the execution drain. A tool call started but not returned inside an
+      async function or promise handler is currently a child of that short-lived fiber and may be interrupted when the
+      parent settles even though the promise remains globally tracked.
+- [ ] Track immediate rejected promises. `Promise.reject(value)` does not enter `pendingSettlements`, so abandoning it
+      produces no unhandled-rejection diagnostic.
+- [ ] Drain handled work after ordinary program failure, then preserve the original failure. Today pending work drains
+      only after success, so a rejecting race winner or a later program throw interrupts race losers and `Promise.all`
+      siblings. Timeout and external interruption should still cancel immediately rather than drain.
+- [ ] Make every `await` continuation asynchronous. Awaiting a plain or already-settled value currently resumes in the
+      same scheduling turn and can reorder state mutation relative to JavaScript.
+- [ ] Return rejected promises for invalid `Promise.all`/`allSettled`/`race` inputs instead of throwing during the call.
+- [ ] Align handler callability with the values CodeMode reports as functions, or document the narrower callback
+      allowlist. For example, unsupported constructor-like callables are currently treated as absent handlers.
+
+### Deliberate deviations and open decisions
+
+- CodeMode drains unfinished work before successful execution closes. This keeps tool effects supervised, but a race
+  loser or fail-fast `Promise.all` sibling that never settles can hold execution open indefinitely when the host supplies
+  no timeout.
+- Promise resolution unwraps only `SandboxPromise`; arbitrary `{ then(resolve, reject) }` values remain data. Full
+  thenable assimilation requires internal callable resolver values, first-settlement arbitration, recursive adoption,
+  and cycle detection. Decide whether that machinery belongs in the bounded runtime.
+- `new Promise`, `Promise.any`, resolver APIs, subclasses/species, and the broader prototype surface are unavailable.
+  Consider `Promise.any` independently; custom constructors and subclassing are not current goals.
+- Combinators currently accept arrays plus CodeMode's spreadable strings, Maps, and Sets, while documentation and
+  diagnostics describe array inputs. Choose and document one contract.
+- `Promise.race([])` raises a clear error instead of creating a permanently pending promise.
+- Rejection tracking is execution-scoped and checked at drain time, not an ECMAScript microtask-level unhandled
+  rejection model.
+
+### Required coverage
+
+- Nested unreturned work from async functions and `then`/`catch`/`finally` handlers.
+- Abandoned immediate, chained, and combinator rejections.
+- Plain-value and already-settled `await` ordering.
+- Ordinary program failure versus timeout/external interruption while handled work remains.
+- Never-settling race losers and fail-fast `Promise.all` siblings under an explicit timeout.
+- Shared or duplicate promises across combinators, discarded inner chains, and reaction ordering.
+
 ## Intentionally Unsupported
 ## Intentionally Unsupported
 
 
 These are product boundaries rather than DSL backlog:
 These are product boundaries rather than DSL backlog:
@@ -149,10 +198,6 @@ the adapter TODO. Delete entries when completed.
 The supported JavaScript subset should grow when common model-generated code improves tool orchestration. These are
 The supported JavaScript subset should grow when common model-generated code improves tool orchestration. These are
 current omissions to implement, not intentional product boundaries.
 current omissions to implement, not intentional product boundaries.
 
 
-- [ ] Decide whether thenable assimilation belongs in the bounded runtime. Promise resolution currently unwraps only
-      `SandboxPromise`; arbitrary `{ then(resolve, reject) }` values remain data. Full assimilation requires internal
-      callable resolver values, first-settlement arbitration, recursive adoption, and cycle detection.
-- [ ] Consider `Promise.any`.
 - [ ] Support async iteration and `for await...of`. Define behavior first for the runtime's supported promise and
 - [ ] Support async iteration and `for await...of`. Define behavior first for the runtime's supported promise and
       collection values, then extend it to bounded host streams when a stream boundary exists.
       collection values, then extend it to bounded host streams when a stream boundary exists.
 - [ ] Support callback-bearing standard-library variants that models commonly generate: the mapper argument to
 - [ ] Support callback-bearing standard-library variants that models commonly generate: the mapper argument to

+ 1 - 1
packages/codemode/src/tool-runtime.ts

@@ -615,7 +615,7 @@ export const prepare = <R>(tools: HostTools<R>, catalogBudget = defaultCatalogBu
     "",
     "",
     "## Language",
     "## Language",
     "",
     "",
-    "Use common JavaScript data operations, functions, control flow, selected standard-library methods, and tool calls. Promises support await, then/catch/finally, and the listed Promise combinators. Built-ins include Date, RegExp, Map, Set, URL, URLSearchParams, and URI encoding helpers.",
+    "Use common JavaScript data operations, functions, control flow, selected standard-library methods, and tool calls. Promises support await, then/catch/finally, and Promise.all/allSettled/race/resolve/reject. Built-ins include Date, RegExp, Map, Set, URL, URLSearchParams, and URI encoding helpers.",
     "Modules/imports, classes, generators, timers, fetch, eval, prototype access, and unlisted methods are unavailable. Use Code Mode tools for external operations.",
     "Modules/imports, classes, generators, timers, fetch, eval, prototype access, and unlisted methods are unavailable. Use Code Mode tools for external operations.",
     "Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.",
     "Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.",
   ]
   ]