import type { FunctionDefinition, GraphNodeDefinition } from "../types.js"; import type { ImportedFunctionSignature } from "../compilationUnit.js"; import type { BuiltinSignature } from "./types.js"; /** * Names of Agency's *built-in* functions — language primitives with no * `def` source. Users cannot redefine these via `def` or `node`. Single * source of truth — imported by typeChecker/index.ts. * * Stdlib functions (print, read, fetch, etc.) are NOT here. They are * regular Agency code in stdlib/index.agency and users may shadow them. * * Built-ins fall into two parse-time categories: * * 1. Parsed as plain `functionCall`. Their typed signatures live in * BUILTIN_FUNCTION_TYPES, so they resolve as `kind: "builtin"` before * reaching the reserved-name fallback below. Listed here too as * defense-in-depth (if a BUILTIN_FUNCTION_TYPES entry is ever removed, * the reservation still prevents user redefinition). * * 2. Parsed as their own AST node — never reach functionCall handling: * * schema(Type) → SchemaExpression — a language primitive that * bridges *type space* and *value space*: the argument is a * VariableType (not a value expression), and at runtime it * constructs a zod schema from that type. Reserved here only so * that `def schema()` can't create parse ambiguity. The typechecker * currently synthesizes its result type as "any" — populating it * with a structured `Schema` type is future work. * * interrupt ... → InterruptStatement * debugger → DebuggerStatement */ export declare const RESERVED_FUNCTION_NAMES: Set; /** * Registry of JavaScript / Node.js globals that compiled Agency output * is allowed to call. Each entry is either: * - kind: "callable" — a function (sig is optional; populating it * later enables type-checking) * - kind: "namespace" — an object with named members, each of which * is itself a JsRegistryEntry * - kind: "value" — a plain value such as `Infinity`: a name that * resolves as a variable and cannot be called * * Phase 1 uses only the structure (existence checks). Phase 2 will * populate `sig` for entries we want type-checked; the typechecker * starts enforcing arity/types when `sig` is present. * * Names already supported natively by Agency (null, undefined) or rare * enough to defer (NaN) are intentionally absent. */ export type JsRegistryEntry = { kind: "callable"; sig?: BuiltinSignature; } | { kind: "value"; } | { kind: "namespace"; members: Record; /** When set, this namespace is ALSO directly callable (e.g. * `String(x)` coerces to string, `Number(x)` to number). */ callableSig?: BuiltinSignature; }; export declare const JS_GLOBALS: Record; /** * Allowlist of JavaScript globals a program may reference under * `--agency-only` (`typechecker.jsGlobals: "sandbox"`). An entry here is a * PROMISE that the name cannot reach the host. Review every addition against * that promise — this list is a reviewed security boundary, not a * convenience registry like `JS_GLOBALS`. * * It is written out in full rather than filtered from `JS_GLOBALS`, so a * name added to `JS_GLOBALS` for interop never silently becomes allowed * here. It is NOT a subset of `JS_GLOBALS`: it adds constructor names * (`Set`, `Map`, `RegExp`, `Intl`) that `new X()` resolves against but that * `JS_GLOBALS` never listed. * * Deliberately EXCLUDED (each reaches or names the host): `process`, * `console`, `Buffer`, `globalThis`, `eval`, `Function`, `fetch`, `Reflect`, * `Proxy`, `Symbol`, `require`, `import`, `setTimeout`/`setInterval` (run * code after the frame returns, outside the runtime's bookkeeping), and * `Object.create`/`getPrototypeOf`/`setPrototypeOf`/`getOwnPropertyNames` * (prototype and descriptor access). */ export declare const SANDBOX_JS_GLOBALS: Record; /** * Walk a namespace path through a registry. Returns the leaf entry if * the full chain resolves, otherwise null. Defaults to `JS_GLOBALS`; * pass `SANDBOX_JS_GLOBALS` for the `--agency-only` allowlist. * * Examples: * lookupJsMember(["JSON", "parse"]) → { kind: "callable", sig: undefined } * lookupJsMember(["JSON", "banana"]) → null * lookupJsMember(["NotAGlobal", "x"]) → null */ export declare function lookupJsMember(path: string[], registry?: Record): JsRegistryEntry | null; type ResolveCallInput = { functionDefs: Record; nodeDefs: Record; importedFunctions: Record; /** Names imported via `import node { ... } from "..."`. */ importedNodeNames: readonly string[]; /** Names imported via `import { foo } from "./helpers.js"` (non-Agency). */ jsImportedNames?: object; scopeHas: (name: string) => boolean; /** JS-global registry to resolve against. Defaults to `JS_GLOBALS`; the * `--agency-only` sandbox passes `SANDBOX_JS_GLOBALS`. */ registry?: Record; }; /** * Tagged union describing where a call name resolved to. The diagnostic * only cares whether `kind === "unresolved"`; the other kinds are * informational so future analyses can distinguish "user code" from * "language built-in" from "JS interop" without re-running the lookup. * * def — locally-defined `def` or `node` in this file. * imported — function or node imported from another `.agency` file * (`import { foo } from "./other.agency"` or * `import node { foo } from "./other.agency"`). * builtin — has a typed signature in `BUILTIN_FUNCTION_TYPES`. * True language primitives only: `success`, `failure`, * `llm`, `approve`, `reject`, `propagate`, `checkpoint`, * `getCheckpoint`, `restore`, `isSuccess`, `isFailure`. * Stdlib functions (`print`, `fetch`, `read`, etc.) * resolve as `imported` instead, via the auto-injected * `import { ... } from "std::index"` statement. * scopeBinding — bound in the local scope (lambda, `partial`, * `for` variable, etc.). * jsGlobal — flat callable JS global (`parseInt`, `setTimeout`). * Namespace member calls like `JSON.parse(...)` are * handled separately via `lookupJsMember`. * unresolved — none of the above. The diagnostic emits. * * The other RESERVED_FUNCTION_NAMES entries (`schema` → SchemaExpression, * `interrupt` → InterruptStatement, `debugger` → DebuggerStatement) are * parsed into their own AST node type and never reach `resolveCall` as a * `functionCall`, so there's no `kind: "reserved"` here. */ export type CallResolution = { kind: "def"; } | { kind: "imported"; } | { kind: "jsImported"; } | { kind: "builtin"; } | { kind: "scopeBinding"; } | { kind: "jsGlobal"; } | { kind: "unresolved"; }; /** * Resolution order matters: * * 1. Local `def`/`node` — user code wins. * 2. Imported from another file — cross-file `def`/`node`. Stdlib * functions (print, fetch, read, * …) resolve here via the * auto-injected std::index import. * 3. JS-imported name — `import { foo } from "./helpers.js"`. * 4. `BUILTIN_FUNCTION_TYPES` — Agency language primitives. * 5. Local scope binding — lambdas, `partial`, `for` vars. * 6. Flat JS global callable — parseInt, setTimeout, etc. * 7. Otherwise: unresolved. */ /** * Inputs for {@link isJsGlobalBase}. The shape mirrors {@link ResolveCallInput} * but adds import-node bookkeeping so user definitions of `JSON` / `Math` / * etc. (via `node`, `import node`, or a `let`) cleanly opt out of * JS_GLOBALS validation. */ export type ShadowingInput = { scope: { has(name: string): boolean; }; functionDefs: object; nodeDefs: object; importedFunctions: object; importedNodeNames: readonly string[]; jsImportedNames?: object; }; /** * `true` only when `name` resolves to a JS global *and* nothing user-defined * shadows it. Use this to gate JS-namespace member validation (e.g. * `JSON.parse(...)`) and avoid checking against `JS_GLOBALS` when the user * has their own `node JSON()` / `let JSON = …`. */ export declare function isJsGlobalBase(name: string, input: ShadowingInput, registry?: Record): boolean; export declare function resolveCall(name: string, input: ResolveCallInput): CallResolution; export {};