@stigmer/react 3.1.2 → 3.1.4
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/composer/SessionComposer.d.ts +37 -5
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -5
- package/composer/SessionComposer.js.map +1 -1
- package/execution/ArtifactApplyButton.d.ts +25 -0
- package/execution/ArtifactApplyButton.d.ts.map +1 -0
- package/execution/ArtifactApplyButton.js +21 -0
- package/execution/ArtifactApplyButton.js.map +1 -0
- package/execution/ArtifactContentBody.d.ts +36 -0
- package/execution/ArtifactContentBody.d.ts.map +1 -0
- package/execution/ArtifactContentBody.js +62 -0
- package/execution/ArtifactContentBody.js.map +1 -0
- package/execution/ArtifactDocument.d.ts +48 -0
- package/execution/ArtifactDocument.d.ts.map +1 -0
- package/execution/ArtifactDocument.js +89 -0
- package/execution/ArtifactDocument.js.map +1 -0
- package/execution/ArtifactPreviewModal.d.ts +9 -9
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +18 -145
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactRow.d.ts +49 -0
- package/execution/ArtifactRow.d.ts.map +1 -0
- package/execution/ArtifactRow.js +56 -0
- package/execution/ArtifactRow.js.map +1 -0
- package/execution/ArtifactsWidget.d.ts +6 -9
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +15 -19
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/FileChangeProgressBar.d.ts +47 -0
- package/execution/FileChangeProgressBar.d.ts.map +1 -0
- package/execution/FileChangeProgressBar.js +49 -0
- package/execution/FileChangeProgressBar.js.map +1 -0
- package/execution/FileChangesView.d.ts.map +1 -1
- package/execution/FileChangesView.js +19 -4
- package/execution/FileChangesView.js.map +1 -1
- package/execution/FileReviewAtoms.d.ts +27 -0
- package/execution/FileReviewAtoms.d.ts.map +1 -0
- package/execution/FileReviewAtoms.js +64 -0
- package/execution/FileReviewAtoms.js.map +1 -0
- package/execution/FileReviewCard.d.ts +2 -2
- package/execution/FileReviewCard.d.ts.map +1 -1
- package/execution/FileReviewCard.js +32 -62
- package/execution/FileReviewCard.js.map +1 -1
- package/execution/MessageEntry.d.ts +0 -9
- package/execution/MessageEntry.d.ts.map +1 -1
- package/execution/MessageEntry.js +1 -11
- package/execution/MessageEntry.js.map +1 -1
- package/execution/MessageThread.d.ts +3 -4
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +25 -11
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts +16 -10
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +31 -7
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanCompletionCard.d.ts +4 -4
- package/execution/PlanCompletionCard.d.ts.map +1 -1
- package/execution/PlanCompletionCard.js +4 -4
- package/execution/PlanCompletionCard.js.map +1 -1
- package/execution/PlanStreamingCard.d.ts +7 -2
- package/execution/PlanStreamingCard.d.ts.map +1 -1
- package/execution/PlanStreamingCard.js +11 -6
- package/execution/PlanStreamingCard.js.map +1 -1
- package/execution/index.d.ts +12 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +6 -1
- package/execution/index.js.map +1 -1
- package/execution/use-build-from-plan-hotkey.d.ts +1 -1
- package/execution/use-build-from-plan-hotkey.js +1 -1
- package/execution/useArtifactContent.d.ts +6 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +7 -2
- package/execution/useArtifactContent.js.map +1 -1
- package/execution/useArtifactCopy.d.ts +51 -0
- package/execution/useArtifactCopy.d.ts.map +1 -0
- package/execution/useArtifactCopy.js +75 -0
- package/execution/useArtifactCopy.js.map +1 -0
- package/execution/useArtifactDownload.d.ts.map +1 -1
- package/execution/useArtifactDownload.js +17 -5
- package/execution/useArtifactDownload.js.map +1 -1
- package/execution/useArtifactInspection.d.ts +85 -0
- package/execution/useArtifactInspection.d.ts.map +1 -0
- package/execution/useArtifactInspection.js +143 -0
- package/execution/useArtifactInspection.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +16 -3
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +1 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/MermaidDiagram.d.ts +47 -0
- package/internal/MermaidDiagram.d.ts.map +1 -0
- package/internal/MermaidDiagram.js +118 -0
- package/internal/MermaidDiagram.js.map +1 -0
- package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
- package/internal/file-tree/FileTreeNode.js +2 -2
- package/internal/file-tree/FileTreeNode.js.map +1 -1
- package/internal/markdown-components.d.ts.map +1 -1
- package/internal/markdown-components.js +34 -0
- package/internal/markdown-components.js.map +1 -1
- package/internal/mermaid-loader.d.ts +26 -0
- package/internal/mermaid-loader.d.ts.map +1 -0
- package/internal/mermaid-loader.js +34 -0
- package/internal/mermaid-loader.js.map +1 -0
- package/internal/mermaid-theme-css.d.ts +41 -0
- package/internal/mermaid-theme-css.d.ts.map +1 -0
- package/internal/mermaid-theme-css.js +179 -0
- package/internal/mermaid-theme-css.js.map +1 -0
- package/internal/slug.d.ts +9 -5
- package/internal/slug.d.ts.map +1 -1
- package/internal/slug.js +10 -6
- package/internal/slug.js.map +1 -1
- package/library/detect-plan-artifact.d.ts +27 -9
- package/library/detect-plan-artifact.d.ts.map +1 -1
- package/library/detect-plan-artifact.js +26 -6
- package/library/detect-plan-artifact.js.map +1 -1
- package/library/index.d.ts +1 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js +1 -1
- package/library/index.js.map +1 -1
- package/package.json +5 -4
- package/session/PlanEditor.js +1 -1
- package/session/PlanEditor.js.map +1 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +122 -30
- package/session/SessionViewer.js.map +1 -1
- package/session/artifact-document.d.ts +13 -0
- package/session/artifact-document.d.ts.map +1 -0
- package/session/artifact-document.js +22 -0
- package/session/artifact-document.js.map +1 -0
- package/session/facets/ArtifactsTab.d.ts +30 -4
- package/session/facets/ArtifactsTab.d.ts.map +1 -1
- package/session/facets/ArtifactsTab.js +32 -10
- package/session/facets/ArtifactsTab.js.map +1 -1
- package/session/index.d.ts +2 -1
- package/session/index.d.ts.map +1 -1
- package/session/index.js +4 -1
- package/session/index.js.map +1 -1
- package/session/plan-document.d.ts +14 -2
- package/session/plan-document.d.ts.map +1 -1
- package/session/plan-document.js +14 -3
- package/session/plan-document.js.map +1 -1
- package/session/usePlanDraft.d.ts +1 -1
- package/session/useSessionArtifacts.d.ts +17 -0
- package/session/useSessionArtifacts.d.ts.map +1 -1
- package/session/useSessionArtifacts.js +16 -5
- package/session/useSessionArtifacts.js.map +1 -1
- package/session/useSessionConversation.d.ts +26 -3
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +51 -6
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useSessionPanel.d.ts +20 -0
- package/session/useSessionPanel.d.ts.map +1 -1
- package/session/useSessionPanel.js +13 -0
- package/session/useSessionPanel.js.map +1 -1
- package/session/useSessionRailViews.d.ts +13 -1
- package/session/useSessionRailViews.d.ts.map +1 -1
- package/session/useSessionRailViews.js +5 -3
- package/session/useSessionRailViews.js.map +1 -1
- package/src/composer/SessionComposer.tsx +76 -7
- package/src/composer/__tests__/SessionComposer-editing.test.tsx +128 -0
- package/src/execution/ArtifactApplyButton.tsx +79 -0
- package/src/execution/ArtifactContentBody.tsx +255 -0
- package/src/execution/ArtifactDocument.tsx +344 -0
- package/src/execution/ArtifactPreviewModal.tsx +49 -415
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/FileChangeProgressBar.tsx +121 -0
- package/src/execution/FileChangesView.tsx +18 -4
- package/src/execution/FileReviewAtoms.tsx +89 -0
- package/src/execution/FileReviewCard.tsx +57 -90
- package/src/execution/MessageEntry.tsx +0 -62
- package/src/execution/MessageThread.tsx +29 -15
- package/src/execution/PlanArtifactCard.tsx +130 -52
- package/src/execution/PlanCompletionCard.tsx +5 -5
- package/src/execution/PlanStreamingCard.tsx +15 -11
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
- package/src/execution/__tests__/MessageThread.test.tsx +1 -1
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +52 -18
- package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
- package/src/execution/__tests__/PlanStreamingCard.test.tsx +11 -4
- package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +104 -8
- package/src/execution/__tests__/message-entry.test.tsx +56 -39
- package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/index.ts +20 -2
- package/src/execution/use-build-from-plan-hotkey.ts +1 -1
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactCopy.ts +109 -0
- package/src/execution/useArtifactDownload.ts +17 -5
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +17 -3
- package/src/index.ts +17 -2
- package/src/internal/MermaidDiagram.tsx +164 -0
- package/src/internal/__tests__/a11y/mermaid-theme.a11y.test.tsx +122 -0
- package/src/internal/__tests__/markdown-components.test.tsx +93 -1
- package/src/internal/__tests__/mermaid-diagram.test.tsx +282 -0
- package/src/internal/file-tree/FileTreeNode.tsx +13 -2
- package/src/internal/markdown-components.tsx +36 -1
- package/src/internal/mermaid-loader.ts +44 -0
- package/src/internal/mermaid-theme-css.ts +178 -0
- package/src/internal/slug.ts +10 -6
- package/src/library/__tests__/detect-plan-artifact.test.ts +21 -0
- package/src/library/detect-plan-artifact.ts +32 -10
- package/src/library/index.ts +2 -0
- package/src/session/PlanEditor.tsx +1 -1
- package/src/session/SessionViewer.tsx +227 -51
- package/src/session/__tests__/PlanEditor.test.tsx +4 -4
- package/src/session/__tests__/PlanStreamingDocument.test.tsx +1 -1
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +57 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +216 -1
- package/src/session/__tests__/useSessionPanel.test.ts +70 -1
- package/src/session/artifact-document.ts +23 -0
- package/src/session/facets/ArtifactsTab.tsx +66 -18
- package/src/session/facets/__tests__/ArtifactsTab.test.tsx +111 -47
- package/src/session/index.ts +5 -1
- package/src/session/plan-document.ts +14 -3
- package/src/session/usePlanDraft.ts +1 -1
- package/src/session/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +80 -10
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/src/workspace/EditorTabs.tsx +38 -9
- package/src/workspace/ExplorerTree.tsx +10 -4
- package/src/workspace/FileViewer.tsx +27 -6
- package/src/workspace/WorkspaceContentSearch.tsx +44 -20
- package/src/workspace/WorkspaceEditor.tsx +19 -16
- package/src/workspace/WorkspaceFileSearch.tsx +51 -28
- package/src/workspace/WorkspaceSurface.tsx +106 -48
- package/src/workspace/__tests__/EditorTabs.test.tsx +4 -2
- package/src/workspace/__tests__/ExplorerTree.test.tsx +52 -0
- package/src/workspace/__tests__/FileViewer.test.tsx +18 -0
- package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +38 -5
- package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
- package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +27 -6
- package/src/workspace/__tests__/WorkspaceSurface.test.tsx +70 -0
- package/src/workspace/__tests__/a11y/harness.tsx +140 -0
- package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
- package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
- package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
- package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
- package/styles.css +1 -1
- package/workspace/EditorTabs.d.ts +21 -3
- package/workspace/EditorTabs.d.ts.map +1 -1
- package/workspace/EditorTabs.js +18 -6
- package/workspace/EditorTabs.js.map +1 -1
- package/workspace/ExplorerTree.js +8 -4
- package/workspace/ExplorerTree.js.map +1 -1
- package/workspace/FileViewer.d.ts.map +1 -1
- package/workspace/FileViewer.js +19 -8
- package/workspace/FileViewer.js.map +1 -1
- package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
- package/workspace/WorkspaceContentSearch.js +23 -2
- package/workspace/WorkspaceContentSearch.js.map +1 -1
- package/workspace/WorkspaceEditor.d.ts.map +1 -1
- package/workspace/WorkspaceEditor.js +8 -10
- package/workspace/WorkspaceEditor.js.map +1 -1
- package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
- package/workspace/WorkspaceFileSearch.js +23 -3
- package/workspace/WorkspaceFileSearch.js.map +1 -1
- package/workspace/WorkspaceSurface.d.ts.map +1 -1
- package/workspace/WorkspaceSurface.js +54 -18
- package/workspace/WorkspaceSurface.js.map +1 -1
- package/execution/ArtifactCard.d.ts +0 -81
- package/execution/ArtifactCard.d.ts.map +0 -1
- package/execution/ArtifactCard.js +0 -130
- package/execution/ArtifactCard.js.map +0 -1
- package/src/execution/ArtifactCard.tsx +0 -320
|
@@ -5,7 +5,7 @@ import { PlanStreamingCard } from "../PlanStreamingCard";
|
|
|
5
5
|
afterEach(cleanup);
|
|
6
6
|
|
|
7
7
|
describe("PlanStreamingCard — live plan stand-in", () => {
|
|
8
|
-
it("is a busy region with the streaming title,
|
|
8
|
+
it("is a busy region with the streaming title, status, and live size", () => {
|
|
9
9
|
render(
|
|
10
10
|
<PlanStreamingCard
|
|
11
11
|
title="Refactor the auth flow"
|
|
@@ -17,11 +17,18 @@ describe("PlanStreamingCard — live plan stand-in", () => {
|
|
|
17
17
|
const region = screen.getByRole("region", { name: "Plan being written" });
|
|
18
18
|
expect(region.getAttribute("aria-busy")).toBe("true");
|
|
19
19
|
expect(region.textContent).toContain("Refactor the auth flow");
|
|
20
|
-
expect(region.textContent).toContain("plan.md");
|
|
21
20
|
expect(region.textContent).toContain("Writing…");
|
|
22
21
|
expect(region.textContent).toContain("2.0 KB");
|
|
23
22
|
});
|
|
24
23
|
|
|
24
|
+
it("shows no filename mid-stream (the final name is not known yet)", () => {
|
|
25
|
+
render(
|
|
26
|
+
<PlanStreamingCard title="Refactor the auth flow" sizeBytes={2048} onOpenPlan={vi.fn()} />,
|
|
27
|
+
);
|
|
28
|
+
const region = screen.getByRole("region", { name: "Plan being written" });
|
|
29
|
+
expect(region.textContent).not.toContain("plan.md");
|
|
30
|
+
});
|
|
31
|
+
|
|
25
32
|
it("falls back to 'Writing plan…' until the title has streamed in", () => {
|
|
26
33
|
render(<PlanStreamingCard sizeBytes={12} onOpenPlan={vi.fn()} />);
|
|
27
34
|
expect(screen.getByText("Writing plan…")).toBeTruthy();
|
|
@@ -37,8 +44,8 @@ describe("PlanStreamingCard — live plan stand-in", () => {
|
|
|
37
44
|
|
|
38
45
|
it("offers no Download or Build actions — nothing final exists yet", () => {
|
|
39
46
|
render(<PlanStreamingCard sizeBytes={64} onOpenPlan={vi.fn()} />);
|
|
40
|
-
expect(screen.
|
|
41
|
-
expect(screen.queryByText("Build
|
|
47
|
+
expect(screen.queryByRole("button", { name: /download/i })).toBeNull();
|
|
48
|
+
expect(screen.queryByText("Build")).toBeNull();
|
|
42
49
|
});
|
|
43
50
|
|
|
44
51
|
it("renders without an action when onOpenPlan is absent", () => {
|
|
@@ -638,7 +638,7 @@ describe("buildThreadItems plan-writing (live streaming collapse)", () => {
|
|
|
638
638
|
});
|
|
639
639
|
});
|
|
640
640
|
|
|
641
|
-
describe("buildThreadItems build-from-plan prompt", () => {
|
|
641
|
+
describe("buildThreadItems build-from-plan prompt suppression", () => {
|
|
642
642
|
function promptItem(items: readonly ThreadItem[], execId: string) {
|
|
643
643
|
return items.find(
|
|
644
644
|
(i): i is Extract<ThreadItem, { kind: "message" }> =>
|
|
@@ -646,7 +646,7 @@ describe("buildThreadItems build-from-plan prompt", () => {
|
|
|
646
646
|
);
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
-
it("
|
|
649
|
+
it("synthesizes NO prompt item for a build turn (the label is machine-written, not user prose)", () => {
|
|
650
650
|
const exec = makeExecution({
|
|
651
651
|
id: "exec-build",
|
|
652
652
|
specMessage: "Build from plan",
|
|
@@ -656,14 +656,17 @@ describe("buildThreadItems build-from-plan prompt", () => {
|
|
|
656
656
|
});
|
|
657
657
|
|
|
658
658
|
const items = buildThreadItems([exec], null, null, false, undefined);
|
|
659
|
-
const prompt = promptItem(items, "exec-build");
|
|
660
659
|
|
|
661
|
-
expect(
|
|
662
|
-
|
|
663
|
-
expect(
|
|
660
|
+
expect(promptItem(items, "exec-build")).toBeUndefined();
|
|
661
|
+
// The turn's agent output still renders — only the fake prompt is gone.
|
|
662
|
+
expect(
|
|
663
|
+
items.some(
|
|
664
|
+
(i) => i.kind === "message" && i.message.content === "Implementing…",
|
|
665
|
+
),
|
|
666
|
+
).toBe(true);
|
|
664
667
|
});
|
|
665
668
|
|
|
666
|
-
it("
|
|
669
|
+
it("still synthesizes the prompt for an ordinary turn", () => {
|
|
667
670
|
const exec = makeExecution({
|
|
668
671
|
id: "exec-normal",
|
|
669
672
|
interactionMode: InteractionMode.AGENT,
|
|
@@ -674,6 +677,99 @@ describe("buildThreadItems build-from-plan prompt", () => {
|
|
|
674
677
|
const prompt = promptItem(items, "exec-normal");
|
|
675
678
|
|
|
676
679
|
expect(prompt).toBeDefined();
|
|
677
|
-
expect(prompt!.
|
|
680
|
+
expect(prompt!.message.content).toBe("test message");
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it("offers no retryMessage on a failed build turn's error item (a plain resend would drop the flag)", () => {
|
|
684
|
+
const exec = makeExecution({
|
|
685
|
+
id: "exec-build-fail",
|
|
686
|
+
specMessage: "Build from plan",
|
|
687
|
+
phase: ExecutionPhase.EXECUTION_FAILED,
|
|
688
|
+
interactionMode: InteractionMode.AGENT,
|
|
689
|
+
buildFromPlan: true,
|
|
690
|
+
});
|
|
691
|
+
exec.status!.error = "runner crashed";
|
|
692
|
+
|
|
693
|
+
const items = buildThreadItems([exec], null, null, false, undefined);
|
|
694
|
+
const errorItem = items.find(
|
|
695
|
+
(i): i is Extract<ThreadItem, { kind: "execution-error" }> =>
|
|
696
|
+
i.kind === "execution-error",
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
expect(errorItem).toBeDefined();
|
|
700
|
+
expect(errorItem!.retryMessage).toBeUndefined();
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("keeps retryMessage on an ordinary failed turn", () => {
|
|
704
|
+
const exec = makeExecution({
|
|
705
|
+
id: "exec-fail",
|
|
706
|
+
specMessage: "do the thing",
|
|
707
|
+
phase: ExecutionPhase.EXECUTION_FAILED,
|
|
708
|
+
interactionMode: InteractionMode.AGENT,
|
|
709
|
+
});
|
|
710
|
+
exec.status!.error = "runner crashed";
|
|
711
|
+
|
|
712
|
+
const items = buildThreadItems([exec], null, null, false, undefined);
|
|
713
|
+
const errorItem = items.find(
|
|
714
|
+
(i): i is Extract<ThreadItem, { kind: "execution-error" }> =>
|
|
715
|
+
i.kind === "execution-error",
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
expect(errorItem!.retryMessage).toBe("do the thing");
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
describe("buildThreadItems pending-bubble tail guard", () => {
|
|
723
|
+
function pendingItem(items: readonly ThreadItem[]) {
|
|
724
|
+
return items.find(
|
|
725
|
+
(i): i is Extract<ThreadItem, { kind: "message" }> =>
|
|
726
|
+
i.kind === "message" && i.key === "pending-user-turn",
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
it("suppresses the pending bubble once the same turn has streamed in (succeeded send)", () => {
|
|
731
|
+
const exec = makeExecution({
|
|
732
|
+
id: "exec-1",
|
|
733
|
+
specMessage: "hello there",
|
|
734
|
+
interactionMode: InteractionMode.AGENT,
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
const items = buildThreadItems(
|
|
738
|
+
[exec],
|
|
739
|
+
null,
|
|
740
|
+
"hello there",
|
|
741
|
+
false,
|
|
742
|
+
undefined,
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
expect(pendingItem(items)).toBeUndefined();
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it("renders a FAILED pending message even when an older execution carries identical text", () => {
|
|
749
|
+
// Two build attempts of the same plan share the message "Build from
|
|
750
|
+
// plan". A failed second send created no execution, so the first build
|
|
751
|
+
// turn must never swallow the failed bubble — the failure would
|
|
752
|
+
// otherwise render nothing at all (build turns synthesize no prompt).
|
|
753
|
+
const firstBuild = makeExecution({
|
|
754
|
+
id: "exec-build-1",
|
|
755
|
+
specMessage: "Build from plan",
|
|
756
|
+
interactionMode: InteractionMode.AGENT,
|
|
757
|
+
buildFromPlan: true,
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
const items = buildThreadItems(
|
|
761
|
+
[firstBuild],
|
|
762
|
+
null,
|
|
763
|
+
"Build from plan",
|
|
764
|
+
false,
|
|
765
|
+
undefined,
|
|
766
|
+
undefined,
|
|
767
|
+
true, // pendingMessageFailed
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
const failed = pendingItem(items);
|
|
771
|
+
expect(failed).toBeDefined();
|
|
772
|
+
expect(failed!.isFailed).toBe(true);
|
|
773
|
+
expect(failed!.message.content).toBe("Build from plan");
|
|
678
774
|
});
|
|
679
775
|
});
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
-
import { render, cleanup } from "@testing-library/react";
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
|
+
import { render, cleanup, waitFor } from "@testing-library/react";
|
|
3
3
|
import { create } from "@bufbuild/protobuf";
|
|
4
4
|
import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
5
5
|
import { MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
6
6
|
import { MessageEntry } from "../MessageEntry";
|
|
7
|
+
import type { MermaidModule } from "../../internal/mermaid-loader";
|
|
8
|
+
|
|
9
|
+
// Stub only the library loader: the real MermaidDiagram + markdown overrides
|
|
10
|
+
// run end-to-end through Streamdown, so these tests cover the full chat path
|
|
11
|
+
// from fenced source to rendered diagram.
|
|
12
|
+
vi.mock("../../internal/mermaid-loader", () => ({
|
|
13
|
+
loadMermaid: vi.fn().mockResolvedValue({
|
|
14
|
+
initialize: vi.fn(),
|
|
15
|
+
render: vi
|
|
16
|
+
.fn()
|
|
17
|
+
.mockResolvedValue({ svg: "<svg data-testid='mermaid-svg'></svg>" }),
|
|
18
|
+
} as unknown as MermaidModule),
|
|
19
|
+
}));
|
|
7
20
|
|
|
8
21
|
afterEach(cleanup);
|
|
9
22
|
|
|
@@ -169,6 +182,41 @@ describe("MessageEntry — AI messages (Streamdown)", () => {
|
|
|
169
182
|
article!.querySelectorAll('span[class*="hljs-"]').length,
|
|
170
183
|
).toBeGreaterThan(0);
|
|
171
184
|
});
|
|
185
|
+
|
|
186
|
+
it("renders a completed ```mermaid fence as a diagram, not a code block", async () => {
|
|
187
|
+
const msg = makeMessage(
|
|
188
|
+
MessageType.MESSAGE_AI,
|
|
189
|
+
"Here is the flow:\n\n```mermaid\nflowchart LR\n A --> B\n```",
|
|
190
|
+
);
|
|
191
|
+
const { container } = render(<MessageEntry message={msg} />);
|
|
192
|
+
|
|
193
|
+
const article = queryArticle(container, "AI response");
|
|
194
|
+
await waitFor(() =>
|
|
195
|
+
expect(
|
|
196
|
+
article!.querySelector('[role="img"][aria-label="Mermaid diagram"]'),
|
|
197
|
+
).not.toBeNull(),
|
|
198
|
+
);
|
|
199
|
+
expect(article!.querySelector("[data-testid='mermaid-svg']")).not.toBeNull();
|
|
200
|
+
// The fenced source is no longer shown as a code block.
|
|
201
|
+
expect(article!.querySelector("pre")).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("keeps an unclosed ```mermaid fence as a code block while streaming", () => {
|
|
205
|
+
const msg = makeMessage(
|
|
206
|
+
MessageType.MESSAGE_AI,
|
|
207
|
+
"```mermaid\nflowchart LR\n A --> B",
|
|
208
|
+
{ isStreaming: true },
|
|
209
|
+
);
|
|
210
|
+
const { container } = render(<MessageEntry message={msg} />);
|
|
211
|
+
|
|
212
|
+
const article = queryArticle(container, "AI response");
|
|
213
|
+
// Mid-stream the source stays a plain code block — no diagram, and no
|
|
214
|
+
// half-parsed error flashing.
|
|
215
|
+
expect(
|
|
216
|
+
article!.querySelector('[role="img"][aria-label="Mermaid diagram"]'),
|
|
217
|
+
).toBeNull();
|
|
218
|
+
expect(article!.textContent).toContain("flowchart LR");
|
|
219
|
+
});
|
|
172
220
|
});
|
|
173
221
|
|
|
174
222
|
// ---------------------------------------------------------------------------
|
|
@@ -195,51 +243,20 @@ describe("MessageEntry — Human messages", () => {
|
|
|
195
243
|
});
|
|
196
244
|
|
|
197
245
|
// ---------------------------------------------------------------------------
|
|
198
|
-
// Build-from-plan
|
|
246
|
+
// Build-from-plan turns — no special human-message treatment
|
|
199
247
|
// ---------------------------------------------------------------------------
|
|
200
248
|
|
|
201
|
-
describe("MessageEntry — Build-from-plan
|
|
202
|
-
it("renders
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const chip = queryArticle(container, "Build from plan");
|
|
209
|
-
expect(chip).not.toBeNull();
|
|
210
|
-
expect(chip!.textContent).toContain("Build from plan");
|
|
211
|
-
// The ordinary prompt bubble must not render alongside the chip.
|
|
212
|
-
expect(queryArticle(container, "User message")).toBeNull();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("keeps the ordinary bubble when the flag is absent", () => {
|
|
249
|
+
describe("MessageEntry — Build-from-plan turns", () => {
|
|
250
|
+
it("renders any human message as the ordinary prompt bubble (the thread hides build turns before they reach MessageEntry)", () => {
|
|
251
|
+
// The retired chip treatment lived here; today buildThreadItems skips
|
|
252
|
+
// synthesizing the prompt item for a build turn entirely, so a human
|
|
253
|
+
// message that DOES reach MessageEntry is always ordinary prose.
|
|
216
254
|
const msg = makeMessage(MessageType.MESSAGE_HUMAN, "Build from plan");
|
|
217
255
|
const { container } = render(<MessageEntry message={msg} />);
|
|
218
256
|
|
|
219
257
|
expect(queryArticle(container, "User message")).not.toBeNull();
|
|
220
258
|
expect(queryArticle(container, "Build from plan")).toBeNull();
|
|
221
259
|
});
|
|
222
|
-
|
|
223
|
-
it("never offers Edit on the chip (the label is not user prose)", () => {
|
|
224
|
-
const msg = makeMessage(MessageType.MESSAGE_HUMAN, "Build from plan");
|
|
225
|
-
const { container } = render(
|
|
226
|
-
<MessageEntry message={msg} isBuildFromPlan onEdit={() => {}} />,
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
expect(
|
|
230
|
-
container.querySelector('[aria-label="Edit message"]'),
|
|
231
|
-
).toBeNull();
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("ignores the flag on non-human messages", () => {
|
|
235
|
-
const msg = makeMessage(MessageType.MESSAGE_AI, "Implementing…");
|
|
236
|
-
const { container } = render(
|
|
237
|
-
<MessageEntry message={msg} isBuildFromPlan />,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
expect(queryArticle(container, "AI response")).not.toBeNull();
|
|
241
|
-
expect(queryArticle(container, "Build from plan")).toBeNull();
|
|
242
|
-
});
|
|
243
260
|
});
|
|
244
261
|
|
|
245
262
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { renderHook, act, waitFor } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
5
|
+
import { StigmerContext } from "../../context";
|
|
6
|
+
import { useArtifactCopy } from "../useArtifactCopy";
|
|
7
|
+
|
|
8
|
+
function wrapperFor(stigmer: Stigmer) {
|
|
9
|
+
return ({ children }: { children: ReactNode }) => (
|
|
10
|
+
<StigmerContext.Provider value={stigmer}>{children}</StigmerContext.Provider>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function makeStigmer(getArtifactContent = vi.fn()) {
|
|
15
|
+
return { agentExecution: { getArtifactContent } } as unknown as Stigmer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
writeText.mockClear();
|
|
22
|
+
vi.stubGlobal("navigator", { clipboard: { writeText } });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("useArtifactCopy", () => {
|
|
26
|
+
it("fetches content at click time and writes decoded text to the clipboard", async () => {
|
|
27
|
+
const bytes = new TextEncoder().encode("# Plan\n\nsteps");
|
|
28
|
+
const fn = vi.fn().mockResolvedValue({ content: bytes });
|
|
29
|
+
const { result } = renderHook(() => useArtifactCopy("aex_1"), {
|
|
30
|
+
wrapper: wrapperFor(makeStigmer(fn)),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result.current.copied).toBe(false);
|
|
34
|
+
await act(async () => {
|
|
35
|
+
await result.current.copy("artifacts/aex_1/plan.md");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
39
|
+
const req = fn.mock.calls[0][0];
|
|
40
|
+
expect(req.executionId).toBe("aex_1");
|
|
41
|
+
expect(req.storageKey).toBe("artifacts/aex_1/plan.md");
|
|
42
|
+
expect(writeText).toHaveBeenCalledWith("# Plan\n\nsteps");
|
|
43
|
+
expect(result.current.copied).toBe(true);
|
|
44
|
+
expect(result.current.error).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("resets the copied flag after the feedback window", async () => {
|
|
48
|
+
vi.useFakeTimers();
|
|
49
|
+
const bytes = new TextEncoder().encode("x");
|
|
50
|
+
const fn = vi.fn().mockResolvedValue({ content: bytes });
|
|
51
|
+
const { result } = renderHook(() => useArtifactCopy("aex_1"), {
|
|
52
|
+
wrapper: wrapperFor(makeStigmer(fn)),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await act(async () => {
|
|
56
|
+
await result.current.copy("artifacts/aex_1/plan.md");
|
|
57
|
+
});
|
|
58
|
+
expect(result.current.copied).toBe(true);
|
|
59
|
+
|
|
60
|
+
act(() => {
|
|
61
|
+
vi.advanceTimersByTime(2000);
|
|
62
|
+
});
|
|
63
|
+
expect(result.current.copied).toBe(false);
|
|
64
|
+
vi.useRealTimers();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("is a no-op when the execution id is unknown", async () => {
|
|
68
|
+
const fn = vi.fn();
|
|
69
|
+
const { result } = renderHook(() => useArtifactCopy(null), {
|
|
70
|
+
wrapper: wrapperFor(makeStigmer(fn)),
|
|
71
|
+
});
|
|
72
|
+
await act(async () => {
|
|
73
|
+
await result.current.copy("artifacts/x/plan.md");
|
|
74
|
+
});
|
|
75
|
+
expect(fn).not.toHaveBeenCalled();
|
|
76
|
+
expect(writeText).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("surfaces an error when the content RPC fails", async () => {
|
|
80
|
+
const fn = vi.fn().mockRejectedValue(new Error("boom"));
|
|
81
|
+
const { result } = renderHook(() => useArtifactCopy("aex_1"), {
|
|
82
|
+
wrapper: wrapperFor(makeStigmer(fn)),
|
|
83
|
+
});
|
|
84
|
+
await act(async () => {
|
|
85
|
+
await result.current.copy("artifacts/aex_1/plan.md");
|
|
86
|
+
});
|
|
87
|
+
await waitFor(() => expect(result.current.error?.message).toBe("boom"));
|
|
88
|
+
expect(result.current.copied).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -38,6 +38,8 @@ describe("useArtifactDownload", () => {
|
|
|
38
38
|
const req = fn.mock.calls[0][0];
|
|
39
39
|
expect(req.executionId).toBe("aex_1");
|
|
40
40
|
expect(req.storageKey).toBe("artifacts/aex_1/file.bin");
|
|
41
|
+
// Save-to-disk actions force an attachment disposition on the minted URL.
|
|
42
|
+
expect(req.asAttachment).toBe(true);
|
|
41
43
|
expect(result.current.isDownloading).toBe(false);
|
|
42
44
|
expect(result.current.error).toBeNull();
|
|
43
45
|
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { renderHook, act, waitFor } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import type { Stigmer } from "@stigmer/sdk";
|
|
6
|
+
import { ExecutionArtifactSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
7
|
+
import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
8
|
+
import { StigmerContext } from "../../context";
|
|
9
|
+
import { useArtifactInspection } from "../useArtifactInspection";
|
|
10
|
+
|
|
11
|
+
function fileArtifact(name: string) {
|
|
12
|
+
return create(ExecutionArtifactSchema, {
|
|
13
|
+
name,
|
|
14
|
+
kind: ExecutionArtifactKind.FILE,
|
|
15
|
+
sizeBytes: 64n,
|
|
16
|
+
sandboxPath: `.stigmer/${name}`,
|
|
17
|
+
storageKey: `artifacts/aex_1/${name}`,
|
|
18
|
+
contentHash: "hash-1",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function dirArtifact(name: string) {
|
|
23
|
+
return create(ExecutionArtifactSchema, {
|
|
24
|
+
name,
|
|
25
|
+
kind: ExecutionArtifactKind.DIRECTORY,
|
|
26
|
+
sizeBytes: 512n,
|
|
27
|
+
sandboxPath: `.stigmer/${name}`,
|
|
28
|
+
storageKey: `artifacts/aex_1/${name}`,
|
|
29
|
+
entries: ["SKILL.md", "run.sh"],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function contentResult(text: string) {
|
|
34
|
+
return {
|
|
35
|
+
content: new TextEncoder().encode(text),
|
|
36
|
+
contentType: "text/plain",
|
|
37
|
+
truncated: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function wrapperFor(stigmer: Stigmer) {
|
|
42
|
+
return ({ children }: { children: ReactNode }) => (
|
|
43
|
+
<StigmerContext.Provider value={stigmer}>{children}</StigmerContext.Provider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const writeText = vi.fn().mockResolvedValue(undefined);
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
writeText.mockClear();
|
|
51
|
+
vi.stubGlobal("navigator", { clipboard: { writeText } });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const AGENT_YAML = [
|
|
55
|
+
"apiVersion: agentic.stigmer.ai/v1",
|
|
56
|
+
"kind: Agent",
|
|
57
|
+
"metadata:",
|
|
58
|
+
" name: my-agent",
|
|
59
|
+
"spec: {}",
|
|
60
|
+
"",
|
|
61
|
+
].join("\n");
|
|
62
|
+
|
|
63
|
+
describe("useArtifactInspection — content + copy", () => {
|
|
64
|
+
it("fetches text content and copies it to the clipboard on demand", async () => {
|
|
65
|
+
const getArtifactContent = vi.fn().mockResolvedValue(contentResult("hello world"));
|
|
66
|
+
const stigmer = { agentExecution: { getArtifactContent } } as unknown as Stigmer;
|
|
67
|
+
|
|
68
|
+
const { result } = renderHook(
|
|
69
|
+
() => useArtifactInspection(fileArtifact("notes.txt"), "aex_1", "acme"),
|
|
70
|
+
{ wrapper: wrapperFor(stigmer) },
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
await waitFor(() => expect(result.current.content).toBe("hello world"));
|
|
74
|
+
expect(result.current.isDirectory).toBe(false);
|
|
75
|
+
expect(result.current.isDetected).toBe(false);
|
|
76
|
+
expect(result.current.ctaLabel).toBeNull();
|
|
77
|
+
|
|
78
|
+
act(() => {
|
|
79
|
+
result.current.copy();
|
|
80
|
+
});
|
|
81
|
+
expect(writeText).toHaveBeenCalledWith("hello world");
|
|
82
|
+
await waitFor(() => expect(result.current.copied).toBe(true));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("does not fetch content for a directory artifact", () => {
|
|
86
|
+
const getArtifactContent = vi.fn().mockReturnValue(new Promise(() => {}));
|
|
87
|
+
const stigmer = { agentExecution: { getArtifactContent } } as unknown as Stigmer;
|
|
88
|
+
|
|
89
|
+
const { result } = renderHook(
|
|
90
|
+
() => useArtifactInspection(dirArtifact("skill-pack"), "aex_1", "acme"),
|
|
91
|
+
{ wrapper: wrapperFor(stigmer) },
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result.current.isDirectory).toBe(true);
|
|
95
|
+
// Directory content is not fetched via the text-content RPC (skill
|
|
96
|
+
// detection uses its own entry-scoped fetch).
|
|
97
|
+
expect(
|
|
98
|
+
getArtifactContent.mock.calls.every(
|
|
99
|
+
(call) => call[0]?.entryPath !== undefined,
|
|
100
|
+
),
|
|
101
|
+
).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("useArtifactInspection — detection + apply", () => {
|
|
106
|
+
it("labels a detected Agent YAML and offers an Apply CTA", async () => {
|
|
107
|
+
const getArtifactContent = vi.fn().mockResolvedValue(contentResult(AGENT_YAML));
|
|
108
|
+
const stigmer = { agentExecution: { getArtifactContent } } as unknown as Stigmer;
|
|
109
|
+
|
|
110
|
+
const { result } = renderHook(
|
|
111
|
+
() => useArtifactInspection(fileArtifact("agent.yaml"), "aex_1", "acme"),
|
|
112
|
+
{ wrapper: wrapperFor(stigmer) },
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
await waitFor(() => expect(result.current.isDetected).toBe(true));
|
|
116
|
+
expect(result.current.detectionLabel).toBe("Agent detected");
|
|
117
|
+
expect(result.current.ctaLabel).toBe("Apply to acme");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("applies a detected Agent and fires onApplied with the result", async () => {
|
|
121
|
+
const getArtifactContent = vi.fn().mockResolvedValue(contentResult(AGENT_YAML));
|
|
122
|
+
const apply = vi.fn().mockResolvedValue({
|
|
123
|
+
metadata: { name: "my-agent", org: "acme", slug: "my-agent" },
|
|
124
|
+
});
|
|
125
|
+
const stigmer = {
|
|
126
|
+
agentExecution: { getArtifactContent },
|
|
127
|
+
agent: { apply },
|
|
128
|
+
} as unknown as Stigmer;
|
|
129
|
+
const onApplied = vi.fn();
|
|
130
|
+
|
|
131
|
+
const { result } = renderHook(
|
|
132
|
+
() =>
|
|
133
|
+
useArtifactInspection(fileArtifact("agent.yaml"), "aex_1", "acme", {
|
|
134
|
+
onApplied,
|
|
135
|
+
}),
|
|
136
|
+
{ wrapper: wrapperFor(stigmer) },
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await waitFor(() => expect(result.current.isDetected).toBe(true));
|
|
140
|
+
|
|
141
|
+
await act(async () => {
|
|
142
|
+
await result.current.apply();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(apply).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(apply.mock.calls[0][0].name).toBe("my-agent");
|
|
147
|
+
await waitFor(() => expect(result.current.applyResult?.kind).toBe("Agent"));
|
|
148
|
+
expect(onApplied).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(onApplied.mock.calls[0][0].name).toBe("my-agent");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -83,3 +83,38 @@ describe("useCreateAgentExecution — executionConfig mapping", () => {
|
|
|
83
83
|
expect(mockCreate.mock.calls[0][0].executionConfig).toBeUndefined();
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
describe("useCreateAgentExecution — supersede link (edit-and-resubmit)", () => {
|
|
88
|
+
it("maps supersedesExecutionId into the create call", async () => {
|
|
89
|
+
const { result } = renderHook(() => useCreateAgentExecution(), {
|
|
90
|
+
wrapper: createWrapper(makeMockClient()),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await act(async () => {
|
|
94
|
+
await result.current.create({
|
|
95
|
+
org: "acme",
|
|
96
|
+
sessionId: "ses-1",
|
|
97
|
+
message: "corrected message",
|
|
98
|
+
supersedesExecutionId: "aex-old",
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(mockCreate.mock.calls[0][0].supersedesExecutionId).toBe("aex-old");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("leaves supersedesExecutionId undefined for ordinary sends", async () => {
|
|
106
|
+
const { result } = renderHook(() => useCreateAgentExecution(), {
|
|
107
|
+
wrapper: createWrapper(makeMockClient()),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await act(async () => {
|
|
111
|
+
await result.current.create({
|
|
112
|
+
org: "acme",
|
|
113
|
+
sessionId: "ses-1",
|
|
114
|
+
message: "Hello",
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(mockCreate.mock.calls[0][0].supersedesExecutionId).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
});
|
package/src/execution/index.ts
CHANGED
|
@@ -62,6 +62,9 @@ export type {
|
|
|
62
62
|
export { useArtifactDownload } from "./useArtifactDownload.js";
|
|
63
63
|
export type { UseArtifactDownloadReturn } from "./useArtifactDownload.js";
|
|
64
64
|
|
|
65
|
+
export { useArtifactCopy } from "./useArtifactCopy.js";
|
|
66
|
+
export type { UseArtifactCopyReturn } from "./useArtifactCopy.js";
|
|
67
|
+
|
|
65
68
|
export { useToolOutputContent } from "./useToolOutputContent.js";
|
|
66
69
|
export type {
|
|
67
70
|
UseToolOutputContentReturn,
|
|
@@ -230,18 +233,33 @@ export type { FileReviewCardProps } from "./FileReviewCard.js";
|
|
|
230
233
|
export { FileReviewDock } from "./FileReviewDock.js";
|
|
231
234
|
export type { FileReviewDockProps } from "./FileReviewDock.js";
|
|
232
235
|
|
|
236
|
+
export { FileChangeProgressBar } from "./FileChangeProgressBar.js";
|
|
237
|
+
export type { FileChangeProgressBarProps } from "./FileChangeProgressBar.js";
|
|
238
|
+
|
|
233
239
|
export { ApprovalContext, useApproval } from "./ApprovalContext.js";
|
|
234
240
|
export type { ApprovalContextValue, UseApprovalResult } from "./ApprovalContext.js";
|
|
235
241
|
|
|
236
242
|
export { FileReviewContext, useFileReviewRowState } from "./FileReviewContext.js";
|
|
237
243
|
export type { FileReviewContextValue } from "./FileReviewContext.js";
|
|
238
244
|
|
|
239
|
-
export {
|
|
240
|
-
export type {
|
|
245
|
+
export { ArtifactRow } from "./ArtifactRow.js";
|
|
246
|
+
export type { ArtifactRowProps } from "./ArtifactRow.js";
|
|
241
247
|
|
|
242
248
|
export { ArtifactContentRenderer } from "./ArtifactContentRenderer.js";
|
|
243
249
|
export type { ArtifactContentRendererProps } from "./ArtifactContentRenderer.js";
|
|
244
250
|
|
|
251
|
+
export { ArtifactContentBody } from "./ArtifactContentBody.js";
|
|
252
|
+
export type { ArtifactContentBodyProps } from "./ArtifactContentBody.js";
|
|
253
|
+
|
|
254
|
+
export { ArtifactDocument } from "./ArtifactDocument.js";
|
|
255
|
+
export type { ArtifactDocumentProps } from "./ArtifactDocument.js";
|
|
256
|
+
|
|
257
|
+
export { useArtifactInspection } from "./useArtifactInspection.js";
|
|
258
|
+
export type {
|
|
259
|
+
ArtifactInspection,
|
|
260
|
+
UseArtifactInspectionOptions,
|
|
261
|
+
} from "./useArtifactInspection.js";
|
|
262
|
+
|
|
245
263
|
export { ArtifactPreviewContent, ArtifactPreviewModal } from "./ArtifactPreviewModal.js";
|
|
246
264
|
export type { ArtifactPreviewContentProps, ArtifactPreviewModalProps } from "./ArtifactPreviewModal.js";
|
|
247
265
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useCallback, type KeyboardEvent } from "react";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Card-scoped `Cmd/Ctrl+Enter` accelerator for
|
|
6
|
+
* Card-scoped `Cmd/Ctrl+Enter` accelerator for a plan card's "Build" action.
|
|
7
7
|
*
|
|
8
8
|
* Returns an `onKeyDown` handler for a plan card's root element. Because it is
|
|
9
9
|
* attached to the card (not `window`), it fires only when the keystroke
|