@growthub/cli 0.9.17 → 0.10.0

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.
Files changed (50) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/reference-options/route.js +62 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +13 -2
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolver-templates/route.js +23 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +35 -5
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +15 -1
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +2048 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataTable.jsx +1 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldEditor.jsx +1 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldManager.jsx +9 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ObjectSidebar.jsx +41 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/RecordDrawer.jsx +1 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +244 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +21 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SourceTestPanel.jsx +15 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/StatusPill.jsx +13 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ToggleField.jsx +41 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +99 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +2 -1528
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +66 -5
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/connector-template-authoring.md +8 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/data-model-reference-fields.md +15 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/mcp-chrome-tool-connectors.md +12 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/resolver-template-library.md +17 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +13 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/README.md +12 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/chrome-bridge.js +22 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/custom-http.js +23 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-commerce.js +22 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-crm.js +23 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-project-management.js +22 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-spreadsheet.js +22 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/mcp-tool.js +22 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +50 -0
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/webhook.js +22 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/collect-reference-options.js +133 -0
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/reference-resolver-registry.js +17 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolver-loader.js +6 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/README.md +8 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/local-data-model.js +11 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/source-records.js +34 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/adapters/README.md +5 -3
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +203 -0
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/index.js +1 -0
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +81 -0
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-option-schema.js +59 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-options.js +29 -0
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +527 -23
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +131 -1
  49. package/dist/index.js +3043 -1340
  50. package/package.json +1 -1
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Local workspace-config reference options (dataModel.objects[] rows).
3
+ */
4
+
5
+ import { resolveLocalReferenceOptions } from "@/lib/workspace-data-model";
6
+
7
+ function resolveLocalWorkspaceOptions(workspaceConfig, params) {
8
+ return resolveLocalReferenceOptions(workspaceConfig, params);
9
+ }
10
+
11
+ export { resolveLocalWorkspaceOptions };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Source-record sidecar rows as reference options (growthub.source-records.json).
3
+ */
4
+
5
+ import { readWorkspaceSourceRecords } from "@/lib/workspace-config";
6
+ import { normalizeReferenceOption } from "@/lib/workspace-data-model";
7
+
8
+ async function resolveSourceRecordReferenceOptions(sourceId, { query = "", limit = 25 } = {}) {
9
+ if (!sourceId || typeof sourceId !== "string") {
10
+ return { options: [], reason: "missing-source-id" };
11
+ }
12
+ const sidecar = await readWorkspaceSourceRecords(sourceId.trim());
13
+ const records = Array.isArray(sidecar?.records) ? sidecar.records : [];
14
+ const needle = String(query || "").trim().toLowerCase();
15
+ const opts = [];
16
+ records.forEach((rec, index) => {
17
+ const value = String(rec?.id ?? rec?.runId ?? `idx-${index}`).trim();
18
+ if (!value) return;
19
+ const label = String(rec?.label ?? rec?.runId ?? value).trim() || value;
20
+ if (needle && !`${value} ${label}`.toLowerCase().includes(needle)) return;
21
+ opts.push(
22
+ normalizeReferenceOption({
23
+ value,
24
+ label,
25
+ source: "source-records",
26
+ status: typeof rec?.status === "string" ? rec.status : undefined,
27
+ metadata: { ranAt: rec?.ranAt }
28
+ })
29
+ );
30
+ });
31
+ return { options: opts.filter(Boolean).slice(0, limit), reason: null };
32
+ }
33
+
34
+ export { resolveSourceRecordReferenceOptions };
@@ -2,10 +2,11 @@
2
2
 
3
3
  Drop one `.js` file per execution target here. Each file calls `registerSandboxAdapter()` once at module load.
4
4
 
5
- This is the thin agnostic extension point for the **`sandbox-environment` governed Data Model object**. Two default adapters ship eagerly with every workspace, loaded by `lib/adapters/sandboxes/index.js`:
5
+ This is the thin agnostic extension point for the **`sandbox-environment` governed Data Model object**. Three default adapters ship eagerly with every workspace, loaded by `lib/adapters/sandboxes/index.js`:
6
6
 
