@stigmer/react 3.0.2 → 3.0.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.
Files changed (64) hide show
  1. package/composer/SessionComposer.d.ts +23 -5
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +12 -5
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/composer/index.d.ts +1 -0
  6. package/composer/index.d.ts.map +1 -1
  7. package/composer/index.js +1 -0
  8. package/composer/index.js.map +1 -1
  9. package/composer/interaction-mode.d.ts +21 -0
  10. package/composer/interaction-mode.d.ts.map +1 -0
  11. package/composer/interaction-mode.js +37 -0
  12. package/composer/interaction-mode.js.map +1 -0
  13. package/execution/ArtifactPreviewModal.d.ts +15 -2
  14. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  15. package/execution/ArtifactPreviewModal.js +16 -6
  16. package/execution/ArtifactPreviewModal.js.map +1 -1
  17. package/execution/MessageThread.d.ts +17 -2
  18. package/execution/MessageThread.d.ts.map +1 -1
  19. package/execution/MessageThread.js +7 -7
  20. package/execution/MessageThread.js.map +1 -1
  21. package/execution/PlanArtifactCard.d.ts +13 -5
  22. package/execution/PlanArtifactCard.d.ts.map +1 -1
  23. package/execution/PlanArtifactCard.js +14 -34
  24. package/execution/PlanArtifactCard.js.map +1 -1
  25. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  26. package/execution/useCreateAgentExecution.js +2 -6
  27. package/execution/useCreateAgentExecution.js.map +1 -1
  28. package/internal/VirtualizedThread.d.ts +3 -1
  29. package/internal/VirtualizedThread.d.ts.map +1 -1
  30. package/internal/VirtualizedThread.js +4 -2
  31. package/internal/VirtualizedThread.js.map +1 -1
  32. package/package.json +4 -4
  33. package/session/SessionViewer.d.ts.map +1 -1
  34. package/session/SessionViewer.js +20 -12
  35. package/session/SessionViewer.js.map +1 -1
  36. package/session/inspector/ArtifactsTab.d.ts +7 -1
  37. package/session/inspector/ArtifactsTab.d.ts.map +1 -1
  38. package/session/inspector/ArtifactsTab.js +3 -2
  39. package/session/inspector/ArtifactsTab.js.map +1 -1
  40. package/session/inspector/SessionInspector.d.ts +5 -0
  41. package/session/inspector/SessionInspector.d.ts.map +1 -1
  42. package/session/inspector/SessionInspector.js +2 -2
  43. package/session/inspector/SessionInspector.js.map +1 -1
  44. package/session/useSessionPageFlow.d.ts +12 -1
  45. package/session/useSessionPageFlow.d.ts.map +1 -1
  46. package/session/useSessionPageFlow.js +12 -0
  47. package/session/useSessionPageFlow.js.map +1 -1
  48. package/src/composer/SessionComposer.tsx +34 -9
  49. package/src/composer/__tests__/SessionComposer-contract.test.tsx +42 -2
  50. package/src/composer/__tests__/interaction-mode.test.ts +44 -0
  51. package/src/composer/index.ts +5 -0
  52. package/src/composer/interaction-mode.ts +43 -0
  53. package/src/execution/ArtifactPreviewModal.tsx +61 -1
  54. package/src/execution/MessageThread.tsx +35 -1
  55. package/src/execution/PlanArtifactCard.tsx +45 -85
  56. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +85 -0
  57. package/src/execution/__tests__/PlanArtifactCard.test.tsx +122 -0
  58. package/src/execution/useCreateAgentExecution.ts +2 -7
  59. package/src/internal/VirtualizedThread.tsx +7 -1
  60. package/src/session/SessionViewer.tsx +25 -10
  61. package/src/session/inspector/ArtifactsTab.tsx +11 -1
  62. package/src/session/inspector/SessionInspector.tsx +7 -0
  63. package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +90 -0
  64. package/src/session/useSessionPageFlow.ts +33 -1
@@ -8,12 +8,19 @@ import {
8
8
  } from "../useSessionArtifacts";
9
9
  import { ArtifactCard } from "../../execution/ArtifactCard";
10
10
  import { ArtifactPreviewModal } from "../../execution/ArtifactPreviewModal";
11
+ import { isPlanArtifact } from "../../library/detect-plan-artifact";
11
12
  import type { ApplyResourceResult } from "../../library/useApplyResource";
12
13
 
13
14
  export interface ArtifactsTabProps {
14
15
  readonly executions: readonly AgentExecution[];
15
16
  readonly org: string;
16
17
  readonly onApplied?: (result: ApplyResourceResult) => void;
18
+ /**
19
+ * Implement a plan. When provided, the preview of a `plan.md` artifact
20
+ * shows an Implement action (turn the plan into an Agent run) — the same
21
+ * action as the message-thread plan card.
22
+ */
23
+ readonly onImplementPlan?: () => void;
17
24
  }
