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.

소스 정보

저장소
BuilderIO/agent-native
최근 소스 활동
2026년 10월 7일 13:37
감지된 SKILL.md 언어
영어
스타
7,366
포크
669

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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",
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기