@stigmer/react 3.1.6 → 3.1.8

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 (224) hide show
  1. package/agent/AgentDetailView.d.ts +9 -1
  2. package/agent/AgentDetailView.d.ts.map +1 -1
  3. package/agent/AgentDetailView.js +15 -4
  4. package/agent/AgentDetailView.js.map +1 -1
  5. package/environment/EnvironmentListPanel.d.ts.map +1 -1
  6. package/environment/EnvironmentListPanel.js +11 -3
  7. package/environment/EnvironmentListPanel.js.map +1 -1
  8. package/environment/EnvironmentVariableEditor.d.ts.map +1 -1
  9. package/environment/EnvironmentVariableEditor.js +9 -3
  10. package/environment/EnvironmentVariableEditor.js.map +1 -1
  11. package/environment/index.d.ts +1 -0
  12. package/environment/index.d.ts.map +1 -1
  13. package/environment/index.js +1 -0
  14. package/environment/index.js.map +1 -1
  15. package/environment/shareRestriction.d.ts +13 -0
  16. package/environment/shareRestriction.d.ts.map +1 -0
  17. package/environment/shareRestriction.js +15 -0
  18. package/environment/shareRestriction.js.map +1 -0
  19. package/execution/ToolCallItem.d.ts.map +1 -1
  20. package/execution/ToolCallItem.js +12 -0
  21. package/execution/ToolCallItem.js.map +1 -1
  22. package/execution/ToolRunGroup.js +4 -0
  23. package/execution/ToolRunGroup.js.map +1 -1
  24. package/execution/WriteBackCard.d.ts +14 -5
  25. package/execution/WriteBackCard.d.ts.map +1 -1
  26. package/execution/WriteBackCard.js +103 -32
  27. package/execution/WriteBackCard.js.map +1 -1
  28. package/execution/WriteBacksWidget.d.ts +1 -1
  29. package/execution/WriteBacksWidget.js +2 -2
  30. package/execution/WriteBacksWidget.js.map +1 -1
  31. package/execution/index.d.ts +2 -0
  32. package/execution/index.d.ts.map +1 -1
  33. package/execution/index.js +1 -0
  34. package/execution/index.js.map +1 -1
  35. package/execution/write-back-utils.d.ts +57 -0
  36. package/execution/write-back-utils.d.ts.map +1 -0
  37. package/execution/write-back-utils.js +81 -0
  38. package/execution/write-back-utils.js.map +1 -0
  39. package/index.d.ts +6 -2
  40. package/index.d.ts.map +1 -1
  41. package/index.js +6 -1
  42. package/index.js.map +1 -1
  43. package/internal/dev/dom-counter.d.ts.map +1 -1
  44. package/internal/dev/dom-counter.js +2 -2
  45. package/internal/dev/dom-counter.js.map +1 -1
  46. package/internal/dev/enabled.d.ts +7 -0
  47. package/internal/dev/enabled.d.ts.map +1 -0
  48. package/internal/dev/enabled.js +38 -0
  49. package/internal/dev/enabled.js.map +1 -0
  50. package/internal/dev/profiler-wrapper.d.ts +3 -3
  51. package/internal/dev/profiler-wrapper.d.ts.map +1 -1
  52. package/internal/dev/profiler-wrapper.js +5 -5
  53. package/internal/dev/profiler-wrapper.js.map +1 -1
  54. package/internal/dev/use-key-stability.d.ts.map +1 -1
  55. package/internal/dev/use-key-stability.js +2 -2
  56. package/internal/dev/use-key-stability.js.map +1 -1
  57. package/internal/dev/use-render-tracer.d.ts +2 -2
  58. package/internal/dev/use-render-tracer.d.ts.map +1 -1
  59. package/internal/dev/use-render-tracer.js +4 -4
  60. package/internal/dev/use-render-tracer.js.map +1 -1
  61. package/internal/dev/use-stream-rate.d.ts +2 -1
  62. package/internal/dev/use-stream-rate.d.ts.map +1 -1
  63. package/internal/dev/use-stream-rate.js +4 -3
  64. package/internal/dev/use-stream-rate.js.map +1 -1
  65. package/library/ResourceVisibilityControl.d.ts +3 -0
  66. package/library/ResourceVisibilityControl.d.ts.map +1 -1
  67. package/library/ResourceVisibilityControl.js +22 -8
  68. package/library/ResourceVisibilityControl.js.map +1 -1
  69. package/library/useUpdateVisibility.d.ts +5 -3
  70. package/library/useUpdateVisibility.d.ts.map +1 -1
  71. package/library/useUpdateVisibility.js +7 -2
  72. package/library/useUpdateVisibility.js.map +1 -1
  73. package/library/visibilityLevels.d.ts +17 -0
  74. package/library/visibilityLevels.d.ts.map +1 -1
  75. package/library/visibilityLevels.js +32 -0
  76. package/library/visibilityLevels.js.map +1 -1
  77. package/mcp-server/McpServerConnectDialog.d.ts.map +1 -1
  78. package/mcp-server/McpServerConnectDialog.js +7 -3
  79. package/mcp-server/McpServerConnectDialog.js.map +1 -1
  80. package/mcp-server/McpServerDetailView.d.ts.map +1 -1
  81. package/mcp-server/McpServerDetailView.js +6 -3
  82. package/mcp-server/McpServerDetailView.js.map +1 -1
  83. package/mcp-server/McpServerPicker.d.ts.map +1 -1
  84. package/mcp-server/McpServerPicker.js +3 -1
  85. package/mcp-server/McpServerPicker.js.map +1 -1
  86. package/mcp-server/OAuthRequiredNotice.d.ts +29 -0
  87. package/mcp-server/OAuthRequiredNotice.d.ts.map +1 -0
  88. package/mcp-server/OAuthRequiredNotice.js +29 -0
  89. package/mcp-server/OAuthRequiredNotice.js.map +1 -0
  90. package/mcp-server/StdioSandboxNotice.d.ts +43 -0
  91. package/mcp-server/StdioSandboxNotice.d.ts.map +1 -0
  92. package/mcp-server/StdioSandboxNotice.js +45 -0
  93. package/mcp-server/StdioSandboxNotice.js.map +1 -0
  94. package/mcp-server/useMcpServerCredentials.d.ts +12 -0
  95. package/mcp-server/useMcpServerCredentials.d.ts.map +1 -1
  96. package/mcp-server/useMcpServerCredentials.js +12 -4
  97. package/mcp-server/useMcpServerCredentials.js.map +1 -1
  98. package/package.json +4 -4
  99. package/session/NewSessionViewer.d.ts +11 -1
  100. package/session/NewSessionViewer.d.ts.map +1 -1
  101. package/session/NewSessionViewer.js +28 -8
  102. package/session/NewSessionViewer.js.map +1 -1
  103. package/session/SessionViewer.d.ts +9 -2
  104. package/session/SessionViewer.d.ts.map +1 -1
  105. package/session/SessionViewer.js +26 -12
  106. package/session/SessionViewer.js.map +1 -1
  107. package/session/audience.d.ts +12 -4
  108. package/session/audience.d.ts.map +1 -1
  109. package/session/facets/ChangesTab.d.ts.map +1 -1
  110. package/session/facets/ChangesTab.js +3 -1
  111. package/session/facets/ChangesTab.js.map +1 -1
  112. package/session/useNewSessionFlow.d.ts +12 -0
  113. package/session/useNewSessionFlow.d.ts.map +1 -1
  114. package/session/useNewSessionFlow.js +13 -1
  115. package/session/useNewSessionFlow.js.map +1 -1
  116. package/session/useSessionPageFlow.d.ts +13 -0
  117. package/session/useSessionPageFlow.d.ts.map +1 -1
  118. package/session/useSessionPageFlow.js +7 -2
  119. package/session/useSessionPageFlow.js.map +1 -1
  120. package/sharing/ShareAgentDialog.d.ts +55 -0
  121. package/sharing/ShareAgentDialog.d.ts.map +1 -0
  122. package/sharing/ShareAgentDialog.js +393 -0
  123. package/sharing/ShareAgentDialog.js.map +1 -0
  124. package/sharing/SharedAgentChat.d.ts +84 -0
  125. package/sharing/SharedAgentChat.d.ts.map +1 -0
  126. package/sharing/SharedAgentChat.js +80 -0
  127. package/sharing/SharedAgentChat.js.map +1 -0
  128. package/sharing/index.d.ts +16 -0
  129. package/sharing/index.d.ts.map +1 -0
  130. package/sharing/index.js +11 -0
  131. package/sharing/index.js.map +1 -0
  132. package/sharing/useRotateShareLink.d.ts +42 -0
  133. package/sharing/useRotateShareLink.d.ts.map +1 -0
  134. package/sharing/useRotateShareLink.js +55 -0
  135. package/sharing/useRotateShareLink.js.map +1 -0
  136. package/sharing/useShareAgent.d.ts +68 -0
  137. package/sharing/useShareAgent.d.ts.map +1 -0
  138. package/sharing/useShareAgent.js +41 -0
  139. package/sharing/useShareAgent.js.map +1 -0
  140. package/sharing/useShareToolReadiness.d.ts +48 -0
  141. package/sharing/useShareToolReadiness.d.ts.map +1 -0
  142. package/sharing/useShareToolReadiness.js +93 -0
  143. package/sharing/useShareToolReadiness.js.map +1 -0
  144. package/sharing/useSharedAgentProfile.d.ts +84 -0
  145. package/sharing/useSharedAgentProfile.d.ts.map +1 -0
  146. package/sharing/useSharedAgentProfile.js +72 -0
  147. package/sharing/useSharedAgentProfile.js.map +1 -0
  148. package/sharing/useUpdateAgentSharing.d.ts +86 -0
  149. package/sharing/useUpdateAgentSharing.d.ts.map +1 -0
  150. package/sharing/useUpdateAgentSharing.js +81 -0
  151. package/sharing/useUpdateAgentSharing.js.map +1 -0
  152. package/src/agent/AgentDetailView.tsx +28 -3
  153. package/src/environment/EnvironmentListPanel.tsx +47 -18
  154. package/src/environment/EnvironmentVariableEditor.tsx +27 -17
  155. package/src/environment/__tests__/shareRestriction.test.ts +35 -0
  156. package/src/environment/index.ts +5 -0
  157. package/src/environment/shareRestriction.ts +22 -0
  158. package/src/execution/ToolCallItem.tsx +19 -1
  159. package/src/execution/ToolRunGroup.tsx +4 -0
  160. package/src/execution/WriteBackCard.tsx +285 -88
  161. package/src/execution/WriteBacksWidget.tsx +5 -5
  162. package/src/execution/__tests__/ToolCallItem.test.tsx +51 -0
  163. package/src/execution/__tests__/ToolRunGroup.test.tsx +22 -0
  164. package/src/execution/__tests__/WriteBackCard.test.tsx +64 -10
  165. package/src/execution/__tests__/write-back-utils.test.ts +135 -0
  166. package/src/execution/index.ts +7 -0
  167. package/src/execution/write-back-utils.ts +107 -0
  168. package/src/index.ts +34 -0
  169. package/src/internal/dev/__tests__/use-key-stability.test.ts +4 -0
  170. package/src/internal/dev/__tests__/use-render-tracer.test.ts +4 -0
  171. package/src/internal/dev/dom-counter.ts +2 -3
  172. package/src/internal/dev/enabled.ts +39 -0
  173. package/src/internal/dev/profiler-wrapper.tsx +5 -6
  174. package/src/internal/dev/use-key-stability.ts +2 -3
  175. package/src/internal/dev/use-render-tracer.ts +4 -5
  176. package/src/internal/dev/use-stream-rate.ts +4 -4
  177. package/src/library/ResourceVisibilityControl.tsx +23 -7
  178. package/src/library/__tests__/visibilityLevels.test.ts +33 -0
  179. package/src/library/useUpdateVisibility.ts +9 -3
  180. package/src/library/visibilityLevels.ts +36 -0
  181. package/src/mcp-server/McpServerConnectDialog.tsx +23 -11
  182. package/src/mcp-server/McpServerDetailView.tsx +22 -9
  183. package/src/mcp-server/McpServerPicker.tsx +12 -9
  184. package/src/mcp-server/OAuthRequiredNotice.tsx +77 -0
  185. package/src/mcp-server/StdioSandboxNotice.tsx +98 -0
  186. package/src/mcp-server/__tests__/OAuthRequiredNotice.test.tsx +25 -0
  187. package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +71 -0
  188. package/src/mcp-server/useMcpServerCredentials.ts +25 -4
  189. package/src/session/NewSessionViewer.tsx +67 -31
  190. package/src/session/SessionViewer.tsx +67 -36
  191. package/src/session/__tests__/audienceWiring.test.tsx +99 -0
  192. package/src/session/__tests__/useNewSessionFlow.test.tsx +75 -1
  193. package/src/session/__tests__/useSessionPageFlow.guest.test.tsx +112 -0
  194. package/src/session/audience.ts +12 -4
  195. package/src/session/facets/ChangesTab.tsx +6 -4
  196. package/src/session/useNewSessionFlow.ts +26 -1
  197. package/src/session/useSessionPageFlow.ts +24 -2
  198. package/src/sharing/ShareAgentDialog.tsx +1255 -0
  199. package/src/sharing/SharedAgentChat.tsx +283 -0
  200. package/src/sharing/__tests__/ShareAgentDialog.test.tsx +745 -0
  201. package/src/sharing/__tests__/SharedAgentChat.test.tsx +222 -0
  202. package/src/sharing/__tests__/useRotateShareLink.test.tsx +96 -0
  203. package/src/sharing/__tests__/useShareAgent.test.tsx +127 -0
  204. package/src/sharing/__tests__/useShareToolReadiness.test.tsx +182 -0
  205. package/src/sharing/__tests__/useSharedAgentProfile.test.tsx +201 -0
  206. package/src/sharing/__tests__/useUpdateAgentSharing.test.tsx +200 -0
  207. package/src/sharing/index.ts +30 -0
  208. package/src/sharing/useRotateShareLink.ts +76 -0
  209. package/src/sharing/useShareAgent.tsx +106 -0
  210. package/src/sharing/useShareToolReadiness.ts +129 -0
  211. package/src/sharing/useSharedAgentProfile.ts +131 -0
  212. package/src/sharing/useUpdateAgentSharing.ts +151 -0
  213. package/src/switch/Switch.tsx +92 -0
  214. package/src/switch/__tests__/Switch.test.tsx +88 -0
  215. package/src/switch/index.ts +2 -0
  216. package/styles.css +1 -1
  217. package/switch/Switch.d.ts +47 -0
  218. package/switch/Switch.d.ts.map +1 -0
  219. package/switch/Switch.js +36 -0
  220. package/switch/Switch.js.map +1 -0
  221. package/switch/index.d.ts +3 -0
  222. package/switch/index.d.ts.map +1 -0
  223. package/switch/index.js +2 -0
  224. package/switch/index.js.map +1 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * write-back-utils — the structured seam between the runner's raw
