@stigmer/react 0.0.52 → 0.0.54
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/deployment-mode.d.ts +35 -0
- package/deployment-mode.d.ts.map +1 -0
- package/deployment-mode.js +41 -0
- package/deployment-mode.js.map +1 -0
- package/execution/ApprovalCard.d.ts +8 -6
- package/execution/ApprovalCard.d.ts.map +1 -1
- package/execution/ApprovalCard.js +34 -96
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ArtifactCard.d.ts +11 -1
- package/execution/ArtifactCard.d.ts.map +1 -1
- package/execution/ArtifactCard.js +22 -3
- package/execution/ArtifactCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +1 -1
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactsWidget.d.ts +26 -19
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +24 -26
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/McpToolDetail.d.ts +48 -0
- package/execution/McpToolDetail.d.ts.map +1 -0
- package/execution/McpToolDetail.js +159 -0
- package/execution/McpToolDetail.js.map +1 -0
- package/execution/MessageThread.d.ts +10 -1
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +19 -17
- package/execution/MessageThread.js.map +1 -1
- package/execution/SandboxContext.d.ts +32 -0
- package/execution/SandboxContext.d.ts.map +1 -0
- package/execution/SandboxContext.js +26 -0
- package/execution/SandboxContext.js.map +1 -0
- package/execution/ToolArgsView.d.ts +41 -0
- package/execution/ToolArgsView.d.ts.map +1 -0
- package/execution/ToolArgsView.js +134 -0
- package/execution/ToolArgsView.js.map +1 -0
- package/execution/ToolCallDetail.d.ts +11 -4
- package/execution/ToolCallDetail.d.ts.map +1 -1
- package/execution/ToolCallDetail.js +32 -101
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/ToolCallGroup.d.ts.map +1 -1
- package/execution/ToolCallGroup.js +3 -2
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.d.ts +2 -0
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +13 -3
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/WriteBackCard.d.ts +34 -0
- package/execution/WriteBackCard.d.ts.map +1 -0
- package/execution/WriteBackCard.js +75 -0
- package/execution/WriteBackCard.js.map +1 -0
- package/execution/WriteBacksWidget.d.ts +49 -0
- package/execution/WriteBacksWidget.d.ts.map +1 -0
- package/execution/WriteBacksWidget.js +44 -0
- package/execution/WriteBacksWidget.js.map +1 -0
- package/execution/__tests__/file-path-resolver.test.d.ts +2 -0
- package/execution/__tests__/file-path-resolver.test.d.ts.map +1 -0
- package/execution/__tests__/file-path-resolver.test.js +180 -0
- package/execution/__tests__/file-path-resolver.test.js.map +1 -0
- package/execution/file-path-resolver.d.ts +3 -3
- package/execution/file-path-resolver.d.ts.map +1 -1
- package/execution/file-path-resolver.js +23 -12
- package/execution/file-path-resolver.js.map +1 -1
- package/execution/index.d.ts +16 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +9 -1
- package/execution/index.js.map +1 -1
- package/execution/sandbox-path-normalizer.d.ts +46 -0
- package/execution/sandbox-path-normalizer.d.ts.map +1 -0
- package/execution/sandbox-path-normalizer.js +73 -0
- package/execution/sandbox-path-normalizer.js.map +1 -0
- package/execution/tool-categories.d.ts +35 -8
- package/execution/tool-categories.d.ts.map +1 -1
- package/execution/tool-categories.js +76 -10
- package/execution/tool-categories.js.map +1 -1
- package/execution/tool-rendering-primitives.d.ts +61 -0
- package/execution/tool-rendering-primitives.d.ts.map +1 -0
- package/execution/tool-rendering-primitives.js +106 -0
- package/execution/tool-rendering-primitives.js.map +1 -0
- package/execution/useArtifactContent.d.ts +5 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +6 -2
- package/execution/useArtifactContent.js.map +1 -1
- package/execution/useWorkspaceWriteBacks.d.ts +40 -0
- package/execution/useWorkspaceWriteBacks.d.ts.map +1 -0
- package/execution/useWorkspaceWriteBacks.js +41 -0
- package/execution/useWorkspaceWriteBacks.js.map +1 -0
- package/github/GitHubRepoPicker.d.ts +5 -2
- package/github/GitHubRepoPicker.d.ts.map +1 -1
- package/github/GitHubRepoPicker.js +133 -36
- package/github/GitHubRepoPicker.js.map +1 -1
- package/github/index.d.ts +1 -0
- package/github/index.d.ts.map +1 -1
- package/github/index.js +1 -0
- package/github/index.js.map +1 -1
- package/github/useGitHubSearch.d.ts +20 -0
- package/github/useGitHubSearch.d.ts.map +1 -0
- package/github/useGitHubSearch.js +127 -0
- package/github/useGitHubSearch.js.map +1 -0
- package/index.d.ts +9 -6
- package/index.d.ts.map +1 -1
- package/index.js +7 -3
- package/index.js.map +1 -1
- package/internal/CloudFeatureNotice.d.ts +19 -0
- package/internal/CloudFeatureNotice.d.ts.map +1 -0
- package/internal/CloudFeatureNotice.js +21 -0
- package/internal/CloudFeatureNotice.js.map +1 -0
- package/mcp-server/McpServerDetailView.d.ts +15 -1
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +11 -3
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/package.json +4 -4
- package/provider.d.ts +14 -2
- package/provider.d.ts.map +1 -1
- package/provider.js +3 -2
- package/provider.js.map +1 -1
- package/session/index.d.ts +4 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +2 -0
- package/session/index.js.map +1 -1
- package/session/useSessionArtifacts.d.ts +73 -0
- package/session/useSessionArtifacts.d.ts.map +1 -0
- package/session/useSessionArtifacts.js +95 -0
- package/session/useSessionArtifacts.js.map +1 -0
- package/session/useSessionWriteBacks.d.ts +56 -0
- package/session/useSessionWriteBacks.d.ts.map +1 -0
- package/session/useSessionWriteBacks.js +56 -0
- package/session/useSessionWriteBacks.js.map +1 -0
- package/src/deployment-mode.ts +46 -0
- package/src/execution/ApprovalCard.tsx +130 -283
- package/src/execution/ArtifactCard.tsx +40 -0
- package/src/execution/ArtifactPreviewModal.tsx +2 -0
- package/src/execution/ArtifactsWidget.tsx +51 -43
- package/src/execution/McpToolDetail.tsx +283 -0
- package/src/execution/MessageThread.tsx +18 -0
- package/src/execution/SandboxContext.ts +47 -0
- package/src/execution/ToolArgsView.tsx +279 -0
- package/src/execution/ToolCallDetail.tsx +54 -220
- package/src/execution/ToolCallGroup.tsx +3 -2
- package/src/execution/ToolCallItem.tsx +21 -3
- package/src/execution/WriteBackCard.tsx +210 -0
- package/src/execution/WriteBacksWidget.tsx +82 -0
- package/src/execution/__tests__/file-path-resolver.test.ts +295 -0
- package/src/execution/file-path-resolver.ts +24 -12
- package/src/execution/index.ts +38 -0
- package/src/execution/sandbox-path-normalizer.ts +80 -0
- package/src/execution/tool-categories.ts +89 -9
- package/src/execution/tool-rendering-primitives.tsx +253 -0
- package/src/execution/useArtifactContent.ts +6 -1
- package/src/execution/useWorkspaceWriteBacks.ts +56 -0
- package/src/github/GitHubRepoPicker.tsx +413 -108
- package/src/github/index.ts +5 -0
- package/src/github/useGitHubSearch.ts +162 -0
- package/src/index.ts +27 -0
- package/src/internal/CloudFeatureNotice.tsx +60 -0
- package/src/mcp-server/McpServerDetailView.tsx +24 -2
- package/src/provider.tsx +18 -2
- package/src/session/index.ts +12 -0
- package/src/session/useSessionArtifacts.ts +143 -0
- package/src/session/useSessionWriteBacks.ts +94 -0
- package/styles.css +1 -1
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
extractPrimaryArg,
|
|
17
17
|
type ToolCategory,
|
|
18
18
|
} from "./tool-categories";
|
|
19
|
+
import { useSandboxNormalize } from "./SandboxContext";
|
|
19
20
|
|
|
20
21
|
export interface ToolCallItemProps {
|
|
21
22
|
readonly toolCall: ToolCall;
|
|
@@ -66,7 +67,7 @@ export function ToolCallItem({
|
|
|
66
67
|
const duration = formatDuration(toolCall.startedAt, toolCall.completedAt);
|
|
67
68
|
const isSubAgent = subAgentExecution != null;
|
|
68
69
|
|
|
69
|
-
const categoryInfo = resolveToolCategory(toolCall.name);
|
|
70
|
+
const categoryInfo = resolveToolCategory(toolCall.name, toolCall.mcpServerSlug);
|
|
70
71
|
const CategoryIcon = CATEGORY_ICON[categoryInfo.category];
|
|
71
72
|
const primaryArg = extractPrimaryArg(toolCall);
|
|
72
73
|
|
|
@@ -74,6 +75,7 @@ export function ToolCallItem({
|
|
|
74
75
|
? subAgentExecution.subject || subAgentExecution.name || categoryInfo.label
|
|
75
76
|
: categoryInfo.label;
|
|
76
77
|
|
|
78
|
+
const normalize = useSandboxNormalize();
|
|
77
79
|
const approvalBadge = getApprovalBadge(toolCall);
|
|
78
80
|
|
|
79
81
|
// Completed/skipped Read items are non-expandable — the clickable
|
|
@@ -144,7 +146,11 @@ export function ToolCallItem({
|
|
|
144
146
|
);
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
const displaySubtitle = isSubAgent
|
|
149
|
+
const displaySubtitle = isSubAgent
|
|
150
|
+
? null
|
|
151
|
+
: categoryInfo.category === "shell" && primaryArg
|
|
152
|
+
? normalize(primaryArg)
|
|
153
|
+
: primaryArg;
|
|
148
154
|
|
|
149
155
|
return (
|
|
150
156
|
<div className={cn("border-b border-border/50 last:border-b-0", className)}>
|
|
@@ -255,7 +261,7 @@ const STATUS_COLOR: Record<ItemStatus, string> = {
|
|
|
255
261
|
// Category-specific icons (inline SVG, SDK pattern)
|
|
256
262
|
// ---------------------------------------------------------------------------
|
|
257
263
|
|
|
258
|
-
const CATEGORY_ICON: Record<ToolCategory, () => React.JSX.Element> = {
|
|
264
|
+
export const CATEGORY_ICON: Record<ToolCategory, () => React.JSX.Element> = {
|
|
259
265
|
shell: TerminalIcon,
|
|
260
266
|
read: FileIcon,
|
|
261
267
|
write: FilePenIcon,
|
|
@@ -265,6 +271,7 @@ const CATEGORY_ICON: Record<ToolCategory, () => React.JSX.Element> = {
|
|
|
265
271
|
list: FolderIcon,
|
|
266
272
|
think: BrainIcon,
|
|
267
273
|
"sub-agent": BotIcon,
|
|
274
|
+
mcp: McpPlugIcon,
|
|
268
275
|
unknown: WrenchIcon,
|
|
269
276
|
};
|
|
270
277
|
|
|
@@ -355,6 +362,17 @@ function WrenchIcon() {
|
|
|
355
362
|
);
|
|
356
363
|
}
|
|
357
364
|
|
|
365
|
+
function McpPlugIcon() {
|
|
366
|
+
return (
|
|
367
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
|
|
368
|
+
<path d="M4 1.5V4" />
|
|
369
|
+
<path d="M8 1.5V4" />
|
|
370
|
+
<path d="M2.5 4H9.5V6.5C9.5 8.43 7.93 10 6 10C4.07 10 2.5 8.43 2.5 6.5V4Z" />
|
|
371
|
+
<path d="M6 10V11" />
|
|
372
|
+
</svg>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
358
376
|
// ---------------------------------------------------------------------------
|
|
359
377
|
// Status icons
|
|
360
378
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { WorkspaceWriteBack } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
|
|
4
|
+
import { WorkspaceWriteBackPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
|
|
5
|
+
import { cn } from "@stigmer/theme";
|
|
6
|
+
|
|
7
|
+
export interface WriteBackCardProps {
|
|
8
|
+
/** The workspace write-back outcome to render. */
|
|
9
|
+
readonly writeBack: WorkspaceWriteBack;
|
|
10
|
+
/** Additional CSS classes for the root element. */
|
|
11
|
+
readonly className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Renders a single workspace write-back outcome as a compact card.
|
|
16
|
+
*
|
|
17
|
+
* Shows the workspace entry name, branch, diff summary, phase
|
|
18
|
+
* indicator, and a "View PR" link when the pull request was
|
|
19
|
+
* successfully created. On failure, displays the error message.
|
|
20
|
+
*
|
|
21
|
+
* Themed via standard semantic tokens — no hardcoded colors, no
|
|
22
|
+
* Console dependencies. Embedders can theme this card through
|
|
23
|
+
* their Tailwind/CSS configuration.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const { writeBacks } = useWorkspaceWriteBacks(execution);
|
|
28
|
+
*
|
|
29
|
+
* {writeBacks.map((wb) => (
|
|
30
|
+
* <WriteBackCard
|
|
31
|
+
* key={wb.workspaceEntryName}
|
|
32
|
+
* writeBack={wb}
|
|
33
|
+
* />
|
|
34
|
+
* ))}
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @see useWorkspaceWriteBacks — extracts write-back data from an execution
|
|
38
|
+
*/
|
|
39
|
+
export function WriteBackCard({ writeBack, className }: WriteBackCardProps) {
|
|
40
|
+
const isFailed =
|
|
41
|
+
writeBack.phase === WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_FAILED;
|
|
42
|
+
const hasPR = !!writeBack.pullRequestUrl;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
role="article"
|
|
47
|
+
aria-label={`Write-back: ${writeBack.workspaceEntryName}`}
|
|
48
|
+
className={cn(
|
|
49
|
+
"rounded-md border p-3",
|
|
50
|
+
isFailed ? "border-destructive/40" : "border-border",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
{/* Header: icon + workspace name + phase badge */}
|
|
55
|
+
<div className="flex items-start gap-2">
|
|
56
|
+
<span className="mt-0.5 shrink-0 text-muted-foreground">
|
|
57
|
+
<GitBranchIcon />
|
|
58
|
+
</span>
|
|
59
|
+
<div className="min-w-0 flex-1">
|
|
60
|
+
<div className="flex items-center gap-2">
|
|
61
|
+
<span className="truncate text-sm font-medium text-foreground">
|
|
62
|
+
{writeBack.workspaceEntryName}
|
|
63
|
+
</span>
|
|
64
|
+
<PhaseBadge phase={writeBack.phase} />
|
|
65
|
+
</div>
|
|
66
|
+
{writeBack.branchName && (
|
|
67
|
+
<div className="mt-0.5 truncate font-mono text-xs text-muted-foreground">
|
|
68
|
+
{writeBack.branchName}
|
|
69
|
+
{writeBack.baseBranch && (
|
|
70
|
+
<span className="text-muted-foreground/60">
|
|
71
|
+
{" \u2190 "}
|
|
72
|
+
{writeBack.baseBranch}
|
|
73
|
+
</span>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Diff summary */}
|
|
81
|
+
{writeBack.diffSummary && (
|
|
82
|
+
<div className="mt-1.5 whitespace-pre-wrap font-mono text-xs text-muted-foreground">
|
|
83
|
+
{writeBack.diffSummary}
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
{/* Error message */}
|
|
88
|
+
{isFailed && writeBack.error && (
|
|
89
|
+
<div className="mt-1.5 rounded bg-destructive/10 px-2 py-1 text-xs text-destructive">
|
|
90
|
+
{writeBack.error}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{/* PR link */}
|
|
95
|
+
{hasPR && (
|
|
96
|
+
<div className="mt-2">
|
|
97
|
+
<a
|
|
98
|
+
href={writeBack.pullRequestUrl}
|
|
99
|
+
target="_blank"
|
|
100
|
+
rel="noopener noreferrer"
|
|
101
|
+
className={cn(
|
|
102
|
+
"inline-flex items-center gap-1 text-xs font-medium text-primary transition-colors hover:text-primary/80",
|
|
103
|
+
FOCUS_RING_CLASSES,
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
<ExternalLinkIcon />
|
|
107
|
+
View PR #{writeBack.pullRequestNumber}
|
|
108
|
+
</a>
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Phase badge
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
const PHASE_CONFIG: Record<
|
|
120
|
+
number,
|
|
121
|
+
{ label: string; className: string }
|
|
122
|
+
> = {
|
|
123
|
+
[WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_COMMITTED]: {
|
|
124
|
+
label: "Committed",
|
|
125
|
+
className: "bg-muted text-muted-foreground",
|
|
126
|
+
},
|
|
127
|
+
[WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_PUSHED]: {
|
|
128
|
+
label: "Pushed",
|
|
129
|
+
className: "bg-muted text-muted-foreground",
|
|
130
|
+
},
|
|
131
|
+
[WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_PR_CREATED]: {
|
|
132
|
+
label: "PR Created",
|
|
133
|
+
className: "bg-primary/10 text-primary",
|
|
134
|
+
},
|
|
135
|
+
[WorkspaceWriteBackPhase.WORKSPACE_WRITE_BACK_FAILED]: {
|
|
136
|
+
label: "Failed",
|
|
137
|
+
className: "bg-destructive/10 text-destructive",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
function PhaseBadge({ phase }: { phase: number }) {
|
|
142
|
+
const config = PHASE_CONFIG[phase];
|
|
143
|
+
if (!config) return null;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<span
|
|
147
|
+
className={cn(
|
|
148
|
+
"inline-flex shrink-0 items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium leading-none",
|
|
149
|
+
config.className,
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
{config.label}
|
|
153
|
+
</span>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Shared style constants
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
const FOCUS_RING_CLASSES =
|
|
162
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:rounded-sm";
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Inline SVG icons
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
function GitBranchIcon() {
|
|
169
|
+
return (
|
|
170
|
+
<svg
|
|
171
|
+
width="14"
|
|
172
|
+
height="14"
|
|
173
|
+
viewBox="0 0 14 14"
|
|
174
|
+
fill="none"
|
|
175
|
+
stroke="currentColor"
|
|
176
|
+
strokeWidth="1.5"
|
|
177
|
+
strokeLinecap="round"
|
|
178
|
+
strokeLinejoin="round"
|
|
179
|
+
aria-hidden="true"
|
|
180
|
+
>
|
|
181
|
+
<circle cx="4" cy="3.5" r="1.5" />
|
|
182
|
+
<circle cx="4" cy="10.5" r="1.5" />
|
|
183
|
+
<circle cx="10" cy="5.5" r="1.5" />
|
|
184
|
+
<path d="M4 5V9" />
|
|
185
|
+
<path d="M10 7V5.5" />
|
|
186
|
+
<path d="M4 5C4 5 4 7 7 7C10 7 10 5.5 10 5.5" />
|
|
187
|
+
</svg>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function ExternalLinkIcon() {
|
|
192
|
+
return (
|
|
193
|
+
<svg
|
|
194
|
+
width="10"
|
|
195
|
+
height="10"
|
|
196
|
+
viewBox="0 0 12 12"
|
|
197
|
+
fill="none"
|
|
198
|
+
stroke="currentColor"
|
|
199
|
+
strokeWidth="1.5"
|
|
200
|
+
strokeLinecap="round"
|
|
201
|
+
strokeLinejoin="round"
|
|
202
|
+
className="shrink-0"
|
|
203
|
+
aria-hidden="true"
|
|
204
|
+
>
|
|
205
|
+
<path d="M9 3L3 9" />
|
|
206
|
+
<path d="M5 3H9V7" />
|
|
207
|
+
<path d="M9 8V10H2V3H4" />
|
|
208
|
+
</svg>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { useSessionWriteBacks } from "../session/useSessionWriteBacks";
|
|
6
|
+
import { WriteBackCard } from "./WriteBackCard";
|
|
7
|
+
|
|
8
|
+
export interface WriteBacksWidgetProps {
|
|
9
|
+
/**
|
|
10
|
+
* All executions for the current session — both completed and
|
|
11
|
+
* actively streaming. The widget aggregates write-backs across
|
|
12
|
+
* every execution, deduplicates by `workspace_entry_name` (latest
|
|
13
|
+
* wins), and sorts alphabetically.
|
|
14
|
+
*
|
|
15
|
+
* Renders nothing when the list is empty or no execution has
|
|
16
|
+
* write-backs.
|
|
17
|
+
*/
|
|
18
|
+
readonly executions: readonly AgentExecution[];
|
|
19
|
+
/** Additional CSS classes for the root element. */
|
|
20
|
+
readonly className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Right-sidebar widget that surfaces pull requests created by the
|
|
25
|
+
* platform's incremental git write-back workflow.
|
|
26
|
+
*
|
|
27
|
+
* Write-backs from multiple executions are aggregated and
|
|
28
|
+
* deduplicated by `workspace_entry_name` (latest execution wins),
|
|
29
|
+
* presenting the user with a flat list of PRs — one per workspace
|
|
30
|
+
* entry.
|
|
31
|
+
*
|
|
32
|
+
* Returns `null` when no execution has write-backs, matching the
|
|
33
|
+
* conditional-render pattern of {@link ArtifactsWidget} and
|
|
34
|
+
* {@link ExecutionProgress}.
|
|
35
|
+
*
|
|
36
|
+
* All visual properties flow through `--stgm-*` tokens. Zero
|
|
37
|
+
* Console dependencies.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const conv = useSessionConversation(sessionId, org);
|
|
42
|
+
*
|
|
43
|
+
* <WriteBacksWidget
|
|
44
|
+
* executions={[
|
|
45
|
+
* ...conv.completedExecutions,
|
|
46
|
+
* ...(conv.activeStreamExecution ? [conv.activeStreamExecution] : []),
|
|
47
|
+
* ]}
|
|
48
|
+
* />
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @see {@link WriteBackCard} — compact card per write-back
|
|
52
|
+
* @see {@link useSessionWriteBacks} — headless session-level write-back aggregation hook
|
|
53
|
+
* @see {@link useWorkspaceWriteBacks} — headless single-execution write-back extraction hook
|
|
54
|
+
*/
|
|
55
|
+
export function WriteBacksWidget({
|
|
56
|
+
executions,
|
|
57
|
+
className,
|
|
58
|
+
}: WriteBacksWidgetProps) {
|
|
59
|
+
const { writeBacks, hasWriteBacks, writeBackCount } =
|
|
60
|
+
useSessionWriteBacks(executions);
|
|
61
|
+
|
|
62
|
+
if (!hasWriteBacks) return null;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<section aria-label="Pull Requests" className={cn(className)}>
|
|
66
|
+
<div className="mb-2 flex items-center gap-2">
|
|
67
|
+
<h3 className="text-sm font-medium text-foreground">Pull Requests</h3>
|
|
68
|
+
<span className="inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-muted px-1.5 text-xs tabular-nums text-muted-foreground">
|
|
69
|
+
{writeBackCount}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div role="list" className="space-y-2">
|
|
74
|
+
{writeBacks.map((entry) => (
|
|
75
|
+
<div key={entry.writeBack.workspaceEntryName} role="listitem">
|
|
76
|
+
<WriteBackCard writeBack={entry.writeBack} />
|
|
77
|
+
</div>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { create } from "@bufbuild/protobuf";
|
|
3
|
+
import {
|
|
4
|
+
WorkspaceEntrySchema,
|
|
5
|
+
WorkspaceSourceSchema,
|
|
6
|
+
GitRepoSourceSchema,
|
|
7
|
+
LocalPathSourceSchema,
|
|
8
|
+
} from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
|
|
9
|
+
import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
|
|
10
|
+
import {
|
|
11
|
+
classifyPath,
|
|
12
|
+
resolveGitBrowseUrl,
|
|
13
|
+
resolvePathAction,
|
|
14
|
+
} from "../file-path-resolver";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Factory helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
function gitEntry(name: string, url: string, branch = "main"): WorkspaceEntry {
|
|
21
|
+
return create(WorkspaceEntrySchema, {
|
|
22
|
+
name,
|
|
23
|
+
source: create(WorkspaceSourceSchema, {
|
|
24
|
+
source: {
|
|
25
|
+
case: "gitRepo",
|
|
26
|
+
value: create(GitRepoSourceSchema, { url, branch, commit: "" }),
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function localEntry(name: string, path: string): WorkspaceEntry {
|
|
33
|
+
return create(WorkspaceEntrySchema, {
|
|
34
|
+
name,
|
|
35
|
+
source: create(WorkspaceSourceSchema, {
|
|
36
|
+
source: {
|
|
37
|
+
case: "localPath",
|
|
38
|
+
value: create(LocalPathSourceSchema, { path }),
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// classifyPath
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
describe("classifyPath", () => {
|
|
49
|
+
it("classifies platform paths", () => {
|
|
50
|
+
expect(classifyPath(".stigmer/skills/foo.md")).toEqual({
|
|
51
|
+
kind: "platform",
|
|
52
|
+
subpath: "skills/foo.md",
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("classifies bare .stigmer directory", () => {
|
|
57
|
+
expect(classifyPath(".stigmer")).toEqual({
|
|
58
|
+
kind: "platform",
|
|
59
|
+
subpath: "",
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("strips leading slashes from platform paths", () => {
|
|
64
|
+
expect(classifyPath("/.stigmer/inputs/bar")).toEqual({
|
|
65
|
+
kind: "platform",
|
|
66
|
+
subpath: "inputs/bar",
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("classifies workspace paths", () => {
|
|
71
|
+
expect(classifyPath("src/main.go")).toEqual({
|
|
72
|
+
kind: "workspace",
|
|
73
|
+
remainder: "src/main.go",
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("strips leading slashes from workspace paths", () => {
|
|
78
|
+
expect(classifyPath("/src/main.go")).toEqual({
|
|
79
|
+
kind: "workspace",
|
|
80
|
+
remainder: "src/main.go",
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// resolveGitBrowseUrl
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
describe("resolveGitBrowseUrl", () => {
|
|
90
|
+
it("constructs a correct GitHub blob URL", () => {
|
|
91
|
+
expect(
|
|
92
|
+
resolveGitBrowseUrl(
|
|
93
|
+
"https://github.com/acme/app.git",
|
|
94
|
+
"main",
|
|
95
|
+
"",
|
|
96
|
+
"src/index.ts",
|
|
97
|
+
),
|
|
98
|
+
).toBe("https://github.com/acme/app/blob/main/src/index.ts");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("prefers commit over branch", () => {
|
|
102
|
+
expect(
|
|
103
|
+
resolveGitBrowseUrl(
|
|
104
|
+
"https://github.com/acme/app.git",
|
|
105
|
+
"main",
|
|
106
|
+
"abc123",
|
|
107
|
+
"README.md",
|
|
108
|
+
),
|
|
109
|
+
).toBe("https://github.com/acme/app/blob/abc123/README.md");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("falls back to HEAD when both branch and commit are empty", () => {
|
|
113
|
+
expect(
|
|
114
|
+
resolveGitBrowseUrl(
|
|
115
|
+
"https://github.com/acme/app.git",
|
|
116
|
+
"",
|
|
117
|
+
"",
|
|
118
|
+
"README.md",
|
|
119
|
+
),
|
|
120
|
+
).toBe("https://github.com/acme/app/blob/HEAD/README.md");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns null for non-GitHub hosts", () => {
|
|
124
|
+
expect(
|
|
125
|
+
resolveGitBrowseUrl(
|
|
126
|
+
"https://gitlab.com/acme/app.git",
|
|
127
|
+
"main",
|
|
128
|
+
"",
|
|
129
|
+
"src/main.go",
|
|
130
|
+
),
|
|
131
|
+
).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("returns null for invalid URLs", () => {
|
|
135
|
+
expect(
|
|
136
|
+
resolveGitBrowseUrl("not-a-url", "main", "", "file.txt"),
|
|
137
|
+
).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("strips duplicate org/repo prefix from relPath", () => {
|
|
141
|
+
expect(
|
|
142
|
+
resolveGitBrowseUrl(
|
|
143
|
+
"https://github.com/plantonhq/agent-fleet.git",
|
|
144
|
+
"main",
|
|
145
|
+
"",
|
|
146
|
+
"plantonhq/agent-fleet/mcp-servers/mcp-server-planton.yaml",
|
|
147
|
+
),
|
|
148
|
+
).toBe(
|
|
149
|
+
"https://github.com/plantonhq/agent-fleet/blob/main/mcp-servers/mcp-server-planton.yaml",
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("does not strip when relPath shares a partial prefix", () => {
|
|
154
|
+
expect(
|
|
155
|
+
resolveGitBrowseUrl(
|
|
156
|
+
"https://github.com/acme/app.git",
|
|
157
|
+
"main",
|
|
158
|
+
"",
|
|
159
|
+
"acme/other-thing/file.txt",
|
|
160
|
+
),
|
|
161
|
+
).toBe("https://github.com/acme/app/blob/main/acme/other-thing/file.txt");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("handles clone URLs without .git suffix", () => {
|
|
165
|
+
expect(
|
|
166
|
+
resolveGitBrowseUrl(
|
|
167
|
+
"https://github.com/acme/app",
|
|
168
|
+
"main",
|
|
169
|
+
"",
|
|
170
|
+
"src/lib.rs",
|
|
171
|
+
),
|
|
172
|
+
).toBe("https://github.com/acme/app/blob/main/src/lib.rs");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// resolvePathAction — integration (exercises matchWorkspaceEntry internally)
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
describe("resolvePathAction", () => {
|
|
181
|
+
it("returns copy for empty path", () => {
|
|
182
|
+
const result = resolvePathAction("", []);
|
|
183
|
+
expect(result.action).toBe("copy");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("returns copy for platform paths", () => {
|
|
187
|
+
const entries = [gitEntry("app", "https://github.com/acme/app.git")];
|
|
188
|
+
const result = resolvePathAction(".stigmer/skills/foo.md", entries);
|
|
189
|
+
expect(result.action).toBe("copy");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("returns copy when no workspace entries", () => {
|
|
193
|
+
const result = resolvePathAction("src/main.go", []);
|
|
194
|
+
expect(result.action).toBe("copy");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("single git workspace entry", () => {
|
|
198
|
+
const entries = [
|
|
199
|
+
gitEntry("agent-fleet", "https://github.com/plantonhq/agent-fleet.git"),
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
it("produces a GitHub link for a relative path", () => {
|
|
203
|
+
const result = resolvePathAction(
|
|
204
|
+
"mcp-servers/mcp-server-planton.yaml",
|
|
205
|
+
entries,
|
|
206
|
+
);
|
|
207
|
+
expect(result).toEqual({
|
|
208
|
+
action: "link",
|
|
209
|
+
url: "https://github.com/plantonhq/agent-fleet/blob/main/mcp-servers/mcp-server-planton.yaml",
|
|
210
|
+
tooltip: "Open on GitHub",
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("multiple git workspace entries — first-segment match", () => {
|
|
216
|
+
const entries = [
|
|
217
|
+
gitEntry(
|
|
218
|
+
"mcp-server-planton",
|
|
219
|
+
"https://github.com/plantonhq/mcp-server-planton.git",
|
|
220
|
+
),
|
|
221
|
+
gitEntry(
|
|
222
|
+
"agent-fleet",
|
|
223
|
+
"https://github.com/plantonhq/agent-fleet.git",
|
|
224
|
+
),
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
it("matches entry by first path segment and strips it", () => {
|
|
228
|
+
const result = resolvePathAction(
|
|
229
|
+
"agent-fleet/mcp-servers/mcp-server-planton.yaml",
|
|
230
|
+
entries,
|
|
231
|
+
);
|
|
232
|
+
expect(result).toEqual({
|
|
233
|
+
action: "link",
|
|
234
|
+
url: "https://github.com/plantonhq/agent-fleet/blob/main/mcp-servers/mcp-server-planton.yaml",
|
|
235
|
+
tooltip: "Open on GitHub",
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("multiple git workspace entries — deep segment match (bug fix)", () => {
|
|
241
|
+
const entries = [
|
|
242
|
+
gitEntry(
|
|
243
|
+
"mcp-server-planton",
|
|
244
|
+
"https://github.com/plantonhq/mcp-server-planton.git",
|
|
245
|
+
),
|
|
246
|
+
gitEntry(
|
|
247
|
+
"agent-fleet",
|
|
248
|
+
"https://github.com/plantonhq/agent-fleet.git",
|
|
249
|
+
),
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
it("matches entry by deeper segment when org prefix is present", () => {
|
|
253
|
+
const result = resolvePathAction(
|
|
254
|
+
"plantonhq/agent-fleet/mcp-servers/mcp-server-planton.yaml",
|
|
255
|
+
entries,
|
|
256
|
+
);
|
|
257
|
+
expect(result).toEqual({
|
|
258
|
+
action: "link",
|
|
259
|
+
url: "https://github.com/plantonhq/agent-fleet/blob/main/mcp-servers/mcp-server-planton.yaml",
|
|
260
|
+
tooltip: "Open on GitHub",
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe("fallback to first entry when no segment matches", () => {
|
|
266
|
+
const entries = [
|
|
267
|
+
gitEntry("my-app", "https://github.com/acme/my-app.git"),
|
|
268
|
+
gitEntry("my-lib", "https://github.com/acme/my-lib.git"),
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
it("falls back to entries[0] with full path as relPath", () => {
|
|
272
|
+
const result = resolvePathAction("unknown/path/file.txt", entries);
|
|
273
|
+
expect(result).toEqual({
|
|
274
|
+
action: "link",
|
|
275
|
+
url: "https://github.com/acme/my-app/blob/main/unknown/path/file.txt",
|
|
276
|
+
tooltip: "Open on GitHub",
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("local path workspace entry", () => {
|
|
282
|
+
const entries = [
|
|
283
|
+
localEntry("my-app", "/Users/dev/projects/my-app"),
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
it("joins local path for copy action", () => {
|
|
287
|
+
const result = resolvePathAction("src/main.go", entries);
|
|
288
|
+
expect(result).toEqual({
|
|
289
|
+
action: "copy",
|
|
290
|
+
value: "/Users/dev/projects/my-app/src/main.go",
|
|
291
|
+
tooltip: "Copy path",
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|