7
- - **`local-process`** (`default-local-process.js`) — spawns python3 / node / bash inside an isolated `/tmp/growthub-sandbox-*` workdir with timeout + captured stdio. Use this when the row is a deterministic script.
8
- - **`local-agent-host`** (`default-local-agent-host.js`) — Paperclip thin local adapter. Routes the row through whichever local agent host CLI the operator has on PATH (Claude Code, Codex, Cursor, Gemini, OpenCode, Pi, Qwen, Hermes, OpenClaw Gateway). Cross-platform — works on macOS, Windows, and Linux. Slugs mirror the canonical `AGENT_ADAPTER_TYPES` enum so a row is portable to the upstream Paperclip server adapter registry without translation.
7
+ - **`local-process`** (`../default-local-process.js`) — spawns python3 / node / bash inside an isolated `/tmp/growthub-sandbox-*` workdir with timeout + captured stdio. Use this when the row is a deterministic script.
8
+ - **`local-agent-host`** (`../default-local-agent-host.js`) — Paperclip thin local adapter. Routes the row through whichever local agent host CLI the operator has on PATH (Claude Code, Codex, Cursor, Gemini, OpenCode, Pi, Qwen, Hermes, OpenClaw Gateway). Cross-platform — works on macOS, Windows, and Linux. Slugs mirror the canonical `AGENT_ADAPTER_TYPES` enum so a row is portable to the upstream Paperclip server adapter registry without translation.
9
+ - **`local-intelligence`** (`../default-local-intelligence.js`) — OpenAI-compatible **JSON-only** chat-completions call to the operator's local stack (Ollama / LM Studio / vLLM / custom URL). The merged **Instructions + Command** field is the user task. Tool intents in the JSON response are **proposals only** — this adapter never executes them.
9
10
 
10
11
  Files added to this drop-zone are loaded by `adapter-loader.js` on the first sandbox-run route invocation. Use the drop-zone for hardened isolation primitives (firejail, gVisor, Docker, Fly Machines, e2b, modal.com) or for additional agent host targets the canonical catalog does not yet cover.
11
12
 
@@ -61,3 +62,4 @@ When the sandbox row’s **`runLocality`** is **`serverless`**, the sandbox-run
61
62
  2. The route mints **`growthub-sandbox-run-v1`** JSON (no secrets inline) and **POST**s using the merged registry/request shape (parity with outbound test routes).
62
63
  3. The handler translates its own queue (KV, Postgres, cron tick, etc.) into a normal JSON or text reply; the route maps that reply into **`stdout`**, optional **`stderr`**, and **`exitCode`** — same **`lastResponse`** / sidecar semantics as **local**.
63
64
  4. Drop-zone adapters labelled **`locality: "serverless"`** are **not** used for delegation today; outbound HTTP replaces them so operators wire **already-supported** integrations + infra without a second resolver graph.
