@stigmer/react 0.0.42 → 0.0.44
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/execution/ArtifactContentRenderer.d.ts +58 -0
- package/execution/ArtifactContentRenderer.d.ts.map +1 -0
- package/execution/ArtifactContentRenderer.js +163 -0
- package/execution/ArtifactContentRenderer.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +3 -2
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +11 -62
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ExecutionPhaseBadge.js +1 -1
- package/execution/ExecutionPhaseBadge.js.map +1 -1
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +21 -2
- package/execution/MessageThread.js.map +1 -1
- package/execution/SetupProgress.d.ts +35 -0
- package/execution/SetupProgress.d.ts.map +1 -0
- package/execution/SetupProgress.js +65 -0
- package/execution/SetupProgress.js.map +1 -0
- package/execution/ToolCallGroup.d.ts.map +1 -1
- package/execution/ToolCallGroup.js +9 -1
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.js +8 -3
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/artifact-utils.d.ts +50 -0
- package/execution/artifact-utils.d.ts.map +1 -1
- package/execution/artifact-utils.js +67 -5
- package/execution/artifact-utils.js.map +1 -1
- package/execution/index.d.ts +6 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +3 -1
- package/execution/index.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/internal/markdown-components.d.ts +10 -0
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +13 -0
- package/internal/markdown-components.js.map +1 -1
- package/mcp-server/ApprovalPolicyGeneratorPanel.d.ts +34 -0
- package/mcp-server/ApprovalPolicyGeneratorPanel.d.ts.map +1 -0
- package/mcp-server/ApprovalPolicyGeneratorPanel.js +55 -0
- package/mcp-server/ApprovalPolicyGeneratorPanel.js.map +1 -0
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +101 -2
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/index.d.ts +8 -0
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +4 -0
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/useDiscoverCapabilities.d.ts +59 -0
- package/mcp-server/useDiscoverCapabilities.d.ts.map +1 -0
- package/mcp-server/useDiscoverCapabilities.js +77 -0
- package/mcp-server/useDiscoverCapabilities.js.map +1 -0
- package/mcp-server/useMcpServerCredentials.d.ts +63 -0
- package/mcp-server/useMcpServerCredentials.d.ts.map +1 -0
- package/mcp-server/useMcpServerCredentials.js +64 -0
- package/mcp-server/useMcpServerCredentials.js.map +1 -0
- package/mcp-server/useTriggerApprovalPolicySession.d.ts +42 -0
- package/mcp-server/useTriggerApprovalPolicySession.d.ts.map +1 -0
- package/mcp-server/useTriggerApprovalPolicySession.js +111 -0
- package/mcp-server/useTriggerApprovalPolicySession.js.map +1 -0
- package/package.json +4 -4
- package/session/__tests__/useSessionConversation.test.js +223 -2
- package/session/__tests__/useSessionConversation.test.js.map +1 -1
- package/session/useSessionConversation.d.ts +8 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +77 -6
- package/session/useSessionConversation.js.map +1 -1
- package/skill/SkillDetailView.js +2 -2
- package/skill/SkillDetailView.js.map +1 -1
- package/src/execution/ArtifactContentRenderer.tsx +376 -0
- package/src/execution/ArtifactPreviewModal.tsx +22 -114
- package/src/execution/ExecutionPhaseBadge.tsx +1 -1
- package/src/execution/MessageThread.tsx +35 -3
- package/src/execution/SetupProgress.tsx +120 -0
- package/src/execution/ToolCallGroup.tsx +15 -1
- package/src/execution/ToolCallItem.tsx +10 -3
- package/src/execution/artifact-utils.ts +88 -4
- package/src/execution/index.ts +9 -0
- package/src/index.ts +16 -0
- package/src/internal/markdown-components.tsx +15 -0
- package/src/mcp-server/ApprovalPolicyGeneratorPanel.tsx +164 -0
- package/src/mcp-server/McpServerDetailView.tsx +428 -2
- package/src/mcp-server/index.ts +15 -0
- package/src/mcp-server/useDiscoverCapabilities.ts +117 -0
- package/src/mcp-server/useMcpServerCredentials.ts +108 -0
- package/src/mcp-server/useTriggerApprovalPolicySession.ts +161 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +355 -2
- package/src/session/useSessionConversation.ts +104 -9
- package/src/skill/SkillDetailView.tsx +2 -2
- package/styles.css +1 -1
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
useCallback,
|
|
5
|
-
useEffect,
|
|
6
|
-
useRef,
|
|
7
|
-
useState,
|
|
8
|
-
type ReactNode,
|
|
9
|
-
} from "react";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
4
|
import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
11
5
|
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
12
6
|
import { cn } from "@stigmer/theme";
|
|
13
7
|
import { useArtifactContent } from "./useArtifactContent";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
formatArtifactSize,
|
|
17
|
-
getArtifactExtension,
|
|
18
|
-
} from "./artifact-utils";
|
|
8
|
+
import { isTextArtifact, formatArtifactSize } from "./artifact-utils";
|
|
9
|
+
import { ArtifactContentRenderer } from "./ArtifactContentRenderer";
|
|
19
10
|
import { useDetectStigmerResource } from "../library/useDetectStigmerResource";
|
|
20
11
|
import { useDetectSkillPackage } from "../library/useDetectSkillPackage";
|
|
21
12
|
import type { SkillPackageDetection } from "../library/detect-skill-package";
|
|
@@ -66,8 +57,9 @@ export interface ArtifactPreviewModalProps {
|
|
|
66
57
|
* Internally orchestrates the same detection pipeline as {@link ArtifactCard}:
|
|
67
58
|
*
|
|
68
59
|
* - **FILE artifacts**: Fetches text content via {@link useArtifactContent},
|
|
69
|
-
*
|
|
70
|
-
*
|
|
60
|
+
* renders via {@link ArtifactContentRenderer} (markdown, YAML, JSON, or
|
|
61
|
+
* plain text based on file type), and detects Agent/McpServer resources
|
|
62
|
+
* via {@link useDetectStigmerResource}.
|
|
71
63
|
*
|
|
72
64
|
* - **DIRECTORY artifacts**: Shows the file listing from
|
|
73
65
|
* `artifact.entries` and detects skill packages via
|
|
@@ -253,14 +245,6 @@ export function ArtifactPreviewModal({
|
|
|
253
245
|
if (!open) setCopied(false);
|
|
254
246
|
}, [open]);
|
|
255
247
|
|
|
256
|
-
// ---------------------------------------------------------------------------
|
|
257
|
-
// Content mode
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
|
|
260
|
-
const ext = getArtifactExtension(artifact);
|
|
261
|
-
const isYaml =
|
|
262
|
-
ext === "yaml" || ext === "yml" || (contentType?.includes("yaml") ?? false);
|
|
263
|
-
|
|
264
248
|
let ctaLabel: string | null = null;
|
|
265
249
|
if (yamlDetection.detected) {
|
|
266
250
|
ctaLabel = `Apply to ${org}`;
|
|
@@ -278,7 +262,7 @@ export function ArtifactPreviewModal({
|
|
|
278
262
|
onCancel={handleCancel}
|
|
279
263
|
aria-label={`Preview ${artifact.name}`}
|
|
280
264
|
className={cn(
|
|
281
|
-
"w-full max-w-
|
|
265
|
+
"fixed inset-0 m-auto w-full max-w-3xl rounded-lg border border-border bg-background p-0 text-foreground shadow-lg outline-none",
|
|
282
266
|
"[&::backdrop]:bg-black/50",
|
|
283
267
|
className,
|
|
284
268
|
)}
|
|
@@ -299,12 +283,13 @@ export function ArtifactPreviewModal({
|
|
|
299
283
|
skillDetection={skillDetection}
|
|
300
284
|
/>
|
|
301
285
|
) : (
|
|
302
|
-
<
|
|
286
|
+
<FileContentStateView
|
|
287
|
+
artifact={artifact}
|
|
303
288
|
content={content}
|
|
289
|
+
contentType={contentType}
|
|
304
290
|
isLoading={isContentLoading}
|
|
305
291
|
error={contentError}
|
|
306
292
|
isTruncated={isTruncated}
|
|
307
|
-
isYaml={isYaml}
|
|
308
293
|
/>
|
|
309
294
|
)}
|
|
310
295
|
</div>
|
|
@@ -406,23 +391,25 @@ function ModalHeader({
|
|
|
406
391
|
}
|
|
407
392
|
|
|
408
393
|
// ---------------------------------------------------------------------------
|
|
409
|
-
//
|
|
394
|
+
// FileContentStateView (internal — loading/error/empty states + renderer)
|
|
410
395
|
// ---------------------------------------------------------------------------
|
|
411
396
|
|
|
412
397
|
const SKELETON_LINE_WIDTHS = [85, 72, 90, 65, 78, 88, 70, 82] as const;
|
|
413
398
|
|
|
414
|
-
function
|
|
399
|
+
function FileContentStateView({
|
|
400
|
+
artifact,
|
|
415
401
|
content,
|
|
402
|
+
contentType,
|
|
416
403
|
isLoading,
|
|
417
404
|
error,
|
|
418
405
|
isTruncated,
|
|
419
|
-
isYaml,
|
|
420
406
|
}: {
|
|
407
|
+
readonly artifact: ExecutionArtifact;
|
|
421
408
|
readonly content: string | null;
|
|
409
|
+
readonly contentType: string | null;
|
|
422
410
|
readonly isLoading: boolean;
|
|
423
411
|
readonly error: string | null;
|
|
424
412
|
readonly isTruncated: boolean;
|
|
425
|
-
readonly isYaml: boolean;
|
|
426
413
|
}) {
|
|
427
414
|
if (isLoading) {
|
|
428
415
|
return (
|
|
@@ -457,16 +444,12 @@ function FileContentView({
|
|
|
457
444
|
}
|
|
458
445
|
|
|
459
446
|
return (
|
|
460
|
-
<
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
{isTruncated
|
|
465
|
-
|
|
466
|
-
Content was truncated. Download the full file for complete content.
|
|
467
|
-
</div>
|
|
468
|
-
)}
|
|
469
|
-
</div>
|
|
447
|
+
<ArtifactContentRenderer
|
|
448
|
+
content={content}
|
|
449
|
+
fileName={artifact.name}
|
|
450
|
+
contentType={contentType}
|
|
451
|
+
isTruncated={isTruncated}
|
|
452
|
+
/>
|
|
470
453
|
);
|
|
471
454
|
}
|
|
472
455
|
|
|
@@ -669,81 +652,6 @@ function ApplyButton({
|
|
|
669
652
|
);
|
|
670
653
|
}
|
|
671
654
|
|
|
672
|
-
// ---------------------------------------------------------------------------
|
|
673
|
-
// YAML syntax highlighting (CSS-only, zero dependencies)
|
|
674
|
-
// ---------------------------------------------------------------------------
|
|
675
|
-
|
|
676
|
-
/**
|
|
677
|
-
* Applies lightweight CSS-only highlighting to YAML content.
|
|
678
|
-
*
|
|
679
|
-
* Processes content line-by-line, wrapping structural tokens in styled
|
|
680
|
-
* `<span>` elements using `--stgm-*` theme tokens:
|
|
681
|
-
*
|
|
682
|
-
* - Keys → `text-primary`
|
|
683
|
-
* - Comments → `text-muted-foreground italic`
|
|
684
|
-
* - Document separators (`---`) → `text-muted-foreground`
|
|
685
|
-
* - Boolean/null values → `font-medium`
|
|
686
|
-
* - Multi-line scalar indicators → `text-muted-foreground`
|
|
687
|
-
*
|
|
688
|
-
* Values and block scalar content render in the default `text-foreground`.
|
|
689
|
-
* Non-YAML content passes through unstyled.
|
|
690
|
-
*/
|
|
691
|
-
function highlightYaml(content: string): ReactNode {
|
|
692
|
-
return content.split("\n").map((line, i) => (
|
|
693
|
-
<span key={i}>
|
|
694
|
-
{i > 0 && "\n"}
|
|
695
|
-
{highlightYamlLine(line)}
|
|
696
|
-
</span>
|
|
697
|
-
));
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function highlightYamlLine(line: string): ReactNode {
|
|
701
|
-
if (!line.trim()) return line;
|
|
702
|
-
|
|
703
|
-
if (/^---\s*$/.test(line) || /^\.\.\.\s*$/.test(line)) {
|
|
704
|
-
return <span className="text-muted-foreground">{line}</span>;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const commentMatch = line.match(/^(\s*)(#.*)$/);
|
|
708
|
-
if (commentMatch) {
|
|
709
|
-
return (
|
|
710
|
-
<>
|
|
711
|
-
{commentMatch[1]}
|
|
712
|
-
<span className="text-muted-foreground italic">{commentMatch[2]}</span>
|
|
713
|
-
</>
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const kvMatch = line.match(/^(\s*(?:-\s+)?)([\w][\w.-]*)(:(?:\s|$))(.*)/);
|
|
718
|
-
if (kvMatch) {
|
|
719
|
-
const [, indent, key, colon, value] = kvMatch;
|
|
720
|
-
return (
|
|
721
|
-
<>
|
|
722
|
-
{indent}
|
|
723
|
-
<span className="text-primary">{key}</span>
|
|
724
|
-
<span className="text-muted-foreground">{colon}</span>
|
|
725
|
-
{value ? highlightYamlValue(value) : null}
|
|
726
|
-
</>
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
return <>{line}</>;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
function highlightYamlValue(value: string): ReactNode {
|
|
734
|
-
const trimmed = value.trim();
|
|
735
|
-
|
|
736
|
-
if (/^[|>][-+]?\s*$/.test(trimmed)) {
|
|
737
|
-
return <span className="text-muted-foreground">{value}</span>;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
if (/^(true|false|null|~)$/.test(trimmed)) {
|
|
741
|
-
return <span className="font-medium">{value}</span>;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
return <>{value}</>;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
655
|
// ---------------------------------------------------------------------------
|
|
748
656
|
// Inline SVG icons (SDK pattern: no external icon dependency)
|
|
749
657
|
// ---------------------------------------------------------------------------
|
|
@@ -18,6 +18,7 @@ import { isTerminalPhase } from "./execution-phases";
|
|
|
18
18
|
import { MessageEntry } from "./MessageEntry";
|
|
19
19
|
import { ToolCallGroup } from "./ToolCallGroup";
|
|
20
20
|
import { ExecutionPhaseBadge } from "./ExecutionPhaseBadge";
|
|
21
|
+
import { SetupProgress } from "./SetupProgress";
|
|
21
22
|
import { ApprovalCard } from "./ApprovalCard";
|
|
22
23
|
import { FilePathContext, type FilePathContextValue } from "./FilePathContext";
|
|
23
24
|
import type { ResolvedPathAction } from "./file-path-resolver";
|
|
@@ -101,7 +102,17 @@ type ThreadItem =
|
|
|
101
102
|
| { readonly kind: "tool-group"; readonly toolCalls: readonly ToolCall[]; readonly subAgentExecutions: readonly SubAgentExecution[]; readonly key: string }
|
|
102
103
|
| { readonly kind: "phase-badge"; readonly phase: ExecutionPhase; readonly key: string }
|
|
103
104
|
| { readonly kind: "pending-message"; readonly content: string; readonly key: string }
|
|
104
|
-
| { readonly kind: "approval-request"; readonly pendingApproval: PendingApproval; readonly key: string }
|
|
105
|
+
| { readonly kind: "approval-request"; readonly pendingApproval: PendingApproval; readonly key: string }
|
|
106
|
+
| { readonly kind: "setup-progress"; readonly workspaceEntries: readonly WorkspaceEntry[]; readonly key: string };
|
|
107
|
+
|
|
108
|
+
function hasAiMessages(execution: AgentExecution): boolean {
|
|
109
|
+
const messages = execution.status?.messages;
|
|
110
|
+
if (!messages || messages.length === 0) return false;
|
|
111
|
+
return messages.some(
|
|
112
|
+
(m) =>
|
|
113
|
+
m.type === MessageType.MESSAGE_AI && (m.content.trim() || m.toolCalls.length > 0),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
105
116
|
|
|
106
117
|
function buildThreadItems(
|
|
107
118
|
executions: readonly AgentExecution[],
|
|
@@ -109,6 +120,7 @@ function buildThreadItems(
|
|
|
109
120
|
pendingUserMessage: string | null | undefined,
|
|
110
121
|
includeApprovals: boolean,
|
|
111
122
|
dismissedApprovalIds: ReadonlySet<string> | undefined,
|
|
123
|
+
workspaceEntries: readonly WorkspaceEntry[] | undefined,
|
|
112
124
|
): ThreadItem[] {
|
|
113
125
|
const items: ThreadItem[] = [];
|
|
114
126
|
const allExecutions = activeStreamExecution
|
|
@@ -168,6 +180,19 @@ function buildThreadItems(
|
|
|
168
180
|
const lastPhase =
|
|
169
181
|
lastExec?.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
|
|
170
182
|
|
|
183
|
+
if (
|
|
184
|
+
activeStreamExecution &&
|
|
185
|
+
(lastPhase === ExecutionPhase.EXECUTION_PENDING ||
|
|
186
|
+
lastPhase === ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED) &&
|
|
187
|
+
!hasAiMessages(activeStreamExecution)
|
|
188
|
+
) {
|
|
189
|
+
items.push({
|
|
190
|
+
kind: "setup-progress",
|
|
191
|
+
workspaceEntries: workspaceEntries ?? [],
|
|
192
|
+
key: "setup-progress",
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
171
196
|
if (
|
|
172
197
|
isTerminalPhase(lastPhase) &&
|
|
173
198
|
lastPhase !== ExecutionPhase.EXECUTION_COMPLETED
|
|
@@ -247,8 +272,8 @@ export function MessageThread({
|
|
|
247
272
|
|
|
248
273
|
const includeApprovals = onApprovalSubmit != null;
|
|
249
274
|
const items = useMemo(
|
|
250
|
-
() => buildThreadItems(executions, activeStreamExecution, pendingUserMessage, includeApprovals, dismissedApprovalIds),
|
|
251
|
-
[executions, activeStreamExecution, pendingUserMessage, includeApprovals, dismissedApprovalIds],
|
|
275
|
+
() => buildThreadItems(executions, activeStreamExecution, pendingUserMessage, includeApprovals, dismissedApprovalIds, workspaceEntries),
|
|
276
|
+
[executions, activeStreamExecution, pendingUserMessage, includeApprovals, dismissedApprovalIds, workspaceEntries],
|
|
252
277
|
);
|
|
253
278
|
|
|
254
279
|
const handleScroll = useCallback(() => {
|
|
@@ -322,6 +347,13 @@ export function MessageThread({
|
|
|
322
347
|
className="mx-4"
|
|
323
348
|
/>
|
|
324
349
|
);
|
|
350
|
+
case "setup-progress":
|
|
351
|
+
return (
|
|
352
|
+
<SetupProgress
|
|
353
|
+
key={item.key}
|
|
354
|
+
workspaceEntries={item.workspaceEntries}
|
|
355
|
+
/>
|
|
356
|
+
);
|
|
325
357
|
case "pending-message":
|
|
326
358
|
return (
|
|
327
359
|
<div
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
|
|
5
|
+
import { cn } from "@stigmer/theme";
|
|
6
|
+
|
|
7
|
+
export interface SetupProgressProps {
|
|
8
|
+
/**
|
|
9
|
+
* Workspace entries from the session spec. When git-sourced entries
|
|
10
|
+
* are present, the indicator shows workspace-specific messaging
|
|
11
|
+
* (e.g. "Setting up workspace...") to match the backend's actual
|
|
12
|
+
* setup sequence.
|
|
13
|
+
*/
|
|
14
|
+
readonly workspaceEntries?: readonly WorkspaceEntry[];
|
|
15
|
+
readonly className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SetupStep {
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly durationMs: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const STEP_INTERVAL_MS = 4000;
|
|
24
|
+
|
|
25
|
+
function buildSteps(
|
|
26
|
+
workspaceEntries: readonly WorkspaceEntry[] | undefined,
|
|
27
|
+
): SetupStep[] {
|
|
28
|
+
const hasGitRepo = workspaceEntries?.some(
|
|
29
|
+
(e) => e.source?.source.case === "gitRepo",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const steps: SetupStep[] = [
|
|
33
|
+
{ message: "Initializing execution\u2026", durationMs: STEP_INTERVAL_MS },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
if (hasGitRepo) {
|
|
37
|
+
steps.push({
|
|
38
|
+
message: "Setting up workspace\u2026",
|
|
39
|
+
durationMs: STEP_INTERVAL_MS,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
steps.push(
|
|
44
|
+
{
|
|
45
|
+
message: "Preparing agent environment\u2026",
|
|
46
|
+
durationMs: STEP_INTERVAL_MS,
|
|
47
|
+
},
|
|
48
|
+
{ message: "Almost ready\u2026", durationMs: STEP_INTERVAL_MS },
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return steps;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Animated inline indicator shown in the message thread while an
|
|
56
|
+
* execution is in the `PENDING` phase and no AI messages have arrived.
|
|
57
|
+
*
|
|
58
|
+
* Cycles through contextual status messages derived from the session
|
|
59
|
+
* configuration (workspace entries, etc.) to communicate that the
|
|
60
|
+
* backend is actively setting up the sandbox, cloning repositories,
|
|
61
|
+
* merging environment variables, loading skills, and connecting MCP
|
|
62
|
+
* servers.
|
|
63
|
+
*
|
|
64
|
+
* Uses time-based progression that approximates the backend's actual
|
|
65
|
+
* setup sequence. A future backend-enriched phase (surfacing real
|
|
66
|
+
* setup phase labels through the execution stream) can replace the
|
|
67
|
+
* timer with server-reported progress.
|
|
68
|
+
*
|
|
69
|
+
* All visual properties flow through `--stgm-*` tokens.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* <SetupProgress workspaceEntries={session.spec?.workspaceEntries} />
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function SetupProgress({
|
|
77
|
+
workspaceEntries,
|
|
78
|
+
className,
|
|
79
|
+
}: SetupProgressProps) {
|
|
80
|
+
const steps = useMemo(() => buildSteps(workspaceEntries), [workspaceEntries]);
|
|
81
|
+
const [stepIndex, setStepIndex] = useState(0);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
setStepIndex(0);
|
|
85
|
+
}, [steps]);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (stepIndex >= steps.length - 1) return;
|
|
89
|
+
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
setStepIndex((i) => Math.min(i + 1, steps.length - 1));
|
|
92
|
+
}, steps[stepIndex].durationMs);
|
|
93
|
+
|
|
94
|
+
return () => clearTimeout(timer);
|
|
95
|
+
}, [stepIndex, steps]);
|
|
96
|
+
|
|
97
|
+
const currentMessage = steps[Math.min(stepIndex, steps.length - 1)].message;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
role="status"
|
|
102
|
+
aria-label={currentMessage}
|
|
103
|
+
className={cn("flex items-center gap-2.5 px-4 py-2", className)}
|
|
104
|
+
>
|
|
105
|
+
<PulseIndicator />
|
|
106
|
+
<span className="text-sm text-muted-foreground animate-in fade-in duration-300">
|
|
107
|
+
{currentMessage}
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function PulseIndicator() {
|
|
114
|
+
return (
|
|
115
|
+
<span className="relative flex h-2 w-2 shrink-0" aria-hidden="true">
|
|
116
|
+
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-muted-foreground opacity-75" />
|
|
117
|
+
<span className="relative inline-flex h-2 w-2 rounded-full bg-muted-foreground" />
|
|
118
|
+
</span>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
5
5
|
import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ApprovalAction,
|
|
8
|
+
ToolCallStatus,
|
|
9
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
7
10
|
import { cn } from "@stigmer/theme";
|
|
8
11
|
import { ToolCallItem } from "./ToolCallItem";
|
|
9
12
|
import { resolveToolCategory, extractPrimaryArg } from "./tool-categories";
|
|
@@ -32,6 +35,14 @@ export interface ToolCallGroupProps {
|
|
|
32
35
|
|
|
33
36
|
type AggregateStatus = "running" | "waiting" | "failed" | "completed" | "pending";
|
|
34
37
|
|
|
38
|
+
function isResolvedApproval(tc: ToolCall): boolean {
|
|
39
|
+
return (
|
|
40
|
+
tc.approvalAction === ApprovalAction.APPROVE ||
|
|
41
|
+
tc.approvalAction === ApprovalAction.SKIP ||
|
|
42
|
+
tc.approvalAction === ApprovalAction.REJECT
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
function deriveAggregateStatus(toolCalls: readonly ToolCall[]): AggregateStatus {
|
|
36
47
|
let hasRunning = false;
|
|
37
48
|
let hasWaiting = false;
|
|
@@ -45,6 +56,9 @@ function deriveAggregateStatus(toolCalls: readonly ToolCall[]): AggregateStatus
|
|
|
45
56
|
allTerminal = false;
|
|
46
57
|
break;
|
|
47
58
|
case ToolCallStatus.TOOL_CALL_WAITING_APPROVAL:
|
|
59
|
+
if (isResolvedApproval(tc)) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
48
62
|
hasWaiting = true;
|
|
49
63
|
allTerminal = false;
|
|
50
64
|
break;
|
|
@@ -61,7 +61,7 @@ export function ToolCallItem({
|
|
|
61
61
|
}: ToolCallItemProps) {
|
|
62
62
|
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
63
63
|
|
|
64
|
-
const status = mapToolCallStatus(toolCall
|
|
64
|
+
const status = mapToolCallStatus(toolCall);
|
|
65
65
|
const StatusIcon = STATUS_ICON[status];
|
|
66
66
|
const duration = formatDuration(toolCall.startedAt, toolCall.completedAt);
|
|
67
67
|
const isSubAgent = subAgentExecution != null;
|
|
@@ -220,11 +220,18 @@ function getApprovalBadge(toolCall: ToolCall): ApprovalBadgeInfo | null {
|
|
|
220
220
|
|
|
221
221
|
type ItemStatus = "running" | "waiting" | "failed" | "completed" | "pending";
|
|
222
222
|
|
|
223
|
-
function mapToolCallStatus(
|
|
224
|
-
switch (status) {
|
|
223
|
+
function mapToolCallStatus(toolCall: ToolCall): ItemStatus {
|
|
224
|
+
switch (toolCall.status) {
|
|
225
225
|
case ToolCallStatus.TOOL_CALL_RUNNING:
|
|
226
226
|
return "running";
|
|
227
227
|
case ToolCallStatus.TOOL_CALL_WAITING_APPROVAL:
|
|
228
|
+
if (
|
|
229
|
+
toolCall.approvalAction === ApprovalAction.APPROVE ||
|
|
230
|
+
toolCall.approvalAction === ApprovalAction.SKIP ||
|
|
231
|
+
toolCall.approvalAction === ApprovalAction.REJECT
|
|
232
|
+
) {
|
|
233
|
+
return "completed";
|
|
234
|
+
}
|
|
228
235
|
return "waiting";
|
|
229
236
|
case ToolCallStatus.TOOL_CALL_FAILED:
|
|
230
237
|
return "failed";
|
|
@@ -41,6 +41,7 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
41
41
|
* Extracts the lowercase file extension from an artifact's name.
|
|
42
42
|
*
|
|
43
43
|
* Returns `null` when the name has no extension or is empty.
|
|
44
|
+
* Delegates to {@link getFileExtension} for the actual parsing.
|
|
44
45
|
*
|
|
45
46
|
* @example
|
|
46
47
|
* ```ts
|
|
@@ -51,10 +52,7 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
51
52
|
export function getArtifactExtension(
|
|
52
53
|
artifact: ExecutionArtifact,
|
|
53
54
|
): string | null {
|
|
54
|
-
|
|
55
|
-
const lastDot = name.lastIndexOf(".");
|
|
56
|
-
if (lastDot === -1 || lastDot === name.length - 1) return null;
|
|
57
|
-
return name.slice(lastDot + 1).toLowerCase();
|
|
55
|
+
return getFileExtension(artifact.name);
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
/**
|
|
@@ -100,6 +98,92 @@ export function isArtifactExpired(artifact: ExecutionArtifact): boolean {
|
|
|
100
98
|
return Date.now() >= expiresMs;
|
|
101
99
|
}
|
|
102
100
|
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Render mode classification
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Content rendering strategy for artifact file preview.
|
|
107
|
+
*
|
|
108
|
+
* Used by {@link ArtifactContentRenderer} to dispatch to the correct
|
|
109
|
+
* renderer, and by platform builders who want to implement custom
|
|
110
|
+
* rendering logic based on file type.
|
|
111
|
+
*
|
|
112
|
+
* - `"markdown"` — rendered HTML via `react-markdown` with themed components
|
|
113
|
+
* - `"yaml"` — CSS-only YAML syntax highlighting
|
|
114
|
+
* - `"json"` — pretty-printed JSON with key/value coloring
|
|
115
|
+
* - `"text"` — monospace plain text with line numbers
|
|
116
|
+
*/
|
|
117
|
+
export type ArtifactRenderMode = "markdown" | "yaml" | "json" | "text";
|
|
118
|
+
|
|
119
|
+
const YAML_EXTENSIONS = new Set(["yaml", "yml"]);
|
|
120
|
+
const JSON_EXTENSIONS = new Set(["json"]);
|
|
121
|
+
const MARKDOWN_EXTENSIONS = new Set(["md", "mdx"]);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Extracts the lowercase file extension from a file name string.
|
|
125
|
+
*
|
|
126
|
+
* Returns `null` when the name has no extension or is empty.
|
|
127
|
+
* Unlike {@link getArtifactExtension}, this operates on a plain
|
|
128
|
+
* string — usable without a full `ExecutionArtifact` object.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* getFileExtension("agent.yaml"); // "yaml"
|
|
133
|
+
* getFileExtension("README.md"); // "md"
|
|
134
|
+
* getFileExtension("Makefile"); // null
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export function getFileExtension(fileName: string): string | null {
|
|
138
|
+
const lastDot = fileName.lastIndexOf(".");
|
|
139
|
+
if (lastDot === -1 || lastDot === fileName.length - 1) return null;
|
|
140
|
+
return fileName.slice(lastDot + 1).toLowerCase();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Determines the optimal rendering strategy for a text artifact.
|
|
145
|
+
*
|
|
146
|
+
* Inspects the file extension first, then falls back to the optional
|
|
147
|
+
* `contentType` MIME string returned by the server. Platform builders
|
|
148
|
+
* can use this to implement custom rendering or to display a mode
|
|
149
|
+
* indicator in their UI.
|
|
150
|
+
*
|
|
151
|
+
* @param fileName - Artifact file name (e.g., `"README.md"`, `"config.yaml"`)
|
|
152
|
+
* @param contentType - Optional MIME content type from the server response
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* getArtifactRenderMode("README.md"); // "markdown"
|
|
157
|
+
* getArtifactRenderMode("config.yaml"); // "yaml"
|
|
158
|
+
* getArtifactRenderMode("data.json"); // "json"
|
|
159
|
+
* getArtifactRenderMode("script.py"); // "text"
|
|
160
|
+
* getArtifactRenderMode("unknown", "application/json"); // "json"
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function getArtifactRenderMode(
|
|
164
|
+
fileName: string,
|
|
165
|
+
contentType?: string | null,
|
|
166
|
+
): ArtifactRenderMode {
|
|
167
|
+
const ext = getFileExtension(fileName);
|
|
168
|
+
|
|
169
|
+
if (ext && MARKDOWN_EXTENSIONS.has(ext)) return "markdown";
|
|
170
|
+
if (ext && YAML_EXTENSIONS.has(ext)) return "yaml";
|
|
171
|
+
if (ext && JSON_EXTENSIONS.has(ext)) return "json";
|
|
172
|
+
|
|
173
|
+
if (contentType) {
|
|
174
|
+
const ct = contentType.toLowerCase();
|
|
175
|
+
if (ct.includes("markdown")) return "markdown";
|
|
176
|
+
if (ct.includes("yaml")) return "yaml";
|
|
177
|
+
if (ct.includes("json")) return "json";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return "text";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Size formatting
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
103
187
|
const SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"] as const;
|
|
104
188
|
|
|
105
189
|
/**
|
package/src/execution/index.ts
CHANGED
|
@@ -24,11 +24,17 @@ export {
|
|
|
24
24
|
isArtifactExpired,
|
|
25
25
|
formatArtifactSize,
|
|
26
26
|
getArtifactExtension,
|
|
27
|
+
getFileExtension,
|
|
28
|
+
getArtifactRenderMode,
|
|
27
29
|
} from "./artifact-utils";
|
|
30
|
+
export type { ArtifactRenderMode } from "./artifact-utils";
|
|
28
31
|
|
|
29
32
|
export { ExecutionPhaseBadge } from "./ExecutionPhaseBadge";
|
|
30
33
|
export type { ExecutionPhaseBadgeProps } from "./ExecutionPhaseBadge";
|
|
31
34
|
|
|
35
|
+
export { SetupProgress } from "./SetupProgress";
|
|
36
|
+
export type { SetupProgressProps } from "./SetupProgress";
|
|
37
|
+
|
|
32
38
|
export { ToolCallGroup } from "./ToolCallGroup";
|
|
33
39
|
export type { ToolCallGroupProps } from "./ToolCallGroup";
|
|
34
40
|
|
|
@@ -65,6 +71,9 @@ export type { ApprovalCardProps } from "./ApprovalCard";
|
|
|
65
71
|
export { ArtifactCard } from "./ArtifactCard";
|
|
66
72
|
export type { ArtifactCardProps } from "./ArtifactCard";
|
|
67
73
|
|
|
74
|
+
export { ArtifactContentRenderer } from "./ArtifactContentRenderer";
|
|
75
|
+
export type { ArtifactContentRendererProps } from "./ArtifactContentRenderer";
|
|
76
|
+
|
|
68
77
|
export { ArtifactPreviewModal } from "./ArtifactPreviewModal";
|
|
69
78
|
export type { ArtifactPreviewModalProps } from "./ArtifactPreviewModal";
|
|
70
79
|
|