@stigmer/react 3.12.3 → 3.12.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 (314) hide show
  1. package/agent/steps/IdentityStep.d.ts.map +1 -1
  2. package/agent/steps/IdentityStep.js +2 -1
  3. package/agent/steps/IdentityStep.js.map +1 -1
  4. package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
  5. package/agent-instance/AgentInstanceDetailPanel.js +2 -1
  6. package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
  7. package/api-key/CreateApiKeyForm.d.ts.map +1 -1
  8. package/api-key/CreateApiKeyForm.js +2 -1
  9. package/api-key/CreateApiKeyForm.js.map +1 -1
  10. package/channel/ChannelConversationsDialog.d.ts.map +1 -1
  11. package/channel/ChannelConversationsDialog.js +2 -1
  12. package/channel/ChannelConversationsDialog.js.map +1 -1
  13. package/channel/ChannelTemplatesDialog.d.ts.map +1 -1
  14. package/channel/ChannelTemplatesDialog.js +2 -1
  15. package/channel/ChannelTemplatesDialog.js.map +1 -1
  16. package/channel/ConnectSlackDialog.d.ts.map +1 -1
  17. package/channel/ConnectSlackDialog.js +2 -1
  18. package/channel/ConnectSlackDialog.js.map +1 -1
  19. package/channel/connect/ServingAppSection.d.ts.map +1 -1
  20. package/channel/connect/ServingAppSection.js +2 -1
  21. package/channel/connect/ServingAppSection.js.map +1 -1
  22. package/channel-app/CreateChannelAppForm.d.ts.map +1 -1
  23. package/channel-app/CreateChannelAppForm.js +2 -1
  24. package/channel-app/CreateChannelAppForm.js.map +1 -1
  25. package/conversation/ConversationListPane.d.ts.map +1 -1
  26. package/conversation/ConversationListPane.js +2 -1
  27. package/conversation/ConversationListPane.js.map +1 -1
  28. package/conversation/ConversationTimelineView.d.ts.map +1 -1
  29. package/conversation/ConversationTimelineView.js +2 -1
  30. package/conversation/ConversationTimelineView.js.map +1 -1
  31. package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
  32. package/cursor-accounts/CursorAccountsConsole.js +2 -1
  33. package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
  34. package/cursor-accounts/MemberKeysPanel.d.ts.map +1 -1
  35. package/cursor-accounts/MemberKeysPanel.js +2 -1
  36. package/cursor-accounts/MemberKeysPanel.js.map +1 -1
  37. package/dashboard/DashboardFailedRuns.d.ts.map +1 -1
  38. package/dashboard/DashboardFailedRuns.js +2 -1
  39. package/dashboard/DashboardFailedRuns.js.map +1 -1
  40. package/dependency-graph/DependencyGraph.d.ts.map +1 -1
  41. package/dependency-graph/DependencyGraph.js +2 -1
  42. package/dependency-graph/DependencyGraph.js.map +1 -1
  43. package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
  44. package/dependency-graph/DependencyTreeNode.js +2 -1
  45. package/dependency-graph/DependencyTreeNode.js.map +1 -1
  46. package/environment/EnvironmentPicker.d.ts.map +1 -1
  47. package/environment/EnvironmentPicker.js +2 -1
  48. package/environment/EnvironmentPicker.js.map +1 -1
  49. package/error/SecretFlowErrorGuide.d.ts.map +1 -1
  50. package/error/SecretFlowErrorGuide.js +2 -1
  51. package/error/SecretFlowErrorGuide.js.map +1 -1
  52. package/execution/ArtifactContentBody.d.ts.map +1 -1
  53. package/execution/ArtifactContentBody.js +2 -1
  54. package/execution/ArtifactContentBody.js.map +1 -1
  55. package/execution/ArtifactsWidget.d.ts.map +1 -1
  56. package/execution/ArtifactsWidget.js +2 -1
  57. package/execution/ArtifactsWidget.js.map +1 -1
  58. package/execution/FileChangeProgressBar.d.ts.map +1 -1
  59. package/execution/FileChangeProgressBar.js +2 -1
  60. package/execution/FileChangeProgressBar.js.map +1 -1
  61. package/execution/TodoList.d.ts.map +1 -1
  62. package/execution/TodoList.js +2 -1
  63. package/execution/TodoList.js.map +1 -1
  64. package/execution/WriteBacksWidget.d.ts.map +1 -1
  65. package/execution/WriteBacksWidget.js +2 -1
  66. package/execution/WriteBacksWidget.js.map +1 -1
  67. package/execution/tool-categories.d.ts +5 -22
  68. package/execution/tool-categories.d.ts.map +1 -1
  69. package/execution/tool-categories.js +8 -38
  70. package/execution/tool-categories.js.map +1 -1
  71. package/iam-policy/PeopleWithAccess.d.ts.map +1 -1
  72. package/iam-policy/PeopleWithAccess.js +2 -1
  73. package/iam-policy/PeopleWithAccess.js.map +1 -1
  74. package/iam-policy/PrincipalPicker.d.ts.map +1 -1
  75. package/iam-policy/PrincipalPicker.js +2 -1
  76. package/iam-policy/PrincipalPicker.js.map +1 -1
  77. package/iam-policy/RoleSelector.d.ts.map +1 -1
  78. package/iam-policy/RoleSelector.js +2 -1
  79. package/iam-policy/RoleSelector.js.map +1 -1
  80. package/identity-provider/CreateIdentityProviderForm.d.ts.map +1 -1
  81. package/identity-provider/CreateIdentityProviderForm.js +2 -1
  82. package/identity-provider/CreateIdentityProviderForm.js.map +1 -1
  83. package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
  84. package/identity-provider/IdentityProviderDetailPanel.js +2 -1
  85. package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
  86. package/identity-provider/IdentityProviderWizard.d.ts.map +1 -1
  87. package/identity-provider/IdentityProviderWizard.js +2 -1
  88. package/identity-provider/IdentityProviderWizard.js.map +1 -1
  89. package/index.d.ts +3 -1
  90. package/index.d.ts.map +1 -1
  91. package/index.js +3 -0
  92. package/index.js.map +1 -1
  93. package/internal/element-resets.d.ts +17 -0
  94. package/internal/element-resets.d.ts.map +1 -0
  95. package/internal/element-resets.js +32 -0
  96. package/internal/element-resets.js.map +1 -0
  97. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  98. package/internal/file-tree/FileTreeNode.js +2 -1
  99. package/internal/file-tree/FileTreeNode.js.map +1 -1
  100. package/internal/markdown-components.d.ts +7 -0
  101. package/internal/markdown-components.d.ts.map +1 -1
  102. package/internal/markdown-components.js +15 -5
  103. package/internal/markdown-components.js.map +1 -1
  104. package/invitation/InvitationManager.d.ts.map +1 -1
  105. package/invitation/InvitationManager.js +2 -1
  106. package/invitation/InvitationManager.js.map +1 -1
  107. package/manifest/ApplyManifestDialog.d.ts.map +1 -1
  108. package/manifest/ApplyManifestDialog.js +2 -1
  109. package/manifest/ApplyManifestDialog.js.map +1 -1
  110. package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
  111. package/mcp-server/steps/IdentityTransportStep.js +2 -1
  112. package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
  113. package/package.json +4 -4
  114. package/platform-client/CreatePlatformClientForm.d.ts.map +1 -1
  115. package/platform-client/CreatePlatformClientForm.js +2 -1
  116. package/platform-client/CreatePlatformClientForm.js.map +1 -1
  117. package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
  118. package/platform-client/PlatformClientDetailPanel.js +2 -1
  119. package/platform-client/PlatformClientDetailPanel.js.map +1 -1
  120. package/pricing-governance/BaselineEditor.d.ts.map +1 -1
  121. package/pricing-governance/BaselineEditor.js +2 -1
  122. package/pricing-governance/BaselineEditor.js.map +1 -1
  123. package/pricing-governance/PricingGovernanceConsole.d.ts.map +1 -1
  124. package/pricing-governance/PricingGovernanceConsole.js +2 -1
  125. package/pricing-governance/PricingGovernanceConsole.js.map +1 -1
  126. package/provider-standing/ProviderStandingAccessNotice.d.ts +11 -0
  127. package/provider-standing/ProviderStandingAccessNotice.d.ts.map +1 -0
  128. package/provider-standing/ProviderStandingAccessNotice.js +14 -0
  129. package/provider-standing/ProviderStandingAccessNotice.js.map +1 -0
  130. package/provider-standing/ProviderStandingConsole.d.ts +24 -0
  131. package/provider-standing/ProviderStandingConsole.d.ts.map +1 -0
  132. package/provider-standing/ProviderStandingConsole.js +98 -0
  133. package/provider-standing/ProviderStandingConsole.js.map +1 -0
  134. package/provider-standing/index.d.ts +6 -0
  135. package/provider-standing/index.d.ts.map +1 -0
  136. package/provider-standing/index.js +6 -0
  137. package/provider-standing/index.js.map +1 -0
  138. package/provider-standing/useProviderStanding.d.ts +34 -0
  139. package/provider-standing/useProviderStanding.d.ts.map +1 -0
  140. package/provider-standing/useProviderStanding.js +26 -0
  141. package/provider-standing/useProviderStanding.js.map +1 -0
  142. package/provider.d.ts +12 -0
  143. package/provider.d.ts.map +1 -1
  144. package/provider.js +12 -0
  145. package/provider.js.map +1 -1
  146. package/resource-creation/StepIndicator.d.ts.map +1 -1
  147. package/resource-creation/StepIndicator.js +2 -1
  148. package/resource-creation/StepIndicator.js.map +1 -1
  149. package/schedule/ScheduleDetailView.d.ts.map +1 -1
  150. package/schedule/ScheduleDetailView.js +3 -2
  151. package/schedule/ScheduleDetailView.js.map +1 -1
  152. package/schedule/ScheduleRunsTable.d.ts.map +1 -1
  153. package/schedule/ScheduleRunsTable.js +2 -1
  154. package/schedule/ScheduleRunsTable.js.map +1 -1
  155. package/session/facets/ArtifactsTab.d.ts.map +1 -1
  156. package/session/facets/ArtifactsTab.js +2 -1
  157. package/session/facets/ArtifactsTab.js.map +1 -1
  158. package/session/facets/ChangesTab.d.ts.map +1 -1
  159. package/session/facets/ChangesTab.js +2 -1
  160. package/session/facets/ChangesTab.js.map +1 -1
  161. package/session/facets/UsageTab.d.ts +10 -3
  162. package/session/facets/UsageTab.d.ts.map +1 -1
  163. package/session/facets/UsageTab.js +42 -6
  164. package/session/facets/UsageTab.js.map +1 -1
  165. package/session/index.d.ts +1 -1
  166. package/session/index.d.ts.map +1 -1
  167. package/session/index.js.map +1 -1
  168. package/session/useSessionUsage.d.ts +25 -0
  169. package/session/useSessionUsage.d.ts.map +1 -1
  170. package/session/useSessionUsage.js +11 -0
  171. package/session/useSessionUsage.js.map +1 -1
  172. package/settings/settings-nav.d.ts.map +1 -1
  173. package/settings/settings-nav.js +7 -1
  174. package/settings/settings-nav.js.map +1 -1
  175. package/settings/useSettingsNavGroups.d.ts.map +1 -1
  176. package/settings/useSettingsNavGroups.js +4 -1
  177. package/settings/useSettingsNavGroups.js.map +1 -1
  178. package/sharing/ShareAgentDialog.d.ts.map +1 -1
  179. package/sharing/ShareAgentDialog.js +2 -1
  180. package/sharing/ShareAgentDialog.js.map +1 -1
  181. package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
  182. package/sidebar/WorkspaceSidebar.js +2 -1
  183. package/sidebar/WorkspaceSidebar.js.map +1 -1
  184. package/skill/SkillFileBrowser.d.ts.map +1 -1
  185. package/skill/SkillFileBrowser.js +2 -1
  186. package/skill/SkillFileBrowser.js.map +1 -1
  187. package/skill/internal/fetchAndUnpackArtifact.d.ts +1 -1
  188. package/skill/internal/fetchAndUnpackArtifact.d.ts.map +1 -1
  189. package/skill/internal/fetchAndUnpackArtifact.js +47 -3
  190. package/skill/internal/fetchAndUnpackArtifact.js.map +1 -1
  191. package/skill/useSkillDiff.d.ts.map +1 -1
  192. package/skill/useSkillDiff.js +7 -1
  193. package/skill/useSkillDiff.js.map +1 -1
  194. package/skill/useSkillUpload.d.ts.map +1 -1
  195. package/skill/useSkillUpload.js +22 -4
  196. package/skill/useSkillUpload.js.map +1 -1
  197. package/src/__tests__/bare-host-resets.layout.test.tsx +184 -0
  198. package/src/agent/steps/IdentityStep.tsx +2 -1
  199. package/src/agent-instance/AgentInstanceDetailPanel.tsx +2 -1
  200. package/src/api-key/CreateApiKeyForm.tsx +2 -1
  201. package/src/channel/ChannelConversationsDialog.tsx +2 -1
  202. package/src/channel/ChannelTemplatesDialog.tsx +2 -1
  203. package/src/channel/ConnectSlackDialog.tsx +2 -1
  204. package/src/channel/connect/ServingAppSection.tsx +2 -1
  205. package/src/channel-app/CreateChannelAppForm.tsx +2 -1
  206. package/src/conversation/ConversationListPane.tsx +2 -5
  207. package/src/conversation/ConversationTimelineView.tsx +2 -1
  208. package/src/cursor-accounts/CursorAccountsConsole.tsx +2 -1
  209. package/src/cursor-accounts/MemberKeysPanel.tsx +2 -1
  210. package/src/dashboard/DashboardFailedRuns.tsx +2 -1
  211. package/src/dependency-graph/DependencyGraph.tsx +2 -1
  212. package/src/dependency-graph/DependencyTreeNode.tsx +2 -1
  213. package/src/environment/EnvironmentPicker.tsx +2 -1
  214. package/src/error/SecretFlowErrorGuide.tsx +3 -2
  215. package/src/execution/ArtifactContentBody.tsx +2 -1
  216. package/src/execution/ArtifactsWidget.tsx +2 -1
  217. package/src/execution/FileChangeProgressBar.tsx +2 -1
  218. package/src/execution/TodoList.tsx +2 -1
  219. package/src/execution/WriteBacksWidget.tsx +2 -1
  220. package/src/execution/tool-categories.ts +17 -41
  221. package/src/iam-policy/PeopleWithAccess.tsx +2 -1
  222. package/src/iam-policy/PrincipalPicker.tsx +2 -0
  223. package/src/iam-policy/RoleSelector.tsx +2 -1
  224. package/src/identity-provider/CreateIdentityProviderForm.tsx +2 -1
  225. package/src/identity-provider/IdentityProviderDetailPanel.tsx +2 -1
  226. package/src/identity-provider/IdentityProviderWizard.tsx +2 -1
  227. package/src/index.ts +13 -0
  228. package/src/internal/element-resets.ts +33 -0
  229. package/src/internal/file-tree/FileTreeNode.tsx +2 -1
  230. package/src/internal/markdown-components.tsx +15 -5
  231. package/src/invitation/InvitationManager.tsx +3 -2
  232. package/src/manifest/ApplyManifestDialog.tsx +2 -1
  233. package/src/mcp-server/steps/IdentityTransportStep.tsx +3 -2
  234. package/src/platform-client/CreatePlatformClientForm.tsx +4 -3
  235. package/src/platform-client/PlatformClientDetailPanel.tsx +4 -3
  236. package/src/pricing-governance/BaselineEditor.tsx +3 -2
  237. package/src/pricing-governance/PricingGovernanceConsole.tsx +2 -1
  238. package/src/provider-standing/ProviderStandingAccessNotice.tsx +36 -0
  239. package/src/provider-standing/ProviderStandingConsole.tsx +216 -0
  240. package/src/provider-standing/__tests__/ProviderStandingConsole.test.tsx +143 -0
  241. package/src/provider-standing/index.ts +8 -0
  242. package/src/provider-standing/useProviderStanding.ts +50 -0
  243. package/src/provider.tsx +12 -0
  244. package/src/resource-creation/StepIndicator.tsx +2 -1
  245. package/src/schedule/ScheduleDetailView.tsx +3 -2
  246. package/src/schedule/ScheduleRunsTable.tsx +2 -1
  247. package/src/session/__tests__/UsageTab.test.tsx +136 -0
  248. package/src/session/__tests__/useSessionUsage.test.ts +19 -0
  249. package/src/session/facets/ArtifactsTab.tsx +2 -1
  250. package/src/session/facets/ChangesTab.tsx +2 -1
  251. package/src/session/facets/UsageTab.tsx +98 -6
  252. package/src/session/index.ts +1 -0
  253. package/src/session/useSessionUsage.ts +37 -0
  254. package/src/settings/__tests__/useSettingsNavGroups.test.tsx +11 -1
  255. package/src/settings/settings-nav.ts +7 -0
  256. package/src/settings/useSettingsNavGroups.ts +8 -1
  257. package/src/sharing/ShareAgentDialog.tsx +2 -1
  258. package/src/sidebar/WorkspaceSidebar.tsx +2 -1
  259. package/src/skill/SkillFileBrowser.tsx +2 -1
  260. package/src/skill/__tests__/SkillFileBrowser.test.tsx +10 -2
  261. package/src/skill/__tests__/fetchAndUnpackArtifact.test.ts +90 -0
  262. package/src/skill/__tests__/useSkillUpload.test.ts +69 -0
  263. package/src/skill/internal/fetchAndUnpackArtifact.ts +54 -4
  264. package/src/skill/useSkillDiff.ts +7 -1
  265. package/src/skill/useSkillUpload.ts +25 -6
  266. package/src/workflow/FailedRunsWidget.tsx +2 -1
  267. package/src/workflow/PendingApprovalsWidget.tsx +2 -1
  268. package/src/workflow/facets/WorkflowArtifactsTab.tsx +2 -1
  269. package/src/workflow/facets/WorkflowUsageTab.tsx +2 -1
  270. package/src/workflow/inspector/WorkflowSummaryPanel.tsx +2 -1
  271. package/src/workflow/inspector/tabs/BranchesTab.tsx +2 -1
  272. package/src/workflow/inspector/tabs/IterationTab.tsx +2 -1
  273. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +2 -1
  274. package/src/workspace/ExplorerTree.tsx +2 -1
  275. package/src/workspace/WorkspaceContentSearch.tsx +2 -1
  276. package/src/workspace/WorkspaceFileSearch.tsx +2 -1
  277. package/src/workspace/WorkspaceSummary.tsx +2 -1
  278. package/styles.css +1 -1
  279. package/workflow/FailedRunsWidget.d.ts.map +1 -1
  280. package/workflow/FailedRunsWidget.js +2 -1
  281. package/workflow/FailedRunsWidget.js.map +1 -1
  282. package/workflow/PendingApprovalsWidget.d.ts.map +1 -1
  283. package/workflow/PendingApprovalsWidget.js +2 -1
  284. package/workflow/PendingApprovalsWidget.js.map +1 -1
  285. package/workflow/facets/WorkflowArtifactsTab.d.ts.map +1 -1
  286. package/workflow/facets/WorkflowArtifactsTab.js +2 -1
  287. package/workflow/facets/WorkflowArtifactsTab.js.map +1 -1
  288. package/workflow/facets/WorkflowUsageTab.d.ts.map +1 -1
  289. package/workflow/facets/WorkflowUsageTab.js +2 -1
  290. package/workflow/facets/WorkflowUsageTab.js.map +1 -1
  291. package/workflow/inspector/WorkflowSummaryPanel.d.ts.map +1 -1
  292. package/workflow/inspector/WorkflowSummaryPanel.js +2 -1
  293. package/workflow/inspector/WorkflowSummaryPanel.js.map +1 -1
  294. package/workflow/inspector/tabs/BranchesTab.d.ts.map +1 -1
  295. package/workflow/inspector/tabs/BranchesTab.js +2 -1
  296. package/workflow/inspector/tabs/BranchesTab.js.map +1 -1
  297. package/workflow/inspector/tabs/IterationTab.d.ts.map +1 -1
  298. package/workflow/inspector/tabs/IterationTab.js +2 -1
  299. package/workflow/inspector/tabs/IterationTab.js.map +1 -1
  300. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  301. package/workflow/instance/WorkflowInstanceDetailPanel.js +2 -1
  302. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  303. package/workspace/ExplorerTree.d.ts.map +1 -1
  304. package/workspace/ExplorerTree.js +2 -1
  305. package/workspace/ExplorerTree.js.map +1 -1
  306. package/workspace/WorkspaceContentSearch.d.ts.map +1 -1
  307. package/workspace/WorkspaceContentSearch.js +2 -1
  308. package/workspace/WorkspaceContentSearch.js.map +1 -1
  309. package/workspace/WorkspaceFileSearch.d.ts.map +1 -1
  310. package/workspace/WorkspaceFileSearch.js +2 -1
  311. package/workspace/WorkspaceFileSearch.js.map +1 -1
  312. package/workspace/WorkspaceSummary.d.ts.map +1 -1
  313. package/workspace/WorkspaceSummary.js +2 -1
  314. package/workspace/WorkspaceSummary.js.map +1 -1
