Skip to main content

data-programs

Save a run-code fetch/join/aggregate script as a refreshable data source panels bind to. Use when a provider-api-request or run-code analysis should become a live panel.

Datos de origen

Repositorio
BuilderIO/agent-native
Última actividad en el origen
7 de octubre de 2026 a las 13:37
Idioma detectado de SKILL.md
inglés
Estrellas
7366
Forks
669

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
data-programs
description
Save a run-code fetch/join/aggregate script as a refreshable data source panels bind to. Use when a provider-api-request or run-code analysis should become a live panel.
# Data Programs A **data program** is a named, stored, agent-authored JS script that fetches, joins, filters, or aggregates provider or app data, and is rendered by a dashboard panel. It is the generic answer to "I can query this once in chat — now make it a panel other people can see, that refreshes on its own." There is no new credential machinery here. A data program is exactly a `run-code` script (same `providerFetch` / `providerFetchAll` / `providerSearchAll` / `appAction` / `workspace*` globals you already use for ad-hoc analysis) plus two things: a stored, named identity, and a small `emit(rows, schema)` contract so the result can be cached and charted. Read `provider-api` first if you have not already — data programs assume you know `provider-api-catalog` / `provider-api-docs` / `provider-api-request`. ## When to reach for this vs. a canned action - The common case (won/lost by pipeline, a standard date-range rollup) already has a first-class action (`hubspot-deals`, etc.) — use that. - A one-time chat question — just query it with `run-code` or `provider-api-request` and answer directly. Don't save a program for something you'll never look at again. - A cohort needs an arbitrary property filter, an IN-search over custom values, or a join against a second provider, **and** the user (or a dashboard) will want to see it again with fresh data — that's a data program. ## Authoring workflow 1. **Prototype in chat first.** Write the fetch/join/aggregate logic as a normal `run-code` script and confirm it returns the rows you expect. 2. **Wrap it to call `emit(rows, schema)` exactly once** at the end instead of returning/printing the result (see contract below). 3. **Save it** with `save-data-program`: ``` save-data-program( name: "risk-meeting-cohort", // stable slug, unique per your account title: "Risk Meeting — HubSpot cohort", description: "...", code: "<the run-code script text>", defaultParams: { riskStatuses: ["Churn Risk"] }, // optional refreshMode: "ttl", // "ttl" | "manual" refreshTtlMs: 900000, // optional, floor 60000 background: false, // true for slow multi-minute joins ) ``` This **dry-runs the code with `defaultParams` before persisting** and rejects the save with a structured error if it fails — a broken program is never stored. On success it returns `{ programId, rowCount, columns, sampleRows }`: use that as your proof the program actually produces the rows you expect before telling the user it's ready. 4. **Bind a dashboard panel to it.** Panels reference programs through the `"program"` source; the panel's `sql` field carries a JSON descriptor instead of a query string: ```json { "programId": "dp_...", "params": { "riskStatuses": ["Churn Risk"] } } ``` The panel renders through the exact same chart/table components as every other panel source — it only ever receives `{ rows, schema }`. 5. **Iterate without re-saving** with `preview-data-program(code, params?)` — same dry-run path, no persisted row. Use this while tuning a script before calling `save-data-program` again. 6. **Refresh on demand** with `run-data-program(programId, params?, forceRefresh?, includeRows?)` — this is what the agent calls to get a compact proof-of-result (`rowCount`, `columns`, `sampleRows`) without waiting for a panel view. Pass `includeRows: true` only when you need the full (capped) row set back in context. ## The sandbox surface Inside a data program you get exactly the `run-code` globals, plus one addition: - `providerFetch(provider, path, init?)`, `providerFetchAll(...)`, `providerSearchAll(...)` — the same generic provider-access helpers used for ad-hoc analysis. See `provider-api` for the pagination/search option shapes. - `appAction(name, params)`, `webFetch`, `workspace*` Resources helpers — same as `run-code`. - `params` — a **frozen** global object: the params passed into this run (`defaultParams` merged by the caller, or whatever was passed to `run-data-program` / the panel descriptor). Read it, never mutate it. - `emit(rows, schema?)` — call this **exactly once**, at the end, with your result. A second call throws. `schema` is optional — `{ name: string, type: string }[]` — inferred automatically from the first 50 rows when omitted (a column is `"json"` if rows disagree on primitive type, otherwise `"number"` / `"string"` / `"boolean"`). `console.log` remains completely free for debugging; it never interferes with `emit()` parsing and is captured separately as a truncated log tail on the run record. Caps enforced on every run (not configurable per-program): | Limit | Value | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Max emitted rows | 10,000 (rows beyond this are dropped, `truncated: true`) | | Max emitted result size | 4 MiB (rows dropped from the end to fit; a single row over 4 MiB is a hard `result_too_large` failure — nothing to safely keep) | | Max active programs per app | 200 (archive unused ones to free room) | | Minimum refresh TTL | 60,000 ms | | Run rows kept per (program, params) | 5 most recent (older ones are pruned automatically) | Truncation is always honest: a partial result comes back with `truncated: true`, never a silent drop. ## Caching and refresh model Every `(programId, paramsHash)` pair (params are canonicalized and hashed, so equivalent param objects share a cache entry) has its own run history. What happens on each call to `runDataProgram` / a panel view: | Situation | Behavior | | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Fresh cache hit (younger than `refreshTtlMs`, or `refreshMode: "manual"` with any prior success) | Returns cached rows instantly, `cacheHit: true` | | Cache is older than the TTL | Re-executes synchronously (panel views get a 25s budget; agent/manual calls get 120s), replacing the cache on success | | `refreshMode: "manual"` | Never auto-refreshes; only `forceRefresh: true` (or `manual_refresh` from the UI) re-runs it | | No cache yet | Executes synchronously like a normal first fetch | | `background: true` program, no fresh cache | Serves the last good run immediately with `stale: true` and enqueues a durable background execution (10-minute budget); the _next_ call finalizes it if it has completed | | Background program, execution still running, no prior success | `background_pending` failure — the panel shows an explicit "running in background" error card, never a blank chart | | Execution fails (timeout, sandbox error, bad `emit()` shape) | Failure result with a structured error code; if a previous successful run exists, it is attached as `lastGoodRun` so the panel can stale-serve instead of going blank | | Program was archived (`delete-data-program`) | Explicit `archived` failure — panels show "This data program was archived", never a silent blank | Use `background: true` only for programs that routinely take longer than the foreground budget (multi-provider joins over large cohorts, deep pagination). Everything else should stay foreground — it is simpler to reason about and serves fresh data faster. ## Error codes | Code | Meaning | What to do | | ---------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------- | | `program_not_found` | No program with that id | Check the id via `list-data-programs` | | `access_denied` | Caller can't see this program | Don't leak existence; ask the owner to share it | | `archived` | Program was soft-deleted | Repoint the panel or unarchive by resaving under the same name | | `timeout` | Didn't finish within the run's budget | Reduce scope, page in fewer items, or mark `background: true` | | `emit_missing` | Script never called `emit(...)` | Add the `emit(rows, schema?)` call at the end | | `emit_shape_invalid` | `emit()` args weren't `(array-of-plain-objects, optional-schema-array)` | Fix the shape; check for a stray second `emit()` call | | `result_too_large` | A single row (or the whole result) exceeds the byte cap | Trim large string fields before emitting | | `sandbox_error` | Uncaught exception in the script | Read the thrown message; guard optional provider fields | | `run_code_unavailable` | The run-code module isn't available in this build | Not something a program author can fix | | `background_pending` | Background program has no result yet | Wait and retry, or check `lastGoodRun` if present | Every failure is a structured `{ code, message }`, never a bare string — treat `code` as the thing to branch on, `message` as the thing to show a human. ## Security notes - **Credentials are always the viewer's, never the author's.** When a shared program's panel is viewed, `providerFetch` inside it resolves auth using the _viewing_ user's own configured provider credentials — not the program author's. A teammate missing a HubSpot key sees a HubSpot auth error on that panel, not someone else's data. - **Only share programs you trust.** Because execution uses the viewer's credentials, only bind to or view a shared data program if you trust its code — it runs with your own access, exactly like opening someone else's extension. - **Data programs are org-internal only, never public.** Unlike dashboards, a data program can never be shared publicly — sharing is restricted to members of your org. This is a hard invariant, not a default. - Raw provider tokens never reach the model or the browser. The only thing that ever leaves the sandbox is the `{ rows, schema }` your `emit()` call produces — the same trust boundary as every other dashboard panel source. - The actions that execute or mutate stored code (`save-data-program`, `preview-data-program`, `run-data-program`, `delete-data-program`) are not callable from the sandboxed extensions/iframe bridge — only from the agent or the server-rendered panel path. ## Worked example: Risk Meeting (HubSpot x Pylon) This is the reference example installed by the `ensure-risk-meeting-dashboard` action (`templates/analytics/actions/ensure-risk-meeting-dashboard.ts`), which idempotently upserts both programs below plus a two-panel "Risk Meeting" dashboard. It demonstrates the three capabilities a hardcoded action can't anticipate for every customer: an arbitrary HubSpot property IN-search, a batched multi-hop association join, and a join against a second, unrelated provider. **`risk-meeting-cohort.js`** — HubSpot deals in a configurable `risk_status` cohort, resolved to their company domain, joined against Pylon account sentiment by domain: ```js // params: { riskStatuses?: string[], enterpriseOnly?: boolean } const DEFAULT_RISK_STATUSES = [ "On the Radar", "Churn Risk", "Confirmed Churn", "No Save Attempted", ]; const riskStatuses = Array.isArray(params.riskStatuses) && params.riskStatuses.length > 0 ? params.riskStatuses : DEFAULT_RISK_STATUSES; const HIGH_RISK_SENTIMENTS = ["frustrated", "high_risk_detractor"]; function chunk(items, size) { const out = []; for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size)); return out; } async function main() { // 1. Arbitrary property IN-search + open-close-date filter — not // expressible through the canned hubspot-deals filters. const dealSearch = await providerFetchAll( "hubspot", "/crm/v3/objects/deals/search", { method: "POST", body: { filterGroups: [ { filters: [ { propertyName: "risk_status", operator: "IN", values: riskStatuses, }, { propertyName: "closedate", operator: "GT", value: String(Date.now()), }, ], }, ], properties: [ "dealname", "risk_status", "risk_summary", "risk_category", "hs_next_step", "churn_notes", "total_contract_value", "customer_success_owner", "dealstage", "closedate", ], limit: 100, }, itemsPath: "results", pagination: { cursorBodyPath: "after",
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub