@stigmer/react 3.1.3 → 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 +31 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +12 -4
- 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 +4 -4
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +15 -142
- 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/index.d.ts +8 -2
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +4 -1
- package/execution/index.js.map +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/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 +11 -0
- 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 +3 -3
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- 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/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/package.json +5 -4
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +91 -18
- 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/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 +17 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +32 -4
- 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 +69 -1
- 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 +39 -405
- package/src/execution/ArtifactRow.tsx +165 -0
- package/src/execution/ArtifactsWidget.tsx +24 -33
- package/src/execution/__tests__/ArtifactContentBody.test.tsx +131 -0
- package/src/execution/__tests__/ArtifactDocument.test.tsx +100 -0
- package/src/execution/__tests__/ArtifactRow.test.tsx +139 -0
- package/src/execution/__tests__/ArtifactsWidget.test.tsx +78 -0
- package/src/execution/__tests__/message-entry.test.tsx +50 -2
- package/src/execution/__tests__/useArtifactInspection.test.tsx +151 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +35 -0
- package/src/execution/index.ts +14 -2
- package/src/execution/useArtifactContent.ts +7 -0
- package/src/execution/useArtifactInspection.ts +252 -0
- package/src/execution/useCreateAgentExecution.ts +12 -0
- package/src/index.ts +11 -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/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/session/SessionViewer.tsx +177 -33
- package/src/session/__tests__/SessionViewer-artifactTabs.test.tsx +210 -0
- package/src/session/__tests__/SessionViewer-editResubmit.test.tsx +233 -0
- package/src/session/__tests__/useSessionArtifacts.test.ts +57 -0
- package/src/session/__tests__/useSessionConversation.test.tsx +172 -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/useSessionArtifacts.ts +16 -5
- package/src/session/useSessionConversation.ts +48 -6
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/session/useSessionPanel.ts +41 -0
- package/src/session/useSessionRailViews.tsx +21 -1
- package/styles.css +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
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { isValidElement, type ComponentProps, type JSX, type ReactNode } from "react";
|
|
2
2
|
import type { Components } from "react-markdown";
|
|
3
3
|
import remarkGfm from "remark-gfm";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { highlightToReact } from "./code-highlight.js";
|
|
6
|
+
import { MermaidDiagram } from "./MermaidDiagram.js";
|
|
6
7
|
|
|
7
8
|
const LANGUAGE_CLASS_PREFIX = "language-";
|
|
9
|
+
const MERMAID_LANGUAGE_CLASS = "language-mermaid";
|
|
8
10
|
|
|
9
11
|
type MdProps<T extends keyof JSX.IntrinsicElements> = ComponentProps<T>;
|
|
10
12
|
|
|
@@ -119,6 +121,30 @@ export function extractLeadingH1(markdown: string): {
|
|
|
119
121
|
return { title: match[1], body: trimmed.slice(match[0].length) };
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Returns the mermaid source when a `pre` element's children are a single
|
|
126
|
+
* fenced code block explicitly tagged `mermaid`, or `null` otherwise.
|
|
127
|
+
*
|
|
128
|
+
* Both renderers (Streamdown for chat, react-markdown for artifacts/skills)
|
|
129
|
+
* hand `pre` exactly one `<code>` child whose `className` carries the fence's
|
|
130
|
+
* info string as `language-*` and whose children are the raw source text.
|
|
131
|
+
* Anything that deviates from that shape — a different language, a missing
|
|
132
|
+
* tag, non-string children — is not a mermaid fence and keeps the ordinary
|
|
133
|
+
* code-block rendering. Only the explicit `mermaid` tag qualifies:
|
|
134
|
+
* inspecting fence bodies to guess diagram intent is the kind of fuzzy
|
|
135
|
+
* heuristic this codebase avoids.
|
|
136
|
+
*/
|
|
137
|
+
function extractMermaidSource(children: ReactNode): string | null {
|
|
138
|
+
if (!isValidElement(children)) return null;
|
|
139
|
+
const { className, children: code } = children.props as {
|
|
140
|
+
className?: unknown;
|
|
141
|
+
children?: unknown;
|
|
142
|
+
};
|
|
143
|
+
if (typeof className !== "string") return null;
|
|
144
|
+
if (!className.split(/\s+/).includes(MERMAID_LANGUAGE_CLASS)) return null;
|
|
145
|
+
return typeof code === "string" ? code : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
/**
|
|
123
149
|
* Styled react-markdown component overrides for SDK markdown surfaces.
|
|
124
150
|
*
|
|
@@ -208,6 +234,15 @@ export const MARKDOWN_COMPONENTS: Components = {
|
|
|
208
234
|
},
|
|
209
235
|
|
|
210
236
|
pre({ children, ...props }: MdProps<"pre">) {
|
|
237
|
+
// A ```mermaid fence renders as a diagram, not a code block. The check
|
|
238
|
+
// lives here (not in the `code` override) because the diagram must
|
|
239
|
+
// replace the <pre> wrapper too — a block-level diagram container inside
|
|
240
|
+
// <pre> is invalid HTML and would inherit code-block chrome.
|
|
241
|
+
const mermaidSource = extractMermaidSource(children);
|
|
242
|
+
if (mermaidSource !== null) {
|
|
243
|
+
return <MermaidDiagram chart={mermaidSource} />;
|
|
244
|
+
}
|
|
245
|
+
|
|
211
246
|
return (
|
|
212
247
|
<pre
|
|
213
248
|
className="mb-3 last:mb-0 overflow-x-auto rounded-md bg-muted p-3"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy loader for the `mermaid` library, isolated in its own module — the
|
|
3
|
+
* same seam pattern as `workflow/layout/elk-layout-engine.ts` — so
|
|
4
|
+
* {@link file://./MermaidDiagram.tsx} stays a pure presentation component and
|
|
5
|
+
* tests can mock the load outcome (resolve, reject) without touching module
|
|
6
|
+
* registries.
|
|
7
|
+
*
|
|
8
|
+
* `mermaid` is an optional peer dependency (DD-013): it is large, so it must
|
|
9
|
+
* never sit on the synchronous bundle path, and a host that has not installed
|
|
10
|
+
* it must degrade gracefully (the caller falls back to a code block).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The mermaid API surface the SDK consumes, typed from the package itself so
|
|
15
|
+
* it stays in lockstep with the installed version.
|
|
16
|
+
*/
|
|
17
|
+
export type MermaidModule = typeof import("mermaid").default;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Holds the in-flight import promise so concurrent diagrams share one load.
|
|
21
|
+
* A failed import clears the cache: rejections are often transient (a
|
|
22
|
+
* chunk-load hiccup mid-deploy), and caching one would break every diagram
|
|
23
|
+
* for the rest of the session.
|
|
24
|
+
*/
|
|
25
|
+
let mermaidImport: Promise<MermaidModule> | null = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Dynamically imports `mermaid`, sharing one in-flight import across callers.
|
|
29
|
+
*
|
|
30
|
+
* @returns The mermaid default export.
|
|
31
|
+
* @throws If `mermaid` cannot be imported (not installed, or the async chunk
|
|
32
|
+
* failed to load). Callers treat this as "diagrams unavailable" and render
|
|
33
|
+
* the fenced source as a plain code block.
|
|
34
|
+
*/
|
|
35
|
+
export function loadMermaid(): Promise<MermaidModule> {
|
|
36
|
+
mermaidImport ??= import("mermaid").then(
|
|
37
|
+
(module) => module.default,
|
|
38
|
+
(error: unknown) => {
|
|
39
|
+
mermaidImport = null;
|
|
40
|
+
throw error;
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
return mermaidImport;
|
|
44
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-driven `themeCSS` for Mermaid diagram interiors.
|
|
3
|
+
*
|
|
4
|
+
* Mermaid's built-in `default`/`dark` themes color a diagram's SVG interior
|
|
5
|
+
* with a fixed palette that ignores the host's `--stgm-*` preset (Corporate,
|
|
6
|
+
* Fintech, ...). The obvious fix — feeding resolved tokens into Mermaid's
|
|
7
|
+
* `themeVariables` — is blocked: our tokens are authored in `oklch()`, and
|
|
8
|
+
* Mermaid's color engine (`khroma`) parses only hex/rgb/hsl/keyword, so it
|
|
9
|
+
* throws or NaNs on `oklch(...)`. (Mermaid also gates every `themeVariables`
|
|
10
|
+
* value through the allowlist `^[\d "#%(),.;A-Za-z]+$`, which excludes `-` and
|
|
11
|
+
* would blank any `var(--...)`.)
|
|
12
|
+
*
|
|
13
|
+
* Instead we hand Mermaid this CSS via `initialize({ themeCSS })`. Mermaid
|
|
14
|
+
* injects it into the diagram's own `<style>` block, scoped under the render
|
|
15
|
+
* id (`#stgm-mermaid-N`), *after* the theme palette it computes. Because every
|
|
16
|
+
* selector in that block is id-prefixed, our rules and the palette's rules sit
|
|
17
|
+
* at equal specificity, so ours win by source order — and `!important` makes
|
|
18
|
+
* that bulletproof against the few palette rules that are themselves important
|
|
19
|
+
* or inlined. Crucially, `themeCSS` never passes through khroma, so the
|
|
20
|
+
* `oklch()`-backed `var(--stgm-*)` references flow straight to the browser,
|
|
21
|
+
* which resolves them against the `.stgm` scope the SVG lives in. That is why
|
|
22
|
+
* interiors track the active preset *and* color mode live, with no JS color
|
|
23
|
+
* reading and no re-render on theme change.
|
|
24
|
+
*
|
|
25
|
+
* `initialize({ theme })` still selects the built-in `default`/`dark` theme as
|
|
26
|
+
* the base, so any element or diagram type we do not target here degrades to
|
|
27
|
+
* Mermaid's stock palette — never a blank or mis-colored diagram.
|
|
28
|
+
*
|
|
29
|
+
* Palette intent (subtle surfaces + brand accents — legibility over brand
|
|
30
|
+
* saturation): node/actor bodies sit on a muted surface with quiet
|
|
31
|
+
* token-colored borders and edges; labels use the standard foreground; the
|
|
32
|
+
* brand `--stgm-primary` is reserved for structural headers (subgraph/cluster
|
|
33
|
+
* titles, sequence loop/section titles, the sequence-number badge).
|
|
34
|
+
*
|
|
35
|
+
* Coverage is the two families agents emit overwhelmingly — flowchart and
|
|
36
|
+
* sequence — plus the shared marker/edge-label chrome. Both label render paths
|
|
37
|
+
* are handled: SVG `text`/`tspan` (via `fill`) and HTML labels inside
|
|
38
|
+
* `foreignObject` (via `color`).
|
|
39
|
+
*/
|
|
40
|
+
export const MERMAID_THEME_CSS = `
|
|
41
|
+
/* Shared: edge arrowheads / markers */
|
|
42
|
+
.marker {
|
|
43
|
+
fill: var(--stgm-border) !important;
|
|
44
|
+
stroke: var(--stgm-border) !important;
|
|
45
|
+
}
|
|
46
|
+
.marker.cross {
|
|
47
|
+
stroke: var(--stgm-border) !important;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Flowchart: node bodies */
|
|
51
|
+
.node rect,
|
|
52
|
+
.node circle,
|
|
53
|
+
.node ellipse,
|
|
54
|
+
.node polygon,
|
|
55
|
+
.node path {
|
|
56
|
+
fill: var(--stgm-muted) !important;
|
|
57
|
+
stroke: var(--stgm-border) !important;
|
|
58
|
+
}
|
|
59
|
+
.node .neo-node {
|
|
60
|
+
stroke: var(--stgm-border) !important;
|
|
61
|
+
}
|
|
62
|
+
.node .katex path {
|
|
63
|
+
fill: var(--stgm-foreground) !important;
|
|
64
|
+
stroke: var(--stgm-foreground) !important;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Flowchart: node labels (SVG text and HTML spans) */
|
|
68
|
+
.label {
|
|
69
|
+
color: var(--stgm-foreground) !important;
|
|
70
|
+
}
|
|
71
|
+
.label text,
|
|
72
|
+
.label span {
|
|
73
|
+
fill: var(--stgm-foreground) !important;
|
|
74
|
+
color: var(--stgm-foreground) !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Flowchart: edges */
|
|
78
|
+
.edgePath .path,
|
|
79
|
+
.flowchart-link {
|
|
80
|
+
stroke: var(--stgm-border) !important;
|
|
81
|
+
}
|
|
82
|
+
.arrowheadPath,
|
|
83
|
+
.root .anchor path {
|
|
84
|
+
fill: var(--stgm-border) !important;
|
|
85
|
+
stroke: var(--stgm-border) !important;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Flowchart: edge labels sit on the diagram surface for legibility */
|
|
89
|
+
.edgeLabel,
|
|
90
|
+
.edgeLabel p,
|
|
91
|
+
.edgeLabel rect,
|
|
92
|
+
.labelBkg {
|
|
93
|
+
background-color: var(--stgm-card) !important;
|
|
94
|
+
fill: var(--stgm-card) !important;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Flowchart: subgraph clusters — muted surface, brand-accent title */
|
|
98
|
+
.cluster rect {
|
|
99
|
+
fill: var(--stgm-muted-subtle) !important;
|
|
100
|
+
stroke: var(--stgm-border) !important;
|
|
101
|
+
}
|
|
102
|
+
.cluster-label text,
|
|
103
|
+
.cluster text {
|
|
104
|
+
fill: var(--stgm-primary) !important;
|
|
105
|
+
}
|
|
106
|
+
.cluster-label span,
|
|
107
|
+
.cluster span {
|
|
108
|
+
color: var(--stgm-primary) !important;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Sequence: actors and lifelines */
|
|
112
|
+
.actor {
|
|
113
|
+
fill: var(--stgm-muted) !important;
|
|
114
|
+
stroke: var(--stgm-border) !important;
|
|
115
|
+
}
|
|
116
|
+
text.actor > tspan {
|
|
117
|
+
fill: var(--stgm-foreground) !important;
|
|
118
|
+
}
|
|
119
|
+
.actor-line {
|
|
120
|
+
stroke: var(--stgm-border) !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Sequence: messages */
|
|
124
|
+
.messageLine0,
|
|
125
|
+
.messageLine1 {
|
|
126
|
+
stroke: var(--stgm-border) !important;
|
|
127
|
+
}
|
|
128
|
+
[id$="-arrowhead"] path,
|
|
129
|
+
[id$="-crosshead"] path {
|
|
130
|
+
fill: var(--stgm-border) !important;
|
|
131
|
+
stroke: var(--stgm-border) !important;
|
|
132
|
+
}
|
|
133
|
+
.messageText {
|
|
134
|
+
fill: var(--stgm-foreground) !important;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Sequence: number badge — brand accent with its paired foreground */
|
|
138
|
+
[id$="-sequencenumber"] {
|
|
139
|
+
fill: var(--stgm-primary) !important;
|
|
140
|
+
}
|
|
141
|
+
.sequenceNumber {
|
|
142
|
+
fill: var(--stgm-primary-foreground) !important;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Sequence: alt/opt/loop boxes and labels */
|
|
146
|
+
.labelBox {
|
|
147
|
+
fill: var(--stgm-muted-subtle) !important;
|
|
148
|
+
stroke: var(--stgm-border) !important;
|
|
149
|
+
}
|
|
150
|
+
.labelText,
|
|
151
|
+
.labelText > tspan {
|
|
152
|
+
fill: var(--stgm-foreground) !important;
|
|
153
|
+
}
|
|
154
|
+
.loopText,
|
|
155
|
+
.loopText > tspan,
|
|
156
|
+
.sectionTitle,
|
|
157
|
+
.sectionTitle > tspan {
|
|
158
|
+
fill: var(--stgm-primary) !important;
|
|
159
|
+
}
|
|
160
|
+
.loopLine {
|
|
161
|
+
stroke: var(--stgm-border) !important;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Sequence: notes and activations */
|
|
165
|
+
.note {
|
|
166
|
+
fill: var(--stgm-muted-subtle) !important;
|
|
167
|
+
stroke: var(--stgm-border) !important;
|
|
168
|
+
}
|
|
169
|
+
.noteText,
|
|
170
|
+
.noteText > tspan {
|
|
171
|
+
fill: var(--stgm-foreground) !important;
|
|
172
|
+
}
|
|
173
|
+
.activation0,
|
|
174
|
+
.activation1 {
|
|
175
|
+
fill: var(--stgm-muted) !important;
|
|
176
|
+
stroke: var(--stgm-border) !important;
|
|
177
|
+
}
|
|
178
|
+
`;
|
package/src/internal/slug.ts
CHANGED
|
@@ -7,23 +7,27 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Rules (applied in order):
|
|
9
9
|
* 1. Convert to lowercase
|
|
10
|
-
* 2. Replace spaces with hyphens
|
|
10
|
+
* 2. Replace spaces and dots (namespace separators) with hyphens
|
|
11
11
|
* 3. Strip everything except `a-z`, `0-9`, and `-`
|
|
12
12
|
* 4. Collapse consecutive hyphens into one
|
|
13
13
|
* 5. Trim leading / trailing hyphens
|
|
14
14
|
*
|
|
15
|
+
* Dots are treated as namespace separators (e.g. `platform.sara` → `platform-sara`),
|
|
16
|
+
* matching the backend generators, so namespace-scoped names stay readable.
|
|
17
|
+
*
|
|
15
18
|
* @example
|
|
16
19
|
* ```ts
|
|
17
|
-
* generateSlug("My Cool Agent")
|
|
18
|
-
* generateSlug("Acme Corp!")
|
|
19
|
-
* generateSlug(" --hello-- ")
|
|
20
|
-
* generateSlug("")
|
|
20
|
+
* generateSlug("My Cool Agent") // "my-cool-agent"
|
|
21
|
+
* generateSlug("Acme Corp!") // "acme-corp"
|
|
22
|
+
* generateSlug(" --hello-- ") // "hello"
|
|
23
|
+
* generateSlug("platform.planton-arch") // "platform-planton-arch"
|
|
24
|
+
* generateSlug("") // ""
|
|
21
25
|
* ```
|
|
22
26
|
*/
|
|
23
27
|
export function generateSlug(name: string): string {
|
|
24
28
|
return name
|
|
25
29
|
.toLowerCase()
|
|
26
|
-
.replace(/ /g, "-")
|
|
30
|
+
.replace(/[ .]/g, "-")
|
|
27
31
|
.replace(/[^a-z0-9-]/g, "")
|
|
28
32
|
.replace(/-{2,}/g, "-")
|
|
29
33
|
.replace(/^-+|-+$/g, "");
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
WorkspaceSurface,
|
|
18
18
|
type SurfaceVirtualDocument,
|
|
19
19
|
} from "../workspace/WorkspaceSurface.js";
|
|
20
|
-
import type { InteractionModeOption, SessionComposerHandle } from "../composer/index.js";
|
|
20
|
+
import type { InteractionModeOption, SessionComposerHandle, SessionComposerSubmitContext } from "../composer/index.js";
|
|
21
21
|
import type { ApplyResourceResult } from "../library/useApplyResource.js";
|
|
22
22
|
import { ResizableSplit } from "../internal/ResizableSplit.js";
|
|
23
23
|
import { SelectionStore } from "../internal/store/selection-store.js";
|
|
@@ -47,11 +47,17 @@ import { usePlanDraft, planDraftKey, type PlanDraftController } from "./usePlanD
|
|
|
47
47
|
import { PlanEditor } from "./PlanEditor.js";
|
|
48
48
|
import { PlanStreamingDocument } from "./PlanStreamingDocument.js";
|
|
49
49
|
import { PLAN_DOCUMENT_ENTRY_ID, PLAN_DOCUMENT_PATH } from "./plan-document.js";
|
|
50
|
+
import { ARTIFACT_DOCUMENT_ENTRY_ID } from "./artifact-document.js";
|
|
51
|
+
import { ArtifactDocument } from "../execution/ArtifactDocument.js";
|
|
50
52
|
import { useSessionRailViews } from "./useSessionRailViews.js";
|
|
51
53
|
import { useSessionPanel, type SessionPanelController } from "./useSessionPanel.js";
|
|
52
54
|
import { SessionPanelChip } from "./SessionPanelChip.js";
|
|
53
55
|
import { useSessionWriteBacks } from "./useSessionWriteBacks.js";
|
|
54
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
useSessionArtifacts,
|
|
58
|
+
artifactKey,
|
|
59
|
+
type SessionArtifactEntry,
|
|
60
|
+
} from "./useSessionArtifacts.js";
|
|
55
61
|
import type { SetupTabProps } from "./facets/SetupTab.js";
|
|
56
62
|
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
57
63
|
import type { RuntimeEnvProvider } from "./runtime-env.js";
|
|
@@ -455,6 +461,28 @@ export function SessionViewer({
|
|
|
455
461
|
[sessionPlan, panel.openPlanDocument],
|
|
456
462
|
);
|
|
457
463
|
|
|
464
|
+
// "Open artifact" (an Artifacts-facet row) → an editor-pane document tab.
|
|
465
|
+
// Writing the editors store re-renders only the panel subtree (which
|
|
466
|
+
// subscribes), never this streaming column. Plan artifacts are routed to the
|
|
467
|
+
// plan tab upstream (in ArtifactsTab), so they never reach here.
|
|
468
|
+
const handleOpenArtifact = useCallback(
|
|
469
|
+
(entry: SessionArtifactEntry) => {
|
|
470
|
+
panel.openArtifact(entry.artifact);
|
|
471
|
+
},
|
|
472
|
+
[panel.openArtifact],
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// Double-click an artifact row → pin its tab (the leading single click already
|
|
476
|
+
// opened the preview via `handleOpenArtifact`). Mirrors the file tree's
|
|
477
|
+
// preview/pin split; plan artifacts are routed to the plan tab upstream (in
|
|
478
|
+
// ArtifactsTab) and never reach here.
|
|
479
|
+
const handleActivateArtifact = useCallback(
|
|
480
|
+
(entry: SessionArtifactEntry) => {
|
|
481
|
+
panel.pinArtifact(entry.artifact);
|
|
482
|
+
},
|
|
483
|
+
[panel.pinArtifact],
|
|
484
|
+
);
|
|
485
|
+
|
|
458
486
|
// Open a transcript tool-call file path in the panel's read-only editor.
|
|
459
487
|
// Resolves the (possibly absolute / subdir-prefixed) path to a
|
|
460
488
|
// repo/root-relative selection the editor can fetch; on a definite hit it
|
|
@@ -561,6 +589,8 @@ export function SessionViewer({
|
|
|
561
589
|
planDraft={planDraft}
|
|
562
590
|
openPlanExecutionId={openPlanExecutionId}
|
|
563
591
|
onOpenPlan={handleOpenPlan}
|
|
592
|
+
onOpenArtifact={handleOpenArtifact}
|
|
593
|
+
onActivateArtifact={handleActivateArtifact}
|
|
564
594
|
enableLocal={enableLocal}
|
|
565
595
|
onBrowseLocalFolder={onBrowseLocalFolder}
|
|
566
596
|
workspaceFileLister={workspaceFileLister}
|
|
@@ -652,18 +682,55 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
652
682
|
void conv.stop();
|
|
653
683
|
}, [conv.stop]);
|
|
654
684
|
|
|
655
|
-
// Edit-and-resubmit: stop the in-flight turn
|
|
656
|
-
// the original text
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
//
|
|
685
|
+
// Edit-and-resubmit: stop the in-flight turn, pre-fill the composer with
|
|
686
|
+
// the original text, and remember which execution is being edited. The
|
|
687
|
+
// append-only execution log is never rewritten — submitting while editing
|
|
688
|
+
// creates a NEW execution that carries `supersedesExecutionId`, and the
|
|
689
|
+
// conversation read model hides the superseded turn so the edited message
|
|
690
|
+
// replaces the original in place (Cursor-style, stigmer/stigmer#181).
|
|
691
|
+
//
|
|
692
|
+
// The editing state is explicit (banner + cancel in the composer) so the
|
|
693
|
+
// supersede link only attaches when the user actually resubmits the edit.
|
|
694
|
+
// Cancelling drops the link — a subsequent unrelated message appends
|
|
695
|
+
// normally without hiding any history.
|
|
696
|
+
const [editingExecutionId, setEditingExecutionId] = useState<string | null>(
|
|
697
|
+
null,
|
|
698
|
+
);
|
|
699
|
+
const activeExecutionId = conv.activeStreamExecution?.metadata?.id ?? null;
|
|
700
|
+
|
|
660
701
|
const handleEditMessage = useCallback(
|
|
661
702
|
(text: string) => {
|
|
662
703
|
void conv.stop();
|
|
704
|
+
setEditingExecutionId(activeExecutionId);
|
|
663
705
|
composerRef.current?.setMessage(text);
|
|
664
706
|
composerRef.current?.focus();
|
|
665
707
|
},
|
|
666
|
-
[conv.stop, composerRef],
|
|
708
|
+
[conv.stop, activeExecutionId, composerRef],
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
const handleCancelEdit = useCallback(() => {
|
|
712
|
+
setEditingExecutionId(null);
|
|
713
|
+
composerRef.current?.setMessage("");
|
|
714
|
+
}, [composerRef]);
|
|
715
|
+
|
|
716
|
+
// Submit wrapper that attaches the supersede link while editing, then
|
|
717
|
+
// exits editing mode. Ordinary (non-editing) submits pass through as-is.
|
|
718
|
+
const handleComposerSubmit = useCallback(
|
|
719
|
+
(
|
|
720
|
+
message: string,
|
|
721
|
+
modelName?: string,
|
|
722
|
+
context?: SessionComposerSubmitContext,
|
|
723
|
+
) => {
|
|
724
|
+
void flow.handleSubmit(
|
|
725
|
+
message,
|
|
726
|
+
modelName,
|
|
727
|
+
editingExecutionId
|
|
728
|
+
? { ...context, supersedesExecutionId: editingExecutionId }
|
|
729
|
+
: context,
|
|
730
|
+
);
|
|
731
|
+
setEditingExecutionId(null);
|
|
732
|
+
},
|
|
733
|
+
[flow.handleSubmit, editingExecutionId],
|
|
667
734
|
);
|
|
668
735
|
|
|
669
736
|
// The composer-docked FileReviewDock sits OUTSIDE MessageThread, so the
|
|
@@ -743,11 +810,13 @@ const ConversationColumn = memo(function ConversationColumn({
|
|
|
743
810
|
</FilePathContext.Provider>
|
|
744
811
|
<SessionComposer
|
|
745
812
|
ref={composerRef}
|
|
746
|
-
onSubmit={
|
|
813
|
+
onSubmit={handleComposerSubmit}
|
|
747
814
|
isSubmitting={conv.isSending}
|
|
748
815
|
disabled={!conv.canSendFollowUp}
|
|
749
816
|
onStop={conv.isStoppable ? handleStop : undefined}
|
|
750
817
|
isStopping={conv.isStopping}
|
|
818
|
+
isEditing={editingExecutionId != null}
|
|
819
|
+
onCancelEdit={handleCancelEdit}
|
|
751
820
|
org={org}
|
|
752
821
|
harness={flow.harness}
|
|
753
822
|
defaultModelId={modelId}
|
|
@@ -815,6 +884,10 @@ interface SessionPanelRegionProps {
|
|
|
815
884
|
readonly openPlanExecutionId: string | null;
|
|
816
885
|
/** Opens a plan in the plan tab (routed to the Artifacts facet's plan.md). */
|
|
817
886
|
readonly onOpenPlan: (executionId: string) => void;
|
|
887
|
+
/** Opens an artifact as an editor-pane document (routed to the Artifacts facet). */
|
|
888
|
+
readonly onOpenArtifact: (entry: SessionArtifactEntry) => void;
|
|
889
|
+
/** Pins an artifact's document tab — the double-click half of the open/pin split. */
|
|
890
|
+
readonly onActivateArtifact: (entry: SessionArtifactEntry) => void;
|
|
818
891
|
readonly enableLocal: boolean;
|
|
819
892
|
readonly onBrowseLocalFolder?: () => Promise<string | null>;
|
|
820
893
|
readonly workspaceFileLister?: WorkspaceFileLister;
|
|
@@ -836,6 +909,8 @@ function SessionPanelRegion({
|
|
|
836
909
|
planDraft,
|
|
837
910
|
openPlanExecutionId,
|
|
838
911
|
onOpenPlan,
|
|
912
|
+
onOpenArtifact,
|
|
913
|
+
onActivateArtifact,
|
|
839
914
|
enableLocal,
|
|
840
915
|
onBrowseLocalFolder,
|
|
841
916
|
workspaceFileLister,
|
|
@@ -847,6 +922,9 @@ function SessionPanelRegion({
|
|
|
847
922
|
const { editors, activeKey, activeFile, reveal } = useWorkspaceEditors(
|
|
848
923
|
panel.editorsStore,
|
|
849
924
|
);
|
|
925
|
+
// Session artifacts, deduped by identity — the source both the Artifacts
|
|
926
|
+
// rail-view badge and the open artifact document tabs resolve against.
|
|
927
|
+
const { artifacts: sessionArtifacts } = useSessionArtifacts(flow.allExecutions);
|
|
850
928
|
|
|
851
929
|
// Honor a pending jump-to-line reveal only while it targets the active editor
|
|
852
930
|
// (an EditorReveal is structurally a RevealTarget). Switching tabs changes
|
|
@@ -902,30 +980,28 @@ function SessionPanelRegion({
|
|
|
902
980
|
// tab and its turn then ended without publishing, with no earlier plan to
|
|
903
981
|
// fall back to.
|
|
904
982
|
const openPlanIsLatest = openPlan?.executionId === sessionPlan?.executionId;
|
|
905
|
-
const
|
|
906
|
-
return
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
},
|
|
928
|
-
];
|
|
983
|
+
const planVirtualDocument = useMemo<SurfaceVirtualDocument>(() => {
|
|
984
|
+
return {
|
|
985
|
+
entryId: PLAN_DOCUMENT_ENTRY_ID,
|
|
986
|
+
path: PLAN_DOCUMENT_PATH,
|
|
987
|
+
content: streamingPlan ? (
|
|
988
|
+
<PlanStreamingDocument
|
|
989
|
+
key={`streaming-${streamingPlan.executionId}`}
|
|
990
|
+
displayText={streamingPlan.displayText}
|
|
991
|
+
/>
|
|
992
|
+
) : openPlan ? (
|
|
993
|
+
<PlanEditor
|
|
994
|
+
key={planDraftKey(openPlan)}
|
|
995
|
+
plan={openPlan}
|
|
996
|
+
draft={openPlanIsLatest ? planDraft : undefined}
|
|
997
|
+
onBuildFromPlan={openPlanIsLatest ? onImplementPlan : undefined}
|
|
998
|
+
buildDisabled={implementPlanDisabled}
|
|
999
|
+
readOnly={!openPlanIsLatest}
|
|
1000
|
+
/>
|
|
1001
|
+
) : (
|
|
1002
|
+
<PlanUnavailableNotice />
|
|
1003
|
+
),
|
|
1004
|
+
};
|
|
929
1005
|
}, [
|
|
930
1006
|
streamingPlan,
|
|
931
1007
|
openPlan,
|
|
@@ -935,6 +1011,50 @@ function SessionPanelRegion({
|
|
|
935
1011
|
implementPlanDisabled,
|
|
936
1012
|
]);
|
|
937
1013
|
|
|
1014
|
+
// The open artifact document tabs (the artifact virtual-document FAMILY).
|
|
1015
|
+
// Built in a SEPARATE memo from the plan doc so artifact-tab churn never
|
|
1016
|
+
// recreates the plan doc (and vice versa). Bounded to what is actually open:
|
|
1017
|
+
// one document per editor whose entry id is the artifact family's, resolved
|
|
1018
|
+
// back to its `SessionArtifactEntry` by the same `artifactKey` used to open
|
|
1019
|
+
// it (single source of truth for artifact identity). Only the ACTIVE doc's
|
|
1020
|
+
// `content` is mounted by the surface, so building these elements is cheap;
|
|
1021
|
+
// keying on `contentHash` remounts a tab whose artifact was overwritten by a
|
|
1022
|
+
// later execution. A tab whose artifact no longer exists degrades to an
|
|
1023
|
+
// honest notice rather than vanishing.
|
|
1024
|
+
const artifactByKey = useMemo(
|
|
1025
|
+
() =>
|
|
1026
|
+
new Map(sessionArtifacts.map((entry) => [artifactKey(entry.artifact), entry])),
|
|
1027
|
+
[sessionArtifacts],
|
|
1028
|
+
);
|
|
1029
|
+
const artifactVirtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(() => {
|
|
1030
|
+
return editors
|
|
1031
|
+
.filter((editor) => editor.entryId === ARTIFACT_DOCUMENT_ENTRY_ID)
|
|
1032
|
+
.map((editor) => {
|
|
1033
|
+
const entry = artifactByKey.get(editor.path);
|
|
1034
|
+
return {
|
|
1035
|
+
entryId: ARTIFACT_DOCUMENT_ENTRY_ID,
|
|
1036
|
+
path: editor.path,
|
|
1037
|
+
content: entry ? (
|
|
1038
|
+
<ArtifactDocument
|
|
1039
|
+
key={entry.artifact.contentHash || editor.path}
|
|
1040
|
+
artifact={entry.artifact}
|
|
1041
|
+
executionId={entry.executionId}
|
|
1042
|
+
org={org}
|
|
1043
|
+
isTerminal={entry.isTerminal}
|
|
1044
|
+
onApplied={onApplied}
|
|
1045
|
+
/>
|
|
1046
|
+
) : (
|
|
1047
|
+
<ArtifactUnavailableNotice />
|
|
1048
|
+
),
|
|
1049
|
+
};
|
|
1050
|
+
});
|
|
1051
|
+
}, [editors, artifactByKey, org, onApplied]);
|
|
1052
|
+
|
|
1053
|
+
const virtualDocuments = useMemo<readonly SurfaceVirtualDocument[]>(
|
|
1054
|
+
() => [planVirtualDocument, ...artifactVirtualDocuments],
|
|
1055
|
+
[planVirtualDocument, artifactVirtualDocuments],
|
|
1056
|
+
);
|
|
1057
|
+
|
|
938
1058
|
const handleRemoveAgent = useCallback(() => {
|
|
939
1059
|
flow.setAgentRef(null);
|
|
940
1060
|
flow.setResolution(null);
|
|
@@ -1001,6 +1121,8 @@ function SessionPanelRegion({
|
|
|
1001
1121
|
onApplied,
|
|
1002
1122
|
onImplementPlan,
|
|
1003
1123
|
onOpenPlan,
|
|
1124
|
+
onOpenArtifact,
|
|
1125
|
+
onActivateArtifact,
|
|
1004
1126
|
});
|
|
1005
1127
|
|
|
1006
1128
|
// Explorer-footer folder attach (desktop only — needs the native picker).
|
|
@@ -1125,6 +1247,28 @@ function PlanUnavailableNotice() {
|
|
|
1125
1247
|
);
|
|
1126
1248
|
}
|
|
1127
1249
|
|
|
1250
|
+
/**
|
|
1251
|
+
* Shown in an artifact document tab whose artifact is no longer among the
|
|
1252
|
+
* session's outputs (an edge only reachable if the executions backing the tab
|
|
1253
|
+
* changed underneath it). The tab stays closable; the content is honest rather
|
|
1254
|
+
* than blank.
|
|
1255
|
+
*/
|
|
1256
|
+
function ArtifactUnavailableNotice() {
|
|
1257
|
+
return (
|
|
1258
|
+
<div
|
|
1259
|
+
role="status"
|
|
1260
|
+
className="mx-auto flex w-full max-w-3xl flex-col items-center gap-1 px-4 py-8 text-center"
|
|
1261
|
+
>
|
|
1262
|
+
<p className="text-xs font-medium text-foreground">
|
|
1263
|
+
This artifact is no longer available.
|
|
1264
|
+
</p>
|
|
1265
|
+
<p className="text-xs text-muted-foreground">
|
|
1266
|
+
It may have been replaced by a newer version of this session.
|
|
1267
|
+
</p>
|
|
1268
|
+
</div>
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1128
1272
|
/**
|
|
1129
1273
|
* Non-blocking notice for a build that fell back to message-only implement
|
|
1130
1274
|
* (the approved plan.md could not be uploaded/attached). The build itself
|