@growthub/cli 0.14.9 → 0.14.11

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 (61) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/callback/route.js +35 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/failure/route.js +35 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/[providerId]/schedule/route.js +423 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/connect/route.js +78 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/credentials/route.js +276 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/[productId]/resources/route.js +173 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/products/sync/route.js +347 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/providers/[providerId]/sync/route.js +293 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/connect/route.js +7 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/provider/sync/route.js +7 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/add-ons/upstash/sync/route.js +197 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +1 -1
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/patch/preflight/route.js +38 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +3 -20
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-api-record/route.js +3 -20
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +407 -290
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflows/[providerId]/route.js +209 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceAddOnsMarketplace.jsx +806 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +141 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +15 -3
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +42 -5
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +5 -1
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +86 -20
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ScheduleCockpit.jsx +363 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +8 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +322 -1
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +2 -2
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/add-ons-client.jsx +197 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/add-ons/page.jsx +23 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/settings-shell.jsx +1 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +734 -61
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +15 -10
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +2 -7
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +29 -19
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +8 -4
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/schedule-cockpit-console.js +287 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/scheduler-orchestration.js +449 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-secrets.js +77 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-readiness.js +583 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-callback.js +63 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-on-scheduler.js +519 -0
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-add-ons.js +957 -0
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-app-readiness.js +212 -0
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +607 -63
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-contract-compliance.js +168 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +21 -0
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-operator-auth.js +32 -0
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-impact.js +133 -0
  49. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-provenance-lineage.js +214 -0
  50. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-stale-surfaces.js +217 -0
  51. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-workflow-impact.js +170 -0
  52. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/provider.png +0 -0
  53. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/qstash.png +0 -0
  54. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/redis.png +0 -0
  55. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/search.png +0 -0
  56. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/public/integrations/upstash/vector.png +0 -0
  57. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/scripts/scheduler-ingress-smoke.mjs +26 -0
  58. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +6 -0
  59. package/assets/worker-kits/growthub-custom-workspace-starter-v1/skills/governed-workspace-mutation/SKILL.md +3 -1
  60. package/dist/index.js +3024 -4191
  61. package/package.json +1 -1