65
+ 5. Rows using **`local-intelligence`** must keep **`runLocality: "local"`** — that adapter performs an outbound HTTP call to the operator's machine only; it cannot be combined with serverless delegation.
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Sandbox adapter — governed local OpenAI-compatible inference (JSON-only).
3
+ *
4
+ * Uses the same mental model as Growthub Local Intelligence: chat-completions
5
+ * transport, normalized JSON envelope on stdout. Tool intents are proposals
6
+ * only — this adapter performs no tool execution and never resolves env secrets
7
+ * beyond what the sandbox-run route already placed on `request.env`.
8
+ */
9
+
10
+ import { Buffer } from "node:buffer";
11
+ import { registerSandboxAdapter } from "./sandbox-adapter-registry.js";
12
+
13
+ const MAX_OUT = 256 * 1024;
14
+
15
+ function clampStream(buffer) {
16
+ if (buffer.length <= MAX_OUT) return buffer.toString("utf8");
17
+ return `${buffer.slice(0, MAX_OUT).toString("utf8")}\n…\n[truncated]`;
18
+ }
19
+
20
+ function resolveChatCompletionsUrl(mode, explicit) {
21
+ const e = String(explicit || "").trim();
22
+ if (e && /^https?:\/\//i.test(e)) {
23
+ if (e.includes("/chat/completions")) return e.replace(/\/+$/, "");
24
+ return `${e.replace(/\/+$/, "")}/chat/completions`;
25
+ }
26
+ const m = String(mode || "ollama").toLowerCase();
27
+ if (m === "custom-openai-compatible") {
28
+ if (!e) throw new Error("localEndpoint (full chat completions URL) is required for custom-openai-compatible mode");
29
+ if (e.includes("/chat/completions")) return e.replace(/\/+$/, "");
30
+ return `${e.replace(/\/+$/, "")}/chat/completions`;
31
+ }
32
+ const ollamaBase = (process.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434/v1").replace(/\/$/, "");
33
+ if (m === "lmstudio") {
34
+ const b = (process.env.LMSTUDIO_BASE_URL || "http://127.0.0.1:1234/v1").replace(/\/$/, "");
35
+ return `${b}/chat/completions`;
36
+ }
37
+ if (m === "vllm") {
38
+ const b = String(process.env.VLLM_BASE_URL || "").trim().replace(/\/$/, "");
39
+ if (!b) throw new Error("VLLM_BASE_URL is required for vllm intelligenceAdapterMode");
40
+ return `${b}/chat/completions`;
41
+ }
42
+ return `${ollamaBase}/chat/completions`;
43
+ }
44
+
45
+ function buildSystemPrompt() {
46
+ return [
47
+ "You are Growthub workspace sandbox local intelligence.",
48
+ "Reply with a single JSON object only, matching:",
49
+ "{\"text\":string optional,\"json\":object optional,\"toolIntents\":[],\"warnings\":[],\"confidence\":number}",
50
+ "toolIntents are proposals only — never claim execution or access to secrets.",
51
+ ].join("\n");
52
+ }
53
+
54
+ async function run(request) {
55
+ const started = Date.now();
56
+ const box = request.intelligenceSandbox;
57
+ if (!box || typeof box.userIntent !== "string") {
58
+ return {
59
+ ok: false,
60
+ exitCode: 1,
61
+ durationMs: Date.now() - started,
62
+ stdout: "",
63
+ stderr: "",
64
+ error: "intelligenceSandbox.userIntent is required for local-intelligence adapter",
65
+ adapterMeta: { adapter: "local-intelligence" },
66
+ };
67
+ }
68
+
69
+ let endpoint;
70
+ try {
71
+ endpoint = resolveChatCompletionsUrl(box.intelligenceAdapterMode, box.localEndpoint);
72
+ } catch (err) {
73
+ return {
74
+ ok: false,
75
+ exitCode: 1,
76
+ durationMs: Date.now() - started,
77
+ stdout: "",
78
+ stderr: "",
79
+ error: err.message || String(err),
80
+ adapterMeta: { adapter: "local-intelligence" },
81
+ };
82
+ }
83
+
84
+ const model =
85
+ String(box.localModel || "").trim()
86
+ || String(process.env.NATIVE_INTELLIGENCE_LOCAL_MODEL || process.env.OLLAMA_MODEL || "").trim()
87
+ || "gemma3:4b";
88
+
89
+ const body = {
90
+ model,
91
+ messages: [
92
+ { role: "system", content: buildSystemPrompt() },
93
+ { role: "user", content: box.userIntent },
94
+ ],
95
+ temperature: 0.3,
96
+ response_format: { type: "json_object" },
97
+ };
98
+
99
+ const controller = new AbortController();
100
+ const ms = Number(request.timeoutMs) > 0 ? Math.min(Number(request.timeoutMs), 600000) : 60000;
101
+ const timer = setTimeout(() => controller.abort(), ms);
102
+
103
+ try {
104
+ const res = await fetch(endpoint, {
105
+ method: "POST",
106
+ headers: { "content-type": "application/json", accept: "application/json" },
107
+ body: JSON.stringify(body),
108
+ signal: controller.signal,
109
+ });
110
+ const buf = Buffer.from(await res.arrayBuffer());
111
+ const text = clampStream(buf);
112
+ const durationMs = Date.now() - started;
113
+ if (!res.ok) {
114
+ return {
115
+ ok: false,
116
+ exitCode: 1,
117
+ durationMs,
118
+ stdout: text,
119
+ stderr: "",
120
+ error: `local model HTTP ${res.status}`,
121
+ adapterMeta: { adapter: "local-intelligence", endpoint, model },
122
+ };
123
+ }
124
+
125
+ let parsed;
126
+ let outer;
127
+ try {
128
+ outer = JSON.parse(text);
129
+ } catch {
130
+ outer = null;
131
+ }
132
+ if (outer && Array.isArray(outer.choices) && outer.choices[0]?.message?.content) {
133
+ const inner = String(outer.choices[0].message.content || "").trim();
134
+ try {
135
+ parsed = JSON.parse(inner);
136
+ } catch {
137
+ parsed = { text: inner, warnings: ["model completion was not valid JSON"], toolIntents: [], confidence: 0 };
138
+ }
139
+ } else if (outer && typeof outer === "object") {
140
+ parsed = outer;
141
+ } else {
142
+ parsed = { text, warnings: ["invalid JSON from model"], toolIntents: [], confidence: 0 };
143
+ }
144
+
145
+ const envelope = {
146
+ version: "growthub-local-model-sandbox-v1",
147
+ taskId: request.runId,
148
+ businessObjectType: "sandbox-environment",
149
+ adapter: {
150
+ kind: "local-intelligence",
151
+ mode: box.intelligenceAdapterMode || "ollama",
152
+ modelId: model,
153
+ endpoint,
154
+ },
155
+ result: {
156
+ text: typeof parsed.text === "string" ? parsed.text : undefined,
157
+ json: parsed.json && typeof parsed.json === "object" ? parsed.json : undefined,
158
+ toolIntents: Array.isArray(parsed.toolIntents) ? parsed.toolIntents : [],
159
+ warnings: Array.isArray(parsed.warnings) ? parsed.warnings : [],
160
+ confidence: typeof parsed.confidence === "number" ? parsed.confidence : 0,
161
+ },
162
+ rawText: text,
163
+ latencyMs: durationMs,
164
+ createdAt: new Date().toISOString(),
165
+ };
166
+
167
+ return {
168
+ ok: true,
169
+ exitCode: 0,
170
+ durationMs,
171
+ stdout: JSON.stringify(envelope, null, 2),
172
+ stderr: "",
173
+ adapterMeta: {
174
+ adapter: "local-intelligence",
175
+ endpoint,
176
+ model,
177
+ locality: "local",
178
+ },
179
+ };
180
+ } catch (err) {
181
+ return {
182
+ ok: false,
183
+ exitCode: 1,
184
+ durationMs: Date.now() - started,
185
+ stdout: "",
186
+ stderr: clampStream(Buffer.from(String(err.message || err), "utf8")),
187
+ error: err.name === "AbortError" ? `timed out after ${ms}ms` : err.message || "fetch failed",
188
+ adapterMeta: { adapter: "local-intelligence", endpoint, model },
189
+ };
190
+ } finally {
191
+ clearTimeout(timer);
192
+ }
193
+ }
194
+
195
+ registerSandboxAdapter({
196
+ id: "local-intelligence",
197
+ label: "Local intelligence (OpenAI-compatible)",
198
+ description:
199
+ "Calls your local Ollama / LM Studio / vLLM Chat Completions endpoint with JSON-only output. Tool intents are proposals — not executed here.",
200
+ locality: "local",
201
+ supportedRuntimes: [],
202
+ run,
203
+ });
@@ -9,6 +9,7 @@
9
9
 
10
10
  import "./default-local-process.js";
11
11
  import "./default-local-agent-host.js";
12
+ import "./default-local-intelligence.js";
12
13
  import { loadAllSandboxAdapters } from "./adapter-loader.js";
13
14
 
14
15
  let baseLoaded = true; // default-local-process registered via static import
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Client-safe field contracts for governed Data Model rows.
3
+ * Maps objectType + field → editor kind and static hints (no secrets).
4
+ */
5
+
6
+ const TRUSTED_STATUSES = ["connected", "approved", "ok", "success"];
7
+
8
+ const SANDBOX_ENVIRONMENT_FIELDS = {
9
+ runLocality: {
10
+ editor: "segmented-toggle",
11
+ options: ["local", "serverless"]
12
+ },
13
+ networkAllow: { editor: "boolean-toggle" },
14
+ lifecycleStatus: {
15
+ editor: "select",
16
+ options: ["draft", "live"]
17
+ },
18
+ runtime: {
19
+ editor: "select",
20
+ options: ["python", "node", "bash"]
21
+ },
22
+ schedulerRegistryId: {
23
+ editor: "reference",
24
+ targetObjectType: "api-registry",
25
+ valueField: "integrationId",
26
+ statusAllowlist: TRUSTED_STATUSES
27
+ },
28
+ envRefs: { editor: "env-ref-multiselect" },
29
+ lastResponse: { editor: "json-preview", readonly: true },
30
+ lastRunId: { editor: "readonly-text" },
31
+ lastSourceId: { editor: "readonly-text" },
32
+ resolverTemplateId: { editor: "readonly-text" },
33
+ connectorKind: { editor: "readonly-text" },
34
+ executionLane: { editor: "readonly-text" }
35
+ };
36
+
37
+ const API_REGISTRY_FIELDS = {
38
+ method: {
39
+ editor: "select",
40
+ options: ["GET", "POST", "PUT", "PATCH", "DELETE"]
41
+ },
42
+ status: { editor: "status-pill" },
43
+ lastResponse: { editor: "json-preview", readonly: true },
44
+ connectorKind: { editor: "text" },
45
+ resolverTemplateId: { editor: "text" },
46
+ schemaVersion: { editor: "text" },
47
+ capabilities: { editor: "text" },
48
+ executionLane: { editor: "text" }
49
+ };
50
+
51
+ const DATA_SOURCE_FIELDS = {
52
+ registryId: {
53
+ editor: "reference",
54
+ targetObjectType: "api-registry",
55
+ valueField: "integrationId",
56
+ statusAllowlist: null
57
+ },
58
+ status: { editor: "status-pill" },
59
+ lastResponse: { editor: "json-preview", readonly: true },
60
+ sourceStorage: {
61
+ editor: "select",
62
+ options: ["", "workspace-source-records"]
63
+ },
64
+ entityType: { editor: "text" },
65
+ sourceId: { editor: "text" },
66
+ resolverTemplateId: { editor: "text" }
67
+ };
68
+
69
+ const BY_OBJECT_TYPE = {
70
+ "sandbox-environment": SANDBOX_ENVIRONMENT_FIELDS,
71
+ "api-registry": API_REGISTRY_FIELDS,
72
+ "data-source": DATA_SOURCE_FIELDS
73
+ };
74
+
75
+ function getFieldContract(objectType, fieldName) {
76
+ const table = BY_OBJECT_TYPE[objectType];
77
+ if (!table || !fieldName) return null;
78
+ return table[fieldName] || null;
79
+ }
80
+
81
+ export { TRUSTED_STATUSES, getFieldContract, BY_OBJECT_TYPE };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Reference option wire shape for POST /api/workspace/reference-options.
3
+ * Kept as plain validation (no runtime authority).
4
+ */
5
+
6
+ function isPlainObject(value) {
7
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8
+ }
9
+
10
+ /**
11
+ * @typedef {Object} ReferenceOption
12
+ * @property {string} value
13
+ * @property {string} label
14
+ * @property {string} [secondaryLabel]
15
+ * @property {"workspace-config"|"source-records"|"resolver"} source
16
+ * @property {string} [objectType]
17
+ * @property {string} [provider]
18
+ * @property {string} [status]
19
+ * @property {Record<string, unknown>} [metadata]
20
+ */
21
+
22
+ function validateReferenceOptionsRequest(body) {
23
+ const errors = [];
24
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
25
+ errors.push("body must be a plain object");
26
+ return { ok: false, errors, value: null };
27
+ }
28
+ const objectId = typeof body.objectId === "string" ? body.objectId.trim() : "";
29
+ const field = typeof body.field === "string" ? body.field.trim() : "";
30
+ if (!objectId) errors.push("objectId must be a non-empty string");
31
+ if (!field) errors.push("field must be a non-empty string");
32
+ const query = typeof body.query === "string" ? body.query : "";
33
+ const cursor = body.cursor === null || body.cursor === undefined || body.cursor === ""
34
+ ? null
35
+ : String(body.cursor);
36
+ let limit = 25;
37
+ if (body.limit !== undefined) {
38
+ const limitRaw = Number(body.limit);
39
+ if (!Number.isFinite(limitRaw) || limitRaw < 1) {
40
+ errors.push("limit must be a positive number");
41
+ } else {
42
+ limit = Math.min(100, Math.max(1, limitRaw));
43
+ }
44
+ }
45
+ const context = isPlainObject(body.context) ? body.context : {};
46
+ if (body.context !== undefined && !isPlainObject(body.context)) {
47
+ errors.push("context must be a plain object when present");
48
+ }
49
+ if (!errors.length) {
50
+ return {
51
+ ok: true,
52
+ errors: [],
53
+ value: { objectId, field, query, cursor, limit, context }
54
+ };
55
+ }
56
+ return { ok: false, errors, value: null };
57
+ }
58
+
59
+ export { validateReferenceOptionsRequest };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Client helpers for reference option loading (browser-safe).
3
+ */
4
+
5
+ const REFERENCE_OPTION_SOURCES = ["workspace-config", "source-records", "resolver"];
6
+
7
+ async function fetchReferenceOptions({
8
+ objectId,
9
+ field,
10
+ query = "",
11
+ cursor = null,
12
+ limit = 25,
13
+ context = {}
14
+ } = {}) {
15
+ const res = await fetch("/api/workspace/reference-options", {
16
+ method: "POST",
17
+ headers: { "content-type": "application/json" },
18
+ body: JSON.stringify({ objectId, field, query, cursor, limit, context })
19
+ });
20
+ const payload = await res.json();
21
+ if (!res.ok) {
22
+ const err = new Error(payload.error || "reference-options failed");
23
+ err.payload = payload;
24
+ throw err;
25
+ }
26
+ return payload;
27
+ }
28
+
29
+ export { REFERENCE_OPTION_SOURCES, fetchReferenceOptions };