@growthub/cli 0.13.9 → 0.14.1
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/apps/workspace/app/api/workspace/env-status/route.js +31 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +227 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +70 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +17 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +6 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +61 -35
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +414 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +339 -77
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +81 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +70 -85
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SidecarExpandView.jsx +37 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SwarmRunCockpit.jsx +625 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +150 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +229 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +224 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +2 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-agent-host.js +139 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +4 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +317 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-response-profile.js +207 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/creation-error-recovery.js +103 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +100 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +246 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +69 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +411 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +215 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-write.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-upgrade.js +89 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +11 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +30 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +8 -6
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-swarm-proposal.js +551 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -1
- package/package.json +1 -1
|
@@ -47,6 +47,14 @@ import {
|
|
|
47
47
|
isHelperConfigured,
|
|
48
48
|
WorkspaceHelperSetupModal,
|
|
49
49
|
} from "../../components/WorkspaceHelperSetupModal.jsx";
|
|
50
|
+
import { SwarmRunCockpit, SwarmAgentTranscript } from "./SwarmRunCockpit.jsx";
|
|
51
|
+
import { SidecarExpandView } from "./SidecarExpandView.jsx";
|
|
52
|
+
import { parseSlashInput } from "./helper-commands.js";
|
|
53
|
+
import {
|
|
54
|
+
deriveHelperWidgetCausationState,
|
|
55
|
+
summarizeSwarmRunProposal,
|
|
56
|
+
SWARM_WORKFLOWS_OBJECT_ID,
|
|
57
|
+
} from "@/lib/workspace-swarm-proposal";
|
|
50
58
|
|
|
51
59
|
// Generic "Tool Call Output" title matches the reference grammar — the
|
|
52
60
|
// user already sees the prompt + assistant response in the chat above,
|
|
@@ -69,6 +77,7 @@ function ToolCallCard({ proposal, content, onOpenArtifact }) {
|
|
|
69
77
|
rationale: proposal?.rationale,
|
|
70
78
|
confidence: proposal?.confidence,
|
|
71
79
|
};
|
|
80
|
+
const showJson = meta.payload != null || meta.affectedField || meta.rationale;
|
|
72
81
|
return (
|
|
73
82
|
<div className="dm-helper-toolcall" data-toolcall-type={proposal?.type}>
|
|
74
83
|
<button
|
|
@@ -89,9 +98,11 @@ function ToolCallCard({ proposal, content, onOpenArtifact }) {
|
|
|
89
98
|
{open && (
|
|
90
99
|
<div className="dm-helper-toolcall-body">
|
|
91
100
|
{content && <div className="dm-helper-toolcall-content">{content}</div>}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
{showJson && (
|
|
102
|
+
<pre className="dm-helper-toolcall-json">
|
|
103
|
+
{JSON.stringify(meta, null, 2)}
|
|
104
|
+
</pre>
|
|
105
|
+
)}
|
|
95
106
|
</div>
|
|
96
107
|
)}
|
|
97
108
|
{canNavigate && (
|
|
@@ -108,6 +119,55 @@ function ToolCallCard({ proposal, content, onOpenArtifact }) {
|
|
|
108
119
|
);
|
|
109
120
|
}
|
|
110
121
|
|
|
122
|
+
function formatRunDuration(ms) {
|
|
123
|
+
const value = Number(ms);
|
|
124
|
+
if (!Number.isFinite(value) || value < 0) return "";
|
|
125
|
+
const totalSeconds = Math.max(0, Math.round(value / 1000));
|
|
126
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
127
|
+
const seconds = totalSeconds % 60;
|
|
128
|
+
if (minutes <= 0) return `${seconds}s`;
|
|
129
|
+
return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function ProposalReviewCard({ proposal, checked, disabled, onCheckedChange, onKeyDown }) {
|
|
133
|
+
const [open, setOpen] = useState(false);
|
|
134
|
+
const summary = summarizePayload(proposal);
|
|
135
|
+
return (
|
|
136
|
+
<div className="dm-helper-toolcall" data-proposal-item="" tabIndex={0} onKeyDown={onKeyDown}>
|
|
137
|
+
<div className="dm-helper-toolcall-row dm-helper-proposal-card-row">
|
|
138
|
+
<input
|
|
139
|
+
type="checkbox"
|
|
140
|
+
checked={!!checked}
|
|
141
|
+
onChange={(e) => onCheckedChange(e.target.checked)}
|
|
142
|
+
disabled={disabled}
|
|
143
|
+
data-proposal-accept=""
|
|
144
|
+
aria-label={`Select ${proposal.type}`}
|
|
145
|
+
/>
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
className="dm-helper-proposal-card-toggle"
|
|
149
|
+
onClick={() => setOpen((v) => !v)}
|
|
150
|
+
aria-expanded={open}
|
|
151
|
+
>
|
|
152
|
+
<span className="dm-helper-toolcall-title">{proposal.type}</span>
|
|
153
|
+
<span className="dm-helper-proposal-field">→ {proposal.affectedField}</span>
|
|
154
|
+
<ChevronDown
|
|
155
|
+
size={14}
|
|
156
|
+
className={`dm-helper-toolcall-chevron${open ? " is-open" : ""}`}
|
|
157
|
+
aria-hidden="true"
|
|
158
|
+
/>
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
{open && (
|
|
162
|
+
<div className="dm-helper-toolcall-body">
|
|
163
|
+
{summary && <p className="dm-helper-proposal-payload" data-proposal-payload="">{summary}</p>}
|
|
164
|
+
{proposal.rationale && <p className="dm-helper-proposal-rationale">{proposal.rationale}</p>}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
111
171
|
// Pair a system apply-receipt message with the actual proposal payload
|
|
112
172
|
// it confirms. The applyResult (rehydrated from row.lastApplied at thread
|
|
113
173
|
// load time) carries the typed payloads keyed in order — we walk the
|
|
@@ -139,8 +199,16 @@ function resolveSystemReceipt(message, applyResult) {
|
|
|
139
199
|
// Resolve where the Open button should navigate based on the proposal
|
|
140
200
|
// shape. Returns null when no navigation makes sense (e.g. explain.object).
|
|
141
201
|
function resolveArtifactTarget(proposal) {
|
|
202
|
+
// Apply receipts in the swarm lane carry an explicit artifact target.
|
|
203
|
+
if (proposal?.artifact?.surface === "swarm-run") return proposal.artifact;
|
|
142
204
|
const pl = proposal?.payload || {};
|
|
143
205
|
switch (proposal?.type) {
|
|
206
|
+
case "swarm.run.propose":
|
|
207
|
+
case "swarm.workflow.save":
|
|
208
|
+
case "swarm.run.resume":
|
|
209
|
+
return pl.name
|
|
210
|
+
? { surface: "swarm-run", objectId: SWARM_WORKFLOWS_OBJECT_ID, name: pl.name }
|
|
211
|
+
: null;
|
|
144
212
|
case "dataModel.object.create":
|
|
145
213
|
case "dataModel.object.update":
|
|
146
214
|
return pl.label || pl.id ? { surface: "data-model", source: pl.label || pl.id } : null;
|
|
@@ -269,12 +337,16 @@ function summarizePayload(proposal) {
|
|
|
269
337
|
return p.objectId ? `binding for: ${p.objectId}` : "";
|
|
270
338
|
case "explain.object":
|
|
271
339
|
return p.target ? `about: ${p.target}` : "informational";
|
|
340
|
+
case "swarm.run.propose":
|
|
341
|
+
case "swarm.workflow.save":
|
|
342
|
+
case "swarm.run.resume":
|
|
343
|
+
return summarizeSwarmRunProposal(proposal);
|
|
272
344
|
default:
|
|
273
345
|
return "";
|
|
274
346
|
}
|
|
275
347
|
}
|
|
276
348
|
|
|
277
|
-
export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, initialPrompt, initialThread, onApplied, onOpenArtifact }) {
|
|
349
|
+
export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, initialPrompt, initialThread, onApplied, onOpenArtifact, onOpenSwarmWorkflow }) {
|
|
278
350
|
const [activeTab, setActiveTab] = useState("assistant");
|
|
279
351
|
const [intent, setIntent] = useState(initialIntent || "create_object");
|
|
280
352
|
const [prompt, setPrompt] = useState(initialPrompt || "");
|
|
@@ -300,6 +372,22 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
300
372
|
const [moreOpen, setMoreOpen] = useState(false);
|
|
301
373
|
const moreMenuRef = useRef(null);
|
|
302
374
|
|
|
375
|
+
// Sidecar internal view (SWARM_RUN_CONTRACT_V1) — the swarm cockpit lives
|
|
376
|
+
// INSIDE the helper sidecar, no new route. "chat" is the existing
|
|
377
|
+
// conversation surface; swarm views render the governed run cockpit;
|
|
378
|
+
// "tool-output" is the expanded transcript frame.
|
|
379
|
+
const [activeView, setActiveView] = useState("chat");
|
|
380
|
+
// Focused workflow row when opened from an apply receipt's Open button.
|
|
381
|
+
const [swarmFocus, setSwarmFocus] = useState(null);
|
|
382
|
+
// Expanded transcript agent (tool-output view) + full-width takeover flag.
|
|
383
|
+
const [expandedAgent, setExpandedAgent] = useState(null);
|
|
384
|
+
const [expandActive, setExpandActive] = useState(false);
|
|
385
|
+
const priorViewRef = useRef("swarm-list");
|
|
386
|
+
// Slash menu — active index; "dismissed" suppresses the menu until the
|
|
387
|
+
// prompt changes again (Esc behavior).
|
|
388
|
+
const [slashIndex, setSlashIndex] = useState(0);
|
|
389
|
+
const [slashDismissed, setSlashDismissed] = useState(false);
|
|
390
|
+
|
|
303
391
|
// Setup tab state
|
|
304
392
|
const [connectionStatus, setConnectionStatus] = useState(null);
|
|
305
393
|
const [pingLoading, setPingLoading] = useState(false);
|
|
@@ -412,6 +500,12 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
412
500
|
setThreadId(null);
|
|
413
501
|
setMessages([]);
|
|
414
502
|
setMoreOpen(false);
|
|
503
|
+
setActiveView("chat");
|
|
504
|
+
setSwarmFocus(null);
|
|
505
|
+
setExpandedAgent(null);
|
|
506
|
+
setExpandActive(false);
|
|
507
|
+
setSlashIndex(0);
|
|
508
|
+
setSlashDismissed(false);
|
|
415
509
|
}
|
|
416
510
|
}, [open]);
|
|
417
511
|
|
|
@@ -426,13 +520,27 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
426
520
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
427
521
|
}, [moreOpen]);
|
|
428
522
|
|
|
429
|
-
// Escape key
|
|
523
|
+
// Escape key — expand mode collapses back to the prior sidecar view first;
|
|
524
|
+
// a second Escape (or Escape outside expand mode) closes the sidecar as
|
|
525
|
+
// before. Predictable, no scroll trap.
|
|
430
526
|
useEffect(() => {
|
|
431
527
|
if (!open) return undefined;
|
|
432
|
-
const handler = (e) => {
|
|
528
|
+
const handler = (e) => {
|
|
529
|
+
if (e.key !== "Escape") return;
|
|
530
|
+
if (expandActive) {
|
|
531
|
+
e.preventDefault();
|
|
532
|
+
setExpandActive(false);
|
|
533
|
+
if (activeView === "tool-output") {
|
|
534
|
+
setActiveView(priorViewRef.current || "swarm-list");
|
|
535
|
+
setExpandedAgent(null);
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
onClose();
|
|
540
|
+
};
|
|
433
541
|
window.addEventListener("keydown", handler);
|
|
434
542
|
return () => window.removeEventListener("keydown", handler);
|
|
435
|
-
}, [open, onClose]);
|
|
543
|
+
}, [open, onClose, expandActive, activeView]);
|
|
436
544
|
|
|
437
545
|
// Cmd+Enter at the window level fires apply when there is a result with
|
|
438
546
|
// accepted proposals. The textarea handler stops propagation when the
|
|
@@ -489,6 +597,11 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
489
597
|
|
|
490
598
|
async function runQuery() {
|
|
491
599
|
if (!prompt.trim() || streaming) return;
|
|
600
|
+
if (!helperWidgetState.ready) {
|
|
601
|
+
setQueryError(helperWidgetState.guidance);
|
|
602
|
+
setActiveTab("chat");
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
492
605
|
setResult(null);
|
|
493
606
|
setQueryError("");
|
|
494
607
|
setStreamBuffer("");
|
|
@@ -620,6 +733,10 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
620
733
|
|
|
621
734
|
const sandboxRow = resolveSandboxEnvRow(workspaceConfig);
|
|
622
735
|
const helperAgentConfigured = isHelperConfigured(workspaceConfig);
|
|
736
|
+
const helperWidgetState = useMemo(
|
|
737
|
+
() => deriveHelperWidgetCausationState(workspaceConfig),
|
|
738
|
+
[workspaceConfig]
|
|
739
|
+
);
|
|
623
740
|
const liveModel = sandboxRow?.localModel || "";
|
|
624
741
|
const liveEndpoint = sandboxRow?.localEndpoint || "";
|
|
625
742
|
const liveAdapter = sandboxRow?.intelligenceAdapterMode || "ollama";
|
|
@@ -747,6 +864,11 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
747
864
|
const acceptedCount = Object.values(accepted).filter(Boolean).length;
|
|
748
865
|
const skippedCount = applyResult?.skipped?.length || 0;
|
|
749
866
|
const hasProposals = result && (result.proposals || []).length > 0;
|
|
867
|
+
const visibleWarnings = (result?.warnings || []).filter((warning) => {
|
|
868
|
+
const text = String(warning || "");
|
|
869
|
+
return !/transcript does not include the actual registry row id or lastResponse payload/i.test(text)
|
|
870
|
+
&& !/No credentials or env values should be stored/i.test(text);
|
|
871
|
+
});
|
|
750
872
|
|
|
751
873
|
// Thread is "active" the moment the user has sent at least one message,
|
|
752
874
|
// OR we have rehydrated a prior thread row. Pills only show on the
|
|
@@ -761,6 +883,106 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
761
883
|
try { promptRef.current?.focus(); } catch {}
|
|
762
884
|
};
|
|
763
885
|
|
|
886
|
+
// Slash menu state derives from the live prompt. The menu only engages
|
|
887
|
+
// when "/" is the first character (parseSlashInput), never mid-sentence.
|
|
888
|
+
const slash = slashDismissed ? { active: false, query: "", matches: [] } : parseSlashInput(prompt);
|
|
889
|
+
const slashActive = slash.active && slash.matches.length > 0 && !streaming && activeView === "chat";
|
|
890
|
+
|
|
891
|
+
// Selecting a command never mutates config: read-only commands switch the
|
|
892
|
+
// sidecar view or seed a prompt; mutating commands seed a governed
|
|
893
|
+
// proposal request (intent + template) that still travels query → review
|
|
894
|
+
// → apply. No command calls sandbox-run or PATCH directly.
|
|
895
|
+
const selectSlashCommand = (cmd) => {
|
|
896
|
+
if (!helperWidgetState.ready) {
|
|
897
|
+
setQueryError(helperWidgetState.guidance);
|
|
898
|
+
setActiveTab("chat");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
setSlashDismissed(false);
|
|
902
|
+
setSlashIndex(0);
|
|
903
|
+
if (cmd.view) {
|
|
904
|
+
setPrompt("");
|
|
905
|
+
setSwarmFocus(null);
|
|
906
|
+
setActiveView(cmd.view);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (cmd.intent) onPickIntent(cmd.intent);
|
|
910
|
+
setPrompt(cmd.promptTemplate ? `${cmd.promptTemplate} ` : "");
|
|
911
|
+
try { promptRef.current?.focus(); } catch {}
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
const handleSlashKeyDown = (e) => {
|
|
915
|
+
if (!slashActive) return false;
|
|
916
|
+
if (e.key === "ArrowDown") {
|
|
917
|
+
e.preventDefault();
|
|
918
|
+
setSlashIndex((i) => (i + 1) % slash.matches.length);
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
if (e.key === "ArrowUp") {
|
|
922
|
+
e.preventDefault();
|
|
923
|
+
setSlashIndex((i) => (i - 1 + slash.matches.length) % slash.matches.length);
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
if (e.key === "Enter" || e.key === "Tab") {
|
|
927
|
+
e.preventDefault();
|
|
928
|
+
e.stopPropagation();
|
|
929
|
+
selectSlashCommand(slash.matches[Math.min(slashIndex, slash.matches.length - 1)]);
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
if (e.key === "Escape") {
|
|
933
|
+
e.preventDefault();
|
|
934
|
+
e.stopPropagation();
|
|
935
|
+
setSlashDismissed(true);
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
return false;
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// Refresh the page-level workspace config after a sandbox-run stamps run
|
|
942
|
+
// state onto a row — same onApplied channel the apply flow already uses.
|
|
943
|
+
const refreshWorkspaceConfig = async () => {
|
|
944
|
+
try {
|
|
945
|
+
const res = await fetch("/api/workspace");
|
|
946
|
+
const data = await res.json();
|
|
947
|
+
if (data?.workspaceConfig && onApplied) onApplied(data.workspaceConfig);
|
|
948
|
+
} catch {
|
|
949
|
+
// Non-fatal — the cockpit's own history polling still reflects the run.
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
// Open a swarm artifact (apply receipt → Open) inside the sidecar cockpit;
|
|
954
|
+
// every other artifact surface keeps routing through the page-level handler.
|
|
955
|
+
const handleOpenArtifact = (target, proposal) => {
|
|
956
|
+
if (target?.surface === "swarm-run") {
|
|
957
|
+
setSwarmFocus({ objectId: target.objectId, name: target.name });
|
|
958
|
+
setActiveView("swarm-detail");
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (typeof onOpenArtifact === "function") onOpenArtifact(target, proposal);
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
const handleExpandTranscript = (agent) => {
|
|
965
|
+
priorViewRef.current = activeView === "tool-output" ? priorViewRef.current : activeView;
|
|
966
|
+
setExpandedAgent(agent);
|
|
967
|
+
setActiveView("tool-output");
|
|
968
|
+
setExpandActive(true);
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
const collapseExpand = () => {
|
|
972
|
+
setExpandActive(false);
|
|
973
|
+
setActiveView(priorViewRef.current || "swarm-list");
|
|
974
|
+
setExpandedAgent(null);
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
const inSwarmView = activeView === "swarm-list" || activeView === "swarm-detail" || activeView === "tool-output";
|
|
978
|
+
const canOpenSwarmWorkflow = Boolean(
|
|
979
|
+
inSwarmView
|
|
980
|
+
&& activeTab === "assistant"
|
|
981
|
+
&& swarmFocus?.objectId
|
|
982
|
+
&& swarmFocus?.name
|
|
983
|
+
&& typeof onOpenSwarmWorkflow === "function"
|
|
984
|
+
);
|
|
985
|
+
|
|
764
986
|
if (!open) return null;
|
|
765
987
|
|
|
766
988
|
return (
|
|
@@ -773,7 +995,7 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
773
995
|
role="dialog"
|
|
774
996
|
aria-label="Workspace helper"
|
|
775
997
|
aria-modal="true"
|
|
776
|
-
style={{ width: panelWidth }}
|
|
998
|
+
style={{ width: expandActive ? "80vw" : panelWidth }}
|
|
777
999
|
>
|
|
778
1000
|
{/* Drag handle */}
|
|
779
1001
|
<div
|
|
@@ -787,22 +1009,50 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
787
1009
|
{/* Header — title left; gear toggles Assistant ↔ Setup, then close. */}
|
|
788
1010
|
<div className="dm-sidecar-header">
|
|
789
1011
|
<div className="dm-sidecar-header-left">
|
|
1012
|
+
{inSwarmView && (
|
|
1013
|
+
<button
|
|
1014
|
+
type="button"
|
|
1015
|
+
className="dm-sidecar-icon-btn"
|
|
1016
|
+
onClick={() => {
|
|
1017
|
+
if (activeView === "tool-output") { collapseExpand(); return; }
|
|
1018
|
+
setActiveView("chat");
|
|
1019
|
+
setSwarmFocus(null);
|
|
1020
|
+
}}
|
|
1021
|
+
aria-label="Back to chat"
|
|
1022
|
+
title="Back to chat"
|
|
1023
|
+
data-swarm-back=""
|
|
1024
|
+
>
|
|
1025
|
+
<ArrowLeft size={14} />
|
|
1026
|
+
</button>
|
|
1027
|
+
)}
|
|
790
1028
|
<span className="dm-sidecar-title" data-helper-title="">
|
|
791
|
-
{
|
|
792
|
-
?
|
|
793
|
-
:
|
|
1029
|
+
{inSwarmView
|
|
1030
|
+
? "Background tasks"
|
|
1031
|
+
: threadActive
|
|
1032
|
+
? deriveThreadDisplayTitle(initialThread, "Workspace Helper")
|
|
1033
|
+
: "Workspace Helper"}
|
|
794
1034
|
</span>
|
|
795
1035
|
</div>
|
|
796
1036
|
<div className="dm-sidecar-header-right">
|
|
797
1037
|
<button
|
|
798
1038
|
type="button"
|
|
799
1039
|
className="dm-sidecar-icon-btn"
|
|
800
|
-
onClick={() =>
|
|
801
|
-
|
|
802
|
-
|
|
1040
|
+
onClick={() => {
|
|
1041
|
+
if (canOpenSwarmWorkflow) {
|
|
1042
|
+
onOpenSwarmWorkflow(swarmFocus);
|
|
1043
|
+
onClose?.();
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
setActiveTab((current) => (current === "setup" ? "assistant" : "setup"));
|
|
1047
|
+
}}
|
|
1048
|
+
disabled={inSwarmView && activeTab === "assistant" && !canOpenSwarmWorkflow}
|
|
1049
|
+
aria-label={inSwarmView && activeTab === "assistant" ? "Open workflow canvas" : activeTab === "setup" ? "Back" : "Setup"}
|
|
1050
|
+
title={inSwarmView && activeTab === "assistant" ? "Open workflow canvas" : activeTab === "setup" ? "Back" : "Setup"}
|
|
803
1051
|
data-tab={activeTab === "setup" ? "assistant" : "setup"}
|
|
804
1052
|
>
|
|
805
|
-
{activeTab === "
|
|
1053
|
+
{inSwarmView && activeTab === "assistant"
|
|
1054
|
+
? <ArrowUpRight size={14} />
|
|
1055
|
+
: activeTab === "setup" ? <ArrowLeft size={14} /> : <Settings size={14} />}
|
|
806
1056
|
</button>
|
|
807
1057
|
<button
|
|
808
1058
|
type="button"
|
|
@@ -816,11 +1066,31 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
816
1066
|
</div>
|
|
817
1067
|
</div>
|
|
818
1068
|
|
|
1069
|
+
{/* Swarm cockpit views — Background tasks list / detail / expanded
|
|
1070
|
+
transcript. Same sidecar shell, no route change; chat state stays
|
|
1071
|
+
mounted-adjacent and untouched while the user inspects runs. */}
|
|
1072
|
+
{activeTab === "assistant" && inSwarmView && (
|
|
1073
|
+
<div className="dm-sidecar-body dm-swarm-body" data-swarm-view={activeView}>
|
|
1074
|
+
{activeView === "tool-output" && expandedAgent ? (
|
|
1075
|
+
<SidecarExpandView title={expandedAgent.label || "Transcript"} onBack={collapseExpand}>
|
|
1076
|
+
<SwarmAgentTranscript agent={expandedAgent} />
|
|
1077
|
+
</SidecarExpandView>
|
|
1078
|
+
) : (
|
|
1079
|
+
<SwarmRunCockpit
|
|
1080
|
+
workspaceConfig={workspaceConfig}
|
|
1081
|
+
focus={activeView === "swarm-detail" ? swarmFocus : null}
|
|
1082
|
+
onConfigRefresh={refreshWorkspaceConfig}
|
|
1083
|
+
onExpandTranscript={handleExpandTranscript}
|
|
1084
|
+
/>
|
|
1085
|
+
)}
|
|
1086
|
+
</div>
|
|
1087
|
+
)}
|
|
1088
|
+
|
|
819
1089
|
{/* Assistant tab — composer-at-bottom layout (Twenty Ask AI parity):
|
|
820
1090
|
conversation/result area on top (flex:1), bottom-anchored composer
|
|
821
1091
|
holds chip stack (empty state) → mode row (active thread) →
|
|
822
1092
|
textarea with attach + mode + send-arrow action row. */}
|
|
823
|
-
{activeTab === "assistant" && (
|
|
1093
|
+
{activeTab === "assistant" && !inSwarmView && (
|
|
824
1094
|
<div className="dm-sidecar-body dm-helper-body">
|
|
825
1095
|
<div className="dm-helper-conversation" ref={conversationRef}>
|
|
826
1096
|
{/* Conversation — ChatGPT-grade multi-turn. User bubble
|
|
@@ -854,11 +1124,7 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
854
1124
|
<ToolCallCard
|
|
855
1125
|
proposal={receipt}
|
|
856
1126
|
content={m.content || ""}
|
|
857
|
-
onOpenArtifact={(p) =>
|
|
858
|
-
if (typeof onOpenArtifact === "function") {
|
|
859
|
-
onOpenArtifact(resolveArtifactTarget(p), p);
|
|
860
|
-
}
|
|
861
|
-
}}
|
|
1127
|
+
onOpenArtifact={(p) => handleOpenArtifact(resolveArtifactTarget(p), p)}
|
|
862
1128
|
/>
|
|
863
1129
|
</div>
|
|
864
1130
|
);
|
|
@@ -916,18 +1182,9 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
916
1182
|
{/* Proposals */}
|
|
917
1183
|
{result && (
|
|
918
1184
|
<div className="dm-helper-result">
|
|
919
|
-
|
|
920
|
-
<
|
|
921
|
-
|
|
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
|
-
))}
|
|
1185
|
+
{!threadActive && result.summary && (
|
|
1186
|
+
<div className="dm-helper-summary">
|
|
1187
|
+
<span>{result.summary}</span>
|
|
931
1188
|
</div>
|
|
932
1189
|
)}
|
|
933
1190
|
|
|
@@ -943,42 +1200,17 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
943
1200
|
aria-label="Proposals"
|
|
944
1201
|
>
|
|
945
1202
|
{result.proposals.map((proposal, i) => {
|
|
946
|
-
const summary = summarizePayload(proposal);
|
|
947
|
-
const conf = typeof proposal.confidence === "number" ? Math.round(proposal.confidence * 100) : null;
|
|
948
1203
|
return (
|
|
949
|
-
<
|
|
1204
|
+
<ProposalReviewCard
|
|
950
1205
|
key={i}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1206
|
+
proposal={proposal}
|
|
1207
|
+
checked={accepted[i]}
|
|
1208
|
+
disabled={applying}
|
|
1209
|
+
onCheckedChange={(checked) =>
|
|
1210
|
+
setAccepted((prev) => ({ ...prev, [i]: checked }))
|
|
1211
|
+
}
|
|
954
1212
|
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>
|
|
1213
|
+
/>
|
|
982
1214
|
);
|
|
983
1215
|
})}
|
|
984
1216
|
</div>
|
|
@@ -1055,13 +1287,10 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
1055
1287
|
)}
|
|
1056
1288
|
|
|
1057
1289
|
{result.receipts && (
|
|
1058
|
-
<
|
|
1059
|
-
|
|
1060
|
-
{
|
|
1061
|
-
|
|
1062
|
-
: "n/a"}{" "}
|
|
1063
|
-
· {result.receipts.latencyMs}ms
|
|
1064
|
-
</p>
|
|
1290
|
+
<div className="dm-helper-run-meta" data-helper-receipt="">
|
|
1291
|
+
<span>{result.receipts.model || "run"}</span>
|
|
1292
|
+
<span>{formatRunDuration(result.receipts.latencyMs)}</span>
|
|
1293
|
+
</div>
|
|
1065
1294
|
)}
|
|
1066
1295
|
</div>
|
|
1067
1296
|
)}
|
|
@@ -1134,20 +1363,53 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
1134
1363
|
) : null}
|
|
1135
1364
|
|
|
1136
1365
|
<div className="dm-helper-composer-input">
|
|
1366
|
+
{!helperWidgetState.ready && (
|
|
1367
|
+
<div className="dm-helper-error" role="status">
|
|
1368
|
+
<span>{helperWidgetState.guidance}</span>
|
|
1369
|
+
</div>
|
|
1370
|
+
)}
|
|
1371
|
+
{slashActive && (
|
|
1372
|
+
<div className="dm-helper-pill-menu dm-helper-slash-menu" role="listbox" data-helper-slash-menu="">
|
|
1373
|
+
{slash.matches.map((cmd, i) => (
|
|
1374
|
+
<button
|
|
1375
|
+
key={cmd.name}
|
|
1376
|
+
type="button"
|
|
1377
|
+
className={`dm-helper-pill-menu-item${i === Math.min(slashIndex, slash.matches.length - 1) ? " active" : ""}`}
|
|
1378
|
+
role="option"
|
|
1379
|
+
aria-selected={i === slashIndex}
|
|
1380
|
+
data-helper-slash-item={cmd.name}
|
|
1381
|
+
disabled={!helperWidgetState.ready}
|
|
1382
|
+
title={!helperWidgetState.ready ? helperWidgetState.guidance : cmd.description || cmd.label}
|
|
1383
|
+
onClick={() => selectSlashCommand(cmd)}
|
|
1384
|
+
onMouseEnter={() => setSlashIndex(i)}
|
|
1385
|
+
>
|
|
1386
|
+
<span className="dm-helper-slash-name">{cmd.name}</span>
|
|
1387
|
+
<span className="dm-field-hint">{cmd.description || cmd.label}</span>
|
|
1388
|
+
</button>
|
|
1389
|
+
))}
|
|
1390
|
+
</div>
|
|
1391
|
+
)}
|
|
1137
1392
|
<textarea
|
|
1138
1393
|
id="helper-prompt"
|
|
1139
1394
|
ref={promptRef}
|
|
1140
1395
|
className="dm-helper-composer-textarea"
|
|
1141
1396
|
rows={threadActive ? 2 : 3}
|
|
1142
1397
|
placeholder={threadActive
|
|
1143
|
-
? 'Continue
|
|
1144
|
-
: '
|
|
1398
|
+
? 'Continue, or type / for commands…'
|
|
1399
|
+
: 'Type / for commands, or ask anything…'}
|
|
1145
1400
|
value={prompt}
|
|
1146
|
-
onChange={(e) =>
|
|
1401
|
+
onChange={(e) => {
|
|
1402
|
+
setPrompt(e.target.value);
|
|
1403
|
+
setSlashDismissed(false);
|
|
1404
|
+
setSlashIndex(0);
|
|
1405
|
+
}}
|
|
1147
1406
|
disabled={streaming}
|
|
1148
1407
|
data-helper-prompt=""
|
|
1149
1408
|
aria-label="Helper prompt"
|
|
1150
1409
|
onKeyDown={(e) => {
|
|
1410
|
+
// Slash menu intercepts navigation/selection keys while
|
|
1411
|
+
// open; Cmd+Enter submission below stays intact.
|
|
1412
|
+
if (handleSlashKeyDown(e)) return;
|
|
1151
1413
|
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
1152
1414
|
e.preventDefault();
|
|
1153
1415
|
// Stop the window-level apply handler from firing on the
|
|
@@ -1172,7 +1434,7 @@ export function HelperSidecar({ open, onClose, workspaceConfig, initialIntent, i
|
|
|
1172
1434
|
type="button"
|
|
1173
1435
|
className="dm-helper-composer-send"
|
|
1174
1436
|
onClick={runQuery}
|
|
1175
|
-
disabled={streaming || !prompt.trim()}
|
|
1437
|
+
disabled={streaming || !prompt.trim() || !helperWidgetState.ready}
|
|
1176
1438
|
data-helper-submit=""
|
|
1177
1439
|
aria-label={streaming ? "Sending" : "Send (⌘+Enter)"}
|
|
1178
1440
|
title={streaming ? "Sending…" : `Send · ${intentLabel(activeIntent)} (⌘+Enter)`}
|