@@ -7,6 +7,7 @@ import {
7
7
  artifactKey,
8
8
  type SessionArtifactEntry,
9
9
  } from "../useSessionArtifacts.js";
10
+ import { UNSTYLED_LIST } from "../../internal/element-resets.js";
10
11
  import { ArtifactPreviewModal } from "../../execution/ArtifactPreviewModal.js";
11
12
  import { ArtifactRow } from "../../execution/ArtifactRow.js";
12
13
  import { isPlanArtifact } from "../../library/detect-plan-artifact.js";
@@ -123,7 +124,7 @@ export function ArtifactsTab({
123
124
 
124
125
  return (
125
126
  <>
126
- <ul role="list" className="stg:flex stg:flex-col">
127
+ <ul role="list" className={`${UNSTYLED_LIST} stg:flex stg:flex-col`}>
127
128
  {artifacts.map((entry) => (
128
129
  <ArtifactRow
129
130
  key={artifactKey(entry.artifact)}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
4
+ import { UNSTYLED_LIST } from "../../internal/element-resets.js";
4
5
  import { useSessionWriteBacks } from "../useSessionWriteBacks.js";
5
6
  import { WriteBackCard } from "../../execution/WriteBackCard.js";
6
7
 
@@ -48,7 +49,7 @@ export function ChangesTab({
48
49
  // Dense row groups matching the Artifacts facet's list — gap separates
49
50
  // entries only in multi-repo sessions (single-entry lists stay seamless).
50
51
  return (
51
- <ul role="list" className="stg:flex stg:flex-col stg:gap-3">
52
+ <ul role="list" className={`${UNSTYLED_LIST} stg:flex stg:flex-col stg:gap-3`}>
52
53
  {writeBacks.map((entry) => (
53
54
  <li key={entry.writeBack.workspaceEntryName}>
54
55
  <WriteBackCard writeBack={entry.writeBack} />
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
4
- import { useSessionUsage } from "../useSessionUsage.js";
5
- import { UsageWidget } from "../../execution/UsageWidget.js";
4
+ import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
5
+ import { useSessionUsage, type ExecutionUsageEntry } from "../useSessionUsage.js";
6
+ import { UsageWidget, formatCost } from "../../execution/UsageWidget.js";
6
7
 
7
8
  export interface UsageTabProps {
8
9
  readonly executions: readonly AgentExecution[];
@@ -11,9 +12,16 @@ export interface UsageTabProps {
11
12
  /**
12
13
  * Usage facet for the session panel (a `useSessionRailViews` rail view).
13
14
  *
14
- * Uses `useSessionUsage` to determine if data exists, then delegates
15
- * rendering to the existing `UsageWidget`. Shows an empty state when
16
- * no usage data is available.
15
+ * Renders the session totals ({@link UsageWidget}) plus the #362 model
16
+ * provenance list: per execution, the billing-RESOLVED model with its
17
+ * cost for a Cursor Auto run, the only honest answer to "which model
18
+ * actually ran and what did it cost", since the requested model is empty
19
+ * by definition there — paired with the tier the runner REQUESTED (the
20
+ * streaming summary's audit record that the account default was never
21
+ * left in control). Requested-vs-billed divergence is a platform alarm
22
+ * (`stigmer.billing.service_tier.mismatch`), not a per-row UI concern.
23
+ *
24
+ * Shows an empty state when no usage data is available.
17
25
  */
18
26
  export function UsageTab({ executions }: UsageTabProps) {
19
27
  const usage = useSessionUsage(executions);
@@ -28,5 +36,89 @@ export function UsageTab({ executions }: UsageTabProps) {
28
36
  );
29
37
  }
30
38
 
31
- return <UsageWidget executions={executions} />;
39
+ return (
40
+ <div className="stg:flex stg:flex-col stg:gap-4">
41
+ <UsageWidget executions={executions} />
42
+ {usage.executionBreakdown.length > 0 && (
43
+ <ExecutionModelList
44
+ entries={usage.executionBreakdown}
45
+ executions={executions}
46
+ />
47
+ )}
48
+ </div>
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Per-execution model provenance rows, in the report's chronological
54
+ * order: `#N resolved-model [tier requested] [Estimated] $cost`.
55
+ */
56
+ function ExecutionModelList({
57
+ entries,
58
+ executions,
59
+ }: {
60
+ readonly entries: readonly ExecutionUsageEntry[];
61
+ readonly executions: readonly AgentExecution[];
62
+ }) {
63
+ return (
64
+ <div
65
+ className="stg:flex stg:flex-col stg:gap-1"
66
+ role="list"
67
+ aria-label="Per-execution model and tier"
68
+ >
69
+ <div className="stg:text-xs stg:font-medium stg:text-foreground">
70
+ Models per run
71
+ </div>
72
+ {entries.map((entry, index) => {
73
+ const requestedTier = requestedTierLabel(executions, entry.executionId);
74
+ return (
75
+ <div
76
+ key={entry.executionId}
77
+ className="stg:flex stg:items-baseline stg:justify-between stg:gap-2 stg:text-xs stg:text-muted-foreground"
78
+ role="listitem"
79
+ >
80
+ <span className="stg:truncate">
81
+ <span className="stg:tabular-nums">#{index + 1}</span>{" "}
82
+ {entry.resolvedModel || "—"}
83
+ {requestedTier && (
84
+ <span className="stg:ml-1 stg:rounded stg:bg-muted stg:px-1 stg:py-0.5 stg:text-[10px] stg:font-medium stg:leading-none">
85
+ {requestedTier} requested
86
+ </span>
87
+ )}
88
+ {entry.isEstimated && (
89
+ <span className="stg:ml-1 stg:rounded stg:bg-muted stg:px-1 stg:py-0.5 stg:text-[10px] stg:font-medium stg:leading-none">
90
+ Estimated
91
+ </span>
92
+ )}
93
+ </span>
94
+ <span className="stg:shrink-0 stg:tabular-nums">
95
+ {formatCost(entry.billableCostUsd)}
96
+ </span>
97
+ </div>
98
+ );
99
+ })}
100
+ </div>
101
+ );
102
+ }
103
+
104
+ /**
105
+ * The tier the runner requested for one execution, from its streaming
106
+ * usage summary — present once the runner has translated the execution
107
+ * config, absent for executions that predate the tier attribute. Never
108
+ * "unspecified": the runner records the RESOLVED tier (#357's audit
109
+ * contract).
110
+ */
111
+ function requestedTierLabel(
112
+ executions: readonly AgentExecution[],
113
+ executionId: string,
114
+ ): string | null {
115
+ const match = executions.find((e) => e.metadata?.id === executionId);
116
+ switch (match?.status?.streamingUsage?.requestedServiceTier) {
117
+ case ServiceTier.FAST:
118
+ return "fast";
119
+ case ServiceTier.STANDARD:
120
+ return "standard";
121
+ default:
122
+ return null;
123
+ }
32
124
  }
@@ -58,6 +58,7 @@ export type { UseSessionFileChangesReturn } from "./useSessionFileChanges.js";
58
58
 
59
59
  export { useSessionUsage } from "./useSessionUsage.js";
60
60
  export type {
61
+ ExecutionUsageEntry,
61
62
  ModelCostEntry,
62
63
  UseSessionUsageReturn,
63
64
  } from "./useSessionUsage.js";
@@ -41,6 +41,25 @@ export interface ModelCostEntry {
41
41
  readonly callCount: number;
42
42
  }
43
43
 
44
+ /**
45
+ * Per-execution model provenance from billing records (stigmer/stigmer#362).
46
+ */
47
+ export interface ExecutionUsageEntry {
48
+ /** Execution identifier (`metadata.id`). */
49
+ readonly executionId: string;
50
+ /**
51
+ * Billing-resolved primary model — what the provider actually served.
52
+ * For a Cursor Auto execution this is the after-the-fact answer to
53
+ * "which model did Auto pick", which the requested model (empty by
54
+ * definition there) can never give.
55
+ */
56
+ readonly resolvedModel: string;
57
+ /** Billable cost in USD for this execution. */
58
+ readonly billableCostUsd: number;
59
+ /** `true` while this execution's records are still provisional. */
60
+ readonly isEstimated: boolean;
61
+ }
62
+
44
63
  /** Return value of {@link useSessionUsage}. */
45
64
  export interface UseSessionUsageReturn {
46
65
  /** Aggregate estimated cost in USD across all models. */
@@ -59,6 +78,13 @@ export interface UseSessionUsageReturn {
59
78
  readonly llmCallCount: number;
60
79
  /** Per-model cost and token breakdown. */
61
80
  readonly modelBreakdown: readonly ModelCostEntry[];
81
+ /**
82
+ * Per-execution resolved model + cost, chronological, from billing
83
+ * records (#362). Empty until the first authoritative record lands —
84
+ * the streaming fallback deliberately contributes none, because the
85
+ * runner only knows what it REQUESTED, never what the provider served.
86
+ */
87
+ readonly executionBreakdown: readonly ExecutionUsageEntry[];
62
88
  /** Model with the highest usage in this session. */
63
89
  readonly primaryModel: string;
64
90
  /** Provider of the primary model. */
@@ -82,6 +108,7 @@ const EMPTY: UseSessionUsageReturn = {
82
108
  cacheCreationTokens: 0,
83
109
  llmCallCount: 0,
84
110
  modelBreakdown: [],
111
+ executionBreakdown: [],
85
112
  primaryModel: "",
86
113
  primaryProvider: "",
87
114
  hasUsage: false,
@@ -122,6 +149,12 @@ function mapReport(report: GetSessionUsageReportOutput): UseSessionUsageReturn {
122
149
  cacheCreationTokens: Number(agg.cacheCreationInputTokens),
123
150
  llmCallCount,
124
151
  modelBreakdown: report.modelBreakdown.map(mapModelUsage),
152
+ executionBreakdown: report.executions.map((e) => ({
153
+ executionId: e.executionId,
154
+ resolvedModel: e.primaryModel,
155
+ billableCostUsd: microsToUsd(e.billableCostMicros),
156
+ isEstimated: e.isEstimated,
157
+ })),
125
158
  primaryModel: agg.primaryModel,
126
159
  primaryProvider: agg.primaryProvider,
127
160
  hasUsage: llmCallCount > 0 || totalTokens > 0 || billableCost > 0,
@@ -169,6 +202,10 @@ function aggregateStreamingUsage(
169
202
  cacheReadTokens,
170
203
  cacheCreationTokens,
171
204
  llmCallCount: turnCount,
205
+ // No executionBreakdown here on purpose: the runner only knows the
206
+ // REQUESTED model; resolved-model rows exist only once billing
207
+ // records land (#362).
208
+ executionBreakdown: [],
172
209
  modelBreakdown: model
173
210
  ? [
174
211
  {
@@ -48,6 +48,7 @@ describe("useSettingsNavGroups", () => {
48
48
  verdicts = {
49
49
  can_manage_model_pricing: true,
50
50
  can_manage_cursor_accounts: true,
51
+ can_view_provider_standing: true,
51
52
  };
52
53
  render(<GroupsProbe />);
53
54
 
@@ -56,7 +57,7 @@ describe("useSettingsNavGroups", () => {
56
57
  `${BASE_LABELS},${PLATFORM_SETTINGS_NAV_GROUP.label}`,
57
58
  );
58
59
  expect(el.getAttribute("data-platform-items")).toBe(
59
- "Pricing Governance,Cursor Accounts",
60
+ "Pricing Governance,Cursor Accounts,Provider Standing",
60
61
  );
61
62
  });
62
63
 
@@ -78,6 +79,15 @@ describe("useSettingsNavGroups", () => {
78
79
  );
79
80
  });
80
81
 
82
+ it("filters per item: standing-only operator sees only Provider Standing", () => {
83
+ verdicts = { can_view_provider_standing: true };
84
+ render(<GroupsProbe />);
85
+
86
+ expect(screen.getByTestId("groups").getAttribute("data-platform-items")).toBe(
87
+ "Provider Standing",
88
+ );
89
+ });
90
+
81
91
  it("returns exactly the base groups when not authorized", () => {
82
92
  function IdentityProbe() {
83
93
  const groups = useSettingsNavGroups();
@@ -1,5 +1,6 @@
1
1
  import type { ComponentType } from "react";
2
2
  import {
3
+ Activity,
3
4
  AppWindow,
4
5
  BarChart3,
5
6
  Box,
@@ -123,5 +124,11 @@ export const PLATFORM_SETTINGS_NAV_GROUP: SettingsNavGroup = {
123
124
  icon: MousePointerClick,
124
125
  requiredPermission: "can_manage_cursor_accounts",
125
126
  },
127
+ {
128
+ href: "/settings/provider-standing",
129
+ label: "Provider Standing",
130
+ icon: Activity,
131
+ requiredPermission: "can_view_provider_standing",
132
+ },
126
133
  ],
127
134
  };
@@ -52,14 +52,21 @@ export function useSettingsNavGroups(): readonly SettingsNavGroup[] {
52
52
  "can_manage_cursor_accounts",
53
53
  { fail: "closed" },
54
54
  );
55
+ const providerStanding = useCheckPermission(
56
+ PLATFORM_RESOURCE,
57
+ "can_view_provider_standing",
58
+ { fail: "closed" },
59
+ );
55
60
 
56
61
  const pricingAllowed = pricing.allowed;
57
62
  const cursorAccountsAllowed = cursorAccounts.allowed;
63
+ const providerStandingAllowed = providerStanding.allowed;
58
64
 
59
65
  return useMemo(() => {
60
66
  const verdicts: Record<string, boolean> = {
61
67
  can_manage_model_pricing: pricingAllowed,
62
68
  can_manage_cursor_accounts: cursorAccountsAllowed,
69
+ can_view_provider_standing: providerStandingAllowed,
63
70
  };
64
71
 
65
72
  const visibleItems = PLATFORM_SETTINGS_NAV_GROUP.items.filter(
@@ -77,5 +84,5 @@ export function useSettingsNavGroups(): readonly SettingsNavGroup[] {
77
84
  ...SETTINGS_NAV_GROUPS,
78
85
  { ...PLATFORM_SETTINGS_NAV_GROUP, items: visibleItems },
79
86
  ];
80
- }, [pricingAllowed, cursorAccountsAllowed]);
87
+ }, [pricingAllowed, cursorAccountsAllowed, providerStandingAllowed]);
81
88
  }
@@ -3,6 +3,7 @@
3
3
  import { useCallback, useId, useMemo, useRef, useState, type FormEvent } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { DialogShell } from "../internal/DialogShell.js";
6
+ import { UNSTYLED_LIST } from "../internal/element-resets.js";
6
7
  import {
7
8
  MAX_ALLOWED_ORIGINS,
8
9
  StigmerError,
@@ -1316,7 +1317,7 @@ function OriginsEditor({
1316
1317
  </p>
1317
1318
 
1318
1319
  {draft.allowedOrigins.length > 0 && (
1319
- <ul className="stg:mt-2 stg:flex stg:flex-col stg:divide-y stg:divide-border stg:rounded-md stg:border stg:border-border">
1320
+ <ul className={cn(UNSTYLED_LIST, "stg:mt-2 stg:flex stg:flex-col stg:divide-y stg:divide-border stg:rounded-md stg:border stg:border-border")}>
1320
1321
  {draft.allowedOrigins.map((origin) => (
1321
1322
  <li
1322
1323
  key={origin}
@@ -15,6 +15,7 @@ import type { RecentActivityEntry, RecentActivityGroup } from "../activity/types
15
15
  import { groupRecentActivityByTime } from "../activity/group-activity.js";
16
16
  import { formatRelativeTime } from "../activity/format-relative-time.js";
17
17
  import { recentActivityStatusBadge } from "../activity/entry-status-badge.js";
18
+ import { UNSTYLED_LIST } from "../internal/element-resets.js";
18
19
  import { ScrollArea } from "../internal/scroll-area.js";
19
20
  import {
20
21
  Tooltip,
@@ -313,7 +314,7 @@ function ActivityGroupList({
313
314
  <p className="stg:text-sidebar-muted-foreground stg:mb-1 stg:px-2 stg:text-[10px] stg:font-medium stg:tracking-wider stg:uppercase">
314
315
  {group.label}
315
316
  </p>
316
- <ul className="stg:space-y-0.5" role="list">
317
+ <ul className={`${UNSTYLED_LIST} stg:space-y-0.5`} role="list">
317
318
  {group.entries.map((entry) => (
318
319
  <ActivityEntry
319
320
  key={entry.id}
@@ -3,6 +3,7 @@
3
3
  import { useMemo, useState } from "react";
4
4
  import Markdown from "react-markdown";
5
5
  import { cn } from "@stigmer/theme";
6
+ import { UNSTYLED_LIST } from "../internal/element-resets.js";
6
7
  import { MARKDOWN_COMPONENTS, REMARK_PLUGINS, stripFrontmatter } from "../internal/markdown-components.js";
7
8
  import { buildFileTree, FileTreeNode } from "../internal/file-tree/index.js";
8
9
  import { useSkillArtifact } from "./useSkillArtifact.js";
@@ -132,7 +133,7 @@ export function SkillFileBrowser({
132
133
  className="stg:border-b stg:border-border stg:md:border-b-0 stg:md:border-r stg:overflow-y-auto"
133
134
  aria-label="Skill package file tree"
134
135
  >
135
- <ul className="stg:py-1" role="tree">
136
+ <ul className={cn(UNSTYLED_LIST, "stg:py-1")} role="tree">
136
137
  {treeNodes.map((node) => (
137
138
  <FileTreeNode
138
139
  key={node.path}
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, afterEach } from "vitest";
2
2
  import { render, screen, fireEvent } from "@testing-library/react";
3
3
  import type { ReactNode } from "react";
4
4
  import { zipSync, strToU8 } from "fflate";
5
- import type { Stigmer } from "@stigmer/sdk";
5
+ import { StigmerError, type Stigmer } from "@stigmer/sdk";
6
6
  import { StigmerContext } from "../../context";
7
7
  import { SkillFileBrowser } from "../SkillFileBrowser";
8
8
 
@@ -12,14 +12,22 @@ function wrapperFor(stigmer: Stigmer) {
12
12
  );
13
13
  }
14
14
 
15
+ /** Connect's Unimplemented code — what a pre-transfer-lane server answers. */
16
+ const CODE_UNIMPLEMENTED = 12;
17
+
15
18
  /**
16
19
  * Builds a real ZIP (via fflate, the same library the unpacker uses) and a
17
20
  * Stigmer client whose `skill.getArtifact` serves it — so tests exercise the
18
21
  * genuine fetch → unzip → browse path instead of stubbing the unpacker.
22
+ * The download-URL mint answers Unimplemented (a pre-transfer-lane server),
23
+ * pinning that the browser degrades to the unary lane it always used.
19
24
  */
20
25
  function makeStigmerServing(files: Record<string, Uint8Array>) {
21
26
  const getArtifact = vi.fn().mockResolvedValue({ artifact: zipSync(files) });
22
- return { skill: { getArtifact } } as unknown as Stigmer;
27
+ const getArtifactDownloadUrl = vi
28
+ .fn()
29
+ .mockRejectedValue(new StigmerError("unknown", "unimplemented", CODE_UNIMPLEMENTED));
30
+ return { skill: { getArtifact, getArtifactDownloadUrl } } as unknown as Stigmer;
23
31
  }
24
32
 
25
33
  function renderBrowser(stigmer: Stigmer) {
@@ -0,0 +1,90 @@
1
+ // Pins for the artifact fetch lane selection (stigmer#701): URL-first via
2
+ // getArtifactDownloadUrl, with the two deliberate browser degrade arms —
3
+ // Unimplemented mint (pre-lane server) and a failed URL fetch (the shape a
4
+ // missing bucket CORS policy takes in a browser).
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import { strToU8, zipSync } from "fflate";
7
+ import { StigmerError, type Stigmer } from "@stigmer/sdk";
8
+ import { fetchAndUnpackArtifact } from "../internal/fetchAndUnpackArtifact.js";
9
+
10
+ const CODE_UNIMPLEMENTED = 12;
11
+ const ZIP = zipSync({ "SKILL.md": strToU8("# hi") });
12
+
13
+ function stigmerWith(overrides: {
14
+ mint?: ReturnType<typeof vi.fn>;
15
+ unary?: ReturnType<typeof vi.fn>;
16
+ fetch?: unknown;
17
+ }): Stigmer {
18
+ return {
19
+ skill: {
20
+ getArtifactDownloadUrl: overrides.mint,
21
+ getArtifact: overrides.unary,
22
+ },
23
+ fetch: overrides.fetch,
24
+ } as unknown as Stigmer;
25
+ }
26
+
27
+ describe("fetchAndUnpackArtifact lane selection", () => {
28
+ it("fetches via the minted download URL, never the unary lane", async () => {
29
+ const mint = vi.fn().mockResolvedValue({ url: "https://r2.example/skills/get" });
30
+ const unary = vi.fn();
31
+ const fetchImpl = vi.fn(async () => new Response(new Uint8Array(ZIP).buffer, { status: 200 }));
32
+
33
+ const result = await fetchAndUnpackArtifact(
34
+ stigmerWith({ mint, unary, fetch: fetchImpl }),
35
+ "skills/org/skill/hash.zip",
36
+ );
37
+
38
+ expect(fetchImpl).toHaveBeenCalledWith("https://r2.example/skills/get");
39
+ expect(unary).not.toHaveBeenCalled();
40
+ expect(result.contentMap.get("SKILL.md")).toBe("# hi");
41
+ });
42
+
43
+ it("degrades to the unary lane when the mint answers Unimplemented (pre-lane server)", async () => {
44
+ const mint = vi
45
+ .fn()
46
+ .mockRejectedValue(new StigmerError("unknown", "unimplemented", CODE_UNIMPLEMENTED));
47
+ const unary = vi.fn().mockResolvedValue({ artifact: ZIP });
48
+
49
+ const result = await fetchAndUnpackArtifact(
50
+ stigmerWith({ mint, unary }),
51
+ "skills/org/skill/hash.zip",
52
+ );
53
+
54
+ expect(unary).toHaveBeenCalledTimes(1);
55
+ expect(result.contentMap.get("SKILL.md")).toBe("# hi");
56
+ });
57
+
58
+ it("degrades to the unary lane when the URL fetch itself fails (browser CORS shape), warning once", async () => {
59
+ const mint = vi.fn().mockResolvedValue({ url: "https://r2.example/skills/get" });
60
+ const unary = vi.fn().mockResolvedValue({ artifact: ZIP });
61
+ const fetchImpl = vi.fn(async () => {
62
+ throw new TypeError("Failed to fetch");
63
+ });
64
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
65
+
66
+ try {
67
+ const result = await fetchAndUnpackArtifact(
68
+ stigmerWith({ mint, unary, fetch: fetchImpl }),
69
+ "skills/org/skill/hash.zip",
70
+ );
71
+ expect(unary).toHaveBeenCalledTimes(1);
72
+ expect(warn).toHaveBeenCalledTimes(1);
73
+ expect(result.contentMap.get("SKILL.md")).toBe("# hi");
74
+ } finally {
75
+ warn.mockRestore();
76
+ }
77
+ });
78
+
79
+ it("propagates a non-Unimplemented mint failure — a real error must not be masked", async () => {
80
+ const mint = vi
81
+ .fn()
82
+ .mockRejectedValue(new StigmerError("not-found", "no such artifact", 5));
83
+ const unary = vi.fn();
84
+
85
+ await expect(
86
+ fetchAndUnpackArtifact(stigmerWith({ mint, unary }), "skills/org/skill/hash.zip"),
87
+ ).rejects.toThrow(/no such artifact/);
88
+ expect(unary).not.toHaveBeenCalled();
89
+ });
90
+ });
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { zipSync, strToU8 } from "fflate";
4
+ import { useSkillUpload } from "../useSkillUpload";
5
+
6
+ const SKILL_MD = "---\nname: my-skill\ndescription: A test skill\n---\n# My Skill\n";
7
+
8
+ /** Build a real ZIP (fflate, the same library the hook unpacks with) as a File. */
9
+ function zipFile(files: Record<string, Uint8Array>): File {
10
+ return new File([zipSync(files)], "skill.zip", { type: "application/zip" });
11
+ }
12
+
13
+ async function process(files: Record<string, Uint8Array>) {
14
+ const { result } = renderHook(() => useSkillUpload());
15
+ await act(() => result.current.processFile(zipFile(files)));
16
+ return result;
17
+ }
18
+
19
+ describe("useSkillUpload", () => {
20
+ it("accepts a root SKILL.md and produces a preview", async () => {
21
+ const result = await process({
22
+ "SKILL.md": strToU8(SKILL_MD),
23
+ "references/schema.md": strToU8("# Schema"),
24
+ });
25
+
26
+ expect(result.current.validationError).toBeNull();
27
+ expect(result.current.preview?.name).toBe("my-skill");
28
+ expect(result.current.preview?.description).toBe("A test skill");
29
+ expect(result.current.artifact).not.toBeNull();
30
+ });
31
+
32
+ // The layout contract is root-only on both editions (DD-018, #452). The
33
+ // preview exists to catch the "zipped the folder instead of its contents"
34
+ // mistake BEFORE upload — accepting nested here and failing at push is the
35
+ // exact confusion issue #684 pins.
36
+ it("rejects a nested-only SKILL.md with the zip-the-contents hint", async () => {
37
+ const result = await process({
38
+ "my-skill/SKILL.md": strToU8(SKILL_MD),
39
+ "my-skill/references/schema.md": strToU8("# Schema"),
40
+ });
41
+
42
+ expect(result.current.preview).toBeNull();
43
+ expect(result.current.artifact).toBeNull();
44
+ expect(result.current.validationError).toBe(
45
+ "Found my-skill/SKILL.md — SKILL.md must be at the archive root. " +
46
+ "Zip the skill folder's contents, not the folder itself.",
47
+ );
48
+ });
49
+
50
+ it("detects a deeply nested SKILL.md for the hint (any depth, server parity)", async () => {
51
+ const result = await process({
52
+ "wrapper/my-skill/SKILL.md": strToU8(SKILL_MD),
53
+ });
54
+
55
+ expect(result.current.preview).toBeNull();
56
+ expect(result.current.validationError).toContain("Found wrapper/my-skill/SKILL.md");
57
+ });
58
+
59
+ it("rejects an archive with no SKILL.md anywhere with the plain root-level message", async () => {
60
+ const result = await process({
61
+ "README.md": strToU8("# Not a skill"),
62
+ });
63
+
64
+ expect(result.current.preview).toBeNull();
65
+ expect(result.current.validationError).toBe(
66
+ "ZIP must contain a SKILL.md file at the root level",
67
+ );
68
+ });
69
+ });
@@ -1,6 +1,6 @@
1
1
  import { create } from "@bufbuild/protobuf";
2
2
  import { GetArtifactRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
3
- import type { Stigmer } from "@stigmer/sdk";
3
+ import { isUnimplemented, type Stigmer } from "@stigmer/sdk";
4
4
  import type { SkillFileEntry } from "../useSkillUpload.js";
5
5
 
6
6
  /** Unpacked artifact: file entries for browsing + content map for reading. */
@@ -20,11 +20,10 @@ export async function fetchAndUnpackArtifact(
20
20
  stigmer: Stigmer,
21
21
  artifactStorageKey: string,
22
22
  ): Promise<UnpackedArtifact> {
23
- const request = create(GetArtifactRequestSchema, { artifactStorageKey });
24
- const response = await stigmer.skill.getArtifact(request);
23
+ const artifact = await fetchArtifactBytes(stigmer, artifactStorageKey);
25
24
 
26
25
  const { unzipSync, strFromU8 } = await import("fflate");
27
- const unzipped = unzipSync(response.artifact);
26
+ const unzipped = unzipSync(artifact);
28
27
 
29
28
  const entries = Object.entries(unzipped);
30
29
  const files: SkillFileEntry[] = entries.map(([path, data]) => ({
@@ -49,3 +48,54 @@ export async function fetchAndUnpackArtifact(
49
48
 
50
49
  return { files, contentMap };
51
50
  }
51
+
52
+ /**
53
+ * Fetch the artifact ZIP bytes, URL-first (the transfer lane, stigmer#675):
54
+ * the bytes ride HTTP, so any valid skill (up to the 100MB limit) is
55
+ * fetchable — the unary `getArtifact` RPC is capped at the server's 10MB
56
+ * gRPC message limit.
57
+ *
58
+ * Fallbacks to the unary lane, deliberately wider than the runner's
59
+ * mint-Unimplemented-only contract because this code runs in a browser:
60
+ *
61
+ * - `Unimplemented` from the mint — a pre-transfer-lane server (the code
62
+ * clients key their fallback on, same as every other lane client).
63
+ * - A failed HTTP fetch of the minted URL — in a browser this is how a
64
+ * missing bucket CORS policy surfaces (an opaque `TypeError`,
65
+ * indistinguishable from a network failure). The unary lane was the
66
+ * status quo and still serves everything under the message cap, so
67
+ * degrading beats breaking the console; the warn keeps it observable.
68
+ */
69
+ async function fetchArtifactBytes(
70
+ stigmer: Stigmer,
71
+ artifactStorageKey: string,
72
+ ): Promise<Uint8Array> {
73
+ const request = create(GetArtifactRequestSchema, { artifactStorageKey });
74
+
75
+ let minted;
76
+ try {
77
+ minted = await stigmer.skill.getArtifactDownloadUrl(request);
78
+ } catch (err) {
79
+ if (isUnimplemented(err)) {
80
+ const response = await stigmer.skill.getArtifact(request);
81
+ return response.artifact;
82
+ }
83
+ throw err;
84
+ }
85
+
86
+ try {
87
+ const doFetch = stigmer.fetch ?? globalThis.fetch;
88
+ const resp = await doFetch(minted.url);
89
+ if (!resp.ok) {
90
+ throw new Error(`artifact download failed with HTTP ${resp.status}`);
91
+ }
92
+ return new Uint8Array(await resp.arrayBuffer());
93
+ } catch (err) {
94
+ console.warn(
95
+ "[stigmer] skill artifact download URL fetch failed (bucket CORS not provisioned for this origin?) — falling back to the unary lane",
96
+ err,
97
+ );
98
+ const response = await stigmer.skill.getArtifact(request);
99
+ return response.artifact;
100
+ }
101
+ }
@@ -21,7 +21,13 @@ export interface UseSkillDiffReturn {
21
21
  readonly error: Error | null;
22
22
  }
23
23
 
24
- /** Maximum artifact size (10 MB) to prevent excessive memory usage. */
24
+ /**
25
+ * Maximum COMBINED unpacked text of both artifacts (10 MB) — a browser
26
+ * memory guard on holding and diffing two extracted file trees, NOT a
27
+ * transport limit. The transfer lane (stigmer#675) removed the download
28
+ * cap, so arbitrarily large valid skills can be *fetched*; what stays
29
+ * bounded is what this hook is willing to diff in tab memory.
30
+ */
25
31
  const MAX_ARTIFACT_BYTES = 10 * 1024 * 1024;
26
32
 
27
33
  // ---------------------------------------------------------------------------