@stigmer/react 3.12.5 → 3.12.6
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/agent/AgentDetailView.js +2 -2
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/index.d.ts +0 -1
- package/agent/index.d.ts.map +1 -1
- package/agent/index.js +0 -1
- package/agent/index.js.map +1 -1
- package/agent/useUpdateAgent.d.ts +3 -3
- package/agent/useUpdateAgent.js +3 -3
- package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.js +5 -7
- package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
- package/agent-instance/useUpdateAgentInstance.d.ts +1 -1
- package/agent-instance/useUpdateAgentInstance.js +1 -1
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +10 -5
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/ChannelRunConfigDialog.d.ts +51 -0
- package/channel/ChannelRunConfigDialog.d.ts.map +1 -0
- package/channel/ChannelRunConfigDialog.js +121 -0
- package/channel/ChannelRunConfigDialog.js.map +1 -0
- package/channel/index.d.ts +2 -0
- package/channel/index.d.ts.map +1 -1
- package/channel/index.js +1 -0
- package/channel/index.js.map +1 -1
- package/channel/useSaveAgentChannel.d.ts.map +1 -1
- package/channel/useSaveAgentChannel.js +6 -3
- package/channel/useSaveAgentChannel.js.map +1 -1
- package/channel-app/ChannelAppDetailPanel.js +9 -9
- package/channel-app/ChannelAppDetailPanel.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +6 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +9 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +22 -3
- package/composer/SessionComposer.js.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
- package/cursor-accounts/CursorAccountsConsole.js +12 -3
- package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
- package/cursor-accounts/cursor-account-coverage.d.ts +30 -1
- package/cursor-accounts/cursor-account-coverage.d.ts.map +1 -1
- package/cursor-accounts/cursor-account-coverage.js +21 -0
- package/cursor-accounts/cursor-account-coverage.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +15 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/identity-account/AccountPreferencesPanel.d.ts +2 -1
- package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
- package/identity-account/AccountPreferencesPanel.js +33 -18
- package/identity-account/AccountPreferencesPanel.js.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.d.ts.map +1 -1
- package/identity-provider/IdentityProviderDetailPanel.js +16 -15
- package/identity-provider/IdentityProviderDetailPanel.js.map +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/mcp-server/McpServerDetailView.js +2 -2
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/mcp-server/index.d.ts +0 -1
- package/mcp-server/index.d.ts.map +1 -1
- package/mcp-server/index.js +0 -1
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/internal/definitionDrift.d.ts +14 -12
- package/mcp-server/internal/definitionDrift.d.ts.map +1 -1
- package/mcp-server/internal/definitionDrift.js +22 -19
- package/mcp-server/internal/definitionDrift.js.map +1 -1
- package/mcp-server/useUpdateMcpServer.d.ts +3 -3
- package/mcp-server/useUpdateMcpServer.js +3 -3
- package/models/ModelSelector.d.ts +33 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +37 -4
- package/models/ModelSelector.js.map +1 -1
- package/models/index.d.ts +2 -0
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -0
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +16 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +13 -0
- package/models/registry.js.map +1 -1
- package/models/thinking-mode.d.ts +27 -0
- package/models/thinking-mode.d.ts.map +1 -0
- package/models/thinking-mode.js +32 -0
- package/models/thinking-mode.js.map +1 -0
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +21 -20
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/package.json +4 -4
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +12 -13
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/schedule/ScheduleDetailView.d.ts.map +1 -1
- package/schedule/ScheduleDetailView.js +21 -10
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleForm.d.ts.map +1 -1
- package/schedule/ScheduleForm.js +13 -3
- package/schedule/ScheduleForm.js.map +1 -1
- package/session/run-config.d.ts +9 -0
- package/session/run-config.d.ts.map +1 -1
- package/session/run-config.js +5 -0
- package/session/run-config.js.map +1 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +6 -0
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts +12 -1
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +15 -24
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +14 -10
- package/session/useSessionPageFlow.js.map +1 -1
- package/session/useUpdateSession.d.ts +18 -24
- package/session/useUpdateSession.d.ts.map +1 -1
- package/session/useUpdateSession.js +18 -24
- package/session/useUpdateSession.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +2 -2
- package/src/agent/index.ts +0 -2
- package/src/agent/useUpdateAgent.ts +3 -3
- package/src/agent-instance/AgentInstanceDetailPanel.tsx +5 -7
- package/src/agent-instance/__tests__/AgentInstanceDetailPanel.test.tsx +75 -0
- package/src/agent-instance/useUpdateAgentInstance.ts +1 -1
- package/src/channel/AgentChannelsPanel.tsx +26 -1
- package/src/channel/ChannelRunConfigDialog.tsx +351 -0
- package/src/channel/__tests__/ChannelRunConfigDialog.test.tsx +210 -0
- package/src/channel/index.ts +2 -0
- package/src/channel/useSaveAgentChannel.ts +6 -3
- package/src/channel-app/ChannelAppDetailPanel.tsx +9 -9
- package/src/channel-app/__tests__/channelAppWhatsApp.test.tsx +2 -0
- package/src/channel-app/__tests__/channelAppWipeSafety.test.tsx +87 -0
- package/src/composer/ComposerToolbar.tsx +9 -0
- package/src/composer/SessionComposer.tsx +36 -2
- package/src/composer/__tests__/SessionComposer-thinkingMode.test.tsx +176 -0
- package/src/cursor-accounts/CursorAccountsConsole.tsx +40 -5
- package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +32 -0
- package/src/cursor-accounts/__tests__/cursor-account-coverage.test.ts +58 -1
- package/src/cursor-accounts/cursor-account-coverage.ts +55 -0
- package/src/execution/useCreateAgentExecution.ts +19 -0
- package/src/identity-account/AccountPreferencesPanel.tsx +154 -68
- package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +36 -2
- package/src/identity-provider/IdentityProviderDetailPanel.tsx +18 -15
- package/src/identity-provider/__tests__/IdentityProviderDetailPanel.test.tsx +80 -0
- package/src/index.ts +2 -2
- package/src/mcp-server/McpServerDetailView.tsx +2 -2
- package/src/mcp-server/index.ts +0 -2
- package/src/mcp-server/internal/definitionDrift.ts +22 -20
- package/src/mcp-server/useUpdateMcpServer.ts +3 -3
- package/src/models/ModelSelector.tsx +94 -1
- package/src/models/index.ts +2 -0
- package/src/models/registry.ts +29 -0
- package/src/models/thinking-mode.ts +48 -0
- package/src/oauth-app/OAuthAppDetailPanel.tsx +23 -21
- package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +94 -0
- package/src/platform-client/PlatformClientDetailPanel.tsx +14 -13
- package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +82 -0
- package/src/schedule/ScheduleDetailView.tsx +29 -4
- package/src/schedule/ScheduleForm.tsx +14 -1
- package/src/session/run-config.ts +16 -0
- package/src/session/useNewSessionFlow.ts +6 -0
- package/src/session/useSessionConversation.ts +32 -40
- package/src/session/useSessionPageFlow.ts +14 -11
- package/src/session/useUpdateSession.ts +18 -24
- package/src/workflow/ApprovalFormBuilder.tsx +20 -5
- package/src/workflow/WorkflowDetailView.tsx +2 -2
- package/src/workflow/__tests__/WorkflowInstanceDetailPanel.test.tsx +83 -0
- package/src/workflow/index.ts +0 -2
- package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +6 -7
- package/src/workflow/useUpdateWorkflow.ts +3 -3
- package/styles.css +1 -1
- package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
- package/workflow/ApprovalFormBuilder.js +10 -8
- package/workflow/ApprovalFormBuilder.js.map +1 -1
- package/workflow/WorkflowDetailView.js +2 -2
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/index.d.ts +0 -1
- package/workflow/index.d.ts.map +1 -1
- package/workflow/index.js +0 -1
- package/workflow/index.js.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.js +6 -7
- package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
- package/workflow/useUpdateWorkflow.d.ts +3 -3
- package/workflow/useUpdateWorkflow.js +3 -3
- package/agent/internal/agentToInput.d.ts +0 -15
- package/agent/internal/agentToInput.d.ts.map +0 -1
- package/agent/internal/agentToInput.js +0 -84
- package/agent/internal/agentToInput.js.map +0 -1
- package/mcp-server/internal/mcpServerToInput.d.ts +0 -12
- package/mcp-server/internal/mcpServerToInput.d.ts.map +0 -1
- package/mcp-server/internal/mcpServerToInput.js +0 -84
- package/mcp-server/internal/mcpServerToInput.js.map +0 -1
- package/session/session-spec-converters.d.ts +0 -24
- package/session/session-spec-converters.d.ts.map +0 -1
- package/session/session-spec-converters.js +0 -72
- package/session/session-spec-converters.js.map +0 -1
- package/src/agent/internal/__tests__/agentToInput.test.ts +0 -145
- package/src/agent/internal/agentToInput.ts +0 -102
- package/src/mcp-server/__tests__/mcpServerToInput.test.ts +0 -201
- package/src/mcp-server/internal/mcpServerToInput.ts +0 -98
- package/src/session/__tests__/session-spec-converters.test.ts +0 -183
- package/src/session/session-spec-converters.ts +0 -86
- package/src/workflow/internal/workflowToInput.ts +0 -96
- package/workflow/internal/workflowToInput.d.ts +0 -12
- package/workflow/internal/workflowToInput.d.ts.map +0 -1
- package/workflow/internal/workflowToInput.js +0 -73
- package/workflow/internal/workflowToInput.js.map +0 -1
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type FormEvent,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useId,
|
|
9
|
+
useMemo,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
4
12
|
import { cn } from "@stigmer/theme";
|
|
5
13
|
import { getUserMessage, toIdentityAccountUpdateInput } from "@stigmer/sdk";
|
|
6
14
|
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
@@ -23,7 +31,8 @@ export interface AccountPreferencesPanelProps {
|
|
|
23
31
|
|
|
24
32
|
/**
|
|
25
33
|
* Self-contained editor for the current user's declared preferences
|
|
26
|
-
* (`IdentityAccountSpec.preferences
|
|
34
|
+
* (`IdentityAccountSpec.preferences`): standing context plus the
|
|
35
|
+
* execution defaults (default harness and per-harness default models).
|
|
27
36
|
*
|
|
28
37
|
* The declared text is snapshotted into the user's own eligible agent
|
|
29
38
|
* executions and delivered to the agent as background context.
|
|
@@ -267,31 +276,60 @@ function AccountPreferencesForm({
|
|
|
267
276
|
</p>
|
|
268
277
|
</div>
|
|
269
278
|
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
label={`Default model — ${HARNESS_META.native.label}`}
|
|
282
|
-
value={defaultNativeModel}
|
|
283
|
-
onChange={setDefaultNativeModel}
|
|
279
|
+
<fieldset className="stg:space-y-2" disabled={isUpdating}>
|
|
280
|
+
<legend className="stg:text-xs stg:font-medium stg:text-foreground stg:mb-1">
|
|
281
|
+
Default harness
|
|
282
|
+
</legend>
|
|
283
|
+
<HarnessOptionRow
|
|
284
|
+
radioName={`${baseId}-default-harness`}
|
|
285
|
+
value=""
|
|
286
|
+
label="Platform default"
|
|
287
|
+
description="Stigmer picks the harness for new sessions."
|
|
288
|
+
checked={defaultHarness === ""}
|
|
289
|
+
onSelect={setDefaultHarness}
|
|
284
290
|
disabled={isUpdating}
|
|
285
291
|
/>
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
label={
|
|
290
|
-
|
|
291
|
-
|
|
292
|
+
<HarnessOptionRow
|
|
293
|
+
radioName={`${baseId}-default-harness`}
|
|
294
|
+
value="native"
|
|
295
|
+
label={HARNESS_META.native.label}
|
|
296
|
+
description={HARNESS_META.native.description}
|
|
297
|
+
checked={defaultHarness === "native"}
|
|
298
|
+
onSelect={setDefaultHarness}
|
|
292
299
|
disabled={isUpdating}
|
|
293
|
-
|
|
294
|
-
|
|
300
|
+
>
|
|
301
|
+
<DefaultModelSelect
|
|
302
|
+
id={`${baseId}-default-native-model`}
|
|
303
|
+
harness="native"
|
|
304
|
+
label="Default model"
|
|
305
|
+
value={defaultNativeModel}
|
|
306
|
+
onChange={setDefaultNativeModel}
|
|
307
|
+
disabled={isUpdating}
|
|
308
|
+
/>
|
|
309
|
+
</HarnessOptionRow>
|
|
310
|
+
<HarnessOptionRow
|
|
311
|
+
radioName={`${baseId}-default-harness`}
|
|
312
|
+
value="cursor"
|
|
313
|
+
label={HARNESS_META.cursor.label}
|
|
314
|
+
description={HARNESS_META.cursor.description}
|
|
315
|
+
checked={defaultHarness === "cursor"}
|
|
316
|
+
onSelect={setDefaultHarness}
|
|
317
|
+
disabled={isUpdating}
|
|
318
|
+
>
|
|
319
|
+
<DefaultModelSelect
|
|
320
|
+
id={`${baseId}-default-cursor-model`}
|
|
321
|
+
harness="cursor"
|
|
322
|
+
label="Default model"
|
|
323
|
+
value={defaultCursorModel}
|
|
324
|
+
onChange={setDefaultCursorModel}
|
|
325
|
+
disabled={isUpdating}
|
|
326
|
+
/>
|
|
327
|
+
</HarnessOptionRow>
|
|
328
|
+
<p className="stg:text-[0.65rem] stg:text-muted-foreground">
|
|
329
|
+
Each harness's default model applies whenever a session runs
|
|
330
|
+
on that harness — even when it isn't your default.
|
|
331
|
+
</p>
|
|
332
|
+
</fieldset>
|
|
295
333
|
</div>
|
|
296
334
|
|
|
297
335
|
{updateError && (
|
|
@@ -332,60 +370,104 @@ function AccountPreferencesForm({
|
|
|
332
370
|
}
|
|
333
371
|
|
|
334
372
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
*
|
|
342
|
-
*
|
|
373
|
+
* One selectable default-harness choice, presented as a bordered row
|
|
374
|
+
* (the `VisibilityOptionRow` presentation shape): radio + label +
|
|
375
|
+
* one-line description, with the harness's own controls nested below.
|
|
376
|
+
*
|
|
377
|
+
* The rows for shipped harnesses embed that harness's default-model
|
|
378
|
+
* select as `children` — the pairing IS the information architecture:
|
|
379
|
+
* a harness and its model default belong to each other, and each model
|
|
380
|
+
* default applies whenever a session runs on that harness, independent
|
|
381
|
+
* of which harness is the default. The non-selected harness's select
|
|
382
|
+
* therefore stays interactive (visually secondary, never disabled).
|
|
383
|
+
*
|
|
384
|
+
* The nested controls live OUTSIDE the radio's `<label>` on purpose:
|
|
385
|
+
* a `<select>` inside the label would toggle the radio on every
|
|
386
|
+
* interaction with it.
|
|
387
|
+
*
|
|
388
|
+
* The shipped-harness rows must stay in lockstep with the proto's
|
|
389
|
+
* `default_harness` in-list validation (`["native", "cursor"]`) — each
|
|
390
|
+
* shipped harness binds to its own proto model field, so a new harness
|
|
391
|
+
* adds a field, a row, and a validation entry together.
|
|
343
392
|
*/
|
|
344
|
-
function
|
|
345
|
-
|
|
393
|
+
function HarnessOptionRow({
|
|
394
|
+
radioName,
|
|
346
395
|
value,
|
|
347
|
-
|
|
396
|
+
label,
|
|
397
|
+
description,
|
|
398
|
+
checked,
|
|
399
|
+
onSelect,
|
|
348
400
|
disabled,
|
|
401
|
+
children,
|
|
349
402
|
}: {
|
|
350
|
-
readonly
|
|
403
|
+
readonly radioName: string;
|
|
351
404
|
readonly value: string;
|
|
352
|
-
readonly
|
|
405
|
+
readonly label: string;
|
|
406
|
+
readonly description: string;
|
|
407
|
+
readonly checked: boolean;
|
|
408
|
+
readonly onSelect: (value: string) => void;
|
|
353
409
|
readonly disabled: boolean;
|
|
410
|
+
readonly children?: ReactNode;
|
|
354
411
|
}) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
412
|
+
// The radio's accessible name is the title alone (aria-labelledby), with
|
|
413
|
+
// the description attached as aria-describedby — the wrapping label keeps
|
|
414
|
+
// the whole text block clickable without bloating the announced name.
|
|
415
|
+
const titleId = `${radioName}-${value || "platform"}-title`;
|
|
416
|
+
const descriptionId = `${radioName}-${value || "platform"}-description`;
|
|
360
417
|
|
|
361
418
|
return (
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
419
|
+
<div
|
|
420
|
+
className={cn(
|
|
421
|
+
"stg:rounded-md stg:border stg:px-3 stg:py-2.5 stg:transition-colors",
|
|
422
|
+
checked
|
|
423
|
+
? "stg:border-primary stg:bg-accent"
|
|
424
|
+
: "stg:border-input stg:hover:bg-accent-hover",
|
|
425
|
+
disabled && "stg:opacity-50",
|
|
426
|
+
)}
|
|
427
|
+
>
|
|
428
|
+
<label
|
|
429
|
+
className={cn(
|
|
430
|
+
"stg:flex stg:cursor-pointer stg:items-start stg:gap-2",
|
|
431
|
+
disabled && "stg:cursor-default",
|
|
432
|
+
)}
|
|
433
|
+
>
|
|
434
|
+
<input
|
|
435
|
+
type="radio"
|
|
436
|
+
name={radioName}
|
|
437
|
+
value={value}
|
|
438
|
+
checked={checked}
|
|
439
|
+
onChange={() => onSelect(value)}
|
|
440
|
+
disabled={disabled}
|
|
441
|
+
aria-labelledby={titleId}
|
|
442
|
+
aria-describedby={descriptionId}
|
|
443
|
+
className="stg:mt-0.5 stg:accent-primary stg:focus-visible:outline-none stg:focus-visible:ring-1 stg:focus-visible:ring-ring"
|
|
444
|
+
/>
|
|
445
|
+
<span className="stg:min-w-0 stg:flex-1">
|
|
446
|
+
<span
|
|
447
|
+
id={titleId}
|
|
448
|
+
className="stg:block stg:text-xs stg:font-medium stg:text-foreground"
|
|
374
449
|
>
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
450
|
+
{label}
|
|
451
|
+
</span>
|
|
452
|
+
<span
|
|
453
|
+
id={descriptionId}
|
|
454
|
+
className="stg:block stg:text-[0.65rem] stg:leading-snug stg:text-muted-foreground"
|
|
455
|
+
>
|
|
456
|
+
{description}
|
|
457
|
+
</span>
|
|
458
|
+
</span>
|
|
459
|
+
</label>
|
|
460
|
+
{children && (
|
|
461
|
+
<div
|
|
462
|
+
className={cn(
|
|
463
|
+
"stg:mt-2 stg:pl-6 stg:transition-opacity",
|
|
464
|
+
!checked && "stg:opacity-70",
|
|
465
|
+
)}
|
|
466
|
+
>
|
|
467
|
+
{children}
|
|
468
|
+
</div>
|
|
469
|
+
)}
|
|
470
|
+
</div>
|
|
389
471
|
);
|
|
390
472
|
}
|
|
391
473
|
|
|
@@ -426,6 +508,10 @@ function DefaultModelSelect({
|
|
|
426
508
|
id={id}
|
|
427
509
|
value={value}
|
|
428
510
|
onChange={(e) => onChange(e.target.value)}
|
|
511
|
+
// Both harness rows show the same visible label; the accessible
|
|
512
|
+
// name stays unique per control by qualifying it with the harness
|
|
513
|
+
// (WCAG label-in-name: the visible text remains a prefix).
|
|
514
|
+
aria-label={`${label} — ${HARNESS_META[harness].label}`}
|
|
429
515
|
disabled={disabled || isLoading}
|
|
430
516
|
className={cn(
|
|
431
517
|
"stg:w-full stg:rounded-md stg:border stg:border-input stg:bg-background stg:px-2.5 stg:py-1.5 stg:text-xs stg:text-foreground",
|
|
@@ -107,7 +107,10 @@ describe("AccountPreferencesPanel", () => {
|
|
|
107
107
|
// Fields this form never renders must round-trip untouched.
|
|
108
108
|
expect(input.idpId).toBe("auth0|abc");
|
|
109
109
|
expect(input.email).toBe("ada@acme.example");
|
|
110
|
-
// The update pipeline addresses the
|
|
110
|
+
// The update pipeline addresses id-first — the id is REQUIRED here:
|
|
111
|
+
// identity accounts are org-less, so the org+slug fallback can never
|
|
112
|
+
// match one (the save-path bug this pin guards against).
|
|
113
|
+
expect(input.id).toBe("ia-1");
|
|
111
114
|
expect(input.org).toBe("acme");
|
|
112
115
|
expect(input.slug).toBe("ada");
|
|
113
116
|
});
|
|
@@ -149,7 +152,7 @@ describe("AccountPreferencesPanel", () => {
|
|
|
149
152
|
},
|
|
150
153
|
});
|
|
151
154
|
|
|
152
|
-
it("renders the harness
|
|
155
|
+
it("renders the harness option rows and registry-fed model selects", async () => {
|
|
153
156
|
renderPanel(createMockStigmer());
|
|
154
157
|
await findSyncedField("Keep answers terse.");
|
|
155
158
|
|
|
@@ -160,6 +163,37 @@ describe("AccountPreferencesPanel", () => {
|
|
|
160
163
|
expect(screen.getByLabelText("Default model — Cursor")).toBeTruthy();
|
|
161
164
|
});
|
|
162
165
|
|
|
166
|
+
it("pairs each harness row with its description (the row IS the context)", async () => {
|
|
167
|
+
renderPanel(createMockStigmer());
|
|
168
|
+
await findSyncedField("Keep answers terse.");
|
|
169
|
+
|
|
170
|
+
// Row subtitles come from HARNESS_META — the single source of harness
|
|
171
|
+
// display copy — plus the platform-default explainer.
|
|
172
|
+
expect(screen.getByText("Stigmer picks the harness for new sessions.")).toBeTruthy();
|
|
173
|
+
expect(screen.getByText("Stigmer's native agent runtime")).toBeTruthy();
|
|
174
|
+
expect(screen.getByText("Cursor IDE agent with codebase indexing")).toBeTruthy();
|
|
175
|
+
// The radios' accessible descriptions are wired via aria-describedby.
|
|
176
|
+
expect(
|
|
177
|
+
screen.getByRole("radio", { name: "Stigmer" }).getAttribute("aria-describedby"),
|
|
178
|
+
).toBeTruthy();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("keeps the non-selected harness's model select editable (per-harness models are independent of the default harness)", async () => {
|
|
182
|
+
renderPanel(createMockStigmer({ whoAmI: vi.fn(async () => ACCOUNT_WITH_DEFAULTS) }));
|
|
183
|
+
await findSyncedField("Keep answers terse.");
|
|
184
|
+
|
|
185
|
+
// Default harness is cursor, yet the Stigmer model stays settable:
|
|
186
|
+
// the launcher applies the model of the session's ACTIVE harness, so
|
|
187
|
+
// hiding or disabling it would remove a real capability.
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
expect(screen.getByRole("radio", { name: "Cursor" })).toHaveProperty("checked", true);
|
|
190
|
+
});
|
|
191
|
+
const nativeSelect = screen.getByLabelText("Default model — Stigmer") as HTMLSelectElement;
|
|
192
|
+
expect(nativeSelect.disabled).toBe(false);
|
|
193
|
+
fireEvent.change(nativeSelect, { target: { value: "claude-sonnet-4.6" } });
|
|
194
|
+
expect(nativeSelect.value).toBe("claude-sonnet-4.6");
|
|
195
|
+
});
|
|
196
|
+
|
|
163
197
|
it("reflects the saved defaults in the form", async () => {
|
|
164
198
|
renderPanel(createMockStigmer({ whoAmI: vi.fn(async () => ACCOUNT_WITH_DEFAULTS) }));
|
|
165
199
|
await findSyncedField("Keep answers terse.");
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { type FormEvent, useCallback, useId, useRef, useState } from "react";
|
|
4
4
|
import { cn } from "@stigmer/theme";
|
|
5
5
|
import { UNSTYLED_FIELDSET } from "../internal/element-resets.js";
|
|
6
|
-
import { getUserMessage } from "@stigmer/sdk";
|
|
6
|
+
import { getUserMessage, toIdentityProviderUpdateInput } from "@stigmer/sdk";
|
|
7
7
|
import type { IdentityProvider } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
|
|
8
8
|
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
9
9
|
import { timestampDate, type Timestamp } from "@bufbuild/protobuf/wkt";
|
|
@@ -132,10 +132,13 @@ export function IdentityProviderDetailPanel({
|
|
|
132
132
|
e.preventDefault();
|
|
133
133
|
clearError();
|
|
134
134
|
try {
|
|
135
|
+
// Full-spec-replace safety: spread the complete mapped input and
|
|
136
|
+
// override only the edited fields, so unlisted spec fields (e.g.
|
|
137
|
+
// rate_limit_budget) survive the save. Fields the form clears
|
|
138
|
+
// (SSO providers have no JIT settings) are set to undefined
|
|
139
|
+
// explicitly — omitting them would carry the stale mapped value.
|
|
135
140
|
const updated = await update({
|
|
136
|
-
|
|
137
|
-
slug: meta?.slug,
|
|
138
|
-
org: meta?.org ?? "",
|
|
141
|
+
...toIdentityProviderUpdateInput(identityProvider),
|
|
139
142
|
displayName: displayName.trim(),
|
|
140
143
|
jwksUri: jwksUri.trim(),
|
|
141
144
|
allowedIssuers: issuers
|
|
@@ -146,16 +149,16 @@ export function IdentityProviderDetailPanel({
|
|
|
146
149
|
userinfoEndpoint: userinfoEndpoint.trim() || undefined,
|
|
147
150
|
isSsoProvider: isSso,
|
|
148
151
|
oidcClientId: isSso ? oidcClientId.trim() : undefined,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
autoGrantRole
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
autoProvisionAccounts: !isSso && autoProvision ? true : undefined,
|
|
153
|
+
autoGrantOnOrg: !isSso && autoGrant ? true : undefined,
|
|
154
|
+
autoGrantRole:
|
|
155
|
+
!isSso && autoGrant && autoGrantRole !== IamRole.iam_role_unspecified
|
|
156
|
+
? autoGrantRole
|
|
157
|
+
: undefined,
|
|
158
|
+
tenantOrgClaim:
|
|
159
|
+
!isSso && autoGrant && tenantOrgClaim.trim()
|
|
160
|
+
? tenantOrgClaim.trim()
|
|
161
|
+
: undefined,
|
|
159
162
|
});
|
|
160
163
|
setMode("view");
|
|
161
164
|
onUpdated?.(updated);
|
|
@@ -164,7 +167,7 @@ export function IdentityProviderDetailPanel({
|
|
|
164
167
|
}
|
|
165
168
|
},
|
|
166
169
|
[
|
|
167
|
-
|
|
170
|
+
identityProvider, displayName, jwksUri, issuers, audience,
|
|
168
171
|
userinfoEndpoint, isSso, oidcClientId, autoProvision, autoGrant,
|
|
169
172
|
autoGrantRole, tenantOrgClaim, update, clearError, onUpdated,
|
|
170
173
|
],
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { create } from "@bufbuild/protobuf";
|
|
4
|
+
import {
|
|
5
|
+
IdentityProviderSchema,
|
|
6
|
+
type IdentityProvider,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
|
|
8
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
9
|
+
import type { IdentityProviderInput } from "@stigmer/sdk";
|
|
10
|
+
import { StigmerContext } from "../../context";
|
|
11
|
+
import { IdentityProviderDetailPanel } from "../IdentityProviderDetailPanel";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Regression suite for the full-spec-replace wipe bug: before the
|
|
15
|
+
* generated-mapper migration, this panel hand-built its update input and
|
|
16
|
+
* NEVER sent `rate_limit_budget` — editing any IdP field zeroed the
|
|
17
|
+
* provider's rate limit. The panel must spread
|
|
18
|
+
* `toIdentityProviderUpdateInput` and override only what it edits.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const IDP: IdentityProvider = create(IdentityProviderSchema, {
|
|
22
|
+
metadata: {
|
|
23
|
+
id: "idp-1",
|
|
24
|
+
name: "Acme Okta",
|
|
25
|
+
slug: "acme-okta",
|
|
26
|
+
org: "acme",
|
|
27
|
+
},
|
|
28
|
+
spec: {
|
|
29
|
+
displayName: "Acme Okta",
|
|
30
|
+
jwksUri: "https://acme.okta.example/jwks",
|
|
31
|
+
allowedIssuers: ["https://acme.okta.example"],
|
|
32
|
+
expectedAudience: "stigmer",
|
|
33
|
+
rateLimitBudget: 120,
|
|
34
|
+
isSsoProvider: false,
|
|
35
|
+
autoProvisionAccounts: true,
|
|
36
|
+
autoGrantOnOrg: true,
|
|
37
|
+
autoGrantRole: IamRole.admin,
|
|
38
|
+
tenantOrgClaim: "org_slug",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function renderPanel(update: ReturnType<typeof vi.fn>) {
|
|
43
|
+
const client = {
|
|
44
|
+
identityProvider: { update },
|
|
45
|
+
} as never;
|
|
46
|
+
return render(
|
|
47
|
+
<StigmerContext.Provider value={client}>
|
|
48
|
+
<IdentityProviderDetailPanel identityProvider={IDP} />
|
|
49
|
+
</StigmerContext.Provider>,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
afterEach(cleanup);
|
|
54
|
+
|
|
55
|
+
describe("IdentityProviderDetailPanel save payload", () => {
|
|
56
|
+
it("round-trips rate_limit_budget on a display-name edit (the wipe bug)", async () => {
|
|
57
|
+
const update = vi.fn(async (_input: IdentityProviderInput) => IDP);
|
|
58
|
+
renderPanel(update);
|
|
59
|
+
|
|
60
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit" }));
|
|
61
|
+
const displayName = await screen.findByLabelText("Display name");
|
|
62
|
+
fireEvent.change(displayName, { target: { value: "Acme Okta (renamed)" } });
|
|
63
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
64
|
+
|
|
65
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
66
|
+
const input = update.mock.calls[0]![0];
|
|
67
|
+
|
|
68
|
+
// The wipe-bug guard: the form does not render the rate limit, yet it
|
|
69
|
+
// must survive the save.
|
|
70
|
+
expect(input.rateLimitBudget).toBe(120);
|
|
71
|
+
// The edited field.
|
|
72
|
+
expect(input.displayName).toBe("Acme Okta (renamed)");
|
|
73
|
+
// JIT settings round-trip from the edit state.
|
|
74
|
+
expect(input.autoGrantRole).toBe(IamRole.admin);
|
|
75
|
+
expect(input.tenantOrgClaim).toBe("org_slug");
|
|
76
|
+
// Addressing fields.
|
|
77
|
+
expect(input.org).toBe("acme");
|
|
78
|
+
expect(input.slug).toBe("acme-okta");
|
|
79
|
+
});
|
|
80
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -567,7 +567,6 @@ export {
|
|
|
567
567
|
McpToolSelector,
|
|
568
568
|
useCreateMcpServer,
|
|
569
569
|
useUpdateMcpServer,
|
|
570
|
-
mcpServerToInput,
|
|
571
570
|
toServerKey,
|
|
572
571
|
IdentityTransportStep,
|
|
573
572
|
EnvironmentAuthStep,
|
|
@@ -696,7 +695,6 @@ export {
|
|
|
696
695
|
useDefaultAgent,
|
|
697
696
|
useCreateAgent,
|
|
698
697
|
useUpdateAgent,
|
|
699
|
-
agentToInput,
|
|
700
698
|
AgentCreationWizard,
|
|
701
699
|
} from "./agent/index.js";
|
|
702
700
|
export type {
|
|
@@ -1311,6 +1309,7 @@ export {
|
|
|
1311
1309
|
ConnectSlackDialog,
|
|
1312
1310
|
ConnectWhatsAppDialog,
|
|
1313
1311
|
ChannelCredentialsDialog,
|
|
1312
|
+
ChannelRunConfigDialog,
|
|
1314
1313
|
ChannelToolCredentials,
|
|
1315
1314
|
ChannelConversationsDialog,
|
|
1316
1315
|
ChannelTemplatesDialog,
|
|
@@ -1334,6 +1333,7 @@ export type {
|
|
|
1334
1333
|
ConnectSlackDialogProps,
|
|
1335
1334
|
ConnectWhatsAppDialogProps,
|
|
1336
1335
|
ChannelCredentialsDialogProps,
|
|
1336
|
+
ChannelRunConfigDialogProps,
|
|
1337
1337
|
ChannelToolCredentialsProps,
|
|
1338
1338
|
ChannelConversationsDialogProps,
|
|
1339
1339
|
ChannelTemplatesDialogProps,
|
|
@@ -16,7 +16,7 @@ import type { EnvVarDeclaration } from "@stigmer/protos/ai/stigmer/agentic/envir
|
|
|
16
16
|
import { useMcpServer } from "./useMcpServer.js";
|
|
17
17
|
import type { UseMcpServerReturn } from "./useMcpServer.js";
|
|
18
18
|
import { useUpdateMcpServer } from "./useUpdateMcpServer.js";
|
|
19
|
-
import {
|
|
19
|
+
import { toMcpServerUpdateInput } from "@stigmer/sdk";
|
|
20
20
|
import { useMcpServerConnect } from "./useMcpServerConnect.js";
|
|
21
21
|
import { useMcpServerCredentials } from "./useMcpServerCredentials.js";
|
|
22
22
|
import {
|
|
@@ -214,7 +214,7 @@ export function McpServerDetailView({
|
|
|
214
214
|
value: import("@stigmer/sdk").McpServerInput[K],
|
|
215
215
|
): Promise<boolean> => {
|
|
216
216
|
if (!mcpServer) return false;
|
|
217
|
-
const input =
|
|
217
|
+
const input = toMcpServerUpdateInput(mcpServer);
|
|
218
218
|
(input as unknown as Record<string, unknown>)[field] = value;
|
|
219
219
|
try {
|
|
220
220
|
const updated = await updateMcpServer(input);
|
package/src/mcp-server/index.ts
CHANGED
|
@@ -95,8 +95,6 @@ export type { UseCreateMcpServerReturn } from "./useCreateMcpServer.js";
|
|
|
95
95
|
export { useUpdateMcpServer } from "./useUpdateMcpServer.js";
|
|
96
96
|
export type { UseUpdateMcpServerReturn } from "./useUpdateMcpServer.js";
|
|
97
97
|
|
|
98
|
-
export { mcpServerToInput } from "./internal/mcpServerToInput.js";
|
|
99
|
-
|
|
100
98
|
export { McpServerCreationWizard } from "./McpServerCreationWizard.js";
|
|
101
99
|
export type {
|
|
102
100
|
McpServerCreationWizardProps,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
2
|
-
import type
|
|
3
|
-
import { mcpServerToInput } from "./mcpServerToInput.js";
|
|
2
|
+
import { toMcpServerUpdateInput, type McpServerInput } from "@stigmer/sdk";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Connection-defining field groups a marketplace definition can drift on.
|
|
@@ -24,9 +23,9 @@ export type DriftFieldId =
|
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* Deep equality over the plain-object projections produced by
|
|
27
|
-
* `
|
|
28
|
-
* is not canonical); `undefined` values and absent keys are
|
|
29
|
-
* (the conversion uses `undefined` for proto defaults).
|
|
26
|
+
* `toMcpServerUpdateInput`. Key order is irrelevant (proto map iteration
|
|
27
|
+
* order is not canonical); `undefined` values and absent keys are
|
|
28
|
+
* equivalent (the conversion uses `undefined` for proto defaults).
|
|
30
29
|
*/
|
|
31
30
|
function deepEqual(a: unknown, b: unknown): boolean {
|
|
32
31
|
if (Object.is(a, b)) return true;
|
|
@@ -72,17 +71,18 @@ function functionalEnvShape(env: McpServerInput["env"]) {
|
|
|
72
71
|
* mirrors and reports which connection-defining field groups differ, or
|
|
73
72
|
* `null` when the connection-defining configuration matches.
|
|
74
73
|
*
|
|
75
|
-
* The comparison runs on `
|
|
76
|
-
* protos: that
|
|
77
|
-
* and its
|
|
78
|
-
*
|
|
74
|
+
* The comparison runs on `toMcpServerUpdateInput` projections rather than
|
|
75
|
+
* raw protos: that generated mapper is the SDK's canonical
|
|
76
|
+
* read-modify-write lens, and its completeness is guaranteed by codegen
|
|
77
|
+
* plus the SDK's schema-tripwire fixture suite — so a new spec field
|
|
78
|
+
* cannot silently bypass this comparison either.
|
|
79
79
|
*/
|
|
80
80
|
export function computeDefinitionDrift(
|
|
81
81
|
current: McpServer,
|
|
82
82
|
template: McpServer,
|
|
83
83
|
): readonly DriftFieldId[] | null {
|
|
84
|
-
const cur =
|
|
85
|
-
const tpl =
|
|
84
|
+
const cur = toMcpServerUpdateInput(current);
|
|
85
|
+
const tpl = toMcpServerUpdateInput(template);
|
|
86
86
|
|
|
87
87
|
const changed: DriftFieldId[] = [];
|
|
88
88
|
|
|
@@ -128,21 +128,22 @@ export function computeDefinitionDrift(
|
|
|
128
128
|
*
|
|
129
129
|
* Definition fields — transport, env declarations, auth, and the cosmetic
|
|
130
130
|
* description/icon/tags/repository metadata — come from the template.
|
|
131
|
-
* The user's own choices survive: identity (name/org/slug/labels
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
131
|
+
* The user's own choices survive: identity (name/org/slug/labels/
|
|
132
|
+
* visibility — the backend ignores visibility on update anyway;
|
|
133
|
+
* `updateVisibility` is its dedicated write path), tool enablement, and
|
|
134
|
+
* pinned approval policies.
|
|
135
135
|
*
|
|
136
|
-
* Built by merging two `
|
|
137
|
-
* set is the explicit override list below and nothing can be
|
|
138
|
-
* translation (the
|
|
136
|
+
* Built by merging two `toMcpServerUpdateInput` projections, so the
|
|
137
|
+
* preserved set is the explicit override list below and nothing can be
|
|
138
|
+
* lost in translation (the mapper is generated complete and guarded by
|
|
139
|
+
* the SDK's schema-tripwire fixture suite).
|
|
139
140
|
*/
|
|
140
141
|
export function buildRefreshInput(
|
|
141
142
|
current: McpServer,
|
|
142
143
|
template: McpServer,
|
|
143
144
|
): McpServerInput {
|
|
144
|
-
const cur =
|
|
145
|
-
const tpl =
|
|
145
|
+
const cur = toMcpServerUpdateInput(current);
|
|
146
|
+
const tpl = toMcpServerUpdateInput(template);
|
|
146
147
|
|
|
147
148
|
return {
|
|
148
149
|
...tpl,
|
|
@@ -151,6 +152,7 @@ export function buildRefreshInput(
|
|
|
151
152
|
org: cur.org,
|
|
152
153
|
slug: cur.slug,
|
|
153
154
|
labels: cur.labels,
|
|
155
|
+
visibility: cur.visibility,
|
|
154
156
|
// User policy: never reset by a definition refresh.
|
|
155
157
|
defaultEnabledTools: cur.defaultEnabledTools,
|
|
156
158
|
pinnedToolApprovals: cur.pinnedToolApprovals,
|
|
@@ -23,9 +23,9 @@ export interface UseUpdateMcpServerReturn {
|
|
|
23
23
|
*
|
|
24
24
|
* Wraps `stigmer.mcpServer.update(input)` with loading and error state.
|
|
25
25
|
* The caller must provide a **complete** `McpServerInput` — the backend
|
|
26
|
-
* performs full spec replacement. Use `
|
|
27
|
-
* the input from a fetched server, modify
|
|
28
|
-
* the result here.
|
|
26
|
+
* performs full spec replacement. Use `toMcpServerUpdateInput()` (from
|
|
27
|
+
* `@stigmer/sdk`) to reconstruct the input from a fetched server, modify
|
|
28
|
+
* the desired field, and pass the result here.
|
|
29
29
|
*/
|
|
30
30
|
export function useUpdateMcpServer(): UseUpdateMcpServerReturn {
|
|
31
31
|
const stigmer = useStigmer();
|