3
+ * `git diff --stat` text and the presentation layer. The parser must be
4
+ * strict: wrong numbers are worse than the raw-text fallback, so anything
5
+ * that is not the exact trailing summary line returns null.
6
+ */
7
+ import { describe, it, expect } from "vitest";
8
+ import { create } from "@bufbuild/protobuf";
9
+ import { WorkspaceWriteBackSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
10
+ import {
11
+ parseDiffStatSummary,
12
+ trailingDiffStatLine,
13
+ writeBackDisplayName,
14
+ } from "../write-back-utils";
15
+
16
+ describe("parseDiffStatSummary", () => {
17
+ it("parses the full three-segment summary line", () => {
18
+ expect(
19
+ parseDiffStatSummary(" 3 files changed, 42 insertions(+), 7 deletions(-)"),
20
+ ).toEqual({ filesChanged: 3, insertions: 42, deletions: 7 });
21
+ });
22
+
23
+ it("parses singular forms (1 file / 1 insertion / 1 deletion)", () => {
24
+ expect(
25
+ parseDiffStatSummary(" 1 file changed, 1 insertion(+), 1 deletion(-)"),
26
+ ).toEqual({ filesChanged: 1, insertions: 1, deletions: 1 });
27
+ });
28
+
29
+ it("parses an insertions-only summary (deletions segment omitted by git)", () => {
30
+ expect(parseDiffStatSummary(" 1 file changed, 55 insertions(+)")).toEqual({
31
+ filesChanged: 1,
32
+ insertions: 55,
33
+ deletions: 0,
34
+ });
35
+ });
36
+
37
+ it("parses a deletions-only summary (insertions segment omitted by git)", () => {
38
+ expect(parseDiffStatSummary(" 2 files changed, 9 deletions(-)")).toEqual({
39
+ filesChanged: 2,
40
+ insertions: 0,
41
+ deletions: 9,
42
+ });
43
+ });
44
+
45
+ it("parses a bare 'N file changed' summary with no count segments", () => {
46
+ expect(parseDiffStatSummary(" 1 file changed")).toEqual({
47
+ filesChanged: 1,
48
+ insertions: 0,
49
+ deletions: 0,
50
+ });
51
+ });
52
+
53
+ it("reads the trailing line of a full --stat block, ignoring per-file lines", () => {
54
+ const stat = [
55
+ " notes.md | 55 ++++++++++",
56
+ " src/{old.ts => new.ts} | 5 +-",
57
+ " 2 files changed, 58 insertions(+), 2 deletions(-)",
58
+ ].join("\n");
59
+ expect(parseDiffStatSummary(stat)).toEqual({
60
+ filesChanged: 2,
61
+ insertions: 58,
62
+ deletions: 2,
63
+ });
64
+ });
65
+
66
+ it("tolerates trailing whitespace/newlines after the summary line", () => {
67
+ expect(parseDiffStatSummary(" 1 file changed, 2 insertions(+)\n\n")).toEqual({
68
+ filesChanged: 1,
69
+ insertions: 2,
70
+ deletions: 0,
71
+ });
72
+ });
73
+
74
+ it("returns null for an empty or blank string", () => {
75
+ expect(parseDiffStatSummary("")).toBeNull();
76
+ expect(parseDiffStatSummary(" \n ")).toBeNull();
77
+ });
78
+
79
+ it("returns null for unrecognized text rather than guessing", () => {
80
+ expect(parseDiffStatSummary(" changed")).toBeNull();
81
+ expect(parseDiffStatSummary("nothing to see here")).toBeNull();
82
+ // A per-file line with no trailing summary must not parse.
83
+ expect(parseDiffStatSummary(" notes.md | 55 +++")).toBeNull();
84
+ });
85
+ });
86
+
87
+ describe("trailingDiffStatLine", () => {
88
+ it("returns the last non-empty line, trimmed", () => {
89
+ expect(trailingDiffStatLine(" a.ts | 1 +\n 1 file changed \n\n")).toBe(
90
+ "1 file changed",
91
+ );
92
+ });
93
+
94
+ it("returns null for blank input", () => {
95
+ expect(trailingDiffStatLine("")).toBeNull();
96
+ expect(trailingDiffStatLine(" \n ")).toBeNull();
97
+ });
98
+ });
99
+
100
+ describe("writeBackDisplayName", () => {
101
+ it("prefers the configured workspace entry name", () => {
102
+ const wb = create(WorkspaceWriteBackSchema, {
103
+ workspaceEntryName: "acme/api",
104
+ pullRequestUrl: "https://github.com/other/repo/pull/1",
105
+ });
106
+ expect(writeBackDisplayName(wb)).toBe("acme/api");
107
+ });
108
+
109
+ // Single-entry sessions can write back under an empty entry name (the
110
+ // runner's resolveEntry convention) — the repo identity from the PR URL is
111
+ // the next-most-honest label.
112
+ it("derives owner/repo from the PR URL when the entry name is empty", () => {
113
+ const wb = create(WorkspaceWriteBackSchema, {
114
+ workspaceEntryName: "",
115
+ pullRequestUrl: "https://github.com/acme/my-app/pull/42",
116
+ });
117
+ expect(writeBackDisplayName(wb)).toBe("acme/my-app");
118
+ });
119
+
120
+ it("falls back to the generic label when neither name nor PR URL exists", () => {
121
+ const wb = create(WorkspaceWriteBackSchema, {
122
+ workspaceEntryName: "",
123
+ pullRequestUrl: "",
124
+ });
125
+ expect(writeBackDisplayName(wb)).toBe("Workspace");
126
+ });
127
+
128
+ it("does not mis-parse a non-PR URL", () => {
129
+ const wb = create(WorkspaceWriteBackSchema, {
130
+ workspaceEntryName: "",
131
+ pullRequestUrl: "https://github.com/acme/my-app",
132
+ });
133
+ expect(writeBackDisplayName(wb)).toBe("Workspace");
134
+ });
135
+ });
@@ -80,6 +80,13 @@ export type { UseWorkspaceWriteBacksReturn } from "./useWorkspaceWriteBacks.js";
80
80
  export { WriteBackCard } from "./WriteBackCard.js";
81
81
  export type { WriteBackCardProps } from "./WriteBackCard.js";
82
82
 
83
+ export {
84
+ parseDiffStatSummary,
85
+ trailingDiffStatLine,
86
+ writeBackDisplayName,
87
+ } from "./write-back-utils.js";
88
+ export type { DiffStatSummary } from "./write-back-utils.js";
89
+
83
90
  export { FileChangesView, FileChangeDiff } from "./FileChangesView.js";
84
91
  export type { FileChangesViewProps, FileChangeDiffProps } from "./FileChangesView.js";
85
92
 
@@ -0,0 +1,107 @@
1
+ import type { WorkspaceWriteBack } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // diff --stat summary parsing
5
+ // ---------------------------------------------------------------------------
6
+
7
+ /**
8
+ * Aggregate change statistics parsed from a `git diff --stat` summary line.
9
+ *
10
+ * @see parseDiffStatSummary
11
+ */
12
+ export interface DiffStatSummary {
13
+ /** Number of files with changes. */
14
+ readonly filesChanged: number;
15
+ /** Total inserted lines across all files. */
16
+ readonly insertions: number;
17
+ /** Total deleted lines across all files. */
18
+ readonly deletions: number;
19
+ }
20
+
21
+ /**
22
+ * The trailing summary line git appends to `--stat` output. All three count
23
+ * segments are optional-but-ordered in git's formatting; an insertions-only
24
+ * diff omits the deletions segment entirely (and vice versa).
25
+ */
26
+ const DIFF_STAT_SUMMARY_RE =
27
+ /^(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?$/;
28
+
29
+ /**
30
+ * Parses the trailing summary line of a `git diff --stat` output into
31
+ * structured counts.
32
+ *
33
+ * `WorkspaceWriteBack.diff_summary` stores the raw `--stat` text (see the
34
+ * runner's `WriteBackCoordinator.updateStatus`). The per-file lines above the
35
+ * summary are NOT parseable honestly — git truncates long paths — but the
36
+ * trailing "N files changed, M insertions(+), K deletions(-)" line is stable
37
+ * and locale-independent, so it is the one part safe to structure.
38
+ *
39
+ * Returns `null` when the text has no recognizable summary line (empty
40
+ * string, or a format drift), letting callers fall back to rendering the raw
41
+ * text rather than showing wrong numbers.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * parseDiffStatSummary(" notes.md | 55 +++\n 1 file changed, 55 insertions(+)");
46
+ * // { filesChanged: 1, insertions: 55, deletions: 0 }
47
+ * ```
48
+ */
49
+ export function parseDiffStatSummary(
50
+ diffSummary: string,
51
+ ): DiffStatSummary | null {
52
+ const line = trailingDiffStatLine(diffSummary);
53
+ if (line === null) return null;
54
+
55
+ const match = DIFF_STAT_SUMMARY_RE.exec(line);
56
+ if (!match) return null;
57
+
58
+ return {
59
+ filesChanged: Number(match[1]),
60
+ insertions: Number(match[2] ?? 0),
61
+ deletions: Number(match[3] ?? 0),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * The last non-empty line of a `diff --stat` text — the summary line when the
67
+ * text is well-formed, and the honest single-line fallback to display when
68
+ * {@link parseDiffStatSummary} cannot structure it. `null` for blank input.
69
+ */
70
+ export function trailingDiffStatLine(diffSummary: string): string | null {
71
+ const lines = diffSummary.split("\n");
72
+ for (let i = lines.length - 1; i >= 0; i--) {
73
+ const line = lines[i].trim();
74
+ if (line) return line;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Display name resolution
81
+ // ---------------------------------------------------------------------------
82
+
83
+ /** Matches `https://github.com/{owner}/{repo}/pull/{n}` (any git host). */
84
+ const PULL_REQUEST_URL_RE = /^https?:\/\/[^/]+\/([^/]+)\/([^/]+)\/pull\/\d+/;
85
+
86
+ /**
87
+ * Resolves the human-facing name for a write-back's header.
88
+ *
89
+ * A write-back names its workspace entry, but single-entry sessions can
90
+ * legitimately write back under an EMPTY `workspace_entry_name` — the runner's
91
+ * `WriteBackCoordinator.resolveEntry` convention, mirrored by
92
+ * `useWorkspaceReadRefs`. (The Console derives entry names from the git URL,
93
+ * so this mostly surfaces for CLI/SDK-created sessions.) Rather than render a
94
+ * blank title, fall back through what the record can still prove:
95
+ *
96
+ * 1. `workspace_entry_name` — the configured name, when present.
97
+ * 2. `owner/repo` parsed from `pull_request_url` — the repository identity.
98
+ * 3. `"Workspace"` — the honest generic label when neither exists.
99
+ */
100
+ export function writeBackDisplayName(writeBack: WorkspaceWriteBack): string {
101
+ if (writeBack.workspaceEntryName) return writeBack.workspaceEntryName;
102
+
103
+ const match = PULL_REQUEST_URL_RE.exec(writeBack.pullRequestUrl);
104
+ if (match) return `${match[1]}/${match[2]}`;
105
+
106
+ return "Workspace";
107
+ }
package/src/index.ts CHANGED
@@ -323,6 +323,9 @@ export {
323
323
  execIdFromStorageKey,
324
324
  useWorkspaceWriteBacks,
325
325
  WriteBackCard,
326
+ parseDiffStatSummary,
327
+ trailingDiffStatLine,
328
+ writeBackDisplayName,
326
329
  FileChangesView,
327
330
  FileChangeDiff,
328
331
  EmptyChangeNotice,
@@ -419,6 +422,7 @@ export type {
419
422
  UseFileChangeContentReturn,
420
423
  UseWorkspaceWriteBacksReturn,
421
424
  WriteBackCardProps,
425
+ DiffStatSummary,
422
426
  FileChangesViewProps,
423
427
  FileChangeDiffProps,
424
428
  EmptyChangeNoticeProps,
@@ -980,6 +984,32 @@ export type {
980
984
  InvitationRedemptionProps,
981
985
  } from "./invitation/index.js";
982
986
 
987
+ // Sharing — shared-agent public profile, the anonymous-visitor chat organism,
988
+ // and the owner-side Share experience (dialog, kebab hook, sharing mutation).
989
+ export {
990
+ useSharedAgentProfile,
991
+ SharedAgentChat,
992
+ sharingAudienceFromProto,
993
+ useUpdateAgentSharing,
994
+ useRotateShareLink,
995
+ ShareAgentDialog,
996
+ useShareAgent,
997
+ validateOrigin,
998
+ MAX_ALLOWED_ORIGINS,
999
+ } from "./sharing/index.js";
1000
+ export type {
1001
+ UseSharedAgentProfileOptions,
1002
+ UseSharedAgentProfileReturn,
1003
+ SharedAgentChatProps,
1004
+ AgentSharingDraft,
1005
+ SharingAudience,
1006
+ UseUpdateAgentSharingReturn,
1007
+ UseRotateShareLinkReturn,
1008
+ ShareAgentDialogProps,
1009
+ UseShareAgentArgs,
1010
+ UseShareAgentReturn,
1011
+ } from "./sharing/index.js";
1012
+
983
1013
  // Error — structured error display with classification, retry, and contextual guidance
984
1014
  export { ErrorMessage, SecretFlowErrorGuide, isSecretFlowError } from "./error/index.js";
985
1015
  export type { ErrorMessageProps, SecretFlowErrorGuideProps } from "./error/index.js";
@@ -1136,6 +1166,10 @@ export type {
1136
1166
  export { Tabs } from "./tabs/index.js";
1137
1167
  export type { TabsProps, TabItem } from "./tabs/index.js";
1138
1168
 
1169
+ // Switch — accessible on/off toggle primitive (WAI-ARIA Switch pattern)
1170
+ export { Switch } from "./switch/index.js";
1171
+ export type { SwitchProps } from "./switch/index.js";
1172
+
1139
1173
  // Resource Detail — headless hooks, action bar, and composed shell for resource detail pages
1140
1174
  export {
1141
1175
  useCopyResource,
@@ -15,10 +15,14 @@ describe("useKeyStability", () => {
15
15
  let warnSpy: ReturnType<typeof vi.spyOn>;
16
16
 
17
17
  beforeEach(() => {
18
+ // Perf logging is opt-in (off by default). Enable it via the browser
19
+ // runtime flag so these behavioral assertions exercise the warning path.
20
+ (globalThis as { __STIGMER_PERF__?: boolean }).__STIGMER_PERF__ = true;
18
21
  warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
19
22
  });
20
23
 
21
24
  afterEach(() => {
25
+ delete (globalThis as { __STIGMER_PERF__?: boolean }).__STIGMER_PERF__;
22
26
  warnSpy.mockRestore();
23
27
  });
24
28
 
@@ -6,10 +6,14 @@ describe("useRenderTracer", () => {
6
6
  let debugSpy: ReturnType<typeof vi.spyOn>;
7
7
 
8
8
  beforeEach(() => {
9
+ // Perf logging is opt-in (off by default). Enable it via the browser
10
+ // runtime flag so these behavioral assertions exercise the logging path.
11
+ (globalThis as { __STIGMER_PERF__?: boolean }).__STIGMER_PERF__ = true;
9
12
  debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {});
10
13
  });
11
14
 
12
15
  afterEach(() => {
16
+ delete (globalThis as { __STIGMER_PERF__?: boolean }).__STIGMER_PERF__;
13
17
  debugSpy.mockRestore();
14
18
  });
15
19
 
@@ -1,6 +1,5 @@
1
1
  import { type RefObject, useEffect, useRef } from "react";
2
-
3
- const DEV = process.env.NODE_ENV !== "production";
2
+ import { isPerfLoggingEnabled } from "./enabled.js";
4
3
 
5
4
  /** Log the node count every Nth trigger. */
6
5
  const LOG_EVERY = 10;
@@ -23,7 +22,7 @@ export function useDomNodeCount(
23
22
  const triggerCountRef = useRef(0);
24
23
 
25
24
  useEffect(() => {
26
- if (!DEV) return;
25
+ if (!isPerfLoggingEnabled()) return;
27
26
 
28
27
  triggerCountRef.current += 1;
29
28
  if (triggerCountRef.current % LOG_EVERY !== 0) return;
@@ -0,0 +1,39 @@
1
+ // Master switch for the SDK's dev-only performance instrumentation (the
2
+ // `[stgm:perf:*]` logs emitted by useStreamRate, useRenderTracer, useDomNodeCount,
3
+ // useKeyStability, and DevProfiler).
4
+ //
5
+ // Off by default in every environment. These logs are pure noise for anyone not
6
+ // actively profiling, and in a non-browser TTY consumer — notably the `stigmer`
7
+ // CLI's Ink UI — console output is not filtered by a devtools log level, so it
8
+ // corrupts the rendered terminal. The old `NODE_ENV !== "production"` gate
9
+ // assumed a browser devtools context (where `console.debug` is hidden by
10
+ // default), which does not hold for Node consumers.
11
+ //
12
+ // Opt in without a rebuild:
13
+ // - Node (CLI, tests): STIGMER_PERF=1 stigmer run ...
14
+ // - Browser (web/desktop devtools): globalThis.__STIGMER_PERF__ = true
15
+ //
16
+ // Still hard-gated on non-production so production bundles dead-code-eliminate
17
+ // the instrumentation entirely (bundlers inline `process.env.NODE_ENV`).
18
+
19
+ /** Reads the Node opt-in env var, guarding environments without `process`. */
20
+ function envFlagEnabled(): boolean {
21
+ const env = typeof process !== "undefined" ? process.env : undefined;
22
+ const value = env?.STIGMER_PERF;
23
+ return value === "1" || value === "true";
24
+ }
25
+
26
+ /** Reads the browser runtime opt-in (toggle live from devtools). */
27
+ function globalFlagEnabled(): boolean {
28
+ return (globalThis as { __STIGMER_PERF__?: unknown }).__STIGMER_PERF__ === true;
29
+ }
30
+
31
+ /**
32
+ * Whether the SDK's dev-perf instrumentation may emit output. Evaluated per call
33
+ * (not cached) so a browser runtime toggle takes effect without a remount.
34
+ * Always `false` in production.
35
+ */
36
+ export function isPerfLoggingEnabled(): boolean {
37
+ if (process.env.NODE_ENV === "production") return false;
38
+ return envFlagEnabled() || globalFlagEnabled();
39
+ }
@@ -1,8 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { Profiler, type ProfilerOnRenderCallback, type ReactNode } from "react";
4
-
5
- const DEV = process.env.NODE_ENV !== "production";
4
+ import { isPerfLoggingEnabled } from "./enabled.js";
6
5
 
7
6
  /** Log every Nth commit to avoid console flood during streaming. */
8
7
  const LOG_EVERY = 10;
@@ -31,9 +30,9 @@ const onRender: ProfilerOnRenderCallback = (
31
30
  * In dev mode, logs commit-level timing (`actualDuration`,
32
31
  * `baseDuration`, mount vs update phase) with sampled output.
33
32
  *
34
- * In production, React strips Profiler callbacks automatically, and
35
- * our dev gate ensures this component renders children with zero
36
- * overhead regardless.
33
+ * Off by default in every environment (and always in production) — enable via
34
+ * {@link isPerfLoggingEnabled}'s opt-in flags. When disabled, children render
35
+ * with zero Profiler overhead.
37
36
  */
38
37
  export function DevProfiler({
39
38
  id,
@@ -42,7 +41,7 @@ export function DevProfiler({
42
41
  readonly id: string;
43
42
  readonly children: ReactNode;
44
43
  }) {
45
- if (!DEV) return children;
44
+ if (!isPerfLoggingEnabled()) return children;
46
45
 
47
46
  return (
48
47
  <Profiler id={id} onRender={onRender}>
@@ -1,6 +1,5 @@
1
1
  import { useRef } from "react";
2
-
3
- const DEV = process.env.NODE_ENV !== "production";
2
+ import { isPerfLoggingEnabled } from "./enabled.js";
4
3
 
5
4
  /** Minimal shape expected from a keyed thread item. */
6
5
  interface KeyedItem {
@@ -25,7 +24,7 @@ interface KeyedItem {
25
24
  export function useKeyStability(items: readonly KeyedItem[]): void {
26
25
  const prevRef = useRef<readonly KeyedItem[] | null>(null);
27
26
 
28
- if (!DEV) return;
27
+ if (!isPerfLoggingEnabled()) return;
29
28
 
30
29
  // Detect duplicate keys within the current render.
31
30
  const seen = new Map<string, number>();
@@ -1,6 +1,5 @@
1
1
  import { useRef } from "react";
2
-
3
- const DEV = process.env.NODE_ENV !== "production";
2
+ import { isPerfLoggingEnabled } from "./enabled.js";
4
3
 
5
4
  /**
6
5
  * Sampling interval: log every Nth render to avoid flooding the
@@ -12,8 +11,8 @@ const LOG_EVERY = 10;
12
11
  * Dev-only hook that tracks render count and reports which props
13
12
  * changed (by shallow referential equality) since the last render.
14
13
  *
15
- * In production builds the function body is dead-code-eliminated by
16
- * bundlers that replace `process.env.NODE_ENV` with `"production"`.
14
+ * Off by default in every environment (and always in production) — enable via
15
+ * {@link isPerfLoggingEnabled}'s opt-in flags.
17
16
  *
18
17
  * @param componentName - Stable label for console output.
19
18
  * @param props - Key/value map of props to track. Pass only the props
@@ -26,7 +25,7 @@ export function useRenderTracer(
26
25
  const countRef = useRef(0);
27
26
  const prevRef = useRef<Record<string, unknown> | null>(null);
28
27
 
29
- if (!DEV) return;
28
+ if (!isPerfLoggingEnabled()) return;
30
29
 
31
30
  countRef.current += 1;
32
31
  const count = countRef.current;
@@ -1,6 +1,5 @@
1
1
  import { useRef } from "react";
2
-
3
- const DEV = process.env.NODE_ENV !== "production";
2
+ import { isPerfLoggingEnabled } from "./enabled.js";
4
3
 
5
4
  /** Rolling window size for interval statistics. */
6
5
  const WINDOW_SIZE = 30;
@@ -42,12 +41,13 @@ const NOOP_TRACKER: StreamRateTracker = {
42
41
  * loop (not during render), so it uses a ref-backed mutable object
43
42
  * rather than React state.
44
43
  *
45
- * In production the hook returns a no-op tracker.
44
+ * Returns a no-op tracker unless perf logging is explicitly enabled
45
+ * (see {@link isPerfLoggingEnabled}) — off by default in every environment.
46
46
  */
47
47
  export function useStreamRate(): StreamRateTracker {
48
48
  const stateRef = useRef<StreamRateState | null>(null);
49
49
 
50
- if (!DEV) return NOOP_TRACKER;
50
+ if (!isPerfLoggingEnabled()) return NOOP_TRACKER;
51
51
 
52
52
  if (!stateRef.current) {
53
53
  stateRef.current = {
@@ -8,6 +8,7 @@ import { useSsoProvider } from "../identity-provider/useSsoProvider.js";
8
8
  import { VisibilityBadge, VisibilitySelector } from "./VisibilitySelector.js";
9
9
  import {
10
10
  blueprintVisibilityLevels,
11
+ environmentVisibilityLevels,
11
12
  INSTANCE_VISIBILITY_LEVELS,
12
13
  } from "./visibilityLevels.js";
13
14
  import {
@@ -28,6 +29,7 @@ const FGA_KIND: Record<VisibilityResourceKind, string> = {
28
29
  mcpServer: "mcp_server",
29
30
  agentInstance: "agent_instance",
30
31
  workflowInstance: "workflow_instance",
32
+ environment: "environment",
31
33
  };
32
34
 
33
35
  const INSTANCE_KINDS: ReadonlySet<VisibilityResourceKind> = new Set([
@@ -79,6 +81,9 @@ export interface ResourceVisibilityControlProps {
79
81
  * set collapses to Private / Public.
80
82
  * - Instances: Private / Organization / Public — platform is excluded by
81
83
  * design to preserve tenant isolation.
84
+ * - Environments: Private / Organization only — secret values never leave
85
+ * the org boundary. In `local` mode there are no levels to choose (the
86
+ * OSS backend is single-user), so the control degrades to a badge.
82
87
  *
83
88
  * The backend remains the enforcer (`ValidateVisibilityStep` rejects
84
89
  * platform without an IdP); the gate here only prevents offering an option
@@ -96,18 +101,23 @@ export function ResourceVisibilityControl({
96
101
  const deploymentMode = useDeploymentMode();
97
102
 
98
103
  const isInstance = INSTANCE_KINDS.has(kind);
104
+ const isEnvironment = kind === "environment";
99
105
  // The IdP lookup only matters for blueprints in cloud mode; passing null
100
106
  // makes the hook a stable no-op everywhere else.
101
107
  const idpLookupOrg =
102
- !isInstance && deploymentMode === "cloud" ? (org ?? null) : null;
108
+ !isInstance && !isEnvironment && deploymentMode === "cloud"
109
+ ? (org ?? null)
110
+ : null;
103
111
  const { ssoProvider } = useSsoProvider(idpLookupOrg);
104
112
 
105
- const options = isInstance
106
- ? INSTANCE_VISIBILITY_LEVELS
107
- : blueprintVisibilityLevels({
108
- deploymentMode,
109
- hasIdentityProvider: ssoProvider !== null,
110
- });
113
+ const options = isEnvironment
114
+ ? environmentVisibilityLevels(deploymentMode)
115
+ : isInstance
116
+ ? INSTANCE_VISIBILITY_LEVELS
117
+ : blueprintVisibilityLevels({
118
+ deploymentMode,
119
+ hasIdentityProvider: ssoProvider !== null,
120
+ });
111
121
 
112
122
  const handleChange = useCallback(
113
123
  async (next: ApiResourceVisibility) => {
@@ -125,6 +135,12 @@ export function ResourceVisibilityControl({
125
135
 
126
136
  const badge = <VisibilityBadge visibility={visibility} className={className} />;
127
137
 
138
+ // Fewer than two levels means there is nothing to choose (e.g. an
139
+ // environment in local mode) — stay legible with the read-only badge.
140
+ if (options.length < 2) {
141
+ return badge;
142
+ }
143
+
128
144
  return (
129
145
  <PermissionGate
130
146
  resource={{ kind: FGA_KIND[kind], id: resourceId }}
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
3
+ import { environmentVisibilityLevels } from "../visibilityLevels";
4
+
5
+ describe("environmentVisibilityLevels", () => {
6
+ it("offers exactly Private and Organization in cloud mode", () => {
7
+ const levels = environmentVisibilityLevels("cloud");
8
+ expect(levels.map((l) => l.value)).toEqual([
9
+ ApiResourceVisibility.visibility_private,
10
+ ApiResourceVisibility.visibility_org,
11
+ ]);
12
+ });
13
+
14
+ it("never offers public or platform — secrets stay inside the org", () => {
15
+ for (const mode of ["cloud", "local"] as const) {
16
+ const values = environmentVisibilityLevels(mode).map((l) => l.value);
17
+ expect(values).not.toContain(ApiResourceVisibility.visibility_public);
18
+ expect(values).not.toContain(ApiResourceVisibility.visibility_platform);
19
+ }
20
+ });
21
+
22
+ it("confirms before escalating to org (credentials become runtime-usable org-wide)", () => {
23
+ const org = environmentVisibilityLevels("cloud").find(
24
+ (l) => l.value === ApiResourceVisibility.visibility_org,
25
+ );
26
+ expect(org?.confirmPrompt).toBeTruthy();
27
+ expect(org?.description).toContain("secret values stay hidden");
28
+ });
29
+
30
+ it("collapses to a single read-only level in local mode", () => {
31
+ expect(environmentVisibilityLevels("local")).toHaveLength(1);
32
+ });
33
+ });
@@ -14,7 +14,8 @@ export type VisibilityResourceKind =
14
14
  | "mcpServer"
15
15
  | "workflow"
16
16
  | "agentInstance"
17
- | "workflowInstance";
17
+ | "workflowInstance"
18
+ | "environment";
18
19
 
19
20
  /** Return value of {@link useUpdateVisibility}. */
20
21
  export interface UseUpdateVisibilityReturn {
@@ -35,8 +36,10 @@ export interface UseUpdateVisibilityReturn {
35
36
  * Behavior hook that updates the visibility of a resource.
36
37
  *
37
38
  * Supports blueprints (Agent, Workflow, Skill, MCP Server) with the
38
- * full private/org/public/platform spectrum, and instances
39
- * (AgentInstance, WorkflowInstance) with private/org/public.
39
+ * full private/org/public/platform spectrum, instances
40
+ * (AgentInstance, WorkflowInstance) with private/org/public, and
41
+ * environments with private/org (secret values never leave the org
42
+ * boundary, so broader levels are rejected by the backend).
40
43
  *
41
44
  * Wraps the generated `stigmer.{kind}.updateVisibility()` SDK method
42
45
  * with loading and error state management. The hook is stateless with
@@ -99,6 +102,9 @@ export function useUpdateVisibility(
99
102
  case "workflowInstance":
100
103
  await stigmer.workflowInstance.updateVisibility(input);
101
104
  break;
105
+ case "environment":
106
+ await stigmer.environment.updateVisibility(input);
107
+ break;
102
108
  }
103
109
  } catch (err) {
104
110
  setError(toError(err));