@growthub/cli 0.14.6 → 0.14.9
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.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/SKILL.md +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +5 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +5 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +58 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +31 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/StatusPill.jsx +22 -6
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ToggleField.jsx +5 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceDataModelCanvas.jsx +457 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +188 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +67 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-map/page.jsx +14 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +48 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-node-status.js +55 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-impact.js +198 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/skills/governed-workspace-mutation/SKILL.md +11 -0
- package/package.json +1 -1
package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css
CHANGED
|
@@ -8977,10 +8977,14 @@ body.workspace-rail-collapsed .workspace-builder.workspace-lens-page,
|
|
|
8977
8977
|
.workspace-lens-surface { width: 100%; padding: 0; background: #f7f7f8; overflow-y: auto; overflow-x: hidden; }
|
|
8978
8978
|
.workspace-lens-shell { width: 100%; max-width: none; margin: 0; padding: 24px 28px 32px; }
|
|
8979
8979
|
.workspace-lens-locked { border: 1px solid #e5e7eb; border-radius: 10px; background: #fff; padding: 28px; max-width: 560px; margin: 48px auto; display: flex; flex-direction: column; gap: 8px; }
|
|
8980
|
-
.workspace-lens-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
|
|
8980
|
+
.workspace-lens-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
|
|
8981
|
+
.workspace-lens-head > div { min-width: 0; }
|
|
8981
8982
|
.workspace-lens-title { font-size: 18px; font-weight: 600; color: #111827; margin: 0; }
|
|
8982
8983
|
.workspace-lens-subtitle { font-size: 13px; color: #6b7280; margin: 4px 0 0; }
|
|
8983
|
-
.workspace-lens-
|
|
8984
|
+
.workspace-lens-map-link { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 34px; border: 1px solid #d8dee8; border-radius: 8px; background: #ffffff; color: #111827; padding: 0 12px; text-decoration: none; font-size: 13px; font-weight: 600; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05); }
|
|
8985
|
+
.workspace-lens-map-link:hover { background: #f9fafb; border-color: #cfd7e3; color: #111827; }
|
|
8986
|
+
.workspace-lens-map-link:focus-visible { outline: 2px solid #bfdbfe; outline-offset: 2px; }
|
|
8987
|
+
.workspace-lens-map-link svg { color: #4b5563; flex-shrink: 0; }
|
|
8984
8988
|
.workspace-lens-controls.workspace-builder-filterbar { margin: 0 0 14px; border: 1px solid #e8edf3; border-radius: 8px; background: #fff; }
|
|
8985
8989
|
.workspace-lens-filters.workspace-builder-filterbar__segments { max-width: 100%; overflow-x: auto; }
|
|
8986
8990
|
.workspace-lens-filter { white-space: nowrap; }
|
|
@@ -9034,12 +9038,17 @@ body.workspace-rail-collapsed .workspace-builder.workspace-lens-page,
|
|
|
9034
9038
|
.workspace-lens-action-menu button:hover,
|
|
9035
9039
|
.workspace-lens-action-menu a:hover { background: #f3f4f6; }
|
|
9036
9040
|
@media (max-width: 1100px) {
|
|
9041
|
+
.workspace-lens-head { align-items: flex-start; }
|
|
9037
9042
|
.workspace-lens-control-grid { grid-template-columns: 1fr; }
|
|
9038
9043
|
.workspace-lens-branch-row { grid-template-columns: 1fr; align-items: flex-start; padding: 10px 12px; gap: 8px; }
|
|
9039
9044
|
.workspace-lens-branch-actions { justify-content: flex-start; flex-wrap: wrap; }
|
|
9040
9045
|
.workspace-lens-branch-summary,
|
|
9041
9046
|
.workspace-lens-branch-next { margin-left: 22px; }
|
|
9042
9047
|
}
|
|
9048
|
+
@media (max-width: 640px) {
|
|
9049
|
+
.workspace-lens-head { flex-direction: column; }
|
|
9050
|
+
.workspace-lens-map-link { align-self: stretch; }
|
|
9051
|
+
}
|
|
9043
9052
|
.workspace-lens-stream { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
9044
9053
|
.workspace-lens-card { border: 1px solid #e8edf3; border-radius: 10px; background: #fff; padding: 12px 14px; }
|
|
9045
9054
|
.workspace-lens-card.is-ready { opacity: 0.7; }
|
|
@@ -9261,3 +9270,180 @@ body.workspace-rail-collapsed .workspace-builder.workspace-lens-page,
|
|
|
9261
9270
|
.dm-swarm-expand-head { justify-content: flex-start; gap: 8px; }
|
|
9262
9271
|
.dm-swarm-expand-body { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 14px; }
|
|
9263
9272
|
.dm-swarm-expand-body .dm-helper-toolcall-json { max-height: none; }
|
|
9273
|
+
|
|
9274
|
+
/* ════════════════════════════════════════════════════════════════════════
|
|
9275
|
+
SaaS facelift layer — Attio-inspired polish (v0.14.8)
|
|
9276
|
+
Additive only. Refines existing dm-* light-theme primitives via the
|
|
9277
|
+
cascade (equal specificity, later wins) and introduces the shared
|
|
9278
|
+
vocabulary consumed by the table, drawer, Workspace Map, and run-status
|
|
9279
|
+
work. No new theme, no token renames — existing classes keep working.
|
|
9280
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
9281
|
+
:root {
|
|
9282
|
+
/* Calm, unified surface system for the light data-model surfaces. */
|
|
9283
|
+
--dm-line: #e7eaee; /* default hairline border */
|
|
9284
|
+
--dm-line-soft: #f0f2f5; /* inner cell / row separators */
|
|
9285
|
+
--dm-surface: #ffffff;
|
|
9286
|
+
--dm-surface-soft: #fafbfc; /* headers, toolbars, footers */
|
|
9287
|
+
--dm-hover: #f6f8fa; /* calm row/control hover */
|
|
9288
|
+
--dm-ink: #1f2733; /* primary text */
|
|
9289
|
+
--dm-muted: #6b7686; /* secondary text */
|
|
9290
|
+
--dm-faint: #9aa4b2; /* tertiary / placeholder */
|
|
9291
|
+
--dm-accent: #4f6bed; /* single accent — active states only */
|
|
9292
|
+
--dm-accent-soft: #eef1fe; /* accent background wash */
|
|
9293
|
+
--dm-accent-line: #c9d3fb; /* accent border */
|
|
9294
|
+
--dm-radius: 8px;
|
|
9295
|
+
--dm-radius-sm: 6px;
|
|
9296
|
+
--dm-radius-lg: 12px;
|
|
9297
|
+
--dm-shadow-pop: 0 12px 32px rgba(20, 28, 46, 0.12), 0 2px 6px rgba(20, 28, 46, 0.06);
|
|
9298
|
+
--dm-ok: #16a34a;
|
|
9299
|
+
--dm-ok-soft: #f0fdf4;
|
|
9300
|
+
--dm-ok-line: #bbf7d0;
|
|
9301
|
+
--dm-bad: #dc2626;
|
|
9302
|
+
--dm-bad-soft: #fef2f2;
|
|
9303
|
+
--dm-bad-line: #fecaca;
|
|
9304
|
+
--dm-warn: #d97706;
|
|
9305
|
+
--dm-warn-soft: #fffbeb;
|
|
9306
|
+
--dm-warn-line: #fde68a;
|
|
9307
|
+
--dm-run: #4f6bed;
|
|
9308
|
+
--dm-run-soft: #eef1fe;
|
|
9309
|
+
--dm-run-line: #c9d3fb;
|
|
9310
|
+
}
|
|
9311
|
+
|
|
9312
|
+
/* ── Toolbar + buttons: softer borders, unified radius/hover ───────────── */
|
|
9313
|
+
.dm-db-toolbar { border-bottom-color: var(--dm-line); }
|
|
9314
|
+
.dm-btn-ghost { border-radius: var(--dm-radius-sm); border-color: var(--dm-line); color: var(--dm-muted); }
|
|
9315
|
+
.dm-btn-ghost:hover { background: var(--dm-hover); border-color: #d3d9e0; color: var(--dm-ink); }
|
|
9316
|
+
.dm-btn-ghost.is-active { background: var(--dm-accent-soft); border-color: var(--dm-accent-line); color: var(--dm-accent); }
|
|
9317
|
+
.dm-btn-outline { border-radius: var(--dm-radius-sm); border-color: var(--dm-line); }
|
|
9318
|
+
.dm-btn-primary-sm { border-radius: var(--dm-radius-sm); }
|
|
9319
|
+
|
|
9320
|
+
/* ── Compact search field for the table toolbar (P1) ──────────────────── */
|
|
9321
|
+
.dm-toolbar-search { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 9px; border: 1px solid var(--dm-line); border-radius: var(--dm-radius-sm); background: var(--dm-surface); color: var(--dm-faint); transition: border-color .12s, box-shadow .12s; }
|
|
9322
|
+
.dm-toolbar-search:focus-within { border-color: var(--dm-accent-line); box-shadow: 0 0 0 3px var(--dm-accent-soft); }
|
|
9323
|
+
.dm-toolbar-search input { border: 0; outline: 0; background: transparent; font: inherit; font-size: 12px; color: var(--dm-ink); width: 150px; }
|
|
9324
|
+
.dm-toolbar-search input::placeholder { color: var(--dm-faint); }
|
|
9325
|
+
.dm-toolbar-count { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border-radius: 999px; background: var(--dm-surface-soft); border: 1px solid var(--dm-line); color: var(--dm-muted); font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums; }
|
|
9326
|
+
.dm-toolbar-divider { width: 1px; align-self: stretch; margin: 2px 2px; background: var(--dm-line); }
|
|
9327
|
+
|
|
9328
|
+
/* ── Filter / sort pill chips: calmer, clearer active state ───────────── */
|
|
9329
|
+
.dm-filter-chip { border-radius: var(--dm-radius-sm); border-color: var(--dm-accent-line); background: var(--dm-accent-soft); color: var(--dm-accent); font-weight: 600; transition: background .12s, border-color .12s; }
|
|
9330
|
+
.dm-filter-chip:hover { background: #e4e9fd; }
|
|
9331
|
+
.dm-filter-chip-count { margin-left: 1px; min-width: 16px; height: 16px; padding: 0 4px; display: inline-flex; align-items: center; justify-content: center; border-radius: 999px; background: var(--dm-accent); color: #fff; font-size: 10px; font-weight: 700; }
|
|
9332
|
+
.dm-selection-count { border-color: var(--dm-line); background: var(--dm-surface); color: var(--dm-muted); }
|
|
9333
|
+
|
|
9334
|
+
/* ── Grid: calmer hairlines + hover, refined sticky header ────────────── */
|
|
9335
|
+
.dm-db-grid-wrap { border-color: var(--dm-line); border-radius: var(--dm-radius); }
|
|
9336
|
+
.dm-db-grid th { background: var(--dm-surface-soft); color: var(--dm-muted); border-bottom-color: var(--dm-line); border-right-color: var(--dm-line-soft); letter-spacing: .01em; }
|
|
9337
|
+
.dm-db-grid td { border-bottom-color: var(--dm-line-soft); border-right-color: var(--dm-line-soft); }
|
|
9338
|
+
.dm-db-grid tbody tr:hover td { background: var(--dm-hover); }
|
|
9339
|
+
.dm-db-grid tbody tr.selected td { background: var(--dm-accent-soft); box-shadow: inset 2px 0 0 var(--dm-accent); }
|
|
9340
|
+
.dm-db-field-type { background: var(--dm-surface-soft); color: var(--dm-faint); border-radius: 5px; }
|
|
9341
|
+
|
|
9342
|
+
/* ── Object sidebar: calmer active state (less heavy than solid black) ── */
|
|
9343
|
+
.dm-obj-row { border-radius: var(--dm-radius-sm); }
|
|
9344
|
+
.dm-obj-row:hover { background: var(--dm-hover); }
|
|
9345
|
+
.dm-obj-row.active { background: var(--dm-accent-soft); color: var(--dm-accent); font-weight: 600; }
|
|
9346
|
+
.dm-obj-row.active .dm-obj-icon { color: var(--dm-accent); }
|
|
9347
|
+
.dm-obj-row.active .dm-badge { background: rgba(79,107,237,.12); color: var(--dm-accent); border-color: var(--dm-accent-line); }
|
|
9348
|
+
/* Record count on the live object picker rows (P2). The picker — not the
|
|
9349
|
+
legacy ObjectSidebar — is the rendered object nav. */
|
|
9350
|
+
.dm-picker-meta { margin-left: auto; flex: 0 0 auto; color: var(--dm-faint); font-size: 11px; font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
9351
|
+
.dm-picker-item.active .dm-picker-meta { color: var(--dm-muted); font-weight: 500; }
|
|
9352
|
+
|
|
9353
|
+
/* ── Badges: thinner, calmer ──────────────────────────────────────────── */
|
|
9354
|
+
.dm-badge { border-radius: 5px; border-color: var(--dm-line); }
|
|
9355
|
+
|
|
9356
|
+
/* ════ Live status chips — shared by StatusPill + run-status work (P4) ══ */
|
|
9357
|
+
.dm-status-chip { display: inline-flex; align-items: center; gap: 5px; height: 20px; padding: 0 8px; border: 1px solid var(--dm-line); border-radius: 999px; background: var(--dm-surface-soft); color: var(--dm-muted); font-size: 11px; font-weight: 650; line-height: 1; white-space: nowrap; }
|
|
9358
|
+
.dm-status-chip > .dm-status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--dm-faint); flex: 0 0 auto; }
|
|
9359
|
+
.dm-status-chip.is-ok { border-color: var(--dm-ok-line); background: var(--dm-ok-soft); color: #166534; }
|
|
9360
|
+
.dm-status-chip.is-ok > .dm-status-dot { background: var(--dm-ok); }
|
|
9361
|
+
.dm-status-chip.is-bad { border-color: var(--dm-bad-line); background: var(--dm-bad-soft); color: #991b1b; }
|
|
9362
|
+
.dm-status-chip.is-bad > .dm-status-dot { background: var(--dm-bad); }
|
|
9363
|
+
.dm-status-chip.is-warn { border-color: var(--dm-warn-line); background: var(--dm-warn-soft); color: #92400e; }
|
|
9364
|
+
.dm-status-chip.is-warn > .dm-status-dot { background: var(--dm-warn); }
|
|
9365
|
+
.dm-status-chip.is-running { border-color: var(--dm-run-line); background: var(--dm-run-soft); color: #3a4fc0; }
|
|
9366
|
+
.dm-status-chip.is-running > .dm-status-dot { background: var(--dm-run); animation: dm-pulse 1.15s ease-in-out infinite; }
|
|
9367
|
+
.dm-status-chip.is-waiting > .dm-status-dot { background: var(--dm-faint); }
|
|
9368
|
+
@keyframes dm-pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.72); } }
|
|
9369
|
+
@media (prefers-reduced-motion: reduce) { .dm-status-chip.is-running > .dm-status-dot { animation: none; } }
|
|
9370
|
+
|
|
9371
|
+
/* ── Toggle switch — upgrades ToggleField's checkbox into a real switch ─ */
|
|
9372
|
+
.dm-switch-row { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; font-size: 13px; color: var(--dm-ink); }
|
|
9373
|
+
.dm-switch-row.is-disabled { opacity: .55; cursor: not-allowed; }
|
|
9374
|
+
.dm-switch-row > input { position: absolute; opacity: 0; width: 0; height: 0; }
|
|
9375
|
+
.dm-switch-track { position: relative; flex: 0 0 auto; width: 34px; height: 20px; margin-top: 1px; border-radius: 999px; background: #d7dce3; transition: background .14s; }
|
|
9376
|
+
.dm-switch-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(20,28,46,.28); transition: transform .14s; }
|
|
9377
|
+
.dm-switch-row > input:checked + .dm-switch-track { background: var(--dm-accent); }
|
|
9378
|
+
.dm-switch-row > input:checked + .dm-switch-track::after { transform: translateX(14px); }
|
|
9379
|
+
.dm-switch-row > input:focus-visible + .dm-switch-track { box-shadow: 0 0 0 3px var(--dm-accent-soft); }
|
|
9380
|
+
.dm-switch-label { display: grid; gap: 2px; }
|
|
9381
|
+
.dm-switch-desc { color: var(--dm-muted); font-size: 12px; }
|
|
9382
|
+
|
|
9383
|
+
/* ════ Workspace Map / Data Model Canvas (P3) ═════════════════════════════
|
|
9384
|
+
Schema node-canvas reusing the dotted-grid + card language. Read-only. */
|
|
9385
|
+
.wm-shell { display: flex; flex-direction: column; min-height: 0; flex: 1; background: var(--dm-surface); }
|
|
9386
|
+
.wm-toolbar { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--dm-line); background: var(--dm-surface); flex-wrap: wrap; }
|
|
9387
|
+
.wm-back-link { width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--dm-line); border-radius: 8px; background: var(--dm-surface); color: var(--dm-muted); text-decoration: none; box-shadow: 0 1px 2px rgba(20,28,46,.04); flex: 0 0 auto; }
|
|
9388
|
+
.wm-back-link:hover { background: var(--dm-hover); color: var(--dm-ink); border-color: #cfd6df; }
|
|
9389
|
+
.wm-back-link:focus-visible { outline: 2px solid #bfdbfe; outline-offset: 2px; }
|
|
9390
|
+
.wm-toolbar h1 { margin: 0; font-size: 15px; font-weight: 650; color: var(--dm-ink); }
|
|
9391
|
+
.wm-toolbar .wm-sub { font-size: 12px; color: var(--dm-faint); }
|
|
9392
|
+
.wm-legend { display: inline-flex; align-items: center; gap: 12px; margin-left: auto; flex-wrap: wrap; }
|
|
9393
|
+
.wm-legend-item { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--dm-muted); }
|
|
9394
|
+
.wm-legend-swatch { width: 9px; height: 9px; border-radius: 3px; }
|
|
9395
|
+
.wm-canvas { position: relative; flex: 1; min-height: 460px; overflow: auto; background-color: #fbfcfd; background-image: radial-gradient(circle, #dde2e8 1px, transparent 1px); background-size: 22px 22px; cursor: grab; touch-action: none; }
|
|
9396
|
+
.wm-canvas.is-panning { cursor: grabbing; user-select: none; }
|
|
9397
|
+
.wm-canvas-inner { position: relative; }
|
|
9398
|
+
.wm-canvas-scale { position: relative; transform-origin: 0 0; }
|
|
9399
|
+
.wm-edge { position: absolute; pointer-events: none; }
|
|
9400
|
+
.wm-edge path { stroke: #cbd5e1; stroke-width: 1.25; fill: none; stroke-linecap: round; stroke-linejoin: round; }
|
|
9401
|
+
.wm-edge .wm-arrow-head { fill: #cbd5e1; stroke: none; }
|
|
9402
|
+
.wm-edge path.is-source { stroke: #cbd5e1; }
|
|
9403
|
+
.wm-edge path.is-warn { stroke: var(--dm-warn-line); stroke-dasharray: 4 4; }
|
|
9404
|
+
.wm-node { position: absolute; width: 220px; border: 1px solid var(--dm-line); border-radius: var(--dm-radius-lg); background: var(--dm-surface); box-shadow: 0 1px 2px rgba(20,28,46,.06); cursor: pointer; transition: box-shadow .12s, border-color .12s, transform .12s; text-align: left; padding: 0; font: inherit; }
|
|
9405
|
+
.wm-node:hover { border-color: #cfd6df; box-shadow: var(--dm-shadow-pop); transform: translateY(-1px); }
|
|
9406
|
+
.wm-node.is-selected { border-color: var(--dm-accent); box-shadow: 0 0 0 3px var(--dm-accent-soft); }
|
|
9407
|
+
.wm-node-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--dm-line-soft); }
|
|
9408
|
+
.wm-node-icon { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 7px; background: var(--dm-surface-soft); color: var(--dm-muted); flex: 0 0 auto; }
|
|
9409
|
+
.wm-node-title { flex: 1; min-width: 0; font-size: 13px; font-weight: 650; color: var(--dm-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9410
|
+
.wm-node-body { padding: 9px 12px; display: grid; gap: 4px; }
|
|
9411
|
+
.wm-node-stat { font-size: 12px; color: var(--dm-muted); font-variant-numeric: tabular-nums; }
|
|
9412
|
+
.wm-node-fields { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
|
|
9413
|
+
.wm-node-field { font-size: 10.5px; color: var(--dm-faint); background: var(--dm-surface-soft); border: 1px solid var(--dm-line-soft); border-radius: 4px; padding: 1px 5px; }
|
|
9414
|
+
.wm-node-warn { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--dm-warn); }
|
|
9415
|
+
.wm-zoom { position: sticky; bottom: 12px; left: 12px; display: inline-flex; gap: 4px; padding: 4px; border: 1px solid var(--dm-line); border-radius: 999px; background: var(--dm-surface); box-shadow: var(--dm-shadow-pop); width: fit-content; margin: 12px; }
|
|
9416
|
+
.wm-zoom button { width: 28px; height: 28px; border: 0; border-radius: 999px; background: transparent; color: var(--dm-muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
|
|
9417
|
+
.wm-zoom button:hover { background: var(--dm-hover); color: var(--dm-ink); }
|
|
9418
|
+
.wm-empty { display: grid; place-items: center; gap: 8px; padding: 64px 24px; text-align: center; color: var(--dm-muted); }
|
|
9419
|
+
.wm-empty strong { font-size: 16px; color: var(--dm-ink); }
|
|
9420
|
+
/* Read-only selected-node detail panel — additive, docks top-right of canvas. */
|
|
9421
|
+
.wm-detail { position: sticky; top: 12px; margin: -12px 12px 12px auto; float: right; width: 248px; background: var(--dm-surface); border: 1px solid var(--dm-line); border-radius: var(--dm-radius-lg); box-shadow: var(--dm-shadow-pop); z-index: 5; }
|
|
9422
|
+
.wm-detail-head { display: flex; align-items: center; gap: 8px; padding: 10px 10px 10px 12px; border-bottom: 1px solid var(--dm-line-soft); }
|
|
9423
|
+
.wm-detail-title { flex: 1; min-width: 0; font-size: 13px; font-weight: 650; color: var(--dm-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9424
|
+
.wm-detail-close { width: 24px; height: 24px; border: 0; border-radius: 6px; background: transparent; color: var(--dm-faint); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
|
|
9425
|
+
.wm-detail-close:hover { background: var(--dm-hover); color: var(--dm-ink); }
|
|
9426
|
+
.wm-detail-meta { margin: 0; padding: 10px 12px; display: grid; gap: 6px; }
|
|
9427
|
+
.wm-detail-row { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
|
|
9428
|
+
.wm-detail-row dt { color: var(--dm-faint); }
|
|
9429
|
+
.wm-detail-row dd { margin: 0; color: var(--dm-ink); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9430
|
+
.wm-detail-cta { margin: 0 12px 12px; width: calc(100% - 24px); justify-content: center; }
|
|
9431
|
+
|
|
9432
|
+
/* Per-node run-status pill on the Workflow Canvas — docked outside the node's
|
|
9433
|
+
top-right corner. Driven by the general orchestration stream
|
|
9434
|
+
(orchestration.node.* deltas) + the persisted nodeTrace; clickable to open
|
|
9435
|
+
the Live Runs trace. */
|
|
9436
|
+
.dm-orchestration-node { position: relative; overflow: visible; }
|
|
9437
|
+
.dm-orchestration-canvas__step { overflow: visible; }
|
|
9438
|
+
.dm-orchestration-node__status { position: absolute; top: -18px; right: 14px; z-index: 3; height: 16px; min-height: 16px; padding: 0 6px; gap: 3px; border: 1px solid var(--dm-line); background: var(--dm-surface); box-shadow: 0 1px 3px rgba(20,28,46,.12); cursor: pointer; font-size: 9.5px; font-weight: 700; line-height: 1; transform: translateY(-2px); }
|
|
9439
|
+
.dm-orchestration-node__status > .dm-status-dot { width: 5px; height: 5px; }
|
|
9440
|
+
.dm-orchestration-node__status:hover { box-shadow: 0 2px 6px rgba(20,28,46,.2); }
|
|
9441
|
+
.dm-orchestration-node__status:focus-visible { outline: 2px solid var(--dm-accent); outline-offset: 1px; }
|
|
9442
|
+
|
|
9443
|
+
/* ── Table empty / no-results state (P1) ──────────────────────────────── */
|
|
9444
|
+
.dm-db-empty-row td { padding: 0 !important; background: var(--dm-surface) !important; cursor: default !important; }
|
|
9445
|
+
.dm-db-empty-row:hover td { background: var(--dm-surface) !important; }
|
|
9446
|
+
.dm-db-empty-state { display: grid; justify-items: center; gap: 6px; padding: 48px 24px; text-align: center; }
|
|
9447
|
+
.dm-db-empty-state strong { font-size: 15px; color: var(--dm-ink); font-weight: 650; }
|
|
9448
|
+
.dm-db-empty-state span { font-size: 12.5px; color: var(--dm-muted); max-width: 360px; }
|
|
9449
|
+
.dm-db-empty-actions { display: inline-flex; gap: 8px; margin-top: 8px; }
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
validateOrchestrationGraph
|
|
45
45
|
} from "@/lib/orchestration-graph";
|
|
46
46
|
import { resolveConnectorAction } from "@/lib/orchestration-sidecar-routing";
|
|
47
|
+
import { deriveOrchestrationNodeStatuses } from "@/lib/orchestration-node-status";
|
|
47
48
|
import {
|
|
48
49
|
nodeSandboxRecordRef,
|
|
49
50
|
patchSandboxRowInConfig,
|
|
@@ -83,6 +84,45 @@ function withUiCacheFlag(workspaceConfig, flag, value) {
|
|
|
83
84
|
return { ...workspaceConfig, dataModel: { ...dm, objects: nextObjects } };
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
// Read the sandbox-run NDJSON delta stream (same shape SwarmRunCockpit
|
|
88
|
+
// consumes): push each growthub-sandbox-run-delta-v1 event to `onEvent` for
|
|
89
|
+
// live canvas hydration, and return the sandbox-run.final payload (the run
|
|
90
|
+
// result). Falls back to plain JSON when the response is not a stream.
|
|
91
|
+
async function readSandboxRunStream(response, onEvent) {
|
|
92
|
+
if (!response?.body || typeof response.body.getReader !== "function") {
|
|
93
|
+
return response.json().catch(() => null);
|
|
94
|
+
}
|
|
95
|
+
const reader = response.body.getReader();
|
|
96
|
+
const decoder = new TextDecoder();
|
|
97
|
+
let buffer = "";
|
|
98
|
+
let finalPayload = null;
|
|
99
|
+
const handle = async (line) => {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
if (!trimmed) return;
|
|
102
|
+
try {
|
|
103
|
+
const event = JSON.parse(trimmed);
|
|
104
|
+
if (event.kind !== "growthub-sandbox-run-delta-v1") return;
|
|
105
|
+
if (event.type === "sandbox-run.final") finalPayload = event.payload || finalPayload;
|
|
106
|
+
else if (typeof onEvent === "function") {
|
|
107
|
+
onEvent((prev) => [...prev, event].slice(-300));
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 90));
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// Ignore malformed cosmetic chunks; the final payload still arrives.
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
while (true) {
|
|
115
|
+
const { value, done } = await reader.read();
|
|
116
|
+
if (done) break;
|
|
117
|
+
buffer += decoder.decode(value, { stream: true });
|
|
118
|
+
const lines = buffer.split("\n");
|
|
119
|
+
buffer = lines.pop() || "";
|
|
120
|
+
for (const line of lines) await handle(line);
|
|
121
|
+
}
|
|
122
|
+
if (buffer.trim()) await handle(buffer);
|
|
123
|
+
return finalPayload;
|
|
124
|
+
}
|
|
125
|
+
|
|
86
126
|
// Workspace Metadata Graph V1 — read-only dependency metadata for workflow
|
|
87
127
|
// sidecars. The runtime path (sandbox-run, publish, draft/live) is
|
|
88
128
|
// unchanged; this only exposes typed dependency descriptors so the sidecar
|
|
@@ -281,6 +321,7 @@ export default function WorkflowSurface() {
|
|
|
281
321
|
const [publishing, setPublishing] = useState(false);
|
|
282
322
|
const [saveMessage, setSaveMessage] = useState("");
|
|
283
323
|
const [running, setRunning] = useState(false);
|
|
324
|
+
const [liveRunEvents, setLiveRunEvents] = useState([]);
|
|
284
325
|
const [runMessage, setRunMessage] = useState("");
|
|
285
326
|
const [sidecarMode, setSidecarMode] = useState(runId ? "trace" : "graph");
|
|
286
327
|
|
|
@@ -327,12 +368,29 @@ export default function WorkflowSurface() {
|
|
|
327
368
|
|
|
328
369
|
useEffect(() => { load(); }, [load]);
|
|
329
370
|
|
|
371
|
+
// Reset live per-node deltas when the active workflow changes, so a prior
|
|
372
|
+
// run's stream never bleeds onto a different workflow's canvas — the new
|
|
373
|
+
// workflow settles from its own persisted nodeTrace until it is run.
|
|
374
|
+
useEffect(() => { setLiveRunEvents([]); }, [objectId, rowId]);
|
|
375
|
+
|
|
330
376
|
const resolved = useMemo(
|
|
331
377
|
() => (workspaceConfig ? findSandboxRowByWorkflowRef(workspaceConfig, objectId, rowId) : { object: null, row: null, rowIndex: -1 }),
|
|
332
378
|
[workspaceConfig, objectId, rowId]
|
|
333
379
|
);
|
|
334
380
|
|
|
335
381
|
const sandboxRow = resolved.row;
|
|
382
|
+
|
|
383
|
+
// Per-node Workflow Canvas pill status — GENERAL orchestration (not swarm).
|
|
384
|
+
// Live from the streamed orchestration.node.* deltas while a run is in
|
|
385
|
+
// flight; settled from the persisted run record's nodeTrace once complete.
|
|
386
|
+
const runNodeStatuses = useMemo(() => {
|
|
387
|
+
let record = sandboxRow?.lastResponse;
|
|
388
|
+
if (typeof record === "string") {
|
|
389
|
+
try { record = JSON.parse(record); } catch { record = null; }
|
|
390
|
+
}
|
|
391
|
+
const map = deriveOrchestrationNodeStatuses({ events: liveRunEvents, record });
|
|
392
|
+
return Object.keys(map).length ? map : null;
|
|
393
|
+
}, [liveRunEvents, sandboxRow]);
|
|
336
394
|
const hasGraphValue = (value) => Boolean(parseOrchestrationGraph(value));
|
|
337
395
|
const effectiveFieldName = hasGraphValue(sandboxRow?.[fieldName])
|
|
338
396
|
? fieldName
|
|
@@ -564,17 +622,21 @@ export default function WorkflowSurface() {
|
|
|
564
622
|
: null;
|
|
565
623
|
setRunning(true);
|
|
566
624
|
setRunMessage("");
|
|
625
|
+
setLiveRunEvents([]);
|
|
567
626
|
try {
|
|
568
627
|
const draft = await saveDraft({ orchestrationDraftStatus: "testing" });
|
|
569
628
|
const draftGraph = draft?.serialized || serializeCurrentGraph();
|
|
570
|
-
const body = { objectId, name: rowId, useDraft: true, draftGraph };
|
|
629
|
+
const body = { objectId, name: rowId, useDraft: true, draftGraph, stream: true };
|
|
571
630
|
if (runInputs) body.runInputs = runInputs;
|
|
572
631
|
const res = await fetch("/api/workspace/sandbox-run", {
|
|
573
632
|
method: "POST",
|
|
574
|
-
headers: { "content-type": "application/json" },
|
|
633
|
+
headers: { "content-type": "application/json", accept: "application/x-ndjson" },
|
|
575
634
|
body: JSON.stringify(body),
|
|
576
635
|
});
|
|
577
|
-
|
|
636
|
+
// Consume the NDJSON delta stream: per-node orchestration.node.* events
|
|
637
|
+
// hydrate the canvas pills live; the sandbox-run.final payload is the
|
|
638
|
+
// run result we persist. Falls back to plain JSON if not a stream.
|
|
639
|
+
const payload = (await readSandboxRunStream(res, setLiveRunEvents)) || {};
|
|
578
640
|
const responseText = redactSecretsFromText(JSON.stringify(payload.response ?? payload, null, 2));
|
|
579
641
|
const status = payload.ok && String(payload.status || "").toLowerCase() === "connected" ? "connected" : "failed";
|
|
580
642
|
const pass = isPassingRun(payload);
|
|
@@ -1042,6 +1104,8 @@ export default function WorkflowSurface() {
|
|
|
1042
1104
|
setConfigTab("node");
|
|
1043
1105
|
}}
|
|
1044
1106
|
onConnectorAction={handleConnectorAction}
|
|
1107
|
+
nodeStatuses={runNodeStatuses}
|
|
1108
|
+
onNodeStatusClick={(node) => { setSelectedNodeId(String(node?.id || "")); openTraceMode(); }}
|
|
1045
1109
|
statusLabel={isDraftMode ? "Draft" : "Live"}
|
|
1046
1110
|
/>
|
|
1047
1111
|
{nextNodeId && (
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Suspense } from "react";
|
|
4
|
+
import WorkspaceDataModelCanvas from "../data-model/components/WorkspaceDataModelCanvas.jsx";
|
|
5
|
+
|
|
6
|
+
// Read-only workspace-level schema canvas. No mutation lane, no new runtime —
|
|
7
|
+
// it reads /api/workspace and renders the derived metadata graph.
|
|
8
|
+
export default function WorkspaceMapPage() {
|
|
9
|
+
return (
|
|
10
|
+
<Suspense fallback={null}>
|
|
11
|
+
<WorkspaceDataModelCanvas />
|
|
12
|
+
</Suspense>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -301,11 +301,43 @@ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs,
|
|
|
301
301
|
const inputPayload = { ...baseInputPayload, ...manualPayload };
|
|
302
302
|
const consumedInputKeys = Object.keys(manualPayload);
|
|
303
303
|
const transformConfig = extractTransformConfig(graph);
|
|
304
|
+
const transformNode = (Array.isArray(graph.nodes) ? graph.nodes : [])
|
|
305
|
+
.find((n) => n?.type === "transform-filter" || n?.type === "normalize-output");
|
|
304
306
|
const resultNode = graph.nodes?.find((n) => n?.type === "tool-result");
|
|
305
307
|
const successCodes = Array.isArray(resultNode?.config?.successStatusCodes)
|
|
306
308
|
? resultNode.config.successStatusCodes.map(Number).filter(Number.isFinite)
|
|
307
309
|
: [200];
|
|
308
310
|
|
|
311
|
+
// Per-node execution deltas for the GENERAL orchestration pipeline (not
|
|
312
|
+
// swarm-specific): stream node lifecycle through the same onEvent NDJSON hook
|
|
313
|
+
// the route already wires, and record a terminal nodeTrace persisted on the
|
|
314
|
+
// run record. Every event corresponds to a real pipeline stage executing.
|
|
315
|
+
const onEvent = typeof executionContext?.onEvent === "function" ? executionContext.onEvent : null;
|
|
316
|
+
const runId = String(executionContext?.runId || "").trim();
|
|
317
|
+
const nodeTrace = [];
|
|
318
|
+
const emitNode = (nodeId, status, error) => {
|
|
319
|
+
const id = String(nodeId || "").trim();
|
|
320
|
+
if (!id) return;
|
|
321
|
+
if (status === "completed" || status === "failed" || status === "skipped") {
|
|
322
|
+
nodeTrace.push(error ? { id, status, error: String(error) } : { id, status });
|
|
323
|
+
}
|
|
324
|
+
if (onEvent) {
|
|
325
|
+
onEvent({
|
|
326
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
327
|
+
type: `orchestration.node.${status}`,
|
|
328
|
+
nodeId: id,
|
|
329
|
+
runId,
|
|
330
|
+
emittedAt: new Date().toISOString(),
|
|
331
|
+
...(error ? { error: String(error) } : {})
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Input stage — payload is assembled above; it ran.
|
|
337
|
+
if (inputNode?.id) { emitNode(inputNode.id, "started"); emitNode(inputNode.id, "completed"); }
|
|
338
|
+
|
|
339
|
+
// API Registry call stage.
|
|
340
|
+
if (apiNode?.id) emitNode(apiNode.id, "started");
|
|
309
341
|
const raw = await executeApiRegistryCall(
|
|
310
342
|
workspaceConfig,
|
|
311
343
|
apiNode.config,
|
|
@@ -320,14 +352,29 @@ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs,
|
|
|
320
352
|
raw.exitCode = 1;
|
|
321
353
|
raw.error = `HTTP ${httpStatus} is not in successStatusCodes`;
|
|
322
354
|
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (apiNode?.id) {
|
|
358
|
+
if (raw.ok) emitNode(apiNode.id, "completed");
|
|
359
|
+
else emitNode(apiNode.id, "failed", raw.error);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (raw.ok && raw.rawPayload !== undefined) {
|
|
363
|
+
if (transformNode?.id) emitNode(transformNode.id, "started");
|
|
323
364
|
const transformed = transformProviderPayload(raw.rawPayload, transformConfig);
|
|
324
365
|
raw.stdout = typeof transformed === "string"
|
|
325
366
|
? transformed
|
|
326
367
|
: normalizeJsonAtPath(transformed, "");
|
|
327
368
|
delete raw.rawPayload;
|
|
328
369
|
delete raw.httpStatus;
|
|
370
|
+
if (transformNode?.id) emitNode(transformNode.id, "completed");
|
|
371
|
+
if (resultNode?.id) { emitNode(resultNode.id, "started"); emitNode(resultNode.id, "completed"); }
|
|
329
372
|
} else if (raw.error) {
|
|
330
373
|
raw.error = redactSecretsFromText(raw.error);
|
|
374
|
+
// Downstream stages never executed → record them as skipped (not-run), not
|
|
375
|
+
// failed: only the api stage actually failed.
|
|
376
|
+
if (transformNode?.id) emitNode(transformNode.id, "skipped");
|
|
377
|
+
if (resultNode?.id) emitNode(resultNode.id, "skipped");
|
|
331
378
|
}
|
|
332
379
|
|
|
333
380
|
if (raw.stdout) {
|
|
@@ -336,6 +383,7 @@ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs,
|
|
|
336
383
|
|
|
337
384
|
return {
|
|
338
385
|
...raw,
|
|
386
|
+
nodeTrace,
|
|
339
387
|
adapterMeta: {
|
|
340
388
|
...(raw.adapterMeta || {}),
|
|
341
389
|
orchestrationProvider: graph.provider,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration per-node run status — GENERAL (not swarm).
|
|
3
|
+
*
|
|
4
|
+
* Maps the real per-node execution signal of a sandbox/orchestration run onto
|
|
5
|
+
* a plain { nodeId: status } map the Workflow Canvas pills consume:
|
|
6
|
+
*
|
|
7
|
+
* - live: growthub-sandbox-run-delta-v1 events of type
|
|
8
|
+
* `orchestration.node.{started|completed|failed|skipped}` streamed
|
|
9
|
+
* from POST /api/workspace/sandbox-run while the run is in flight.
|
|
10
|
+
* - settled: the persisted run record's `nodeTrace` (written by the
|
|
11
|
+
* orchestration runner) once the run completes.
|
|
12
|
+
*
|
|
13
|
+
* This is the general orchestration pipeline signal (input → api-registry-call
|
|
14
|
+
* → transform → tool-result, human-input, etc.) — distinct from the swarm
|
|
15
|
+
* cockpit projection. Each entry corresponds to a real pipeline stage that
|
|
16
|
+
* executed; nothing is fabricated. Pure, never throws.
|
|
17
|
+
*
|
|
18
|
+
* Status vocabulary returned: "running" | "completed" | "failed" | "skipped".
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const NODE_EVENT_PREFIX = "orchestration.node.";
|
|
22
|
+
|
|
23
|
+
function deriveOrchestrationNodeStatuses({ events, record } = {}) {
|
|
24
|
+
const out = {};
|
|
25
|
+
try {
|
|
26
|
+
// Live events win while a run streams — later events overwrite earlier.
|
|
27
|
+
const list = Array.isArray(events) ? events : [];
|
|
28
|
+
for (const event of list) {
|
|
29
|
+
if (!event || typeof event !== "object") continue;
|
|
30
|
+
if (event.kind && event.kind !== "growthub-sandbox-run-delta-v1") continue;
|
|
31
|
+
const type = String(event.type || "");
|
|
32
|
+
if (!type.startsWith(NODE_EVENT_PREFIX)) continue;
|
|
33
|
+
const id = String(event.nodeId || "").trim();
|
|
34
|
+
if (!id) continue;
|
|
35
|
+
const phase = type.slice(NODE_EVENT_PREFIX.length);
|
|
36
|
+
out[id] = phase === "started" ? "running" : phase;
|
|
37
|
+
}
|
|
38
|
+
if (Object.keys(out).length) return out;
|
|
39
|
+
|
|
40
|
+
// Settled from the persisted per-node trace.
|
|
41
|
+
const trace = record && Array.isArray(record.nodeTrace) ? record.nodeTrace : [];
|
|
42
|
+
for (const entry of trace) {
|
|
43
|
+
if (!entry || typeof entry !== "object") continue;
|
|
44
|
+
const id = String(entry.id || "").trim();
|
|
45
|
+
if (!id) continue;
|
|
46
|
+
const status = String(entry.status || "").trim();
|
|
47
|
+
if (status) out[id] = status;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
} catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { deriveOrchestrationNodeStatuses };
|