@growthub/cli 0.13.9 → 0.14.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 (25) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +31 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +130 -5
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +17 -1
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +5 -2
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +200 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +396 -5
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +75 -55
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +2 -2
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +100 -6
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +176 -5
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +2 -4
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +317 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-response-profile.js +207 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/creation-error-recovery.js +103 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +100 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +63 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +215 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-write.js +67 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-upgrade.js +89 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +11 -4
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +8 -1
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +7 -1
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +200 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -1
  25. package/package.json +1 -1
@@ -69,6 +69,7 @@ function ToolCallCard({ proposal, content, onOpenArtifact }) {
69
69
  rationale: proposal?.rationale,
70
70
  confidence: proposal?.confidence,
71
71
  };
72
+ const showJson = meta.payload != null || meta.affectedField || meta.rationale;
72
73
  return (
73
74
  <div className="dm-helper-toolcall" data-toolcall-type={proposal?.type}>
74
75
  <button
@@ -89,9 +90,11 @@ function ToolCallCard({ proposal, content, onOpenArtifact }) {
89
90
  {open && (
90
91
  <div className="dm-helper-toolcall-body">
91
92
  {content && <div className="dm-helper-toolcall-content">{content}</div>}
92
- <pre className="dm-helper-toolcall-json">
93
- {JSON.stringify(meta, null, 2)}
94
- </pre>
93
+ {showJson && (
94
+ <pre className="dm-helper-toolcall-json">
95
+ {JSON.stringify(meta, null, 2)}
96
+ </pre>
97
+ )}
95
98
  </div>
96
99
  )}
97
100
  {canNavigate && (
@@ -108,6 +111,55 @@ function ToolCallCard({ proposal, content, onOpenArtifact }) {
108
111
  );
109
112
  }
110
113
 
114
+ function formatRunDuration(ms) {
115
+ const value = Number(ms);
116
+ if (!Number.isFinite(value) || value < 0) return "";
117
+ const totalSeconds = Math.max(0, Math.round(value / 1000));
118
+ const minutes = Math.floor(totalSeconds / 60);
119
+ const seconds = totalSeconds % 60;
120
+ if (minutes <= 0) return `${seconds}s`;
121
+ return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
122
+ }
123
+
124
+ function ProposalReviewCard({ proposal, checked, disabled, onCheckedChange, onKeyDown }) {
125
+ const [open, setOpen] = useState(false);
126
+ const summary = summarizePayload(proposal);
127
+ return (
128
+ <div className="dm-helper-toolcall" data-proposal-item="" tabIndex={0} onKeyDown={onKeyDown}>
129
+ <div className="dm-helper-toolcall-row dm-helper-proposal-card-row">
130
+ <input
131
+ type="checkbox"
132
+ checked={!!checked}
133
+ onChange={(e) => onCheckedChange(e.target.checked)}
134
+ disabled={disabled}
135
+ data-proposal-accept=""
136
+ aria-label={`Select ${proposal.type}`}
137
+ />
138
+ <button
139
+ type="button"
140
+ className="dm-helper-proposal-card-toggle"
141
+ onClick={() => setOpen((v) => !v)}
142
+ aria-expanded={open}
143
+ >
144
+ <span className="dm-helper-toolcall-title">{proposal.type}</span>
145
+ <span className="dm-helper-proposal-field">→ {proposal.affectedField}</span>
146
+ <ChevronDown
147
+ size={14}
148
+ className={`dm-helper-toolcall-chevron${open ? " is-open" : ""}`}
149
+ aria-hidden="true"
150
+ />
151
+ </button>
152
+ </div>
153
+ {open && (
154
+ <div className="dm-helper-toolcall-body">
155
+ {summary && <p className="dm-helper-proposal-payload" data-proposal-payload="">{summary}</p>}
156
+ {proposal.rationale && <p className="dm-helper-proposal-rationale">{proposal.rationale}</p>}
157
+ </div>
158
+ )}
159
+ </div>
160
+ );
161
+ }
162
+
111
163
  // Pair a system apply-receipt message with the actual proposal payload
112
164
  // it confirms. The applyResult (rehydrated from row.lastApplied at thread
113
165
  // load time) carries the typed payloads keyed in order — we walk the
@@ -747,6 +799,11 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
747
799
  const acceptedCount = Object.values(accepted).filter(Boolean).length;
748
800
  const skippedCount = applyResult?.skipped?.length || 0;
749
801
  const hasProposals = result && (result.proposals || []).length > 0;
802
+ const visibleWarnings = (result?.warnings || []).filter((warning) => {
803
+ const text = String(warning || "");
804
+ return !/transcript does not include the actual registry row id or lastResponse payload/i.test(text)
805
+ && !/No credentials or env values should be stored/i.test(text);
806
+ });
750
807
 
751
808
  // Thread is "active" the moment the user has sent at least one message,
752
809
  // OR we have rehydrated a prior thread row. Pills only show on the
@@ -916,18 +973,9 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
916
973
  {/* Proposals */}
917
974
  {result && (
918
975
  <div className="dm-helper-result">
919
- <div className="dm-helper-summary">
920
- <span>{result.summary}</span>
921
- </div>
922
-
923
- {(result.warnings || []).length > 0 && (
924
- <div className="dm-helper-warnings">
925
- {result.warnings.map((w, i) => (
926
- <div key={i} className="dm-helper-warning">
927
- <AlertCircle size={12} />
928
- <span>{w}</span>
929
- </div>
930
- ))}
976
+ {!threadActive && result.summary && (
977
+ <div className="dm-helper-summary">
978
+ <span>{result.summary}</span>
931
979
  </div>
932
980
  )}
933
981
 
@@ -943,42 +991,17 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
943
991
  aria-label="Proposals"
944
992
  >
945
993
  {result.proposals.map((proposal, i) => {
946
- const summary = summarizePayload(proposal);
947
- const conf = typeof proposal.confidence === "number" ? Math.round(proposal.confidence * 100) : null;
948
994
  return (
949
- <label
995
+ <ProposalReviewCard
950
996
  key={i}
951
- className={`dm-helper-proposal${accepted[i] ? " accepted" : ""}`}
952
- data-proposal-item=""
953
- tabIndex={0}
997
+ proposal={proposal}
998
+ checked={accepted[i]}
999
+ disabled={applying}
1000
+ onCheckedChange={(checked) =>
1001
+ setAccepted((prev) => ({ ...prev, [i]: checked }))
1002
+ }
954
1003
  onKeyDown={(e) => handleProposalKeyDown(e, i)}
955
- >
956
- <input
957
- type="checkbox"
958
- checked={!!accepted[i]}
959
- onChange={(e) =>
960
- setAccepted((prev) => ({ ...prev, [i]: e.target.checked }))
961
- }
962
- disabled={applying}
963
- data-proposal-accept=""
964
- tabIndex={-1}
965
- />
966
- <div className="dm-helper-proposal-body">
967
- <div className="dm-helper-proposal-row">
968
- <span className="dm-helper-proposal-type">{proposal.type}</span>
969
- <span className="dm-helper-proposal-field">→ {proposal.affectedField}</span>
970
- {conf !== null && (
971
- <span className="dm-helper-proposal-confidence" data-proposal-confidence={conf}>
972
- {conf}%
973
- </span>
974
- )}
975
- </div>
976
- {summary && (
977
- <p className="dm-helper-proposal-payload" data-proposal-payload="">{summary}</p>
978
- )}
979
- <p className="dm-helper-proposal-rationale">{proposal.rationale}</p>
980
- </div>
981
- </label>
1004
+ />
982
1005
  );
983
1006
  })}
984
1007
  </div>
@@ -1055,13 +1078,10 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
1055
1078
  )}
1056
1079
 
1057
1080
  {result.receipts && (
1058
- <p className="dm-field-hint" style={{ marginTop: 8 }} data-helper-receipt="">
1059
- Run: {result.receipts.model} · confidence{" "}
1060
- {typeof result.receipts.confidence === "number"
1061
- ? `${Math.round(result.receipts.confidence * 100)}%`
1062
- : "n/a"}{" "}
1063
- · {result.receipts.latencyMs}ms
1064
- </p>
1081
+ <div className="dm-helper-run-meta" data-helper-receipt="">
1082
+ <span>{result.receipts.model || "run"}</span>
1083
+ <span>{formatRunDuration(result.receipts.latencyMs)}</span>
1084
+ </div>
1065
1085
  )}
1066
1086
  </div>
1067
1087
  )}
@@ -202,9 +202,9 @@ export function ReferencePicker({
202
202
  <div className="dm-reference-picker">
203
203
  {error && <p className="dm-field-error" style={{ fontSize: 11 }}>{error}</p>}
204
204
  {showRepair && (
205
- <p className="dm-validation-banner" style={{ fontSize: 11, marginBottom: 6 }}>
205
+ <p className="dm-reference-picker-warning">
206
206
  <AlertTriangle size={12} aria-hidden />
207
- <span>Selected reference is missing or filtered out. Pick a new row or adjust API Registry status.</span>
207
+ <span>Missing reference. Pick a row or test the API Registry.</span>
208
208
  </p>
209
209
  )}
210
210
  <SearchableSelect
@@ -5030,7 +5030,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
5030
5030
  .dm-db-status.warn { border-color: #fde68a; background: #fffbeb; color: #92400e; }
5031
5031
  .dm-db-status.warn span { background: #f59e0b; }
5032
5032
  .dm-record-backdrop { position: fixed; inset: 0; z-index: 80; background: rgba(15,23,42,.12); }
5033
- .dm-record-drawer { position: fixed; top: 0; right: 0; bottom: 0; z-index: 81; display: flex; flex-direction: column; width: min(440px, 100vw); background: #fff; border-left: 1px solid #dfe3e8; box-shadow: -10px 0 34px rgba(15,23,42,.16); }
5033
+ .dm-record-drawer { position: fixed; top: 0; right: 0; bottom: 0; z-index: 81; display: flex; flex-direction: column; min-height: 0; width: min(440px, 100vw); background: #fff; border-left: 1px solid #dfe3e8; box-shadow: -10px 0 34px rgba(15,23,42,.16); overflow: hidden; }
5034
5034
  .dm-record-drawer-wide { width: min(780px, 100vw); }
5035
5035
  .dm-api-action-card {
5036
5036
  display: grid;
@@ -5070,6 +5070,59 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
5070
5070
  .dm-api-action-card-body h3 { margin: 0; font-size: 14px; font-weight: 650; color: #111827; }
5071
5071
  .dm-api-action-card-body p { margin: 0; font-size: 12px; line-height: 1.45; color: #4b5563; }
5072
5072
  .dm-api-action-card-cta { flex-shrink: 0; align-self: center; }
5073
+
5074
+ /* Governed creation cockpit — vertical journey inside the api-registry drawer.
5075
+ Overrides the action-card 3-col grid and reuses the workspace's own status
5076
+ chip (.dm-db-status) + dm-btn-* buttons. No new colors, no new primitive. */
5077
+ .dm-cockpit { display: block; grid-template-columns: none; transition: box-shadow .14s, background .14s; }
5078
+ .dm-cockpit.is-collapsed { padding-bottom: 12px; }
5079
+ .dm-cockpit-head { width: 100%; display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; border: 0; background: transparent; padding: 0; text-align: left; cursor: pointer; }
5080
+ .dm-cockpit-head:hover .dm-api-action-card-body h3 { color: #000; }
5081
+ .dm-cockpit-head:focus-visible { outline: 2px solid #bfdbfe; outline-offset: 4px; border-radius: 6px; }
5082
+ .dm-cockpit-count { flex-shrink: 0; font-size: 11px; font-weight: 700; color: #94a3b8; font-variant-numeric: tabular-nums; padding-top: 2px; }
5083
+ .dm-cockpit-steps { list-style: none; margin: 10px 0 0; padding: 0; }
5084
+ .dm-cockpit-step { display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: start; padding: 10px 0; border-top: 1px solid #edf0f3; }
5085
+ .dm-cockpit-step:first-child { border-top: 0; }
5086
+ .dm-cockpit-step-chip { flex-shrink: 0; align-self: start; margin-top: 1px; min-width: 64px; justify-content: center; }
5087
+ .dm-cockpit-step-body { min-width: 0; display: grid; gap: 2px; }
5088
+ .dm-cockpit-step-label { margin: 0; font-size: 13px; font-weight: 600; color: #111827; }
5089
+ .dm-cockpit-step-desc { margin: 0; font-size: 12px; line-height: 1.45; color: #64748b; }
5090
+ .dm-cockpit-step-hint { margin: 0; font-size: 11px; line-height: 1.4; color: #94a3b8; }
5091
+ .dm-cockpit-step button { flex-shrink: 0; align-self: center; }
5092
+ .dm-cockpit-step-muted .dm-cockpit-step-label,
5093
+ .dm-cockpit-step-muted .dm-cockpit-step-desc { color: #94a3b8; }
5094
+ .dm-cockpit-step-next { background: #f8fafc; margin: 0 -14px; padding-left: 14px; padding-right: 14px; border-radius: 6px; }
5095
+ .dm-cockpit-shape { margin-top: 12px; padding-top: 12px; border-top: 1px solid #edf0f3; display: grid; gap: 6px; }
5096
+ .dm-cockpit-shape-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
5097
+ .dm-cockpit-shape-head .dm-api-action-card-eyebrow { margin: 0; }
5098
+ .dm-cockpit-fields { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
5099
+ .dm-cockpit-field { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: #475569; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 2px 8px; }
5100
+ .dm-cockpit-field b { font-weight: 700; color: #94a3b8; text-transform: uppercase; font-size: 10px; letter-spacing: .03em; }
5101
+ .dm-cockpit-receipts { margin-top: 12px; padding-top: 12px; border-top: 1px solid #edf0f3; }
5102
+ .dm-cockpit-receipts ul { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 6px; }
5103
+ .dm-cockpit-receipt { display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: start; }
5104
+ .dm-cockpit-receipt-chip { align-self: start; }
5105
+ .dm-cockpit-receipt-text { font-size: 12px; color: #64748b; line-height: 1.4; }
5106
+
5107
+ /* Serverless upgrade — toolbar button, one-time nudge, and cockpit panel. */
5108
+ .dm-workflow-upgrade-btn { position: relative; }
5109
+ .dm-workflow-upgrade-btn.is-serverless { color: #166534; }
5110
+ .dm-workflow-upgrade-btn.is-pulse { color: #111827; }
5111
+ .dm-workflow-upgrade-btn[data-tooltip]:hover::after {
5112
+ content: attr(data-tooltip); position: absolute; top: 100%; right: 0; margin-top: 6px;
5113
+ white-space: nowrap; background: #111827; color: #fff; font-size: 11px; font-weight: 500;
5114
+ padding: 5px 8px; border-radius: 6px; z-index: 40; box-shadow: 0 4px 12px rgba(15,23,42,.18);
5115
+ }
5116
+ .dm-workflow-upgrade-nudge { align-items: center; justify-content: space-between; gap: 16px; padding: 10px 16px; }
5117
+ .dm-workflow-upgrade-nudge > div:first-child { min-width: 0; padding-right: 10px; }
5118
+ .dm-workflow-upgrade-nudge strong,
5119
+ .dm-workflow-upgrade-nudge span { line-height: 1.35; }
5120
+ .dm-workflow-upgrade-nudge-actions { display: inline-flex; align-items: center; gap: 8px; }
5121
+ .dm-workflow-upgrade-panel { width: auto; margin: 12px 8px 16px; border: 1px solid #e5e7eb; border-radius: 8px; background: #fff; box-shadow: 0 2px 8px rgba(15,23,42,.06); }
5122
+ .dm-workflow-upgrade-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 14px 0; }
5123
+ .dm-workflow-upgrade-panel-head .dm-api-action-card-eyebrow { margin: 0; }
5124
+ .dm-workflow-upgrade-panel .dm-api-action-card { margin: 8px; box-shadow: none; }
5125
+
5073
5126
  .dm-api-review-banner {
5074
5127
  display: grid;
5075
5128
  grid-template-columns: auto 1fr auto;
@@ -5223,6 +5276,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
5223
5276
  display: flex;
5224
5277
  align-items: center;
5225
5278
  gap: 6px;
5279
+ padding-left: 16px;
5226
5280
  min-width: max-content;
5227
5281
  flex: 1 0 auto;
5228
5282
  color: #8b8b91;
@@ -5276,6 +5330,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
5276
5330
  display: flex;
5277
5331
  align-items: center;
5278
5332
  gap: 6px;
5333
+ padding-right: 16px;
5279
5334
  flex: 0 1 auto;
5280
5335
  min-width: 0;
5281
5336
  max-width: 58vw;
@@ -6025,6 +6080,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
6025
6080
  .dm-drawer-hidden-list { display: flex; flex-wrap: wrap; gap: 8px; }
6026
6081
  .dm-record-drawer-head p { margin: 0 0 3px; color: #94a3b8; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
6027
6082
  .dm-record-drawer-head h2 { margin: 0; color: #111827; font-size: 16px; font-weight: 650; }
6083
+ .dm-record-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; scrollbar-gutter: stable; padding: 0 0 28px; }
6028
6084
  .dm-record-testbar { display: flex; align-items: center; gap: 8px; padding: 10px 18px; border-bottom: 1px solid #edf0f3; background: #fbfdff; }
6029
6085
  .dm-record-testbar > span:last-child { min-width: 0; color: #64748b; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6030
6086
  .dm-record-testbar[data-panel="sandbox-agent-auth"] {
@@ -6114,7 +6170,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
6114
6170
  white-space: pre-wrap;
6115
6171
  word-break: break-word;
6116
6172
  }
6117
- .dm-record-fields { display: grid; gap: 8px; padding: 14px 16px 28px; overflow-y: auto; }
6173
+ .dm-record-fields { display: grid; align-content: start; gap: 8px; min-height: 0; padding: 14px 16px 0; }
6118
6174
  .dm-record-field { display: grid; gap: 5px; }
6119
6175
  .dm-record-field span { color: #475569; font-size: 11px; font-weight: 650; }
6120
6176
  .dm-record-field input,
@@ -6137,7 +6193,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
6137
6193
  .dm-radio-row input[type="radio"], .dm-check-row input[type="checkbox"] { width: 14px; height: 14px; margin: 1px 0 0; padding: 0; box-shadow: none; accent-color: #111827; }
6138
6194
  .dm-radio-row span, .dm-check-row span { color: #1f2937; font-size: 12px; font-weight: 500; }
6139
6195
  .dm-check-row { cursor: pointer; }
6140
- .dm-select { position: relative; width: 100%; min-width: 180px; font-size: 11px; }
6196
+ .dm-select { position: relative; width: 100%; min-width: 0; font-size: 11px; }
6141
6197
  .dm-select-trigger { width: 100%; min-height: 32px; display: flex; align-items: center; justify-content: space-between; gap: 8px; border: 1px solid #cbd5e1; border-radius: 7px; background: #fff; color: #111827; box-shadow: 0 1px 2px rgba(15,23,42,.05); font: inherit; font-size: 11px; padding: 6px 10px; text-align: left; cursor: pointer; transition: border-color .12s, box-shadow .12s, background .12s; }
6142
6198
  .dm-select-trigger:hover:not(:disabled) { border-color: #94a3b8; box-shadow: 0 2px 8px rgba(15,23,42,.08); }
6143
6199
  .dm-select.open .dm-select-trigger { border-color: #64748b; box-shadow: 0 0 0 3px rgba(100,116,139,.12), 0 2px 8px rgba(15,23,42,.08); }
@@ -6162,8 +6218,12 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
6162
6218
  .dm-select-pager button { height: 26px; border: 1px solid #e2e8f0; border-radius: 6px; background: #fff; color: #334155; font: inherit; font-size: 12px; padding: 0 8px; cursor: pointer; }
6163
6219
  .dm-select-pager button:disabled { opacity: .45; cursor: not-allowed; }
6164
6220
  .dm-select-pager span { color: #64748b; font-size: 12px; font-weight: 650; }
6165
- .dm-db-grid td:has(.dm-select) { overflow: visible; padding-top: 5px; padding-bottom: 5px; }
6166
- .dm-db-grid td .dm-select { min-width: 230px; }
6221
+ .dm-reference-picker { display: grid; gap: 6px; width: min(240px, 100%); min-width: 0; }
6222
+ .dm-reference-picker-warning { display: inline-flex; align-items: center; gap: 5px; max-width: 100%; margin: 0; padding: 5px 7px; border: 1px solid #fde68a; border-radius: 6px; background: #fffbeb; color: #92400e; font-size: 11px; line-height: 1.25; }
6223
+ .dm-reference-picker-warning svg { flex: 0 0 auto; }
6224
+ .dm-reference-picker-warning span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6225
+ .dm-db-grid td:has(.dm-select) { overflow: visible; padding-top: 5px; padding-bottom: 5px; vertical-align: top; }
6226
+ .dm-db-grid td .dm-select { min-width: 0; }
6167
6227
  .dm-json-field { position: relative; }
6168
6228
  .dm-json-field > span { padding-right: 34px; }
6169
6229
  .dm-json-expand { position: absolute; top: 0; right: 0; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 24px; border: 1px solid #e2e8f0; border-radius: 6px; background: #fff; color: #64748b; box-shadow: 0 1px 2px rgba(15,23,42,.05); opacity: 0; cursor: pointer; transition: opacity .12s, border-color .12s, color .12s, box-shadow .12s; }
@@ -6607,6 +6667,40 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
6607
6667
  background: #f5f5f5;
6608
6668
  border-color: #9ca3af;
6609
6669
  }
6670
+ .dm-helper-run-meta {
6671
+ display: flex;
6672
+ align-items: center;
6673
+ justify-content: space-between;
6674
+ gap: 12px;
6675
+ padding-top: 8px;
6676
+ border-top: 1px solid #e5e7eb;
6677
+ color: #6b7280;
6678
+ font-size: 12px;
6679
+ line-height: 1.3;
6680
+ }
6681
+ .dm-helper-proposal-card-row {
6682
+ grid-template-columns: 18px 1fr;
6683
+ }
6684
+ .dm-helper-proposal-card-row input[type=checkbox] {
6685
+ width: 14px;
6686
+ height: 14px;
6687
+ margin: 0;
6688
+ accent-color: #111827;
6689
+ }
6690
+ .dm-helper-proposal-card-toggle {
6691
+ display: grid;
6692
+ grid-template-columns: minmax(0, max-content) minmax(0, 1fr) auto;
6693
+ align-items: center;
6694
+ gap: 6px;
6695
+ width: 100%;
6696
+ padding: 0;
6697
+ border: 0;
6698
+ background: transparent;
6699
+ color: inherit;
6700
+ font: inherit;
6701
+ text-align: left;
6702
+ cursor: pointer;
6703
+ }
6610
6704
 
6611
6705
  .dm-helper-typing {
6612
6706
  display: inline-flex;
@@ -8960,7 +9054,7 @@ body.workspace-rail-collapsed .workspace-builder.workspace-lens-page,
8960
9054
  .workspace-lens-helper-callout-btn { flex-shrink: 0; font-size: 12px; font-weight: 600; color: #111827; background: #ffffff; border: 1px solid #d1d5db; border-radius: 6px; padding: 7px 12px; cursor: pointer; }
8961
9055
  .workspace-lens-helper-callout-btn:hover:not(:disabled) { background: #f3f4f6; }
8962
9056
  .workspace-lens-helper-callout-btn:disabled { opacity: 0.6; cursor: default; }
8963
- .workspace-helper-setup-modal-backdrop { position: fixed; inset: 0; z-index: 1400; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(17, 24, 39, 0.42); backdrop-filter: blur(3px); }
9057
+ .workspace-helper-setup-modal-backdrop { position: fixed; inset: 0; z-index: 5100; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(17, 24, 39, 0.42); backdrop-filter: blur(3px); }
8964
9058
  .workspace-helper-setup-modal { position: relative; width: min(620px, 100%); max-height: min(720px, calc(100vh - 48px)); overflow: auto; background: #ffffff; border: 1px solid #d1d5db; border-radius: 8px; box-shadow: 0 24px 70px rgba(17, 24, 39, 0.28); padding: 18px; }
8965
9059
  .workspace-helper-setup-modal-close { position: absolute; top: 12px; right: 12px; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid #e5e7eb; border-radius: 6px; background: #fff; color: #6b7280; cursor: pointer; }
8966
9060
  .workspace-helper-setup-modal-close:hover { background: #f9fafb; color: #111827; }
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
4
4
  import Link from "next/link";
5
5
  import { useRouter, useSearchParams } from "next/navigation";
6
6
  import {
7
+ ArrowUpCircle,
7
8
  Bot,
8
9
  ChevronDown,
9
10
  ChevronUp,
@@ -51,7 +52,31 @@ import { AgentSwarmPanel } from "../data-model/components/AgentSwarmPanel.jsx";
51
52
  import { RunSetupPanel } from "./RunSetupPanel.jsx";
52
53
  import { describeRunInputMetadataItems, discoverRunInputSchema } from "@/lib/orchestration-run-inputs";
53
54
  import { selectWorkflowNodeInputSchema } from "@/lib/workspace-metadata-selectors";
54
- import { deriveProvenance, hasConnectionId } from "@/lib/workspace-activation";
55
+ import { deriveProvenance, hasConnectionId, readUiCacheFlag } from "@/lib/workspace-activation";
56
+ import { ApiRegistryCreationCockpit } from "../data-model/components/ApiRegistryCreationCockpit.jsx";
57
+ import { deriveSandboxServerlessState } from "@/lib/sandbox-serverless-flow";
58
+ import { deriveServerlessUpgradeState, SERVERLESS_UPGRADE_DISMISS_FLAG } from "@/lib/serverless-upgrade";
59
+
60
+ // Set a flag on the governed workspace-ui-cache "activation" row (pure helper,
61
+ // same transform the rail/lens one-time dismisses use).
62
+ function withUiCacheFlag(workspaceConfig, flag, value) {
63
+ const dm = workspaceConfig?.dataModel && typeof workspaceConfig.dataModel === "object" ? workspaceConfig.dataModel : {};
64
+ const objects = Array.isArray(dm.objects) ? dm.objects : [];
65
+ const existing = objects.find((o) => o?.id === "workspace-ui-cache");
66
+ const baseRow = (existing?.rows || []).find((r) => r?.id === "activation") || { id: "activation" };
67
+ const nextRow = { ...baseRow, [flag]: value };
68
+ const nextCache = existing
69
+ ? { ...existing, rows: [nextRow, ...(existing.rows || []).filter((r) => r?.id !== "activation")] }
70
+ : {
71
+ id: "workspace-ui-cache", label: "Workspace UI Cache", source: "Workspace UI Cache",
72
+ objectType: "custom", columns: ["id", flag], rows: [nextRow],
73
+ binding: { mode: "manual", source: "Workspace UI Cache" },
74
+ };
75
+ const nextObjects = existing
76
+ ? objects.map((o) => (o?.id === "workspace-ui-cache" ? nextCache : o))
77
+ : [...objects, nextCache];
78
+ return { ...workspaceConfig, dataModel: { ...dm, objects: nextObjects } };
79
+ }
55
80
 
56
81
  // Workspace Metadata Graph V1 — read-only dependency metadata for workflow
57
82
  // sidecars. The runtime path (sandbox-run, publish, draft/live) is
@@ -63,20 +88,33 @@ const WORKFLOW_METADATA_SELECTORS = Object.freeze({
63
88
  });
64
89
 
65
90
  function resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) {
91
+ return resolveRegistryRefForSandbox(workspaceConfig, sandboxRow)?.row || null;
92
+ }
93
+
94
+ function resolveRegistryRefForSandbox(workspaceConfig, sandboxRow) {
66
95
  const graph = parseOrchestrationGraph(sandboxRow?.orchestrationConfig || sandboxRow?.orchestrationGraph);
67
96
  const apiNode = graph?.nodes?.find((n) => n?.type === "api-registry-call");
68
97
  const registryId = String(
69
98
  apiNode?.config?.registryId || apiNode?.config?.integrationId || sandboxRow?.schedulerRegistryId || ""
70
99
  ).trim();
71
- if (!registryId || !workspaceConfig) return null;
100
+ if (!workspaceConfig) return null;
72
101
  const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
102
+ let firstRegistryRow = null;
103
+ let firstRegistryObject = null;
73
104
  for (const objectItem of objects) {
74
105
  if (objectItem?.objectType !== "api-registry") continue;
75
106
  const rows = Array.isArray(objectItem.rows) ? objectItem.rows : [];
76
- const match = rows.find((r) => String(r?.integrationId || "").trim() === registryId);
77
- if (match) return match;
107
+ const firstRow = rows.find((r) => String(r?.integrationId || "").trim());
108
+ if (!firstRegistryRow && firstRow) {
109
+ firstRegistryRow = firstRow;
110
+ firstRegistryObject = objectItem;
111
+ }
112
+ if (registryId) {
113
+ const match = rows.find((r) => String(r?.integrationId || "").trim() === registryId);
114
+ if (match) return { object: objectItem, row: match };
115
+ }
78
116
  }
79
- return null;
117
+ return firstRegistryRow ? { object: firstRegistryObject, row: firstRegistryRow } : null;
80
118
  }
81
119
 
82
120
  function patchSandboxRowInConfig(workspaceConfig, objectId, rowIndex, fields) {
@@ -349,6 +387,23 @@ export default function WorkflowSurface() {
349
387
  const [orchestrationGraph, setOrchestrationGraph] = useState(null);
350
388
  const [dirty, setDirty] = useState(false);
351
389
  const [runSetupOpen, setRunSetupOpen] = useState(false);
390
+ const [upgradeOpen, setUpgradeOpen] = useState(false);
391
+ const [serverlessSignals, setServerlessSignals] = useState({ configuredEnvRefs: [], persistenceAdapters: [] });
392
+
393
+ useEffect(() => {
394
+ let cancelled = false;
395
+ fetch("/api/workspace/env-status", { cache: "no-store" })
396
+ .then((res) => (res.ok ? res.json() : {}))
397
+ .then((payload) => {
398
+ if (cancelled) return;
399
+ setServerlessSignals({
400
+ configuredEnvRefs: Array.isArray(payload.configuredEnvRefs) ? payload.configuredEnvRefs : [],
401
+ persistenceAdapters: Array.isArray(payload.persistenceAdapters) ? payload.persistenceAdapters : [],
402
+ });
403
+ })
404
+ .catch(() => {});
405
+ return () => { cancelled = true; };
406
+ }, [objectId, rowId]);
352
407
 
353
408
  const load = useCallback(async () => {
354
409
  setLoading(true);
@@ -807,6 +862,73 @@ export default function WorkflowSurface() {
807
862
  const showSaveDraft = dirty && !graphUnset;
808
863
  const workflowModeLabel = isDraftMode ? "draft" : lifecycle || "live";
809
864
 
865
+ // Serverless upgrade — same derivation + cockpit as the sandbox/API lanes.
866
+ const upgradeState = deriveServerlessUpgradeState(workspaceConfig || {}, {
867
+ dismissed: readUiCacheFlag(workspaceConfig || {}, SERVERLESS_UPGRADE_DISMISS_FLAG),
868
+ });
869
+ const serverlessState = sandboxRow
870
+ ? deriveSandboxServerlessState({
871
+ sandboxRow,
872
+ workspaceConfig: workspaceConfig || {},
873
+ configuredEnvRefs: serverlessSignals.configuredEnvRefs,
874
+ persistenceAdapters: serverlessSignals.persistenceAdapters,
875
+ })
876
+ : null;
877
+ const isServerlessWorkflow = Boolean(serverlessState?.isServerless);
878
+
879
+ async function patchSandboxAndPersist(fields) {
880
+ if (resolved.rowIndex < 0 || !objectId || !workspaceConfig) return;
881
+ try {
882
+ const next = patchSandboxRowInConfig(workspaceConfig, objectId, resolved.rowIndex, fields);
883
+ await persistWorkspace(next);
884
+ setSaveMessage(fields.runLocality === "serverless"
885
+ ? "Upgraded to serverless. Link a scheduler and configure persistence to close the loop."
886
+ : fields.runLocality === "local"
887
+ ? "Reverted to local execution."
888
+ : "Saved.");
889
+ } catch (err) {
890
+ setSaveMessage(err.message || "Failed to save");
891
+ }
892
+ }
893
+
894
+ async function handleUpgradeAction(action) {
895
+ if (!action) return;
896
+ if (action.id === "toggle-locality") {
897
+ if (isServerlessWorkflow) {
898
+ await patchSandboxAndPersist({ runLocality: "local" });
899
+ return;
900
+ }
901
+ const registryRow = resolveRegistryRowForSandbox(workspaceConfig, sandboxRow);
902
+ const adapterId = String(sandboxRow?.adapter || "").trim();
903
+ await patchSandboxAndPersist({
904
+ runLocality: "serverless",
905
+ schedulerRegistryId: String(registryRow?.integrationId || "").trim(),
906
+ adapter: ["local-agent-host", "local-intelligence"].includes(adapterId) ? "local-process" : adapterId,
907
+ });
908
+ } else if (action.id === "open-settings") {
909
+ router.push(action.href || "/settings");
910
+ } else if (action.id === "link-scheduler") {
911
+ const registryRef = resolveRegistryRefForSandbox(workspaceConfig, sandboxRow);
912
+ if (registryRef?.object?.id && registryRef?.row?.integrationId) {
913
+ router.push(`/data-model?object=${encodeURIComponent(registryRef.object.id)}&row=${encodeURIComponent(registryRef.row.integrationId)}`);
914
+ } else {
915
+ router.push(`/data-model?object=${encodeURIComponent(objectId)}&row=${encodeURIComponent(rowId)}`);
916
+ }
917
+ } else if (action.id === "edit-adapter") {
918
+ // Full scheduler/adapter config lives on the sandbox object's drawer.
919
+ router.push(`/data-model?object=${encodeURIComponent(objectId)}&row=${encodeURIComponent(rowId)}`);
920
+ }
921
+ }
922
+
923
+ async function dismissUpgradeOnboarding() {
924
+ if (!workspaceConfig) return;
925
+ try {
926
+ await persistWorkspace(withUiCacheFlag(workspaceConfig, SERVERLESS_UPGRADE_DISMISS_FLAG, true));
927
+ } catch {
928
+ /* non-fatal */
929
+ }
930
+ }
931
+
810
932
  return (
811
933
  <main className="workspace-builder dm-workflow-page">
812
934
  <WorkspaceRail
@@ -854,6 +976,18 @@ export default function WorkflowSurface() {
854
976
  >
855
977
  <ChevronUp size={14} />
856
978
  </button>
979
+ {sandboxRow && (
980
+ <button
981
+ type="button"
982
+ className={"dm-workflow-icon-btn dm-workflow-upgrade-btn" + (isServerlessWorkflow ? " is-serverless" : (upgradeState.showOnboarding ? " is-pulse" : ""))}
983
+ aria-label={isServerlessWorkflow ? "Serverless workflow — review persistence & scheduling" : "Upgrade to serverless environment to ensure persistence"}
984
+ data-tooltip={isServerlessWorkflow ? "Serverless — review persistence & scheduling" : "Upgrade to serverless environment to ensure persistence"}
985
+ aria-pressed={upgradeOpen}
986
+ onClick={() => setUpgradeOpen((open) => !open)}
987
+ >
988
+ <ArrowUpCircle size={14} />
989
+ </button>
990
+ )}
857
991
  {showDiscardDraft && (
858
992
  <button
859
993
  type="button"
@@ -920,6 +1054,43 @@ export default function WorkflowSurface() {
920
1054
  </div>
921
1055
  ) : null}
922
1056
 
1057
+ {/* One-time serverless upgrade onboarding — shows only when the operator
1058
+ has workflows but none are serverless, and hasn't dismissed it. */}
1059
+ {sandboxRow && !upgradeOpen && upgradeState.showOnboarding ? (
1060
+ <div className="workspace-template-context-banner dm-workflow-upgrade-nudge" role="note">
1061
+ <div>
1062
+ <strong>{upgradeState.headline}</strong>
1063
+ <span style={{ display: "block", marginTop: 2 }}>{upgradeState.subheadline}</span>
1064
+ </div>
1065
+ <div className="dm-workflow-upgrade-nudge-actions">
1066
+ <button type="button" className="dm-btn-primary-sm" onClick={() => setUpgradeOpen(true)}>
1067
+ <ArrowUpCircle size={13} /> Upgrade this workflow
1068
+ </button>
1069
+ <button type="button" className="dm-btn-ghost" onClick={dismissUpgradeOnboarding}>Not now</button>
1070
+ </div>
1071
+ </div>
1072
+ ) : null}
1073
+
1074
+ {/* Serverless cockpit — same derivation + cockpit interface as the API
1075
+ Registry and sandbox lanes. Toggles patch the sandbox row; deep config
1076
+ (scheduler/adapter) routes to the object's Data Model drawer. */}
1077
+ {sandboxRow && upgradeOpen && serverlessState ? (
1078
+ <div className="dm-workflow-upgrade-panel">
1079
+ <div className="dm-workflow-upgrade-panel-head">
1080
+ <span className="dm-api-action-card-eyebrow">Persistence &amp; scheduling</span>
1081
+ <button type="button" className="dm-workflow-icon-btn" aria-label="Close upgrade panel" onClick={() => { setUpgradeOpen(false); dismissUpgradeOnboarding(); }}>
1082
+ <X size={14} />
1083
+ </button>
1084
+ </div>
1085
+ <ApiRegistryCreationCockpit
1086
+ state={serverlessState}
1087
+ onAction={handleUpgradeAction}
1088
+ disabled={saving || publishing || running}
1089
+ eyebrow={isServerlessWorkflow ? "Serverless workflow" : "Upgrade to serverless"}
1090
+ />
1091
+ </div>
1092
+ ) : null}
1093
+
923
1094
  {loading ? (
924
1095
  <p className="dm-workflow-empty">Loading workflow…</p>
925
1096
  ) : error ? (
@@ -28,11 +28,9 @@ import path from "node:path";
28
28
  import { pathToFileURL } from "node:url";
29
29
 
30
30
  const staticLoaded = new Set();
31
- let staticLoadDone = false;
31
+ const nativeImport = new Function("specifier", "return import(specifier)");
32
32
 
33
33
  async function loadStaticResolversOnce() {
34
- if (staticLoadDone) return;
35
- staticLoadDone = true;
36
34
  const resolversDir = path.resolve(/*turbopackIgnore: true*/ process.cwd(), "lib/adapters/integrations/resolvers");
37
35
  try {
38
36
  const entries = await fs.readdir(resolversDir);
@@ -42,7 +40,7 @@ async function loadStaticResolversOnce() {
42
40
  if (staticLoaded.has(file)) return;
43
41
  try {
44
42
  const absolutePath = path.join(resolversDir, file);
45
- await import(/*turbopackIgnore: true*/ pathToFileURL(absolutePath).href);
43
+ await nativeImport(pathToFileURL(absolutePath).href);
46
44
  staticLoaded.add(file);
47
45
  } catch {
48
46
  // Malformed resolver — skip silently; operator needs to fix the file