@@ -0,0 +1,141 @@
1
+ "use client";
2
+
3
+ import { Check, ExternalLink, Play, Terminal, X } from "lucide-react";
4
+ import { getApiRegistrySandboxToolState } from "@/lib/orchestration-graph";
5
+
6
+ export function ApiRegistryActionCard({
7
+ registryRow,
8
+ workspaceConfig,
9
+ disabled,
10
+ onCreateSandboxTool,
11
+ onOpenSandboxTool,
12
+ onRunSandboxTool,
13
+ onTestConnection,
14
+ testing,
15
+ sandboxRunning
16
+ }) {
17
+ const state = getApiRegistrySandboxToolState(registryRow, workspaceConfig);
18
+
19
+ if (state.kind === "incomplete") {
20
+ const checklist = state.checklist || [];
21
+ return (
22
+ <section className="dm-api-action-card dm-api-action-card-muted" aria-label="Complete API setup">
23
+ <div className="dm-api-action-card-body">
24
+ <p className="dm-api-action-card-eyebrow">API Registry</p>
25
+ <h3>Complete API setup</h3>
26
+ <p>
27
+ This API needs a registry ID, base URL, endpoint, method, and auth reference before it can become a sandbox tool.
28
+ </p>
29
+ <ul className="dm-api-action-checklist">
30
+ {checklist.map((item) => (
31
+ <li key={item.field} className={item.ok ? "is-done" : "is-pending"}>
32
+ {item.ok ? <Check size={14} aria-hidden="true" /> : <X size={14} aria-hidden="true" />}
33
+ <span>{item.field}</span>
34
+ </li>
35
+ ))}
36
+ </ul>
37
+ </div>
38
+ </section>
39
+ );
40
+ }
41
+
42
+ if (state.kind === "untested") {
43
+ return (
44
+ <section className="dm-api-action-card dm-api-action-card-muted" aria-label="Test API first">
45
+ <div className="dm-api-action-card-body">
46
+ <p className="dm-api-action-card-eyebrow">Sandbox tool</p>
47
+ <h3>Test this API first</h3>
48
+ <p>{state.message}</p>
49
+ </div>
50
+ <button
51
+ type="button"
52
+ className="dm-btn-primary-sm dm-api-action-card-cta"
53
+ disabled={disabled || testing}
54
+ onClick={onTestConnection}
55
+ >
56
+ {testing ? "Testing…" : "Test connection"}
57
+ </button>
58
+ </section>
59
+ );
60
+ }
61
+
62
+ if (state.kind === "failed") {
63
+ return (
64
+ <section className="dm-api-action-card dm-api-action-card-muted" aria-label="API test failed">
65
+ <div className="dm-api-action-card-body">
66
+ <p className="dm-api-action-card-eyebrow">Connection</p>
67
+ <h3>API test failed</h3>
68
+ <p>{state.message}</p>
69
+ </div>
70
+ <button
71
+ type="button"
72
+ className="dm-btn-outline dm-api-action-card-cta"
73
+ disabled={disabled || testing}
74
+ onClick={onTestConnection}
75
+ >
76
+ {testing ? "Testing…" : "Retest"}
77
+ </button>
78
+ </section>
79
+ );
80
+ }
81
+
82
+ if (state.kind === "existing") {
83
+ const toolName = String(state.row?.Name || "").trim();
84
+ return (
85
+ <section className="dm-api-action-card" aria-label="Sandbox tool ready">
86
+ <div className="dm-api-action-card-icon" aria-hidden="true">
87
+ <Terminal size={18} />
88
+ </div>
89
+ <div className="dm-api-action-card-body">
90
+ <p className="dm-api-action-card-eyebrow">Sandbox tool</p>
91
+ <h3>Sandbox tool ready</h3>
92
+ <p>{toolName} — governed row linked to this API Registry entry.</p>
93
+ </div>
94
+ <div className="dm-api-action-card-actions">
95
+ <button
96
+ type="button"
97
+ className="dm-btn-outline dm-api-action-card-cta"
98
+ disabled={disabled || !toolName}
99
+ onClick={() => onOpenSandboxTool?.({ name: toolName })}
100
+ >
101
+ <ExternalLink size={14} aria-hidden="true" />
102
+ Open sandbox tool
103
+ </button>
104
+ <button
105
+ type="button"
106
+ className="dm-btn-primary-sm dm-api-action-card-cta"
107
+ disabled={disabled || sandboxRunning || !toolName}
108
+ onClick={() => onRunSandboxTool?.({ name: toolName })}
109
+ >
110
+ <Play size={14} aria-hidden="true" />
111
+ {sandboxRunning ? "Running…" : "Run sandbox"}
112
+ </button>
113
+ </div>
114
+ </section>
115
+ );
116
+ }
117
+
118
+ return (
119
+ <section className="dm-api-action-card" aria-label="Create sandbox tool">
120
+ <div className="dm-api-action-card-icon" aria-hidden="true">
121
+ <Terminal size={18} />
122
+ </div>
123
+ <div className="dm-api-action-card-body">
124
+ <p className="dm-api-action-card-eyebrow">API connected</p>
125
+ <h3>Create sandbox tool</h3>
126
+ <p>
127
+ This API is connected. Turn it into a sandbox tool that agents can run safely from this workspace.
128
+ </p>
129
+ <p className="dm-api-action-card-note">No secrets are stored. Nothing runs until you test the sandbox.</p>
130
+ </div>
131
+ <button
132
+ type="button"
133
+ className="dm-btn-primary-sm dm-api-action-card-cta"
134
+ disabled={disabled}
135
+ onClick={onCreateSandboxTool}
136
+ >
137
+ Create sandbox tool
138
+ </button>
139
+ </section>
140
+ );
141
+ }
@@ -120,7 +120,7 @@ function CeoReportCard({ report, onOpen, emphasis }) {
120
120
  // cap is purely by count and disclosed.
121
121
  const CEO_FLEET_VISIBLE_CAP = 50;
122
122
 
123
- function CeoFleetView({ model, onOpenArtifact }) {
123
+ function CeoFleetView({ model, onOpenArtifact, onOpenSchedule }) {
124
124
  const { fleet, attention, reports, governance } = model;
125
125
  // Filter by stable reportId, not name — duplicate Names must never drop or
126
126
  // merge a record from the fleet.
@@ -138,6 +138,18 @@ function CeoFleetView({ model, onOpenArtifact }) {
138
138
  {`${governance.blockedAttempts} blocked attempt${governance.blockedAttempts === 1 ? "" : "s"}`}
139
139
  </span>
140
140
  )}
141
+ {typeof onOpenSchedule === "function" && (
142
+ <button
143
+ type="button"
144
+ className="dm-btn-ghost dm-swarm-card-action"
145
+ onClick={() => onOpenSchedule(null)}
146
+ title="Open the schedule cockpit (/schedule)"
147
+ aria-label="Open schedule cockpit"
148
+ >
149
+ Schedule
150
+ <ArrowUpRight size={12} aria-hidden="true" />
151
+ </button>
152
+ )}
141
153
  </div>
142
154
 
143
155
  {reports.length === 0 && (
@@ -335,7 +347,7 @@ function CeoBootstrapView({ model, onAction, actionBusy, error }) {
335
347
  // Container — derives the mode and wires actions to governed surfaces
336
348
  // ---------------------------------------------------------------------------
337
349
 
338
- export function CeoCockpit({ workspaceConfig, onOpenArtifact, onConfigRefresh, onSeedSwarm, onOpenSetup }) {
350
+ export function CeoCockpit({ workspaceConfig, onOpenArtifact, onConfigRefresh, onSeedSwarm, onOpenSetup, onOpenSchedule }) {
339
351
  // Optional governance rollup — read-only, graceful fallback to config-only.
340
352
  const [receipts, setReceipts] = useState([]);
341
353
  const [activeOperationalTab, setActiveOperationalTab] = useState("history");
@@ -513,7 +525,7 @@ export function CeoCockpit({ workspaceConfig, onOpenArtifact, onConfigRefresh, o
513
525
  </button>
514
526
  </div>
515
527
  {activeOperationalTab === "history" ? (
516
- <CeoFleetView model={fleetModel} onOpenArtifact={handleOpenArtifact} />
528
+ <CeoFleetView model={fleetModel} onOpenArtifact={handleOpenArtifact} onOpenSchedule={onOpenSchedule} />
517
529
  ) : (
518
530
  <CeoAgentTeamsSection
519
531
  teams={teamsModel}
@@ -49,6 +49,7 @@ import {
49
49
  } from "../../components/WorkspaceHelperSetupModal.jsx";
50
50
  import { SwarmRunCockpit, SwarmAgentTranscript } from "./SwarmRunCockpit.jsx";
51
51
  import { CeoCockpit } from "./CeoCockpit.jsx";
52
+ import { ScheduleCockpit } from "./ScheduleCockpit.jsx";
52
53
  import { SidecarExpandView } from "./SidecarExpandView.jsx";
53
54
  import { parseSlashInput } from "./helper-commands.js";
54
55
  import {
@@ -380,6 +381,9 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
380
381
  const [activeView, setActiveView] = useState("chat");
381
382
  // Focused workflow row when opened from an apply receipt's Open button.
382
383
  const [swarmFocus, setSwarmFocus] = useState(null);
384
+ // Optional focus handed into the schedule cockpit (e.g. CEO → /schedule for a
385
+ // selected workflow record). Read-only filter/focus hint, never a mutation.
386
+ const [scheduleFocus, setScheduleFocus] = useState(null);
383
387
  // Expanded transcript agent (tool-output view) + full-width takeover flag.
384
388
  const [expandedAgent, setExpandedAgent] = useState(null);
385
389
  const [expandActive, setExpandActive] = useState(false);
@@ -904,6 +908,7 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
904
908
  if (cmd.view) {
905
909
  setPrompt("");
906
910
  setSwarmFocus(null);
911
+ if (cmd.view === "schedule") setScheduleFocus(null);
907
912
  setActiveView(cmd.view);
908
913
  return;
909
914
  }
@@ -980,6 +985,9 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
980
985
  // oversight surface). Kept separate from inSwarmView so the swarm cockpit's
981
986
  // run machinery and header affordances are untouched.
982
987
  const inCeoView = activeView === "ceo";
988
+ // Schedule cockpit shares the same sidecar shell (read-only operations surface
989
+ // over the existing schedule routes). Same precedent as the CEO view.
990
+ const inScheduleView = activeView === "schedule";
983
991
  const canOpenSwarmWorkflow = Boolean(
984
992
  inSwarmView
985
993
  && activeTab === "assistant"
@@ -1014,7 +1022,7 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1014
1022
  {/* Header — title left; gear toggles Assistant ↔ Setup, then close. */}
1015
1023
  <div className="dm-sidecar-header">
1016
1024
  <div className="dm-sidecar-header-left">
1017
- {(inSwarmView || inCeoView) && (
1025
+ {(inSwarmView || inCeoView || inScheduleView) && (
1018
1026
  <button
1019
1027
  type="button"
1020
1028
  className="dm-sidecar-icon-btn"
@@ -1035,9 +1043,11 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1035
1043
  ? "Background tasks"
1036
1044
  : inCeoView
1037
1045
  ? "CEO Cockpit"
1038
- : threadActive
1039
- ? deriveThreadDisplayTitle(initialThread, "Workspace Helper")
1040
- : "Workspace Helper"}
1046
+ : inScheduleView
1047
+ ? "Schedule Cockpit"
1048
+ : threadActive
1049
+ ? deriveThreadDisplayTitle(initialThread, "Workspace Helper")
1050
+ : "Workspace Helper"}
1041
1051
  </span>
1042
1052
  </div>
1043
1053
  <div className="dm-sidecar-header-right">
@@ -1113,6 +1123,33 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1113
1123
  );
1114
1124
  }}
1115
1125
  onOpenSetup={() => setActiveTab("setup")}
1126
+ onOpenSchedule={(focus) => { setScheduleFocus(focus || null); setActiveView("schedule"); }}
1127
+ />
1128
+ </div>
1129
+ )}
1130
+
1131
+ {/* Schedule cockpit view — the daily operations surface for scheduled
1132
+ workflows. Same sidecar shell, read-only with respect to mutation:
1133
+ every action hands off to an EXISTING governed schedule route
1134
+ (install/pause/resume/readiness/uninstall) or the Add-ons marketplace
1135
+ setup path. No new route, no client-side PATCH, no second runtime. */}
1136
+ {activeTab === "assistant" && inScheduleView && (
1137
+ <div className="dm-sidecar-body dm-swarm-body" data-schedule-view={activeView}>
1138
+ <ScheduleCockpit
1139
+ workspaceConfig={workspaceConfig}
1140
+ focus={scheduleFocus}
1141
+ onConfigRefresh={refreshWorkspaceConfig}
1142
+ onOpenArtifact={(artifact) => { if (artifact) handleOpenArtifact(artifact); }}
1143
+ onSeedSwarm={(seedPrompt) => {
1144
+ setActiveView("chat");
1145
+ onPickIntent("swarm");
1146
+ setPrompt(
1147
+ typeof seedPrompt === "string" && seedPrompt.trim()
1148
+ ? `${seedPrompt.trim()} `
1149
+ : "Propose a governed agent swarm: "
1150
+ );
1151
+ }}
1152
+ onOpenSetup={() => setActiveTab("setup")}
1116
1153
  />
1117
1154
  </div>
1118
1155
  )}
@@ -1121,7 +1158,7 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1121
1158
  conversation/result area on top (flex:1), bottom-anchored composer
1122
1159
  holds chip stack (empty state) → mode row (active thread) →
1123
1160
  textarea with attach + mode + send-arrow action row. */}
1124
- {activeTab === "assistant" && !inSwarmView && !inCeoView && (
1161
+ {activeTab === "assistant" && !inSwarmView && !inCeoView && !inScheduleView && (
1125
1162
  <div className="dm-sidecar-body dm-helper-body">
1126
1163
  <div className="dm-helper-conversation" ref={conversationRef}>
1127
1164
  {/* Conversation — ChatGPT-grade multi-turn. User bubble
@@ -130,6 +130,7 @@ export function OrchestrationGraphCanvas({
130
130
  nodeStatuses,
131
131
  onNodeStatusClick,
132
132
  statusLabel = "Draft",
133
+ readinessFlags,
133
134
  }) {
134
135
  const parsed = useMemo(() => parseOrchestrationGraph(graph) || graph, [graph]);
135
136
  const nodes = useMemo(() => orderedGraphNodes(parsed), [parsed]);
@@ -242,6 +243,9 @@ export function OrchestrationGraphCanvas({
242
243
  const nodeStatusChip = nodeStatuses
243
244
  ? NODE_STATUS_CHIP[String(nodeStatuses[id] || "").toLowerCase()] || null
244
245
  : null;
246
+ // Serverless-readiness flag — ultrathin orange border ONLY. The color
247
+ // is the guidance; no badge, no text. Driven by the readiness scan.
248
+ const readinessFlag = readinessFlags ? readinessFlags[id] || null : null;
245
249
 
246
250
  return (
247
251
  <div key={id || index} className="dm-orchestration-canvas__step">
@@ -292,7 +296,7 @@ export function OrchestrationGraphCanvas({
292
296
  <div
293
297
  role="button"
294
298
  tabIndex={0}
295
- className={`dm-orchestration-node${isSelected ? " dm-orchestration-node--selected" : ""}`}
299
+ className={`dm-orchestration-node${isSelected ? " dm-orchestration-node--selected" : ""}${readinessFlag ? ` dm-orchestration-node--readiness is-${readinessFlag.severity || "warning"}` : ""}`}
296
300
  title={hoverHint(node)}
297
301
  onClick={() => {
298
302
  setInternalSelected(id);
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useMemo, useState } from "react";
4
- import { Check } from "lucide-react";
4
+ import { CalendarClock, Check, ChevronDown, Database, FileInput, ListTree } from "lucide-react";
5
5
  import {
6
6
  detectFieldIdsFromLastResponse,
7
7
  FILTER_CONJUNCTIONS,
@@ -316,7 +316,7 @@ function FieldMapRows({ fieldMap, onChange, disabled, fieldOptions = [] }) {
316
316
  );
317
317
  }
318
318
 
319
- function PayloadKeyRows({ payload, onChange, disabled }) {
319
+ function PayloadKeyRows({ payload, onChange, disabled, flagClassName = "" }) {
320
320
  const entries = Object.entries(payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {});
321
321
 
322
322
  function setEntries(nextEntries) {
@@ -324,7 +324,7 @@ function PayloadKeyRows({ payload, onChange, disabled }) {
324
324
  }
325
325
 
326
326
  return (
327
- <div className="dm-orchestration-config__payload">
327
+ <div className={`dm-orchestration-config__payload${flagClassName}`}>
328
328
  <span className="dm-orchestration-config__field-label">Test payload fields</span>
329
329
  {entries.map(([key, value], index) => (
330
330
  <div key={index} className="dm-orchestration-config__payload-row">
@@ -370,7 +370,8 @@ function PayloadKeyRows({ payload, onChange, disabled }) {
370
370
  );
371
371
  }
372
372
 
373
- function VersionDeltaControls({ node, config, sandboxRow, onChange, disabled }) {
373
+ function VersionDeltaControls({ node, config, sandboxRow, onChange, disabled, flaggedTags, flagSeverity = "warning" }) {
374
+ const flagged = flaggedTags instanceof Set ? flaggedTags : new Set(Array.isArray(flaggedTags) ? flaggedTags : []);
374
375
  const explicitTags = normalizeTags(config?.deltaTags);
375
376
  const inferredTags = inferDeltaTagsForNode(node, config);
376
377
  const selectedTags = explicitTags.length > 0 ? explicitTags : inferredTags;
@@ -409,7 +410,7 @@ function VersionDeltaControls({ node, config, sandboxRow, onChange, disabled })
409
410
  {selectedTags.length > 0 && (
410
411
  <div className="dm-version-delta__tag-fields">
411
412
  {selectedTags.map((tag) => (
412
- <label key={tag} className="dm-orchestration-config__field">
413
+ <label key={tag} className={`dm-orchestration-config__field${flagged.has(tag) ? ` dm-field--readiness is-${flagSeverity}` : ""}`}>
413
414
  <span>{tag} value</span>
414
415
  <input
415
416
  value={deltaValues[tag] ?? getDeltaTagDefaultValue(tag, node, config, sandboxRow)}
@@ -430,7 +431,9 @@ function LocalAgentHostControls({
430
431
  objectId,
431
432
  rowName,
432
433
  disabled,
433
- onSandboxRowPatch
434
+ onSandboxRowPatch,
435
+ adapterFlagClass = "",
436
+ hostFlagClass = ""
434
437
  }) {
435
438
  const row = sandboxRow && typeof sandboxRow === "object" ? sandboxRow : {};
436
439
  const adapter = String(row.adapter || "local-process").trim() || "local-process";
@@ -453,7 +456,7 @@ function LocalAgentHostControls({
453
456
  <p className="dm-orchestration-config__hint">
454
457
  Same runtime fields as the Data Model sandbox sidecar. Local agent host uses the Paperclip thin adapter on this machine.
455
458
  </p>
456
- <label className="dm-orchestration-config__field">
459
+ <label className={`dm-orchestration-config__field${adapterFlagClass}`}>
457
460
  <span>Execution adapter</span>
458
461
  <select
459
462
  value={adapter}
@@ -473,7 +476,7 @@ function LocalAgentHostControls({
473
476
  </select>
474
477
  </label>
475
478
  {adapter === "local-agent-host" && (
476
- <label className="dm-orchestration-config__field">
479
+ <label className={`dm-orchestration-config__field${hostFlagClass}`}>
477
480
  <span>Agent host (Paperclip)</span>
478
481
  <select
479
482
  value={agentHost}
@@ -568,10 +571,15 @@ export function OrchestrationNodeConfigPanel({
568
571
  objectId,
569
572
  rowName,
570
573
  onSandboxRowPatch,
574
+ inputScheduleControls,
575
+ serverlessScheduleOptionAvailable = false,
576
+ serverlessScheduleAvailable = false,
577
+ readinessFlag,
571
578
  activeTab: controlledTab,
572
579
  onTabChange
573
580
  }) {
574
581
  const [internalTab, setInternalTab] = useState("node");
582
+ const [inputModeOpen, setInputModeOpen] = useState(false);
575
583
  const rawActiveTab = controlledTab ?? internalTab;
576
584
 
577
585
  function setActiveTab(tab) {
@@ -594,6 +602,14 @@ export function OrchestrationNodeConfigPanel({
594
602
 
595
603
  const config = node.config || {};
596
604
  const type = String(node.type || "");
605
+ const schedulerAvailable = Boolean(serverlessScheduleOptionAvailable || serverlessScheduleAvailable);
606
+ const inputModeOptions = [
607
+ { value: "manual", label: "Manual", Icon: FileInput },
608
+ { value: "record", label: "Record", Icon: Database },
609
+ { value: "source-record", label: "Source Record", Icon: ListTree },
610
+ ...(schedulerAvailable ? [{ value: "serverless-schedule", label: "Serverless Schedule", Icon: CalendarClock }] : []),
611
+ ];
612
+ const selectedInputMode = inputModeOptions.find((option) => option.value === (config.inputMode || "manual")) || inputModeOptions[0];
597
613
  const meta = config.requestHeadersMetadata || {};
598
614
  const workspaceObjects = (Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [])
599
615
  .filter((object) => object?.id && object?.objectType !== "sandbox-environment" && object?.objectType !== "api-registry");
@@ -605,6 +621,21 @@ export function OrchestrationNodeConfigPanel({
605
621
  onConfigChange?.({ ...config, ...patch });
606
622
  }
607
623
 
624
+ // Serverless-readiness atomic field flag. The scan maps each alert to the exact
625
+ // config / sandbox-row field(s) that must change; we fill ONLY those fields
626
+ // light-orange (the color is the guidance — no extra copy). `row:`-prefixed
627
+ // hints (e.g. the execution adapter) match without the prefix here too.
628
+ const readinessSeverity = readinessFlag?.severity === "blocked" ? "blocked" : "warning";
629
+ const readinessFieldSet = new Set([
630
+ ...(Array.isArray(readinessFlag?.configFields) ? readinessFlag.configFields : []),
631
+ ...(Array.isArray(readinessFlag?.rowFields) ? readinessFlag.rowFields : []),
632
+ ...(Array.isArray(readinessFlag?.fields) ? readinessFlag.fields.map((f) => String(f).replace(/^row:/, "")) : []),
633
+ ]);
634
+ const readinessTagSet = new Set(Array.isArray(readinessFlag?.deltaTags) ? readinessFlag.deltaTags : []);
635
+ function flagFieldClass(...keys) {
636
+ return keys.some((k) => readinessFieldSet.has(k)) ? ` dm-field--readiness is-${readinessSeverity}` : "";
637
+ }
638
+
608
639
  const tabsForType = type === "api-registry-call" || type === "core-action"
609
640
  ? ["configuration", "test", "advanced"]
610
641
  : type === "input" || type === "transform-filter" || type === "data-action" || type === "data-trigger" || type === "ai-agent" || type === "flow-control" || type === "human-input"
@@ -658,22 +689,55 @@ export function OrchestrationNodeConfigPanel({
658
689
 
659
690
  {activeTab === "configuration" && type === "input" && (
660
691
  <div className="dm-orchestration-config__pane">
661
- <label className="dm-orchestration-config__field">
692
+ <div className="dm-orchestration-config__field">
662
693
  <span>Input mode</span>
663
- <select value={config.inputMode || "manual"} disabled={disabled} onChange={(e) => patchConfig({ inputMode: e.target.value })}>
664
- <option value="manual">manual</option>
665
- <option value="record">record</option>
666
- <option value="source-record">source-record</option>
667
- </select>
668
- </label>
694
+ <div className={`dm-select dm-input-mode-select${inputModeOpen ? " open" : ""}${disabled ? " disabled" : ""}`}>
695
+ <button
696
+ type="button"
697
+ className="dm-select-trigger"
698
+ disabled={disabled}
699
+ aria-haspopup="listbox"
700
+ aria-expanded={inputModeOpen}
701
+ onClick={() => setInputModeOpen((open) => !open)}
702
+ >
703
+ <span>{selectedInputMode.label}</span>
704
+ <ChevronDown size={15} aria-hidden="true" />
705
+ </button>
706
+ {inputModeOpen ? (
707
+ <div className="dm-select-popover">
708
+ <div className="dm-select-list" role="listbox" aria-label="Input mode">
709
+ {inputModeOptions.map((option) => (
710
+ <button
711
+ key={option.value}
712
+ type="button"
713
+ role="option"
714
+ aria-selected={option.value === selectedInputMode.value}
715
+ className={`dm-select-option${option.value === selectedInputMode.value ? " selected" : ""}`}
716
+ onMouseDown={(event) => {
717
+ event.preventDefault();
718
+ patchConfig({ inputMode: option.value });
719
+ setInputModeOpen(false);
720
+ }}
721
+ >
722
+ <option.Icon size={14} aria-hidden="true" />
723
+ <span>{option.label}</span>
724
+ </button>
725
+ ))}
726
+ </div>
727
+ </div>
728
+ ) : null}
729
+ </div>
730
+ </div>
669
731
  <PayloadKeyRows
670
732
  payload={config.samplePayload}
671
733
  disabled={disabled}
672
734
  onChange={(samplePayload) => patchConfig({ samplePayload })}
735
+ flagClassName={flagFieldClass("samplePayload", "triggerInput")}
673
736
  />
674
737
  <p className="dm-orchestration-config__hint">
675
738
  Bind values with {"{{input.key}}"} in the API endpoint or body template.
676
739
  </p>
740
+ {inputScheduleControls || null}
677
741
  </div>
678
742
  )}
679
743
 
@@ -694,15 +758,15 @@ export function OrchestrationNodeConfigPanel({
694
758
  ))}
695
759
  </select>
696
760
  </label>
697
- <label className="dm-orchestration-config__field">
761
+ <label className={`dm-orchestration-config__field${flagFieldClass("endpoint")}`}>
698
762
  <span>Endpoint</span>
699
763
  <input value={config.endpoint || ""} disabled={disabled} onChange={(e) => patchConfig({ endpoint: e.target.value })} />
700
764
  </label>
701
- <label className="dm-orchestration-config__field">
765
+ <label className={`dm-orchestration-config__field${flagFieldClass("bodyTemplate")}`}>
702
766
  <span>Body template</span>
703
767
  <textarea rows={3} value={config.bodyTemplate || ""} disabled={disabled} onChange={(e) => patchConfig({ bodyTemplate: e.target.value })} />
704
768
  </label>
705
- <label className="dm-orchestration-config__field">
769
+ <label className={`dm-orchestration-config__field${flagFieldClass("authRef", "registryId", "integrationId")}`}>
706
770
  <span>Auth reference</span>
707
771
  <input value={config.authRef || ""} disabled={disabled} onChange={(e) => patchConfig({ authRef: e.target.value })} />
708
772
  </label>
@@ -865,7 +929,7 @@ export function OrchestrationNodeConfigPanel({
865
929
  </label>
866
930
  </div>
867
931
 
868
- <VersionDeltaControls node={node} config={config} sandboxRow={sandboxRow} disabled={disabled} onChange={onConfigChange} />
932
+ <VersionDeltaControls node={node} config={config} sandboxRow={sandboxRow} disabled={disabled} onChange={onConfigChange} flaggedTags={readinessTagSet} flagSeverity={readinessSeverity} />
869
933
 
870
934
  <details className="dm-orchestration-config__advanced-json dm-orchestration-config__node-json">
871
935
  <summary>Node JSON</summary>
@@ -1075,6 +1139,8 @@ export function OrchestrationNodeConfigPanel({
1075
1139
  rowName={rowName}
1076
1140
  disabled={disabled}
1077
1141
  onSandboxRowPatch={onSandboxRowPatch}
1142
+ adapterFlagClass={flagFieldClass("adapter")}
1143
+ hostFlagClass={flagFieldClass("agentHost")}
1078
1144
  />
1079
1145
  )}
1080
1146
  <label className="dm-orchestration-config__field">
@@ -1473,7 +1539,7 @@ export function OrchestrationNodeConfigPanel({
1473
1539
  )}
1474
1540
 
1475
1541
  {activeTab === "configuration" && type !== "thinAdapter" && (
1476
- <VersionDeltaControls node={node} config={config} sandboxRow={sandboxRow} disabled={disabled} onChange={onConfigChange} />
1542
+ <VersionDeltaControls node={node} config={config} sandboxRow={sandboxRow} disabled={disabled} onChange={onConfigChange} flaggedTags={readinessTagSet} flagSeverity={readinessSeverity} />
1477
1543
  )}
1478
1544
  <div className="dm-workflow-node-config-foot">
1479
1545
  <button type="button" className="dm-workflow-node-options" disabled={disabled}>