18
25
 
19
26
  /**
@@ -23,7 +30,7 @@ export interface ArtifactsTabProps {
23
30
  * `ArtifactPreviewModal` — the same content as `ArtifactsWidget`
24
31
  * without the section heading.
25
32
  */
26
- export function ArtifactsTab({ executions, org, onApplied }: ArtifactsTabProps) {
33
+ export function ArtifactsTab({ executions, org, onApplied, onImplementPlan }: ArtifactsTabProps) {
27
34
  const { artifacts, hasArtifacts } = useSessionArtifacts(executions);
28
35
  const [previewEntry, setPreviewEntry] = useState<SessionArtifactEntry | null>(null);
29
36
 
@@ -70,6 +77,9 @@ export function ArtifactsTab({ executions, org, onApplied }: ArtifactsTabProps)
70
77
  open
71
78
  onClose={handleClosePreview}
72
79
  onApplied={onApplied}
80
+ onImplement={
81
+ isPlanArtifact(previewEntry.artifact) ? onImplementPlan : undefined
82
+ }
73
83
  />
74
84
  )}
75
85
  </>
@@ -39,6 +39,11 @@ export interface SessionInspectorProps {
39
39
  readonly selectedItem: SelectedThreadItem | null;
40
40
  /** Called after a resource is applied from the Artifacts tab. */
41
41
  readonly onApplied?: (result: ApplyResourceResult) => void;
42
+ /**
43
+ * Implement a plan from the Artifacts tab. When provided, plan
44
+ * artifacts (`plan.md`) surface an Implement action in their preview.
45
+ */
46
+ readonly onImplementPlan?: () => void;
42
47
  /**
43
48
  * Session-level configuration for the Configure tab.
44
49
  * Includes core config fields and optional interactive
@@ -79,6 +84,7 @@ export const SessionInspector = memo(function SessionInspector({
79
84
  org,
80
85
  selectedItem,
81
86
  onApplied,
87
+ onImplementPlan,
82
88
  sessionConfig,
83
89
  workspaceConfig,
84
90
  className,
@@ -138,6 +144,7 @@ export const SessionInspector = memo(function SessionInspector({
138
144
  executions={allExecutions}
139
145
  org={org}
140
146
  onApplied={onApplied}
147
+ onImplementPlan={onImplementPlan}
141
148
  />
142
149
  )}
143
150
  {activeTab === "usage" && (
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, cleanup, within } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import type { Stigmer } from "@stigmer/sdk";
5
+ import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
6
+ import { ExecutionArtifactSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
7
+ import {
8
+ ExecutionArtifactKind,
9
+ ExecutionPhase,
10
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
11
+ import { StigmerContext } from "../../../context";
12
+ import { ArtifactsTab } from "../ArtifactsTab";
13
+
14
+ function artifact(name: string) {
15
+ return create(ExecutionArtifactSchema, {
16
+ name,
17
+ kind: ExecutionArtifactKind.FILE,
18
+ sizeBytes: 1024n,
19
+ sandboxPath: `.stigmer/${name}`,
20
+ storageKey: `artifacts/aex_1/${name}`,
21
+ downloadUrl: `https://example.test/${name}`,
22
+ });
23
+ }
24
+
25
+ const execution = create(AgentExecutionSchema, {
26
+ metadata: { id: "aex_1" },
27
+ status: {
28
+ phase: ExecutionPhase.EXECUTION_COMPLETED,
29
+ artifacts: [artifact("plan.md"), artifact("notes.md")],
30
+ },
31
+ });
32
+
33
+ /** Modal content fetch — keep pending so nothing rejects during the test. */
34
+ function createStigmerMock(): Stigmer {
35
+ return {
36
+ agentExecution: {
37
+ getArtifactContent: vi.fn().mockReturnValue(new Promise(() => {})),
38
+ },
39
+ } as unknown as Stigmer;
40
+ }
41
+
42
+ function renderTab(onImplementPlan?: () => void) {
43
+ return render(
44
+ <StigmerContext.Provider value={createStigmerMock()}>
45
+ <ArtifactsTab
46
+ executions={[execution]}
47
+ org="acme"
48
+ onImplementPlan={onImplementPlan}
49
+ />
50
+ </StigmerContext.Provider>,
51
+ );
52
+ }
53
+
54
+ function openPreviewFor(name: string) {
55
+ const row = screen.getByText(name).closest("[role='listitem']") as HTMLElement;
56
+ fireEvent.click(within(row).getByText("Preview"));
57
+ }
58
+
59
+ afterEach(cleanup);
60
+
61
+ describe("ArtifactsTab — plan Implement wiring", () => {
62
+ it("shows Implement in the preview of a plan.md artifact", () => {
63
+ renderTab(vi.fn());
64
+
65
+ openPreviewFor("plan.md");
66
+
67
+ const dialog = document.querySelector("dialog")!;
68
+ expect(within(dialog).getByText("Implement")).toBeTruthy();
69
+ });
70
+
71
+ it("does not show Implement in the preview of a non-plan artifact", () => {
72
+ renderTab(vi.fn());
73
+
74
+ openPreviewFor("notes.md");
75
+
76
+ const dialog = document.querySelector("dialog")!;
77
+ expect(within(dialog).queryByText("Implement")).toBeNull();
78
+ });
79
+
80
+ it("invokes onImplementPlan when Implement is clicked for a plan", () => {
81
+ const onImplementPlan = vi.fn();
82
+ renderTab(onImplementPlan);
83
+
84
+ openPreviewFor("plan.md");
85
+ const dialog = document.querySelector("dialog")!;
86
+ fireEvent.click(within(dialog).getByText("Implement"));
87
+
88
+ expect(onImplementPlan).toHaveBeenCalledTimes(1);
89
+ });
90
+ });
@@ -8,7 +8,8 @@ import { useDefaultAgent } from "../agent";
8
8
  import { useStigmer } from "../hooks";
9
9
  import { useWorkspaceEntries, type UseWorkspaceEntriesReturn } from "../workspace";
10
10
  import { useSessionVariables, type UseSessionVariablesReturn } from "../execution/useSessionVariables";
11
- import type { SessionComposerSubmitContext } from "../composer";
11
+ import type { SessionComposerSubmitContext, InteractionModeOption } from "../composer";
12
+ import { fromProtoInteractionMode } from "../composer";
12
13
  import { fromProtoHarness, type HarnessOption } from "../models/harness";
13
14
  import { Harness, ExecutionTarget } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
14
15
  import { fromProtoExecutionTarget, type ExecutionTargetOption } from "./execution-target";
@@ -59,6 +60,18 @@ export interface UseSessionPageFlowReturn {
59
60
  /** Persisted model selection: `[modelId, setModelId]`. */
60
61
  readonly model: UsePersistedModelReturn;
61
62
 
63
+ /**
64
+ * Composer interaction mode: `[interactionMode, setInteractionMode]`.
65
+ *
66
+ * Derived like {@link model}: the user's explicit override wins, otherwise
67
+ * it reflects the latest execution's mode (so a completed Plan keeps the
68
+ * picker on "Plan" while it awaits review), falling back to `"agent"`.
69
+ */
70
+ readonly interactionMode: readonly [
71
+ InteractionModeOption,
72
+ (mode: InteractionModeOption) => void,
73
+ ];
74
+
62
75
  /** Currently selected agent reference (derived from session, or overridden by user). */
63
76
  readonly agentRef: ResourceRef | null;
64
77
  /** Update the agent reference for future follow-ups. */
@@ -206,6 +219,24 @@ export function useSessionPageFlow(
206
219
  const modelId = persistedModelId ?? lastExecModelId;
207
220
  const model: UsePersistedModelReturn = [modelId, setPersistedModelId] as const;
208
221
 
222
+ // Interaction mode mirrors the model derivation: an explicit user override
223
+ // wins; otherwise reflect the latest execution's mode so a completed Plan
224
+ // keeps the composer on "Plan" until the user implements or switches. This
225
+ // is derived state (always consistent), never an effect-synced copy.
226
+ const lastExecInteractionMode = useMemo(
227
+ () =>
228
+ fromProtoInteractionMode(
229
+ conv.completedExecutions.at(-1)?.spec?.executionConfig?.interactionMode,
230
+ ),
231
+ [conv.completedExecutions],
232
+ );
233
+ const [interactionModeOverride, setInteractionMode] =
234
+ useState<InteractionModeOption | null>(null);
235
+ const interactionMode: UseSessionPageFlowReturn["interactionMode"] = [
236
+ interactionModeOverride ?? lastExecInteractionMode ?? "agent",
237
+ setInteractionMode,
238
+ ] as const;
239
+
209
240
  const workspace = useWorkspaceEntries();
210
241
  const sessionVariables = useSessionVariables();
211
242
  const [mcpServerUsages, setMcpServerUsages] = useState<McpServerUsageInput[]>([]);
@@ -369,6 +400,7 @@ export function useSessionPageFlow(
369
400
  harness,
370
401
  executionTarget,
371
402
  model,
403
+ interactionMode,
372
404
  agentRef,
373
405
  setAgentRef,
374
406
  resolution,