@superatomai/sdk-node 0.0.6-s → 0.0.7-dsp

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.
package/dist/index.d.mts CHANGED
@@ -1406,6 +1406,10 @@ interface ExecutedToolInfo {
1406
1406
  _recordsShown: number;
1407
1407
  _metadata?: any;
1408
1408
  _sampleData: any[];
1409
+ /** Bounded summary over the FULL fetched result (complete structure). */
1410
+ _summary?: any;
1411
+ /** Up to MAIN_AGENT_COMPLETE_ROWS rows — the complete result when small. */
1412
+ _mainAgentRows?: any[];
1409
1413
  };
1410
1414
  outputSchema?: any;
1411
1415
  sourceSchema?: string;
@@ -1619,8 +1623,16 @@ interface AgentWrittenScript {
1619
1623
  * Controls limits, models, and behavior.
1620
1624
  */
1621
1625
  interface AgentConfig {
1622
- /** Max rows a source agent can return (default: 50) */
1626
+ /** Max rows shown to the UI preview / inlined per source (default: 10) */
1623
1627
  maxRowsPerSource: number;
1628
+ /**
1629
+ * Max rows a source query may FETCH from the DB server-side (default: 2000).
1630
+ * Decoupled from what the main agent is shown: the full result is fetched and
1631
+ * summarized (bounded), but only a small/complete slice enters LLM context.
1632
+ * This lets small lookups (benchmark maps) arrive COMPLETE without letting
1633
+ * large results blow up context.
1634
+ */
1635
+ maxRowsFetched: number;
1624
1636
  /** Model for the main agent (routing + analysis in one LLM call) */
1625
1637
  mainAgentModel: string;
1626
1638
  /** Model for source agent query generation */
@@ -1674,8 +1686,18 @@ interface ScriptRecipe {
1674
1686
  tables: string[];
1675
1687
  /** Parameter definitions — what can vary */
1676
1688
  parameters: ScriptParameter[];
1677
- /** The script function body as a string */
1689
+ /** The script function body as a string. Loaded from disk (scripts-store/<fileBase>.ts). */
1678
1690
  scriptBody: string;
1691
+ /**
1692
+ * On-disk filename stem for the body: scripts-store/<fileBase>.ts.
1693
+ * Editable in the IDE. Decided at authoring time (slug of `name`, with a
1694
+ * short id suffix on collision) and stable across promotion.
1695
+ */
1696
+ fileBase?: string;
1697
+ /** sha256 of the on-disk body — lets the runtime detect manual edits. */
1698
+ bodyHash?: string;
1699
+ /** Project scope (single-VM deployments may leave this undefined). */
1700
+ projectId?: string;
1679
1701
  /** Times this script was used successfully */
1680
1702
  successCount: number;
1681
1703
  /** Times this script failed */
@@ -1713,7 +1735,7 @@ interface ScriptRecipe {
1713
1735
  /**
1714
1736
  * Lifecycle stage of this recipe on disk.
1715
1737
  * - 'draft': written by MainAgent's write_script during a turn; filtered out
1716
- * of `ScriptStore.getAll()` so the matcher never picks it.
1738
+ * of FTS results (status='verified' only) so the matcher never picks it.
1717
1739
  * Filename is suffixed with `turnId` to keep concurrent turns
1718
1740
  * from clobbering each other's drafts.
1719
1741
  * - 'verified': promoted after `execute_script` succeeded; the matcher sees it.
@@ -1763,34 +1785,219 @@ interface ScriptParameter {
1763
1785
  interface ScriptComponentSpec {
1764
1786
  /** Registered component name (e.g. "DynamicBarChart") — matched against the available component library. */
1765
1787
  componentType: string;
1766
- /** `executedQuery.sourceId` to bind to (e.g. a tool id or 'computed:_final'), or 'federation' for a cross-source component. */
1788
+ /** `executedQuery.sourceId` to bind to (e.g. a tool id or 'computed:_final'), 'federation' for a cross-source component, or 'markdown' for a content-only narrative block (no data source). */
1767
1789
  sourceRef: string;
1768
1790
  /** Present only when sourceRef === 'federation' — the DuckDB SQL to re-execute on replay. */
1769
1791
  federationSql?: string;
1792
+ /** Present only when sourceRef === 'markdown' — the narrative text to render on replay (markdown has no data source, so its content must be persisted). */
1793
+ content?: string;
1770
1794
  title?: string;
1771
1795
  description?: string;
1772
1796
  /** Validated axis/value keys + aggregation — all referencing real columns of the bound source. */
1773
1797
  config: Record<string, any>;
1774
1798
  }
1799
+ /**
1800
+ * Result from executing a script via ScriptRunner.
1801
+ */
1802
+ interface ScriptResult {
1803
+ /** Whether the script executed successfully */
1804
+ success: boolean;
1805
+ /** Combined data from all queries */
1806
+ data: any[];
1807
+ /** Individual query results tracked during execution */
1808
+ executedQueries: ScriptQueryResult[];
1809
+ /** Error message if failed */
1810
+ error?: string;
1811
+ /**
1812
+ * Where in the lifecycle the error occurred. Lets MainAgent's fix-loop
1813
+ * decide between "rewrite the whole draft" (compile) and "patch the
1814
+ * specific line" (runtime).
1815
+ */
1816
+ errorPhase?: 'compile' | 'runtime' | 'timeout' | 'ipc';
1817
+ /** Total execution time in milliseconds */
1818
+ executionTimeMs: number;
1819
+ }
1820
+ /**
1821
+ * A single query executed during script runtime.
1822
+ * Tracked by ScriptContext for component generation and debugging.
1823
+ */
1824
+ interface ScriptQueryResult {
1825
+ /** Source tool ID */
1826
+ sourceId: string;
1827
+ /** Human-readable source name */
1828
+ sourceName: string;
1829
+ /** The SQL that was executed */
1830
+ sql: string;
1831
+ /** Result data rows */
1832
+ data: any[];
1833
+ /** Number of rows returned */
1834
+ count: number;
1835
+ /** Total rows that matched before limit (if available) */
1836
+ totalCount?: number;
1837
+ /** Query execution time in milliseconds */
1838
+ executionTimeMs: number;
1839
+ /**
1840
+ * True for rows that did NOT come from a real SQL execution — either a
1841
+ * ctx.emit() dataset or the synthesized "computed:_final" entry that
1842
+ * carries the script's post-JS returned data. The component generator
1843
+ * uses this to route the resulting component through the script_dataset
1844
+ * sentinel toolId so the frontend resolves it via the queryCache short-circuit.
1845
+ */
1846
+ virtual?: boolean;
1847
+ }
1848
+ /**
1849
+ * Match tier returned by the LLM script matcher.
1850
+ *
1851
+ * - 'high': the script answers the question directly; only parameter values
1852
+ * may differ. The runtime replays it with extracted params (cheapest path).
1853
+ * - 'near': the script answers a STRUCTURALLY similar question but needs
1854
+ * body modification (different metric, dimension, table, filter shape).
1855
+ * The runtime forks the parent and adapts the body via MainAgent's normal
1856
+ * write_script + execute_script loop — no SourceAgent dispatch needed.
1857
+ * See backend/docs/SCRIPT-FLOW-FORK-ADAPT.md for the full design.
1858
+ * - 'none': no script is relevant; full agent flow runs.
1859
+ */
1860
+ type MatchTier = 'high' | 'near' | 'none';
1861
+ /**
1862
+ * Result from the LLM-based script matcher.
1863
+ *
1864
+ * For `tier: 'high'`, `extractedParams` carries the values to pass to the
1865
+ * existing script. For `tier: 'near'`, `gaps` and `modificationHint` describe
1866
+ * what the fork-author needs to change in the parent body.
1867
+ */
1868
+ interface ScriptMatch {
1869
+ /** The matched script recipe */
1870
+ recipe: ScriptRecipe;
1871
+ /** Match tier — see MatchTier docs */
1872
+ tier: MatchTier;
1873
+ /** Similarity score (0-1, derived from LLM tier) */
1874
+ similarity: number;
1875
+ /**
1876
+ * Legacy confidence level. Mirrors `tier === 'high'`/`'near'` for now;
1877
+ * kept so existing callers compile while we migrate to tier-based logic.
1878
+ */
1879
+ confidence: 'high' | 'medium';
1880
+ /** Parameters extracted from the user question by the LLM (tier='high') */
1881
+ extractedParams?: Record<string, any>;
1882
+ /** What the user question needs that the parent doesn't cover (tier='near') */
1883
+ gaps?: string[];
1884
+ /** One-sentence description of the change the fork-author should make (tier='near') */
1885
+ modificationHint?: string;
1886
+ /** Why the matcher made this choice (for logs and telemetry) */
1887
+ reasoning?: string;
1888
+ }
1775
1889
 
1776
1890
  /**
1777
- * ScriptStorePersistent Storage for Script Recipes
1891
+ * ScriptRecipeStoreinjected metadata backend for the script flow.
1778
1892
  *
1779
- * Layout:
1780
- * scripts-store/
1781
- * metadata/<name>.json ← recipe metadata (params, tables, counts, ...)
1782
- * <name>.ts ← the getData() function body, editable in your IDE
1893
+ * The SDK is standalone (no DB dependency). The backend implements this
1894
+ * interface over Postgres (full-text search + atomic counters) and injects it
1895
+ * via `collections['script-recipes']`, exactly like `collections['source-embeddings']`.
1896
+ * `ScriptStore` consumes it for all METADATA operations while keeping the
1897
+ * executable body on disk as scripts-store/<fileBase>.ts.
1783
1898
  *
1784
- * Each VM deployment is a single project, so no project ID prefix needed.
1785
- * Legacy single-file format (JSON with embedded scriptBody) is still loadable
1786
- * for backwards compatibility and auto-migrated to the split format on next save.
1899
+ * All metadata rows are plain JSON (no scriptBody that lives on disk).
1900
+ * See backend/docs/SCRIPT-FLOW-SCALING-ISSUES.md (#1, #3, #7).
1787
1901
  */
1788
1902
 
1903
+ /** One recipe's metadata as stored in Postgres (mirrors the script_recipes table). */
1904
+ interface ScriptRecipeMetaRow {
1905
+ id: string;
1906
+ projectId?: string | null;
1907
+ version: number;
1908
+ name: string;
1909
+ intentDescription: string;
1910
+ tags: string[] | null;
1911
+ createdFrom: string | null;
1912
+ sourceIds: string[] | null;
1913
+ tables: string[] | null;
1914
+ parameters: ScriptParameter[] | null;
1915
+ components?: ScriptComponentSpec[] | null;
1916
+ fileBase: string;
1917
+ bodyHash?: string | null;
1918
+ successCount: number;
1919
+ failureCount: number;
1920
+ lastUsed: string | null;
1921
+ parentId?: string | null;
1922
+ forkDepth?: number | null;
1923
+ forkReason?: string | null;
1924
+ status: 'draft' | 'verified' | string;
1925
+ turnId?: string | null;
1926
+ lastError?: {
1927
+ phase: 'compile' | 'runtime' | 'timeout' | 'ipc';
1928
+ message: string;
1929
+ at: string;
1930
+ attempt: number;
1931
+ } | null;
1932
+ createdAt?: string | null;
1933
+ updatedAt?: string | null;
1934
+ }
1935
+ interface ScriptRecipeStore {
1936
+ /** FTS shortlist of healthy verified recipes for the matcher (metadata only). */
1937
+ search(params: {
1938
+ prompt: string;
1939
+ projectId?: string;
1940
+ limit?: number;
1941
+ }): Promise<ScriptRecipeMetaRow[]>;
1942
+ /** Fetch one recipe by id (any status). */
1943
+ getById(id: string): Promise<ScriptRecipeMetaRow | null>;
1944
+ /** Count healthy verified recipes (drives the "any scripts?" gate). */
1945
+ count(params?: {
1946
+ projectId?: string;
1947
+ }): Promise<number>;
1948
+ /** Insert or update a recipe row (keyed by id). */
1949
+ upsert(row: ScriptRecipeMetaRow): Promise<void>;
1950
+ /** Atomically bump counters / last-used. */
1951
+ updateStats(id: string, patch: {
1952
+ successDelta?: number;
1953
+ failureDelta?: number;
1954
+ lastUsed?: string;
1955
+ }): Promise<void>;
1956
+ /** Flip a draft to verified, applying provenance + optional fork lineage. */
1957
+ promote(id: string, patch: {
1958
+ sourceIds: string[];
1959
+ tables: string[];
1960
+ fileBase?: string;
1961
+ parentId?: string;
1962
+ forkDepth?: number;
1963
+ forkReason?: string;
1964
+ components?: ScriptComponentSpec[];
1965
+ }): Promise<ScriptRecipeMetaRow | null>;
1966
+ /** Stamp a draft's last execution error. */
1967
+ recordDraftError(id: string, err: {
1968
+ phase: string;
1969
+ message: string;
1970
+ attempt: number;
1971
+ at: string;
1972
+ }): Promise<void>;
1973
+ /** Delete a recipe row (body file removed separately). */
1974
+ remove(id: string): Promise<void>;
1975
+ /** True if `fileBase` is taken by a different recipe in this project. */
1976
+ fileBaseTaken(fileBase: string, excludeId: string, projectId?: string): Promise<boolean>;
1977
+ }
1978
+ /** Pull the injected store off the collections bag (or null if not wired). */
1979
+ declare function resolveScriptRecipeStore(collections: any): ScriptRecipeStore | null;
1980
+
1789
1981
  /**
1790
- * Input for `ScriptStore.saveDraft()`. Identifies the draft by `recipeId` so
1791
- * retries within the same turn overwrite the same files (or omit `recipeId`
1792
- * on the first call to mint a fresh draft).
1982
+ * ScriptStore Postgres metadata + on-disk body for script recipes.
1983
+ *
1984
+ * Split of responsibilities:
1985
+ * - METADATA → injected `ScriptRecipeStore` (Postgres FTS + atomic counters),
1986
+ * resolved from `collections['script-recipes']`.
1987
+ * - BODY → scripts-store/<fileBase>.ts, editable in your IDE. Written
1988
+ * atomically (temp + rename); `bodyHash` (sha256) detects edits.
1989
+ *
1990
+ * The old "read every file every turn + send the whole catalog to the LLM"
1991
+ * matcher is gone — matching is `store.search(prompt)` (FTS shortlist). The
1992
+ * draft/verified filename dance is gone too: `status` is a DB column and the
1993
+ * file keeps a stable `<fileBase>.ts` name across promotion.
1994
+ *
1995
+ * When no metadata store is injected, the store degrades to a safe no-op
1996
+ * (count 0 → script flow disabled) instead of crashing.
1997
+ *
1998
+ * See backend/docs/SCRIPT-FLOW-SCALING-ISSUES.md.
1793
1999
  */
2000
+
1794
2001
  interface SaveDraftInput {
1795
2002
  /** Reuse an existing draft (retry); omit to mint a new one. */
1796
2003
  recipeId?: string;
@@ -1803,142 +2010,92 @@ interface SaveDraftInput {
1803
2010
  scriptBody: string;
1804
2011
  createdFrom: string;
1805
2012
  }
1806
- /**
1807
- * Lineage + provenance fields applied when a draft is promoted to verified.
1808
- * MainAgent gathers `sourceIds` and `tables` from the verified execution; the
1809
- * caller (agent-user-response.ts) supplies the optional fork lineage.
1810
- */
1811
2013
  interface PromoteToVerifiedInput {
1812
2014
  sourceIds: string[];
1813
2015
  tables: string[];
1814
2016
  parentId?: string;
1815
2017
  forkDepth?: number;
1816
2018
  forkReason?: string;
1817
- /** Validated component specs to persist on the recipe (component recipe). */
1818
2019
  components?: ScriptComponentSpec[];
1819
2020
  }
2021
+ interface ScriptStoreOptions {
2022
+ /** Explicit metadata store, or resolved from `collections['script-recipes']`. */
2023
+ store?: ScriptRecipeStore | null;
2024
+ collections?: any;
2025
+ /** Body directory (defaults to <cwd>/scripts-store). */
2026
+ baseDir?: string;
2027
+ /** Project scope stamped on every row. */
2028
+ projectId?: string;
2029
+ }
2030
+ /**
2031
+ * Normalize a scriptBody into the on-disk form (strip a leading comment block,
2032
+ * ensure `export async function getData`). Exported for MainAgent.
2033
+ */
2034
+ declare function normalizeScriptBody(scriptBody: string): string;
1820
2035
  declare class ScriptStore {
1821
- private recipes;
2036
+ private store;
1822
2037
  private storeDir;
1823
- private loaded;
1824
- constructor(baseDir?: string);
1825
- private metadataDir;
1826
- /**
1827
- * Filename base for a recipe. Drafts include the per-turn suffix so two
1828
- * concurrent turns can never clobber each other's draft files; verified
1829
- * recipes use the bare slug.
1830
- */
1831
- private fileBaseName;
1832
- /**
1833
- * Absolute path to the .ts file for a recipe. Callers (e.g. ScriptRunner,
1834
- * MainAgent's execute_script) use this to hand off the path to the tsx child.
1835
- */
1836
- getScriptPath(recipe: ScriptRecipe): string;
1837
- /**
1838
- * Absolute path to the metadata JSON for a recipe.
1839
- */
1840
- private getMetadataPath;
1841
- /**
1842
- * Get all VERIFIED recipes drafts are filtered out so the matcher never
1843
- * considers an unverified script. Loads from disk on first access.
1844
- */
1845
- getAll(): ScriptRecipe[];
1846
- /**
1847
- * Get a recipe by ID — returns drafts as well as verified scripts.
1848
- * Used by MainAgent and the promotion path.
1849
- */
1850
- get(id: string): ScriptRecipe | null;
1851
- /**
1852
- * Number of verified recipes (matches `getAll().length`).
1853
- */
1854
- count(): number;
1855
- /**
1856
- * Save a recipe (create or update).
1857
- * File is named after the script: "order-status-distribution.json"
1858
- */
1859
- save(recipe: ScriptRecipe): void;
1860
- /**
1861
- * Persist (or update) a draft recipe to disk. Always writes immediately so
1862
- * the `.ts` body is visible in the IDE the moment MainAgent calls
1863
- * `write_script`. Within one turn, retries that pass the same `recipeId`
1864
- * overwrite the same files (the LLM rewriting itself); a fresh `recipeId`
1865
- * is minted only on the first call of the turn.
1866
- *
1867
- * Filename includes the `turnId` suffix so concurrent turns never collide.
1868
- */
1869
- saveDraft(input: SaveDraftInput): ScriptRecipe;
1870
- /**
1871
- * Stamp the draft with the most recent execution failure so it is visible
1872
- * in the metadata JSON without grepping logs. No-op if the recipe doesn't
1873
- * exist or has already been promoted.
1874
- */
2038
+ private projectId?;
2039
+ constructor(opts?: ScriptStoreOptions);
2040
+ /** Whether a metadata store is wired (matcher / authoring are gated on this). */
2041
+ hasStore(): boolean;
2042
+ /** Number of healthy verified recipes (gates the script-matching path). */
2043
+ count(): Promise<number>;
2044
+ /**
2045
+ * FTS shortlist for the matcher (metadata only — bodies are loaded lazily by
2046
+ * `get()` once the LLM picks one). Returns verified, healthy recipes ranked
2047
+ * by relevance.
2048
+ */
2049
+ search(prompt: string, limit?: number): Promise<ScriptRecipe[]>;
2050
+ /** Fetch one recipe by id with its body loaded from disk. */
2051
+ get(id: string): Promise<ScriptRecipe | null>;
2052
+ /** Create or update a recipe (metadata upsert + body write when changed). */
2053
+ save(recipe: ScriptRecipe): Promise<void>;
2054
+ /**
2055
+ * Persist (or update) a draft. Within a turn, retries that pass the same
2056
+ * `recipeId` overwrite the same row + file; a fresh `recipeId` mints a new
2057
+ * draft. The body is visible at scripts-store/<fileBase>.ts immediately.
2058
+ */
2059
+ saveDraft(input: SaveDraftInput): Promise<ScriptRecipe>;
2060
+ /** Stamp a draft's last execution error (metadata only). */
1875
2061
  recordDraftError(recipeId: string, err: {
1876
2062
  phase: 'compile' | 'runtime' | 'timeout' | 'ipc';
1877
2063
  message: string;
1878
2064
  attempt: number;
1879
- }): void;
2065
+ }): Promise<void>;
1880
2066
  /**
1881
2067
  * Promote a successfully-executed draft into a verified script.
1882
- *
1883
- * - Renames the on-disk files from `<slug>-<turnId>.{ts,json}` to the bare
1884
- * `<slug>.{ts,json}`. If a verified file with the same slug already exists
1885
- * (concurrent turn won the race, or a prior session has it), the recipe
1886
- * keeps its turn-suffixed filename so we never overwrite verified work —
1887
- * the matcher keys on `recipe.id`, so two siblings with similar slugs is fine.
1888
- * - Flips `status: 'verified'`, clears `lastError`, applies provenance
1889
- * (`sourceIds`, `tables`) and optional fork lineage.
1890
- */
1891
- promoteToVerified(recipeId: string, input: PromoteToVerifiedInput): ScriptRecipe | null;
1892
- /**
1893
- * Drop a draft from disk + memory (e.g. when an outer error path wants to
1894
- * clean up). Per the agreed policy, MainAgent's normal failure path does
1895
- * NOT call this failed drafts are kept on disk for the user to inspect.
1896
- */
1897
- discardDraft(recipeId: string): void;
1898
- /**
1899
- * Delete a recipe by ID.
1900
- */
1901
- delete(id: string): void;
1902
- /**
1903
- * Record a successful execution for a recipe.
1904
- */
1905
- recordSuccess(id: string): void;
1906
- /**
1907
- * Record a failed execution for a recipe.
1908
- */
1909
- recordFailure(id: string): void;
1910
- /**
1911
- * Get the failure rate for a recipe (0 to 1).
1912
- * Returns 0 if the recipe has fewer than 5 total uses.
1913
- */
1914
- getFailureRate(id: string): number;
1915
- private ensureLoaded;
1916
- /**
1917
- * Convert a script name to a safe filename.
1918
- * "Order Status Distribution" → "order-status-distribution"
1919
- */
1920
- private toFileName;
1921
- /**
1922
- * Load all script files from disk.
1923
- *
1924
- * Supports two layouts:
1925
- * - New (preferred): metadata/<name>.json + <name>.ts
1926
- * - Legacy: <name>.json with embedded scriptBody (auto-migrated on next save)
1927
- */
1928
- private loadAllFromDisk;
1929
- /**
1930
- * Save a recipe to disk in split format:
1931
- * metadata/<name>.json (metadata, no scriptBody)
1932
- * <name>.ts (just the function body)
1933
- *
1934
- * If a legacy top-level <name>.json exists for the same recipe, remove it
1935
- * so we don't end up with duplicate sources of truth.
1936
- */
1937
- private saveRecipeToDisk;
1938
- /**
1939
- * Delete a recipe's files from disk (both metadata + body, plus any legacy file).
1940
- */
1941
- private deleteRecipeFromDisk;
2068
+ * The on-disk body already exists at <fileBase>.ts (written at write_script
2069
+ * time) and keeps its name — only the DB row flips status + provenance.
2070
+ */
2071
+ promoteToVerified(recipeId: string, input: PromoteToVerifiedInput): Promise<ScriptRecipe | null>;
2072
+ /**
2073
+ * Drop a draft (row + body file). MainAgent calls this at end-of-turn when a
2074
+ * draft was authored but never verified failed drafts are never matched, so
2075
+ * deleting them immediately avoids unbounded accumulation (#5). No-op if the
2076
+ * recipe isn't a draft (so a promoted/verified script is never removed here).
2077
+ */
2078
+ discardDraft(recipeId: string): Promise<void>;
2079
+ /** Delete a recipe (row + body file). */
2080
+ delete(id: string): Promise<void>;
2081
+ /** Record a successful execution (atomic counter bump). */
2082
+ recordSuccess(id: string): Promise<void>;
2083
+ /** Record a failed execution (atomic counter bump). */
2084
+ recordFailure(id: string): Promise<void>;
2085
+ /** Absolute path to the .ts body for a recipe (used by the runner/MainAgent). */
2086
+ getScriptPath(recipe: ScriptRecipe): string;
2087
+ private removeById;
2088
+ private rowToRecipe;
2089
+ private recipeToRow;
2090
+ /** slug of name, with a short id suffix when the bare slug is already taken. */
2091
+ private computeFileBase;
2092
+ private toSlug;
2093
+ private hash;
2094
+ private bodyPath;
2095
+ private readBody;
2096
+ /** Atomic body write (temp + rename) so concurrent reads never see a partial file. */
2097
+ private writeBody;
2098
+ private unlinkBody;
1942
2099
  }
1943
2100
 
1944
2101
  /**
@@ -1972,7 +2129,16 @@ declare class MainAgent {
1972
2129
  private turnId;
1973
2130
  private createdFromPrompt;
1974
2131
  private scriptState;
1975
- constructor(externalTools: ExternalTool[], config: AgentConfig, scriptStore?: ScriptStore, turnId?: string, streamBuffer?: StreamBuffer, workflows?: WorkflowDescriptor[]);
2132
+ /**
2133
+ * Fork mode — set when this turn is adapting a near-matching parent script.
2134
+ * In fork mode there is no legitimate "answer with bare text" outcome: the
2135
+ * only correct first move is a tool call (write_script, or a source tool for
2136
+ * schema discovery). We therefore force tool use on the first LLM iteration
2137
+ * so the model can't end its turn with a bare "I'll adapt…" preamble and zero
2138
+ * tool calls. Never set on the fresh-authoring / general-question path.
2139
+ */
2140
+ private forkMode;
2141
+ constructor(externalTools: ExternalTool[], config: AgentConfig, scriptStore?: ScriptStore, turnId?: string, streamBuffer?: StreamBuffer, workflows?: WorkflowDescriptor[], forkMode?: boolean);
1976
2142
  private get scriptingEnabled();
1977
2143
  /**
1978
2144
  * Handle a user question using the multi-agent system.
@@ -2069,7 +2235,22 @@ interface LLMOptions {
2069
2235
  temperature?: number;
2070
2236
  topP?: number;
2071
2237
  apiKey?: string;
2238
+ baseURL?: string;
2072
2239
  partial?: (chunk: string) => void;
2240
+ /**
2241
+ * Forces a tool call on the FIRST iteration of streamWithTools only
2242
+ * (subsequent iterations revert to auto). Used by fork mode to stop the
2243
+ * model from ending its turn with a bare "I'll adapt the script…" preamble
2244
+ * and zero tool calls. `{ type: 'any' }` lets the model pick which tool
2245
+ * (write_script in the common case, a source tool for schema discovery);
2246
+ * `{ type: 'tool', name }` pins a specific tool. Anthropic only.
2247
+ */
2248
+ firstIterationToolChoice?: {
2249
+ type: 'any';
2250
+ } | {
2251
+ type: 'tool';
2252
+ name: string;
2253
+ };
2073
2254
  }
2074
2255
  interface Tool {
2075
2256
  name: string;
@@ -2117,6 +2298,15 @@ declare class LLM {
2117
2298
  * "claude-sonnet-4-5" → ["anthropic", "claude-sonnet-4-5"] (default)
2118
2299
  */
2119
2300
  private static _parseModel;
2301
+ /**
2302
+ * Map an Anthropic model id (e.g. "claude-sonnet-4-5-20250929") to the OpenRouter slug
2303
+ * (e.g. "claude-sonnet-4.5"). OpenRouter slugs drop the date suffix and use dotted versions.
2304
+ */
2305
+ private static _toOpenRouterSlug;
2306
+ private static _openrouterOptions;
2307
+ private static _openrouterText;
2308
+ private static _openrouterStream;
2309
+ private static _openrouterStreamWithTools;
2120
2310
  private static _anthropicText;
2121
2311
  private static _anthropicStream;
2122
2312
  private static _anthropicStreamWithTools;
@@ -3262,6 +3452,65 @@ declare class DashboardConversationHistory {
3262
3452
  }
3263
3453
  declare const dashboardConversationHistory: DashboardConversationHistory;
3264
3454
 
3455
+ /**
3456
+ * ScriptMatcher — LLM-Based Script Matching + Parameter Extraction
3457
+ *
3458
+ * Uses ONE LLM call to:
3459
+ * 1. Pick the best matching script from the library (or "none")
3460
+ * 2. Extract parameter values from the user question
3461
+ *
3462
+ * Why LLM over embeddings:
3463
+ * - Embeddings capture topic similarity ("overstock" ≈ "inventory" ≈ "revenue")
3464
+ * but can't distinguish structurally different questions about the same domain
3465
+ * - LLM understands that "overstock by warehouse" needs a different script than
3466
+ * "revenue by warehouse" even though they're semantically close
3467
+ * - One call does both matching AND parameter extraction
3468
+ *
3469
+ * When script library grows past ~50, add an embedding pre-filter
3470
+ * (ChromaDB narrows to top 10 → LLM picks from those 10).
3471
+ */
3472
+
3473
+ declare class ScriptMatcher {
3474
+ private store;
3475
+ constructor(store: ScriptStore);
3476
+ /**
3477
+ * Find the best matching script for a user question.
3478
+ * Uses ONE LLM call that picks the script AND extracts parameters.
3479
+ * Returns null if no script matches.
3480
+ */
3481
+ match(userPrompt: string, apiKey?: string, model?: string): Promise<ScriptMatch | null>;
3482
+ /**
3483
+ * Build the script catalog string for the LLM prompt.
3484
+ * Each script gets: index, ID, name, description, and parameter definitions.
3485
+ */
3486
+ private buildScriptCatalog;
3487
+ }
3488
+
3489
+ /**
3490
+ * ScriptRunner — Execute scripts in an isolated tsx subprocess.
3491
+ *
3492
+ * The subprocess approach replaces the earlier `new Function()` eval and gives us:
3493
+ * - Real sandbox (separate process, SIGKILL on timeout).
3494
+ * - Real TypeScript (tsx transpiles on the fly).
3495
+ * - npm imports available to scripts (clustering, stats, geo, etc.).
3496
+ *
3497
+ * Protocol: NDJSON over the child's stdin/stdout. See script-ipc.ts + backend/docs/SCRIPT-FLOW-IMPLEMENTATION.md.
3498
+ */
3499
+
3500
+ interface RunScriptOptions {
3501
+ /** Data sources the script is allowed to query via ctx.query */
3502
+ externalTools: ExternalTool[];
3503
+ /** Optional — for propagating per-query UI progress to the user */
3504
+ streamBuffer?: StreamBuffer;
3505
+ /** Override the wall-clock timeout (default `SCRIPT_TIMEOUT_MS`, 60s). */
3506
+ timeoutMs?: number;
3507
+ }
3508
+ /**
3509
+ * Execute a recipe by spawning a tsx child on the script's .ts file.
3510
+ * `scriptPath` is the absolute path to the saved `.ts` body.
3511
+ */
3512
+ declare function runScript(recipe: ScriptRecipe, scriptPath: string, params: Record<string, any>, options: RunScriptOptions): Promise<ScriptResult>;
3513
+
3265
3514
  type MessageTypeHandler = (message: IncomingMessage) => void | Promise<void>;
3266
3515
  declare class SuperatomSDK {
3267
3516
  private ws;
@@ -3431,4 +3680,4 @@ declare class SuperatomSDK {
3431
3680
  getConversationSimilarityThreshold(): number;
3432
3681
  }
3433
3682
 
3434
- export { type Action, type AgentConfig, type AgentResponse, BM25L, type BM25LOptions, type BaseLLMConfig, CONTEXT_CONFIG, type CapturedLog, CleanupService, type CollectionHandler, type CollectionOperation, type DBUIBlock, DEFAULT_AGENT_CONFIG, type DatabaseType, type HybridSearchOptions, type IncomingMessage, type KbNodesQueryFilters, type KbNodesRequestPayload, LLM, type LLMUsageEntry, type LogLevel, MainAgent, type Message, type ModelStrategy, type OutputField, type RerankedResult, STORAGE_CONFIG, type SelectedWorkflow, SuperatomSDK, type SuperatomSDKConfig, type TaskType, Thread, ThreadManager, type Tool$1 as Tool, type ToolOutputSchema, UIBlock, UILogCollector, type User, UserManager, type UsersData, type WorkflowDescriptor, anthropicLLM, dashboardConversationHistory, geminiLLM, groqLLM, hybridRerank, llmUsageLogger, logger, openaiLLM, queryCache, rerankChromaResults, rerankConversationResults, userPromptErrorLogger };
3683
+ export { type Action, type AgentConfig, type AgentResponse, BM25L, type BM25LOptions, type BaseLLMConfig, CONTEXT_CONFIG, type CapturedLog, CleanupService, type CollectionHandler, type CollectionOperation, type DBUIBlock, DEFAULT_AGENT_CONFIG, type DatabaseType, type HybridSearchOptions, type IncomingMessage, type KbNodesQueryFilters, type KbNodesRequestPayload, LLM, type LLMUsageEntry, type LogLevel, MainAgent, type Message, type ModelStrategy, type OutputField, type RerankedResult, STORAGE_CONFIG, type ScriptComponentSpec, ScriptMatcher, type ScriptParameter, type ScriptRecipe, type ScriptRecipeMetaRow, type ScriptRecipeStore, type ScriptResult, ScriptStore, type ScriptStoreOptions, type SelectedWorkflow, SuperatomSDK, type SuperatomSDKConfig, type TaskType, Thread, ThreadManager, type Tool$1 as Tool, type ToolOutputSchema, UIBlock, UILogCollector, type User, UserManager, type UsersData, type WorkflowDescriptor, anthropicLLM, dashboardConversationHistory, geminiLLM, groqLLM, hybridRerank, llmUsageLogger, logger, normalizeScriptBody, openaiLLM, queryCache, rerankChromaResults, rerankConversationResults, resolveScriptRecipeStore, runScript, userPromptErrorLogger };