@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
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
AgentExecutionStatusSchema,
|
|
8
8
|
type AgentExecution,
|
|
9
9
|
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
10
|
+
import { AgentExecutionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
10
11
|
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
11
12
|
import { SessionSchema, type Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
12
13
|
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
@@ -25,7 +26,11 @@ function makeSession(id: string): Session {
|
|
|
25
26
|
return session;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
function makeExecution(
|
|
29
|
+
function makeExecution(
|
|
30
|
+
id: string,
|
|
31
|
+
phase: ExecutionPhase,
|
|
32
|
+
opts?: { supersedes?: string },
|
|
33
|
+
): AgentExecution {
|
|
29
34
|
const exec = create(AgentExecutionSchema);
|
|
30
35
|
const metadata = create(ApiResourceMetadataSchema);
|
|
31
36
|
metadata.id = id;
|
|
@@ -33,6 +38,11 @@ function makeExecution(id: string, phase: ExecutionPhase): AgentExecution {
|
|
|
33
38
|
const status = create(AgentExecutionStatusSchema);
|
|
34
39
|
status.phase = phase;
|
|
35
40
|
exec.status = status;
|
|
41
|
+
if (opts?.supersedes) {
|
|
42
|
+
const spec = create(AgentExecutionSpecSchema);
|
|
43
|
+
spec.supersedesExecutionId = opts.supersedes;
|
|
44
|
+
exec.spec = spec;
|
|
45
|
+
}
|
|
36
46
|
return exec;
|
|
37
47
|
}
|
|
38
48
|
|
|
@@ -312,6 +322,50 @@ describe("useSessionConversation", () => {
|
|
|
312
322
|
});
|
|
313
323
|
});
|
|
314
324
|
|
|
325
|
+
it("sets NO optimistic pendingUserMessage for a buildFromPlan send (the thread hides the turn)", async () => {
|
|
326
|
+
methods.listBySession.mockResolvedValue({ entries: [] });
|
|
327
|
+
const newExec = makeExecution("new-exec", ExecutionPhase.EXECUTION_PENDING);
|
|
328
|
+
newExec.metadata!.id = "new-exec";
|
|
329
|
+
methods.executionCreate.mockResolvedValue(newExec);
|
|
330
|
+
|
|
331
|
+
const { result } = renderHook(
|
|
332
|
+
() => useSessionConversation("session-1", "org"),
|
|
333
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
334
|
+
);
|
|
335
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
336
|
+
|
|
337
|
+
await act(async () => {
|
|
338
|
+
await result.current.sendFollowUp("Build from plan", {
|
|
339
|
+
buildFromPlan: true,
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
expect(result.current.pendingUserMessage).toBeNull();
|
|
344
|
+
expect(methods.executionCreate).toHaveBeenCalledWith(
|
|
345
|
+
expect.objectContaining({ message: "Build from plan" }),
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("sets pendingUserMessage when a buildFromPlan send FAILS (failure must be visible)", async () => {
|
|
350
|
+
methods.listBySession.mockResolvedValue({ entries: [] });
|
|
351
|
+
methods.executionCreate.mockRejectedValue(new Error("create boom"));
|
|
352
|
+
|
|
353
|
+
const { result } = renderHook(
|
|
354
|
+
() => useSessionConversation("session-1", "org"),
|
|
355
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
356
|
+
);
|
|
357
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
358
|
+
|
|
359
|
+
await act(async () => {
|
|
360
|
+
await result.current.sendFollowUp("Build from plan", {
|
|
361
|
+
buildFromPlan: true,
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
expect(result.current.sendError?.message).toBe("create boom");
|
|
366
|
+
expect(result.current.pendingUserMessage).toBe("Build from plan");
|
|
367
|
+
});
|
|
368
|
+
|
|
315
369
|
it("preserves the message and surfaces sendError when the send fails", async () => {
|
|
316
370
|
methods.listBySession.mockResolvedValue({ entries: [] });
|
|
317
371
|
methods.executionCreate.mockRejectedValue(new Error("create boom"));
|
|
@@ -505,6 +559,167 @@ describe("useSessionConversation", () => {
|
|
|
505
559
|
});
|
|
506
560
|
});
|
|
507
561
|
|
|
562
|
+
describe("useSessionConversation — supersede filtering (edit-and-resubmit)", () => {
|
|
563
|
+
let methods: MockMethods;
|
|
564
|
+
let mockStigmer: Stigmer;
|
|
565
|
+
let stream: ReturnType<typeof createControllableStream<AgentExecution>>;
|
|
566
|
+
|
|
567
|
+
beforeEach(() => {
|
|
568
|
+
stream = createControllableStream<AgentExecution>();
|
|
569
|
+
methods = {
|
|
570
|
+
sessionGet: vi.fn().mockResolvedValue(makeSession("session-1")),
|
|
571
|
+
listBySession: vi.fn().mockResolvedValue({ entries: [] }),
|
|
572
|
+
executionCreate: vi.fn(),
|
|
573
|
+
subscribe: vi.fn().mockReturnValue(stream.generator),
|
|
574
|
+
submitApproval: vi.fn().mockResolvedValue({}),
|
|
575
|
+
};
|
|
576
|
+
mockStigmer = createMockStigmer(methods);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("hides a superseded execution from completedExecutions", async () => {
|
|
580
|
+
const cancelled = makeExecution("e1", ExecutionPhase.EXECUTION_CANCELLED);
|
|
581
|
+
const successor = makeExecution("e2", ExecutionPhase.EXECUTION_COMPLETED, {
|
|
582
|
+
supersedes: "e1",
|
|
583
|
+
});
|
|
584
|
+
methods.listBySession.mockResolvedValue({
|
|
585
|
+
entries: [cancelled, successor],
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const { result } = renderHook(
|
|
589
|
+
() => useSessionConversation("session-1", "org"),
|
|
590
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
594
|
+
expect(result.current.completedExecutions).toHaveLength(1);
|
|
595
|
+
expect(result.current.completedExecutions[0]).toBe(successor);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it("hides every link of a chained supersede (A <- B <- C)", async () => {
|
|
599
|
+
const a = makeExecution("e1", ExecutionPhase.EXECUTION_CANCELLED);
|
|
600
|
+
const b = makeExecution("e2", ExecutionPhase.EXECUTION_CANCELLED, {
|
|
601
|
+
supersedes: "e1",
|
|
602
|
+
});
|
|
603
|
+
const c = makeExecution("e3", ExecutionPhase.EXECUTION_COMPLETED, {
|
|
604
|
+
supersedes: "e2",
|
|
605
|
+
});
|
|
606
|
+
methods.listBySession.mockResolvedValue({ entries: [a, b, c] });
|
|
607
|
+
|
|
608
|
+
const { result } = renderHook(
|
|
609
|
+
() => useSessionConversation("session-1", "org"),
|
|
610
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
614
|
+
expect(result.current.completedExecutions).toHaveLength(1);
|
|
615
|
+
expect(result.current.completedExecutions[0]).toBe(c);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it("filters via the streaming successor's link before the list refetch delivers it", async () => {
|
|
619
|
+
// The stopped turn is still the only listed execution; its successor
|
|
620
|
+
// exists only as the live stream — the supersede link must be read off
|
|
621
|
+
// the stream copy for the old turn to disappear immediately.
|
|
622
|
+
const stopped = makeExecution("e1", ExecutionPhase.EXECUTION_IN_PROGRESS);
|
|
623
|
+
methods.listBySession.mockResolvedValue({ entries: [stopped] });
|
|
624
|
+
|
|
625
|
+
const successorStream = createControllableStream<AgentExecution>();
|
|
626
|
+
methods.subscribe.mockReturnValue(successorStream.generator);
|
|
627
|
+
methods.executionCreate.mockResolvedValue(
|
|
628
|
+
makeExecution("e2", ExecutionPhase.EXECUTION_PENDING),
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
const { result } = renderHook(
|
|
632
|
+
() => useSessionConversation("session-1", "org"),
|
|
633
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
634
|
+
);
|
|
635
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
636
|
+
|
|
637
|
+
await act(async () => {
|
|
638
|
+
await result.current.sendFollowUp("corrected message", {
|
|
639
|
+
supersedesExecutionId: "e1",
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
act(() => {
|
|
644
|
+
successorStream.push(
|
|
645
|
+
makeExecution("e2", ExecutionPhase.EXECUTION_IN_PROGRESS, {
|
|
646
|
+
supersedes: "e1",
|
|
647
|
+
}),
|
|
648
|
+
);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
await waitFor(() => {
|
|
652
|
+
expect(result.current.completedExecutions).toHaveLength(0);
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it("treats a dangling supersede link as a no-op", async () => {
|
|
657
|
+
const completed = makeExecution("e1", ExecutionPhase.EXECUTION_COMPLETED);
|
|
658
|
+
const successor = makeExecution("e2", ExecutionPhase.EXECUTION_COMPLETED, {
|
|
659
|
+
supersedes: "does-not-exist",
|
|
660
|
+
});
|
|
661
|
+
methods.listBySession.mockResolvedValue({
|
|
662
|
+
entries: [completed, successor],
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const { result } = renderHook(
|
|
666
|
+
() => useSessionConversation("session-1", "org"),
|
|
667
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
671
|
+
expect(result.current.completedExecutions).toHaveLength(2);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("sendFollowUp forwards supersedesExecutionId to execution creation", async () => {
|
|
675
|
+
methods.listBySession.mockResolvedValue({ entries: [] });
|
|
676
|
+
methods.executionCreate.mockResolvedValue(
|
|
677
|
+
makeExecution("e2", ExecutionPhase.EXECUTION_PENDING),
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
const { result } = renderHook(
|
|
681
|
+
() => useSessionConversation("session-1", "org"),
|
|
682
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
683
|
+
);
|
|
684
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
685
|
+
|
|
686
|
+
await act(async () => {
|
|
687
|
+
await result.current.sendFollowUp("corrected message", {
|
|
688
|
+
supersedesExecutionId: "e1",
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
expect(methods.executionCreate).toHaveBeenCalledWith(
|
|
693
|
+
expect.objectContaining({
|
|
694
|
+
message: "corrected message",
|
|
695
|
+
supersedesExecutionId: "e1",
|
|
696
|
+
}),
|
|
697
|
+
);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it("keeps active-id resolution on the raw list when the superseded turn is still winding down", async () => {
|
|
701
|
+
// e1 was cancelled but has not reached a terminal phase yet; e2 (its
|
|
702
|
+
// successor) is the active turn. The display filter hides e1, but the
|
|
703
|
+
// active execution must still resolve to e2 from the UNfiltered list.
|
|
704
|
+
const windingDown = makeExecution("e1", ExecutionPhase.EXECUTION_IN_PROGRESS);
|
|
705
|
+
const successor = makeExecution("e2", ExecutionPhase.EXECUTION_IN_PROGRESS, {
|
|
706
|
+
supersedes: "e1",
|
|
707
|
+
});
|
|
708
|
+
methods.listBySession.mockResolvedValue({
|
|
709
|
+
entries: [windingDown, successor],
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
const { result } = renderHook(
|
|
713
|
+
() => useSessionConversation("session-1", "org"),
|
|
714
|
+
{ wrapper: createWrapper(mockStigmer) },
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
718
|
+
expect(result.current.activeStreamExecution?.metadata?.id).toBe("e2");
|
|
719
|
+
expect(result.current.completedExecutions).toHaveLength(0);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
|
|
508
723
|
describe("useSessionConversation — local runner worker lifecycle", () => {
|
|
509
724
|
let methods: MockMethods;
|
|
510
725
|
let mockStigmer: Stigmer;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from "vitest";
|
|
2
2
|
import { renderHook, act, cleanup } from "@testing-library/react";
|
|
3
|
-
import {
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import {
|
|
5
|
+
ExecutionArtifactKind,
|
|
6
|
+
ExecutionPhase,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
8
|
+
import { ExecutionArtifactSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
|
|
4
9
|
import { useSessionPanel, type UseSessionPanelOptions } from "../useSessionPanel";
|
|
5
10
|
import { PLAN_DOCUMENT_ENTRY_ID, PLAN_DOCUMENT_PATH } from "../plan-document";
|
|
11
|
+
import { ARTIFACT_DOCUMENT_ENTRY_ID } from "../artifact-document";
|
|
12
|
+
import { artifactKey } from "../useSessionArtifacts";
|
|
6
13
|
import type { SelectedThreadItem } from "../../internal/store/selection-store";
|
|
7
14
|
|
|
8
15
|
// ---------------------------------------------------------------------------
|
|
@@ -255,6 +262,68 @@ describe("useSessionPanel — plan document tab", () => {
|
|
|
255
262
|
});
|
|
256
263
|
});
|
|
257
264
|
|
|
265
|
+
describe("useSessionPanel — artifact document tabs", () => {
|
|
266
|
+
function artifact(sandboxPath: string, name: string) {
|
|
267
|
+
return create(ExecutionArtifactSchema, {
|
|
268
|
+
name,
|
|
269
|
+
kind: ExecutionArtifactKind.FILE,
|
|
270
|
+
sandboxPath,
|
|
271
|
+
storageKey: `artifacts/aex_1/${name}`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
it("openArtifact opens the panel with a PREVIEW tab keyed by artifactKey", () => {
|
|
276
|
+
const { result } = renderPanel();
|
|
277
|
+
const a = artifact(".stigmer/notes.md", "notes.md");
|
|
278
|
+
act(() => result.current.openArtifact(a));
|
|
279
|
+
|
|
280
|
+
expect(result.current.isOpen).toBe(true);
|
|
281
|
+
expect(result.current.editorsStore.getSnapshot().editors).toEqual([
|
|
282
|
+
{ entryId: ARTIFACT_DOCUMENT_ENTRY_ID, path: artifactKey(a), preview: true },
|
|
283
|
+
]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("reuses the single preview slot as different artifacts are browsed", () => {
|
|
287
|
+
const { result } = renderPanel();
|
|
288
|
+
act(() => result.current.openArtifact(artifact(".stigmer/a.md", "a.md")));
|
|
289
|
+
act(() => result.current.openArtifact(artifact(".stigmer/b.md", "b.md")));
|
|
290
|
+
|
|
291
|
+
// VS Code preview semantics: single-click browsing reuses one tab.
|
|
292
|
+
expect(result.current.editorsStore.getSnapshot().editors).toEqual([
|
|
293
|
+
{ entryId: ARTIFACT_DOCUMENT_ENTRY_ID, path: ".stigmer/b.md", preview: true },
|
|
294
|
+
]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("does not evict a pinned plan tab when an artifact opens", () => {
|
|
298
|
+
const { result } = renderPanel();
|
|
299
|
+
act(() => result.current.openPlanDocument());
|
|
300
|
+
act(() => result.current.openArtifact(artifact(".stigmer/a.md", "a.md")));
|
|
301
|
+
|
|
302
|
+
expect(result.current.editorsStore.getSnapshot().editors).toEqual([
|
|
303
|
+
{ entryId: PLAN_DOCUMENT_ENTRY_ID, path: PLAN_DOCUMENT_PATH, preview: false },
|
|
304
|
+
{ entryId: ARTIFACT_DOCUMENT_ENTRY_ID, path: ".stigmer/a.md", preview: true },
|
|
305
|
+
]);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("pinArtifact promotes an open artifact preview to a persistent tab", () => {
|
|
309
|
+
const { result } = renderPanel();
|
|
310
|
+
const a = artifact(".stigmer/notes.md", "notes.md");
|
|
311
|
+
// The single-click open leaves a preview tab; the double-click pins it.
|
|
312
|
+
act(() => result.current.openArtifact(a));
|
|
313
|
+
act(() => result.current.pinArtifact(a));
|
|
314
|
+
|
|
315
|
+
expect(result.current.editorsStore.getSnapshot().editors).toEqual([
|
|
316
|
+
{ entryId: ARTIFACT_DOCUMENT_ENTRY_ID, path: artifactKey(a), preview: false },
|
|
317
|
+
]);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("pinArtifact is a no-op when the artifact is not open (mirrors the store's generic pin)", () => {
|
|
321
|
+
const { result } = renderPanel();
|
|
322
|
+
act(() => result.current.pinArtifact(artifact(".stigmer/x.md", "x.md")));
|
|
323
|
+
expect(result.current.editorsStore.getSnapshot().editors).toEqual([]);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
258
327
|
describe("useSessionPanel — defaultView (home view)", () => {
|
|
259
328
|
it("seeds the view to the provided defaultView (launcher homes on Config)", () => {
|
|
260
329
|
const { result } = renderPanel({ defaultView: "configure" });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Identity of the session's artifact document tabs in the workspace surface's
|
|
2
|
+
// editor group.
|
|
3
|
+
//
|
|
4
|
+
// Kept in its own module (not in ArtifactDocument.tsx) so behavior-only
|
|
5
|
+
// consumers — useSessionPanel opens an artifact tab, SessionViewer routes to it
|
|
6
|
+
// — depend on the identity without pulling the component (and its content
|
|
7
|
+
// renderer) into their module graph (DD-003 headless-first). Mirrors
|
|
8
|
+
// plan-document.ts.
|
|
9
|
+
|
|
10
|
+
import { virtualEntryId } from "../internal/store/index.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Virtual entry id of the artifact document FAMILY. Unlike the plan (one tab
|
|
14
|
+
* per session), artifacts are many: every opened artifact shares this one
|
|
15
|
+
* entry id and is distinguished by its {@link artifactKey}-derived `path`, so
|
|
16
|
+
* `editorKey(ARTIFACT_DOCUMENT_ENTRY_ID, path)` yields one tab per artifact —
|
|
17
|
+
* the VS Code "each file is a tab" model. The NUL-namespaced virtual id can
|
|
18
|
+
* never alias a real workspace entry, and its distinct `"artifact"` kind can
|
|
19
|
+
* never collide with the plan tab's `"plan"` kind.
|
|
20
|
+
*
|
|
21
|
+
* @see artifactKey — the `path` component (an artifact's stable identity)
|
|
22
|
+
*/
|
|
23
|
+
export const ARTIFACT_DOCUMENT_ENTRY_ID = virtualEntryId("artifact");
|
|
@@ -4,10 +4,11 @@ import { useCallback, useState } from "react";
|
|
|
4
4
|
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
5
5
|
import {
|
|
6
6
|
useSessionArtifacts,
|
|
7
|
+
artifactKey,
|
|
7
8
|
type SessionArtifactEntry,
|
|
8
9
|
} from "../useSessionArtifacts.js";
|
|
9
|
-
import { ArtifactCard } from "../../execution/ArtifactCard.js";
|
|
10
10
|
import { ArtifactPreviewModal } from "../../execution/ArtifactPreviewModal.js";
|
|
11
|
+
import { ArtifactRow } from "../../execution/ArtifactRow.js";
|
|
11
12
|
import { isPlanArtifact } from "../../library/detect-plan-artifact.js";
|
|
12
13
|
import type { ApplyResourceResult } from "../../library/useApplyResource.js";
|
|
13
14
|
|
|
@@ -29,14 +30,39 @@ export interface ArtifactsTabProps {
|
|
|
29
30
|
* every artifact.
|
|
30
31
|
*/
|
|
31
32
|
readonly onOpenPlan?: (executionId: string) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Open an artifact as an editor-pane document (the session panel's
|
|
35
|
+
* artifact-family virtual-document tab — the VS Code "each file is a tab"
|
|
36
|
+
* model). When provided, clicking a non-plan artifact routes here instead of
|
|
37
|
+
* the preview modal. When omitted (panel-less hosts) the modal remains the
|
|
38
|
+
* open mechanism. Plan artifacts always defer to {@link onOpenPlan} first.
|
|
39
|
+
*/
|
|
40
|
+
readonly onOpenArtifact?: (entry: SessionArtifactEntry) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Pin a non-plan artifact's document tab — the double-click half of the
|
|
43
|
+
* open/activate split (mirrors the file tree's `onOpenFile`/`onActivateFile`).
|
|
44
|
+
* When provided, each row binds a double-click that promotes the preview the
|
|
45
|
+
* single click opened. Omit in panel-less/modal hosts: with no handler the
|
|
46
|
+
* row binds no double-click, so a double-click is just two harmless single
|
|
47
|
+
* clicks. Plans always route to {@link onOpenPlan} (their tab is already
|
|
48
|
+
* pinned), so double-click == single-click for a plan.
|
|
49
|
+
*/
|
|
50
|
+
readonly onActivateArtifact?: (entry: SessionArtifactEntry) => void;
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
/**
|
|
35
54
|
* Artifacts facet for the session panel (a `useSessionRailViews` rail view).
|
|
36
55
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
56
|
+
* A VS Code-style dense file list of the session's outputs: one compact
|
|
57
|
+
* {@link ArtifactRow} per artifact (shared file-type icon + name + size, with a
|
|
58
|
+
* hover/focus Download). Clicking a row opens the artifact — a `plan.md` in the
|
|
59
|
+
* plan document tab ({@link onOpenPlan}), any other artifact in an editor-pane
|
|
60
|
+
* document ({@link onOpenArtifact}), or, for panel-less hosts that inject
|
|
61
|
+
* neither, the {@link ArtifactPreviewModal} popup fallback.
|
|
62
|
+
*
|
|
63
|
+
* Deliberately metadata-only: rows do NOT fetch content for resource detection
|
|
64
|
+
* (that would be one request per artifact on open) — detection and the
|
|
65
|
+
* Apply/Push action live in the opened document/modal instead.
|
|
40
66
|
*/
|
|
41
67
|
export function ArtifactsTab({
|
|
42
68
|
executions,
|
|
@@ -44,20 +70,41 @@ export function ArtifactsTab({
|
|
|
44
70
|
onApplied,
|
|
45
71
|
onImplementPlan,
|
|
46
72
|
onOpenPlan,
|
|
73
|
+
onOpenArtifact,
|
|
74
|
+
onActivateArtifact,
|
|
47
75
|
}: ArtifactsTabProps) {
|
|
48
76
|
const { artifacts, hasArtifacts } = useSessionArtifacts(executions);
|
|
49
77
|
const [previewEntry, setPreviewEntry] = useState<SessionArtifactEntry | null>(null);
|
|
50
78
|
|
|
51
|
-
const
|
|
79
|
+
const handleOpen = useCallback(
|
|
52
80
|
(entry: SessionArtifactEntry) => {
|
|
53
|
-
//
|
|
81
|
+
// A plan is a first-class document — it opens in the dedicated plan tab,
|
|
82
|
+
// never as a generic artifact or a popup.
|
|
54
83
|
if (onOpenPlan && isPlanArtifact(entry.artifact)) {
|
|
55
84
|
onOpenPlan(entry.executionId);
|
|
56
85
|
return;
|
|
57
86
|
}
|
|
87
|
+
// Editor-pane document when the host injected it; otherwise the modal.
|
|
88
|
+
if (onOpenArtifact) {
|
|
89
|
+
onOpenArtifact(entry);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
58
92
|
setPreviewEntry(entry);
|
|
59
93
|
},
|
|
60
|
-
[onOpenPlan],
|
|
94
|
+
[onOpenPlan, onOpenArtifact],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const handleActivate = useCallback(
|
|
98
|
+
(entry: SessionArtifactEntry) => {
|
|
99
|
+
// A plan's tab is always pinned, so double-click matches single-click:
|
|
100
|
+
// route to the plan tab, never pin a generic artifact.
|
|
101
|
+
if (onOpenPlan && isPlanArtifact(entry.artifact)) {
|
|
102
|
+
onOpenPlan(entry.executionId);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
onActivateArtifact?.(entry);
|
|
106
|
+
},
|
|
107
|
+
[onOpenPlan, onActivateArtifact],
|
|
61
108
|
);
|
|
62
109
|
|
|
63
110
|
const handleClosePreview = useCallback(() => {
|
|
@@ -76,19 +123,20 @@ export function ArtifactsTab({
|
|
|
76
123
|
|
|
77
124
|
return (
|
|
78
125
|
<>
|
|
79
|
-
<
|
|
126
|
+
<ul role="list" className="flex flex-col">
|
|
80
127
|
{artifacts.map((entry) => (
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
128
|
+
<ArtifactRow
|
|
129
|
+
key={artifactKey(entry.artifact)}
|
|
130
|
+
artifact={entry.artifact}
|
|
131
|
+
executionId={entry.executionId}
|
|
132
|
+
hasNameCollision={entry.hasNameCollision}
|
|
133
|
+
onOpen={() => handleOpen(entry)}
|
|
134
|
+
onActivate={
|
|
135
|
+
onActivateArtifact ? () => handleActivate(entry) : undefined
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
90
138
|
))}
|
|
91
|
-
</
|
|
139
|
+
</ul>
|
|
92
140
|
|
|
93
141
|
{previewEntry && (
|
|
94
142
|
<ArtifactPreviewModal
|