@stigmer/react 3.12.4 → 3.12.5
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/attachment/AttachmentChipList.js +16 -6
- package/attachment/AttachmentChipList.js.map +1 -1
- package/attachment/index.d.ts +1 -1
- package/attachment/index.d.ts.map +1 -1
- package/attachment/index.js.map +1 -1
- package/attachment/useAttachments.d.ts +51 -8
- package/attachment/useAttachments.d.ts.map +1 -1
- package/attachment/useAttachments.js +74 -3
- package/attachment/useAttachments.js.map +1 -1
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +17 -10
- package/composer/SessionComposer.js.map +1 -1
- package/conversation/ConversationComposer.d.ts +19 -3
- package/conversation/ConversationComposer.d.ts.map +1 -1
- package/conversation/ConversationComposer.js +4 -4
- package/conversation/ConversationComposer.js.map +1 -1
- package/conversation/ConversationTemplatePickerDialog.d.ts +63 -0
- package/conversation/ConversationTemplatePickerDialog.d.ts.map +1 -0
- package/conversation/ConversationTemplatePickerDialog.js +151 -0
- package/conversation/ConversationTemplatePickerDialog.js.map +1 -0
- package/conversation/ConversationsWorkbench.d.ts.map +1 -1
- package/conversation/ConversationsWorkbench.js +16 -5
- package/conversation/ConversationsWorkbench.js.map +1 -1
- package/conversation/index.d.ts +3 -1
- package/conversation/index.d.ts.map +1 -1
- package/conversation/index.js +1 -0
- package/conversation/index.js.map +1 -1
- package/conversation/useConversationParticipation.d.ts +41 -4
- package/conversation/useConversationParticipation.d.ts.map +1 -1
- package/conversation/useConversationParticipation.js +13 -2
- package/conversation/useConversationParticipation.js.map +1 -1
- package/identity-account/AccountPreferencesPanel.d.ts +35 -0
- package/identity-account/AccountPreferencesPanel.d.ts.map +1 -0
- package/identity-account/AccountPreferencesPanel.js +187 -0
- package/identity-account/AccountPreferencesPanel.js.map +1 -0
- package/identity-account/index.d.ts +8 -0
- package/identity-account/index.d.ts.map +1 -1
- package/identity-account/index.js +4 -0
- package/identity-account/index.js.map +1 -1
- package/identity-account/useAccountExecutionDefaults.d.ts +36 -0
- package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -0
- package/identity-account/useAccountExecutionDefaults.js +46 -0
- package/identity-account/useAccountExecutionDefaults.js.map +1 -0
- package/identity-account/useMyIdentityAccount.d.ts +55 -0
- package/identity-account/useMyIdentityAccount.d.ts.map +1 -0
- package/identity-account/useMyIdentityAccount.js +37 -0
- package/identity-account/useMyIdentityAccount.js.map +1 -0
- package/identity-account/useUpdateIdentityAccount.d.ts +42 -0
- package/identity-account/useUpdateIdentityAccount.d.ts.map +1 -0
- package/identity-account/useUpdateIdentityAccount.js +53 -0
- package/identity-account/useUpdateIdentityAccount.js.map +1 -0
- package/index.d.ts +11 -9
- package/index.d.ts.map +1 -1
- package/index.js +8 -5
- package/index.js.map +1 -1
- package/internal/StandingContextField.d.ts +37 -0
- package/internal/StandingContextField.d.ts.map +1 -0
- package/internal/StandingContextField.js +24 -0
- package/internal/StandingContextField.js.map +1 -0
- package/library/InstanceVisibilitySelector.d.ts.map +1 -1
- package/library/InstanceVisibilitySelector.js +9 -2
- package/library/InstanceVisibilitySelector.js.map +1 -1
- package/library/ResourceVisibilityControl.d.ts +13 -3
- package/library/ResourceVisibilityControl.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.js +33 -5
- package/library/ResourceVisibilityControl.js.map +1 -1
- package/library/VisibilityOptionRow.d.ts +6 -0
- package/library/VisibilityOptionRow.d.ts.map +1 -1
- package/library/VisibilityOptionRow.js +11 -1
- package/library/VisibilityOptionRow.js.map +1 -1
- package/library/VisibilitySelector.d.ts.map +1 -1
- package/library/VisibilitySelector.js +14 -2
- package/library/VisibilitySelector.js.map +1 -1
- package/library/index.d.ts +4 -2
- package/library/index.d.ts.map +1 -1
- package/library/index.js +2 -1
- package/library/index.js.map +1 -1
- package/library/useCanSetPublicVisibility.d.ts +30 -0
- package/library/useCanSetPublicVisibility.d.ts.map +1 -0
- package/library/useCanSetPublicVisibility.js +41 -0
- package/library/useCanSetPublicVisibility.js.map +1 -0
- package/library/visibilityLevels.d.ts +32 -1
- package/library/visibilityLevels.d.ts.map +1 -1
- package/library/visibilityLevels.js +34 -14
- package/library/visibilityLevels.js.map +1 -1
- package/mcp-server/useMcpServerConnect.d.ts +8 -5
- package/mcp-server/useMcpServerConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerConnect.js +2 -1
- package/mcp-server/useMcpServerConnect.js.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.d.ts.map +1 -1
- package/mcp-server/useMcpServerOAuthConnect.js +7 -2
- package/mcp-server/useMcpServerOAuthConnect.js.map +1 -1
- package/oauth-app/CreateOAuthAppForm.d.ts +2 -2
- package/oauth-app/CreateOAuthAppForm.d.ts.map +1 -1
- package/oauth-app/CreateOAuthAppForm.js +13 -4
- package/oauth-app/CreateOAuthAppForm.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +32 -5
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/organization/OrgPreferencesPanel.d.ts +32 -0
- package/organization/OrgPreferencesPanel.d.ts.map +1 -0
- package/organization/OrgPreferencesPanel.js +90 -0
- package/organization/OrgPreferencesPanel.js.map +1 -0
- package/organization/OrgProfilePanel.d.ts.map +1 -1
- package/organization/OrgProfilePanel.js +5 -4
- package/organization/OrgProfilePanel.js.map +1 -1
- package/organization/index.d.ts +2 -0
- package/organization/index.d.ts.map +1 -1
- package/organization/index.js +1 -0
- package/organization/index.js.map +1 -1
- package/package.json +4 -4
- package/session/NewSessionViewer.d.ts +10 -1
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +2 -1
- package/session/NewSessionViewer.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +27 -1
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +45 -8
- package/session/useNewSessionFlow.js.map +1 -1
- package/settings/AccountPreferencesSection.d.ts +3 -0
- package/settings/AccountPreferencesSection.d.ts.map +1 -0
- package/settings/AccountPreferencesSection.js +10 -0
- package/settings/AccountPreferencesSection.js.map +1 -0
- package/settings/OrgPreferencesSection.d.ts +3 -0
- package/settings/OrgPreferencesSection.d.ts.map +1 -0
- package/settings/OrgPreferencesSection.js +13 -0
- package/settings/OrgPreferencesSection.js.map +1 -0
- package/settings/index.d.ts +2 -0
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +2 -0
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +17 -1
- package/settings/settings-nav.js.map +1 -1
- package/src/attachment/AttachmentChipList.tsx +19 -8
- package/src/attachment/__tests__/AttachmentChipList.test.tsx +12 -0
- package/src/attachment/__tests__/useAttachments.test.ts +293 -0
- package/src/attachment/index.ts +1 -0
- package/src/attachment/useAttachments.ts +143 -11
- package/src/composer/SessionComposer.tsx +23 -14
- package/src/conversation/ConversationComposer.tsx +38 -4
- package/src/conversation/ConversationTemplatePickerDialog.tsx +441 -0
- package/src/conversation/ConversationsWorkbench.tsx +40 -4
- package/src/conversation/__tests__/ConversationComposer.test.tsx +56 -1
- package/src/conversation/__tests__/ConversationTemplatePickerDialog.test.tsx +224 -0
- package/src/conversation/__tests__/useConversationParticipation.test.tsx +45 -4
- package/src/conversation/index.ts +4 -0
- package/src/conversation/useConversationParticipation.ts +58 -6
- package/src/identity-account/AccountPreferencesPanel.tsx +446 -0
- package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +233 -0
- package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +116 -0
- package/src/identity-account/__tests__/useMyIdentityAccount.test.tsx +121 -0
- package/src/identity-account/index.ts +11 -0
- package/src/identity-account/useAccountExecutionDefaults.ts +63 -0
- package/src/identity-account/useMyIdentityAccount.ts +75 -0
- package/src/identity-account/useUpdateIdentityAccount.ts +73 -0
- package/src/index.ts +24 -2
- package/src/internal/StandingContextField.tsx +92 -0
- package/src/library/InstanceVisibilitySelector.tsx +9 -2
- package/src/library/ResourceVisibilityControl.tsx +46 -13
- package/src/library/VisibilityOptionRow.tsx +28 -6
- package/src/library/VisibilitySelector.tsx +14 -2
- package/src/library/__tests__/VisibilitySelector.test.tsx +48 -1
- package/src/library/__tests__/visibilityLevels.test.ts +67 -1
- package/src/library/index.ts +6 -1
- package/src/library/useCanSetPublicVisibility.ts +56 -0
- package/src/library/visibilityLevels.ts +65 -14
- package/src/mcp-server/__tests__/useMcpServerConnect.asyncLane.test.tsx +117 -0
- package/src/mcp-server/useMcpServerConnect.ts +9 -6
- package/src/mcp-server/useMcpServerOAuthConnect.ts +7 -2
- package/src/oauth-app/CreateOAuthAppForm.tsx +58 -3
- package/src/oauth-app/OAuthAppDetailPanel.tsx +84 -3
- package/src/organization/OrgPreferencesPanel.tsx +221 -0
- package/src/organization/OrgProfilePanel.tsx +5 -4
- package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +145 -0
- package/src/organization/__tests__/OrgProfilePanel.test.tsx +108 -0
- package/src/organization/index.ts +2 -0
- package/src/session/NewSessionViewer.tsx +12 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +151 -0
- package/src/session/useNewSessionFlow.ts +76 -9
- package/src/settings/AccountPreferencesSection.tsx +25 -0
- package/src/settings/OrgPreferencesSection.tsx +35 -0
- package/src/settings/__tests__/settings-nav.test.ts +27 -0
- package/src/settings/index.ts +2 -0
- package/src/settings/settings-nav.ts +18 -0
- package/src/workflow/ApprovalFormBuilder.tsx +5 -27
- package/styles.css +1 -1
- package/workflow/ApprovalFormBuilder.d.ts.map +1 -1
- package/workflow/ApprovalFormBuilder.js +8 -5
- package/workflow/ApprovalFormBuilder.js.map +1 -1
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
OrganizationSchema,
|
|
6
|
+
type Organization,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
8
|
+
import type { OrganizationInput } from "@stigmer/sdk";
|
|
9
|
+
import { StigmerContext } from "../../context";
|
|
10
|
+
import { DeploymentModeContext } from "../../deployment-mode";
|
|
11
|
+
import { OrgPreferencesPanel } from "../OrgPreferencesPanel";
|
|
12
|
+
|
|
13
|
+
const ORG: Organization = create(OrganizationSchema, {
|
|
14
|
+
metadata: { id: "acme", name: "Acme Corp", slug: "acme", org: "acme" },
|
|
15
|
+
spec: {
|
|
16
|
+
description: "We make everything.",
|
|
17
|
+
logoUrl: "https://acme.example/logo.png",
|
|
18
|
+
preferences: { standingContext: "We deploy to us-east-1." },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function createMockStigmer(overrides?: {
|
|
23
|
+
update?: ReturnType<typeof vi.fn>;
|
|
24
|
+
canEdit?: boolean;
|
|
25
|
+
}) {
|
|
26
|
+
return {
|
|
27
|
+
organization: {
|
|
28
|
+
get: vi.fn(async () => ORG),
|
|
29
|
+
update: overrides?.update ?? vi.fn(async () => ORG),
|
|
30
|
+
},
|
|
31
|
+
iamPolicy: {
|
|
32
|
+
checkMyPermission: vi.fn(async () => ({
|
|
33
|
+
isAuthorized: overrides?.canEdit ?? true,
|
|
34
|
+
})),
|
|
35
|
+
},
|
|
36
|
+
} as never;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function renderPanel(client: unknown) {
|
|
40
|
+
return render(
|
|
41
|
+
<StigmerContext.Provider value={client as never}>
|
|
42
|
+
<DeploymentModeContext.Provider value="local">
|
|
43
|
+
<OrgPreferencesPanel orgId="acme" />
|
|
44
|
+
</DeploymentModeContext.Provider>
|
|
45
|
+
</StigmerContext.Provider>,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
afterEach(cleanup);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Waits until the form has synced from server data (the panel copies the
|
|
53
|
+
* fetched value into local state in a passive effect, one flush after the
|
|
54
|
+
* field first renders) — interacting earlier races the sync.
|
|
55
|
+
*/
|
|
56
|
+
async function findSyncedField(value: string) {
|
|
57
|
+
const field = await screen.findByLabelText("Standing context");
|
|
58
|
+
await waitFor(() => expect(field).toHaveProperty("value", value));
|
|
59
|
+
return field;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("OrgPreferencesPanel", () => {
|
|
63
|
+
it("loads and displays the declared standing context", async () => {
|
|
64
|
+
renderPanel(createMockStigmer());
|
|
65
|
+
|
|
66
|
+
await findSyncedField("We deploy to us-east-1.");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("saves the full mapped input — unedited profile fields survive (wipe-bug guard)", async () => {
|
|
70
|
+
const update = vi.fn(async (_input: OrganizationInput) => ORG);
|
|
71
|
+
renderPanel(createMockStigmer({ update }));
|
|
72
|
+
|
|
73
|
+
const field = await findSyncedField("We deploy to us-east-1.");
|
|
74
|
+
fireEvent.change(field, { target: { value: "Prefer terse answers." } });
|
|
75
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
76
|
+
|
|
77
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
78
|
+
const input = update.mock.calls[0]![0];
|
|
79
|
+
|
|
80
|
+
expect(input.preferences).toEqual({
|
|
81
|
+
standingContext: "Prefer terse answers.",
|
|
82
|
+
});
|
|
83
|
+
// Fields this form never renders must round-trip untouched.
|
|
84
|
+
expect(input.name).toBe("Acme Corp");
|
|
85
|
+
expect(input.description).toBe("We make everything.");
|
|
86
|
+
expect(input.logoUrl).toBe("https://acme.example/logo.png");
|
|
87
|
+
expect(input.org).toBe("acme");
|
|
88
|
+
expect(input.slug).toBe("acme");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("clears the standing context when the text is emptied", async () => {
|
|
92
|
+
const update = vi.fn(async (_input: OrganizationInput) => ORG);
|
|
93
|
+
renderPanel(createMockStigmer({ update }));
|
|
94
|
+
|
|
95
|
+
const field = await findSyncedField("We deploy to us-east-1.");
|
|
96
|
+
fireEvent.change(field, { target: { value: "" } });
|
|
97
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
98
|
+
|
|
99
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
100
|
+
expect(update.mock.calls[0]![0].preferences).toEqual({
|
|
101
|
+
standingContext: undefined,
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("discard resets the field to the server value", async () => {
|
|
106
|
+
renderPanel(createMockStigmer());
|
|
107
|
+
|
|
108
|
+
const field = await findSyncedField("We deploy to us-east-1.");
|
|
109
|
+
fireEvent.change(field, { target: { value: "Edited but regretted" } });
|
|
110
|
+
fireEvent.click(screen.getByRole("button", { name: "Discard" }));
|
|
111
|
+
|
|
112
|
+
expect(field).toHaveProperty("value", "We deploy to us-east-1.");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("renders read-only with an explanation when the caller lacks can_edit", async () => {
|
|
116
|
+
renderPanel(createMockStigmer({ canEdit: false }));
|
|
117
|
+
|
|
118
|
+
const field = await screen.findByLabelText("Standing context");
|
|
119
|
+
await waitFor(() =>
|
|
120
|
+
expect(field).toHaveProperty("readOnly", true),
|
|
121
|
+
);
|
|
122
|
+
expect(
|
|
123
|
+
screen.getByText("Only organization admins can edit these preferences."),
|
|
124
|
+
).toBeTruthy();
|
|
125
|
+
expect(screen.queryByRole("button", { name: "Save changes" })).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("shows the fetch error with a retry action when loading fails", async () => {
|
|
129
|
+
const client = {
|
|
130
|
+
organization: {
|
|
131
|
+
get: vi.fn(async () => {
|
|
132
|
+
throw new Error("boom");
|
|
133
|
+
}),
|
|
134
|
+
update: vi.fn(),
|
|
135
|
+
},
|
|
136
|
+
iamPolicy: {
|
|
137
|
+
checkMyPermission: vi.fn(async () => ({ isAuthorized: true })),
|
|
138
|
+
},
|
|
139
|
+
} as never;
|
|
140
|
+
renderPanel(client);
|
|
141
|
+
|
|
142
|
+
expect(await screen.findByRole("alert")).toBeTruthy();
|
|
143
|
+
expect(screen.getByRole("button", { name: "Retry" })).toBeTruthy();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
OrganizationSchema,
|
|
6
|
+
type Organization,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
8
|
+
import type { OrganizationInput } from "@stigmer/sdk";
|
|
9
|
+
import { StigmerContext } from "../../context";
|
|
10
|
+
import { DeploymentModeContext } from "../../deployment-mode";
|
|
11
|
+
import { OrgProfilePanel } from "../OrgProfilePanel";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Regression suite for the full-spec-replace wipe bug (oss#293 Phase 1):
|
|
15
|
+
* `organization.update()` wholesale replaces the stored spec, so a profile
|
|
16
|
+
* save that sends only the edited fields silently wipes every other mutable
|
|
17
|
+
* spec field — most visibly `spec.preferences.standing_context` set via the
|
|
18
|
+
* CLI or the preferences page. The panel must spread the complete mapped
|
|
19
|
+
* input and override only what it edits.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const ORG: Organization = create(OrganizationSchema, {
|
|
23
|
+
metadata: {
|
|
24
|
+
id: "acme",
|
|
25
|
+
name: "Acme Corp",
|
|
26
|
+
slug: "acme",
|
|
27
|
+
org: "acme",
|
|
28
|
+
},
|
|
29
|
+
spec: {
|
|
30
|
+
description: "We make everything.",
|
|
31
|
+
logoUrl: "https://acme.example/logo.png",
|
|
32
|
+
preferences: { standingContext: "We deploy to us-east-1." },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function createMockStigmer(overrides?: {
|
|
37
|
+
update?: ReturnType<typeof vi.fn>;
|
|
38
|
+
}) {
|
|
39
|
+
return {
|
|
40
|
+
organization: {
|
|
41
|
+
get: vi.fn(async () => ORG),
|
|
42
|
+
update: overrides?.update ?? vi.fn(async () => ORG),
|
|
43
|
+
},
|
|
44
|
+
} as never;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Deployment mode "local" keeps the IdentityProvidersSummary sub-panel
|
|
48
|
+
// inert (identity_provider is cloud-only), so no extra client stubs are
|
|
49
|
+
// needed — the suite tests the form, not the summary.
|
|
50
|
+
function renderPanel(client: unknown) {
|
|
51
|
+
return render(
|
|
52
|
+
<StigmerContext.Provider value={client as never}>
|
|
53
|
+
<DeploymentModeContext.Provider value="local">
|
|
54
|
+
<OrgProfilePanel orgId="acme" />
|
|
55
|
+
</DeploymentModeContext.Provider>
|
|
56
|
+
</StigmerContext.Provider>,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
afterEach(cleanup);
|
|
61
|
+
|
|
62
|
+
describe("OrgProfilePanel save payload", () => {
|
|
63
|
+
it("round-trips unedited spec fields — preferences survive a profile save", async () => {
|
|
64
|
+
const update = vi.fn(async (_input: OrganizationInput) => ORG);
|
|
65
|
+
const client = createMockStigmer({ update });
|
|
66
|
+
renderPanel(client);
|
|
67
|
+
|
|
68
|
+
// Wait for the server-sync effect to fill the form before editing —
|
|
69
|
+
// interacting on first appearance races the sync.
|
|
70
|
+
const description = await screen.findByLabelText("Description");
|
|
71
|
+
await waitFor(() =>
|
|
72
|
+
expect(description).toHaveProperty("value", "We make everything."),
|
|
73
|
+
);
|
|
74
|
+
fireEvent.change(description, { target: { value: "New description" } });
|
|
75
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
76
|
+
|
|
77
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
78
|
+
const input = update.mock.calls[0]![0];
|
|
79
|
+
|
|
80
|
+
// The edited field.
|
|
81
|
+
expect(input.description).toBe("New description");
|
|
82
|
+
// The unedited fields the form does not render — the wipe-bug guard.
|
|
83
|
+
expect(input.preferences).toEqual({
|
|
84
|
+
standingContext: "We deploy to us-east-1.",
|
|
85
|
+
});
|
|
86
|
+
expect(input.logoUrl).toBe("https://acme.example/logo.png");
|
|
87
|
+
// Addressing fields for the update pipeline's org+slug lookup.
|
|
88
|
+
expect(input.org).toBe("acme");
|
|
89
|
+
expect(input.slug).toBe("acme");
|
|
90
|
+
expect(input.name).toBe("Acme Corp");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("sends the edited name and keeps discard/dirty semantics intact", async () => {
|
|
94
|
+
const update = vi.fn(async (_input: OrganizationInput) => ORG);
|
|
95
|
+
renderPanel(createMockStigmer({ update }));
|
|
96
|
+
|
|
97
|
+
const name = await screen.findByLabelText("Name");
|
|
98
|
+
await waitFor(() => expect(name).toHaveProperty("value", "Acme Corp"));
|
|
99
|
+
fireEvent.change(name, { target: { value: "Acme Corporation" } });
|
|
100
|
+
fireEvent.click(screen.getByRole("button", { name: "Save changes" }));
|
|
101
|
+
|
|
102
|
+
await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
|
|
103
|
+
expect(update.mock.calls[0]![0].name).toBe("Acme Corporation");
|
|
104
|
+
expect(update.mock.calls[0]![0].preferences).toEqual({
|
|
105
|
+
standingContext: "We deploy to us-east-1.",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -16,5 +16,7 @@ export { CreateOrganizationForm } from "./CreateOrganizationForm.js";
|
|
|
16
16
|
export type { CreateOrganizationFormProps } from "./CreateOrganizationForm.js";
|
|
17
17
|
export { OrgProfilePanel } from "./OrgProfilePanel.js";
|
|
18
18
|
export type { OrgProfilePanelProps } from "./OrgProfilePanel.js";
|
|
19
|
+
export { OrgPreferencesPanel } from "./OrgPreferencesPanel.js";
|
|
20
|
+
export type { OrgPreferencesPanelProps } from "./OrgPreferencesPanel.js";
|
|
19
21
|
export { OrgSwitcher } from "./OrgSwitcher.js";
|
|
20
22
|
export type { OrgSwitcherProps } from "./OrgSwitcher.js";
|
|
@@ -9,6 +9,7 @@ import type { WorkspaceContentSearcher } from "../workspace/WorkspaceContentSear
|
|
|
9
9
|
import type { InteractionModeOption } from "../composer/index.js";
|
|
10
10
|
import { SessionComposer } from "../composer/index.js";
|
|
11
11
|
import type { HarnessOption } from "../models/harness.js";
|
|
12
|
+
import type { AccountExecutionDefaults } from "../identity-account/useAccountExecutionDefaults.js";
|
|
12
13
|
import { SessionViewerLayout } from "./SessionViewerLayout.js";
|
|
13
14
|
import { useWorkspaceEditors } from "../internal/store/index.js";
|
|
14
15
|
import { WorkspaceSurface } from "../workspace/WorkspaceSurface.js";
|
|
@@ -163,6 +164,15 @@ export interface NewSessionViewerProps {
|
|
|
163
164
|
*/
|
|
164
165
|
readonly defaultHarness?: HarnessOption;
|
|
165
166
|
|
|
167
|
+
/**
|
|
168
|
+
* The user's account-level execution defaults, typically from
|
|
169
|
+
* `useAccountExecutionDefaults()`. Seeds the harness and model pickers
|
|
170
|
+
* under the layered precedence documented on
|
|
171
|
+
* {@link UseNewSessionFlowOptions.accountDefaults}; explicit device
|
|
172
|
+
* picks always win. Ignored for the `"guest"` audience.
|
|
173
|
+
*/
|
|
174
|
+
readonly accountDefaults?: AccountExecutionDefaults;
|
|
175
|
+
|
|
166
176
|
/** Agent to auto-select on mount (used for draft flows). */
|
|
167
177
|
readonly initialAgentRef?: ResourceRef;
|
|
168
178
|
/**
|
|
@@ -253,6 +263,7 @@ export function NewSessionViewer({
|
|
|
253
263
|
panelOpen,
|
|
254
264
|
onPanelOpenChange,
|
|
255
265
|
defaultHarness,
|
|
266
|
+
accountDefaults,
|
|
256
267
|
initialAgentRef,
|
|
257
268
|
initialInstanceId,
|
|
258
269
|
initialAttachments,
|
|
@@ -269,6 +280,7 @@ export function NewSessionViewer({
|
|
|
269
280
|
onError,
|
|
270
281
|
getRuntimeEnv,
|
|
271
282
|
defaultHarness,
|
|
283
|
+
accountDefaults,
|
|
272
284
|
audience,
|
|
273
285
|
runConfig,
|
|
274
286
|
});
|
|
@@ -805,6 +805,157 @@ describe("useNewSessionFlow", () => {
|
|
|
805
805
|
});
|
|
806
806
|
});
|
|
807
807
|
|
|
808
|
+
describe("accountDefaults (oss#293 Phase 1.5 layered seed)", () => {
|
|
809
|
+
// Two native models so "stored pick outranks account default" is
|
|
810
|
+
// distinguishable; the shared TEST_MODELS carries one per harness.
|
|
811
|
+
const SEED_MODELS = parseRegistryJson({
|
|
812
|
+
models: [
|
|
813
|
+
{ id: "claude-sonnet-4.6", displayName: "Claude Sonnet 4.6", shortDescription: "", speedTier: "fast", provider: "anthropic", harness: "native", costTier: "standard", featured: true, pricing: { inputPricePerMillion: 3, outputPricePerMillion: 15, cacheWritePricePerMillion: 3.75, cacheReadPricePerMillion: 0.3 } },
|
|
814
|
+
{ id: "gpt-5.3", displayName: "GPT 5.3", shortDescription: "", speedTier: "fast", provider: "openai", harness: "native", costTier: "standard", featured: false, pricing: { inputPricePerMillion: 2, outputPricePerMillion: 8, cacheWritePricePerMillion: 2, cacheReadPricePerMillion: 0.2 } },
|
|
815
|
+
{ id: "default", displayName: "Cursor Auto", shortDescription: "", speedTier: "fast", provider: "cursor", harness: "cursor", costTier: "standard", featured: true, pricing: { inputPricePerMillion: 1.25, outputPricePerMillion: 6, cacheWritePricePerMillion: 1.25, cacheReadPricePerMillion: 0.25 } },
|
|
816
|
+
{ id: "composer-2.5", displayName: "Composer 2.5", shortDescription: "", speedTier: "fast", provider: "cursor", harness: "cursor", costTier: "standard", featured: false, pricing: { inputPricePerMillion: 1, outputPricePerMillion: 5, cacheWritePricePerMillion: 1, cacheReadPricePerMillion: 0.1 } },
|
|
817
|
+
],
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
function seedWrapper() {
|
|
821
|
+
const state: ModelRegistryState = { models: SEED_MODELS, isLoading: false, error: null, refetch: () => {} };
|
|
822
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
823
|
+
return (
|
|
824
|
+
<ModelRegistryContext.Provider value={state}>
|
|
825
|
+
{children}
|
|
826
|
+
</ModelRegistryContext.Provider>
|
|
827
|
+
);
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
it("seeds the harness when nothing is stored", () => {
|
|
832
|
+
const opts = { ...defaultOptions(), accountDefaults: { harness: "cursor" as const } };
|
|
833
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
834
|
+
|
|
835
|
+
expect(result.current.harness).toBe("cursor");
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it("a stored harness choice outranks the account default", () => {
|
|
839
|
+
localStorage.setItem(STORAGE_KEY_HARNESS, "native");
|
|
840
|
+
const opts = { ...defaultOptions(), accountDefaults: { harness: "cursor" as const } };
|
|
841
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
842
|
+
|
|
843
|
+
expect(result.current.harness).toBe("native");
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it("the account harness outranks the embedder defaultHarness", () => {
|
|
847
|
+
const opts = {
|
|
848
|
+
...defaultOptions(),
|
|
849
|
+
defaultHarness: "native" as const,
|
|
850
|
+
accountDefaults: { harness: "cursor" as const },
|
|
851
|
+
};
|
|
852
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
853
|
+
|
|
854
|
+
expect(result.current.harness).toBe("cursor");
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("never persists the seeded harness — only explicit choices", () => {
|
|
858
|
+
const opts = { ...defaultOptions(), accountDefaults: { harness: "cursor" as const } };
|
|
859
|
+
renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
860
|
+
|
|
861
|
+
expect(localStorage.getItem(STORAGE_KEY_HARNESS)).toBeNull();
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it("a late-arriving harness seed applies when the user has not picked (whoAmI resolves after mount)", () => {
|
|
865
|
+
const base: Parameters<typeof useNewSessionFlow>[0] = defaultOptions();
|
|
866
|
+
const { result, rerender } = renderHook(
|
|
867
|
+
(opts: Parameters<typeof useNewSessionFlow>[0]) => useNewSessionFlow(opts),
|
|
868
|
+
{ wrapper: seedWrapper(), initialProps: base },
|
|
869
|
+
);
|
|
870
|
+
expect(result.current.harness).toBe("native");
|
|
871
|
+
|
|
872
|
+
rerender({ ...base, accountDefaults: { harness: "cursor" as const } });
|
|
873
|
+
|
|
874
|
+
expect(result.current.harness).toBe("cursor");
|
|
875
|
+
expect(localStorage.getItem(STORAGE_KEY_HARNESS)).toBeNull();
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it("a late-arriving harness seed never overrides an explicit pick this mount", () => {
|
|
879
|
+
const base: Parameters<typeof useNewSessionFlow>[0] = defaultOptions();
|
|
880
|
+
const { result, rerender } = renderHook(
|
|
881
|
+
(opts: Parameters<typeof useNewSessionFlow>[0]) => useNewSessionFlow(opts),
|
|
882
|
+
{ wrapper: seedWrapper(), initialProps: base },
|
|
883
|
+
);
|
|
884
|
+
act(() => result.current.setHarness("native"));
|
|
885
|
+
|
|
886
|
+
rerender({ ...base, accountDefaults: { harness: "cursor" as const } });
|
|
887
|
+
|
|
888
|
+
expect(result.current.harness).toBe("native");
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
it("seeds the model for the active harness when nothing is stored", () => {
|
|
892
|
+
const opts = { ...defaultOptions(), accountDefaults: { nativeModel: "gpt-5.3" } };
|
|
893
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
894
|
+
|
|
895
|
+
expect(result.current.modelId).toBe("gpt-5.3");
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it("a stored model pick outranks the account default", async () => {
|
|
899
|
+
localStorage.setItem(STORAGE_KEY_MODEL_NATIVE, "claude-sonnet-4.6");
|
|
900
|
+
const opts = { ...defaultOptions(), accountDefaults: { nativeModel: "gpt-5.3" } };
|
|
901
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
902
|
+
|
|
903
|
+
// The stored restore lands in a passive effect once the registry is
|
|
904
|
+
// ready; the account seed must not win the race before it.
|
|
905
|
+
await act(async () => {});
|
|
906
|
+
expect(result.current.modelId).toBe("claude-sonnet-4.6");
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it("self-heals a stale account model to the platform default (undefined)", () => {
|
|
910
|
+
const opts = { ...defaultOptions(), accountDefaults: { nativeModel: "retired-model" } };
|
|
911
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
912
|
+
|
|
913
|
+
expect(result.current.modelId).toBeUndefined();
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it("never persists the seeded model — only explicit choices", () => {
|
|
917
|
+
const opts = { ...defaultOptions(), accountDefaults: { nativeModel: "gpt-5.3" } };
|
|
918
|
+
renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
919
|
+
|
|
920
|
+
expect(localStorage.getItem(STORAGE_KEY_MODEL_NATIVE)).toBeNull();
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it("follows the active harness: cursorModel seeds cursor sessions", () => {
|
|
924
|
+
const opts = {
|
|
925
|
+
...defaultOptions(),
|
|
926
|
+
accountDefaults: { harness: "cursor" as const, nativeModel: "gpt-5.3", cursorModel: "composer-2.5" },
|
|
927
|
+
};
|
|
928
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
929
|
+
|
|
930
|
+
expect(result.current.harness).toBe("cursor");
|
|
931
|
+
expect(result.current.modelId).toBe("composer-2.5");
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
it("submits the seeded model explicitly — the pill promise (#663) and the DD-003 seed rule", async () => {
|
|
935
|
+
const opts = { ...defaultOptions(), accountDefaults: { nativeModel: "gpt-5.3" } };
|
|
936
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
937
|
+
|
|
938
|
+
await act(async () => {
|
|
939
|
+
await result.current.submit("Hello");
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
expect(mockCreateExecution.mock.calls[0][0].modelName).toBe("gpt-5.3");
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
it("guest audience ignores accountDefaults entirely", () => {
|
|
946
|
+
const opts = {
|
|
947
|
+
...defaultOptions(),
|
|
948
|
+
audience: "guest" as const,
|
|
949
|
+
accountDefaults: { harness: "native" as const, nativeModel: "gpt-5.3", cursorModel: "composer-2.5" },
|
|
950
|
+
};
|
|
951
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: seedWrapper() });
|
|
952
|
+
|
|
953
|
+
// Platform share policy: cursor harness, no client model.
|
|
954
|
+
expect(result.current.harness).toBe("cursor");
|
|
955
|
+
expect(result.current.modelId).toBeUndefined();
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
808
959
|
describe("submit while default agent is loading", () => {
|
|
809
960
|
it("awaits default agent and creates session when fetch resolves", async () => {
|
|
810
961
|
const resolvedAgent = { status: { defaultInstanceId: "awaited-inst" } };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { getUserMessage, type McpServerUsageInput, type ResourceRef } from "@stigmer/sdk";
|
|
5
|
+
import type { AccountExecutionDefaults } from "../identity-account/useAccountExecutionDefaults.js";
|
|
5
6
|
import type { AgentResolution } from "../agent/index.js";
|
|
6
7
|
import { useDefaultAgent } from "../agent/index.js";
|
|
7
8
|
import { useModelRegistry } from "../models/index.js";
|
|
@@ -102,6 +103,26 @@ export interface UseNewSessionFlowOptions {
|
|
|
102
103
|
* @default DEFAULT_HARNESS ("native")
|
|
103
104
|
*/
|
|
104
105
|
readonly defaultHarness?: HarnessOption;
|
|
106
|
+
/**
|
|
107
|
+
* The user's account-level execution defaults
|
|
108
|
+
* (`IdentityAccountPreferences.default_*`), typically from
|
|
109
|
+
* `useAccountExecutionDefaults()`. A SEED in the layered precedence —
|
|
110
|
+
* explicit device-local choices always outrank it:
|
|
111
|
+
*
|
|
112
|
+
* - harness: stored choice > `accountDefaults.harness` >
|
|
113
|
+
* {@link defaultHarness} > platform default. May arrive after mount
|
|
114
|
+
* (whoAmI resolves async); a late value still seeds unless the user
|
|
115
|
+
* has already picked a harness this mount.
|
|
116
|
+
* - model: stored per-harness choice > the account's model for the
|
|
117
|
+
* active harness (validated against the registry; a stale model
|
|
118
|
+
* silently falls through) > the harness default.
|
|
119
|
+
*
|
|
120
|
+
* Seeded values are never persisted to localStorage — the account
|
|
121
|
+
* preference keeps applying until the user decides. Ignored for the
|
|
122
|
+
* `"guest"` audience (platform share policy owns guest config), and
|
|
123
|
+
* consumers must not fetch identity for guest/embed surfaces.
|
|
124
|
+
*/
|
|
125
|
+
readonly accountDefaults?: AccountExecutionDefaults;
|
|
105
126
|
/**
|
|
106
127
|
* Who this flow serves. `"guest"` adapts the orchestration to the
|
|
107
128
|
* guest principal's permission model: the org default-agent lookup
|
|
@@ -162,7 +183,12 @@ export interface UseNewSessionFlowReturn {
|
|
|
162
183
|
/** Switch the harness. Resets the model if invalid for the new harness. */
|
|
163
184
|
readonly setHarness: (harness: HarnessOption) => void;
|
|
164
185
|
|
|
165
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* The model an untouched send will run: the explicit device pick when
|
|
188
|
+
* one exists (persisted per-harness to localStorage), else the account
|
|
189
|
+
* default for the active harness. `undefined` resolves to the harness
|
|
190
|
+
* default downstream.
|
|
191
|
+
*/
|
|
166
192
|
readonly modelId: string | undefined;
|
|
167
193
|
/** Update the selected model. Automatically persists to localStorage. */
|
|
168
194
|
readonly setModelId: (id: string) => void;
|
|
@@ -269,6 +295,10 @@ export function useNewSessionFlow(
|
|
|
269
295
|
sessionContext,
|
|
270
296
|
} = options;
|
|
271
297
|
const isGuest = options.audience === "guest";
|
|
298
|
+
// Guests never inherit account defaults — the platform share policy owns
|
|
299
|
+
// guest execution config (GUEST_HARNESS reasoning), and identity-derived
|
|
300
|
+
// preferences must never shape a share/embed surface.
|
|
301
|
+
const accountDefaults = isGuest ? undefined : options.accountDefaults;
|
|
272
302
|
// Guests never carry a client pin: guest execution config is owned by
|
|
273
303
|
// the server-side share policy (GUEST_HARNESS reasoning). Validated at
|
|
274
304
|
// render so a statically-wrong pin (fast tier, no model) fails in the
|
|
@@ -293,13 +323,30 @@ export function useNewSessionFlow(
|
|
|
293
323
|
// touch localStorage: a browser previously used in the Console must not
|
|
294
324
|
// leak its stored harness into a share/embed session.
|
|
295
325
|
if (isGuest) return GUEST_HARNESS;
|
|
296
|
-
if (typeof window === "undefined")
|
|
326
|
+
if (typeof window === "undefined")
|
|
327
|
+
return accountDefaults?.harness ?? defaultHarness ?? DEFAULT_HARNESS;
|
|
297
328
|
// Only explicit user choices are persisted (see setHarness), so a
|
|
298
|
-
// stored value always outranks the embedder's
|
|
329
|
+
// stored value always outranks the account default and the embedder's
|
|
330
|
+
// defaultHarness.
|
|
299
331
|
const stored = localStorage.getItem(STORAGE_KEY_HARNESS);
|
|
300
332
|
if (stored === "native" || stored === "cursor") return stored;
|
|
301
|
-
return defaultHarness ?? DEFAULT_HARNESS;
|
|
333
|
+
return accountDefaults?.harness ?? defaultHarness ?? DEFAULT_HARNESS;
|
|
302
334
|
});
|
|
335
|
+
// Set once the user picks a harness this mount — a late-arriving account
|
|
336
|
+
// default must never override an explicit choice (the composer's
|
|
337
|
+
// userOverrodeModel idiom).
|
|
338
|
+
const harnessTouchedRef = useRef(false);
|
|
339
|
+
|
|
340
|
+
// Account defaults resolve async (whoAmI): when the harness seed arrives
|
|
341
|
+
// after mount, apply it once — unless the user has picked or a stored
|
|
342
|
+
// choice exists. Never persisted: seeding must not masquerade as a user
|
|
343
|
+
// choice, or the account preference would stop applying elsewhere.
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
const seed = accountDefaults?.harness;
|
|
346
|
+
if (isGuest || harnessTouchedRef.current || !seed) return;
|
|
347
|
+
if (typeof window !== "undefined" && localStorage.getItem(STORAGE_KEY_HARNESS)) return;
|
|
348
|
+
setHarnessRaw(seed);
|
|
349
|
+
}, [isGuest, accountDefaults?.harness]);
|
|
303
350
|
|
|
304
351
|
const stigmer = useStigmer();
|
|
305
352
|
const { getModel, isLoading: isModelsLoading } = useModelRegistry({ harness });
|
|
@@ -326,8 +373,26 @@ export function useNewSessionFlow(
|
|
|
326
373
|
|
|
327
374
|
const validModelId = modelId && getModel(modelId) ? modelId : undefined;
|
|
328
375
|
|
|
376
|
+
// The account's model for the active harness, registry-validated the same
|
|
377
|
+
// way as the stored choice (a stale/removed preference silently falls
|
|
378
|
+
// through to the harness default — self-healing). Derived, never written
|
|
379
|
+
// into `modelId` state: the persist effect below stores only explicit
|
|
380
|
+
// choices, and a derived seed cannot leak into localStorage.
|
|
381
|
+
const accountModelForHarness =
|
|
382
|
+
harness === "cursor" ? accountDefaults?.cursorModel : accountDefaults?.nativeModel;
|
|
383
|
+
const accountModelId =
|
|
384
|
+
accountModelForHarness && getModel(accountModelForHarness)
|
|
385
|
+
? accountModelForHarness
|
|
386
|
+
: undefined;
|
|
387
|
+
|
|
388
|
+
// Layered precedence: explicit device pick > account default. The result
|
|
389
|
+
// feeds the composer's model pill AND the submit path, so the pill always
|
|
390
|
+
// shows what an untouched send will run (#663).
|
|
391
|
+
const effectiveModelId = validModelId ?? accountModelId;
|
|
392
|
+
|
|
329
393
|
const setHarness = useCallback(
|
|
330
394
|
(h: HarnessOption) => {
|
|
395
|
+
harnessTouchedRef.current = true;
|
|
331
396
|
setHarnessRaw(h);
|
|
332
397
|
// Guests have no harness picker; if a caller invokes this anyway, the
|
|
333
398
|
// guest surface must never write into the Console's preference keys.
|
|
@@ -415,8 +480,10 @@ export function useNewSessionFlow(
|
|
|
415
480
|
// The owner pin wins over the composer and the restored
|
|
416
481
|
// preference (#664); the pinned tier is stamped only as
|
|
417
482
|
// "fast" — standard stays off the wire, preserving the #357
|
|
418
|
-
// UNSPECIFIED-vs-explicit telemetry distinction.
|
|
419
|
-
|
|
483
|
+
// UNSPECIFIED-vs-explicit telemetry distinction. The effective
|
|
484
|
+
// model carries the account-default seed explicitly (DD-003:
|
|
485
|
+
// the preference is a seed; the execution spec is the record).
|
|
486
|
+
modelName: pinnedModelName ?? selectedModel ?? effectiveModelId,
|
|
420
487
|
runtimeEnv,
|
|
421
488
|
attachments: context?.attachments,
|
|
422
489
|
interactionMode: context?.interactionMode,
|
|
@@ -514,7 +581,7 @@ export function useNewSessionFlow(
|
|
|
514
581
|
autoApproveAll,
|
|
515
582
|
adapter,
|
|
516
583
|
getRuntimeEnv,
|
|
517
|
-
|
|
584
|
+
effectiveModelId,
|
|
518
585
|
pinnedModelName,
|
|
519
586
|
pinnedServiceTier,
|
|
520
587
|
workspace,
|
|
@@ -539,7 +606,7 @@ export function useNewSessionFlow(
|
|
|
539
606
|
return {
|
|
540
607
|
harness,
|
|
541
608
|
setHarness,
|
|
542
|
-
modelId:
|
|
609
|
+
modelId: effectiveModelId,
|
|
543
610
|
setModelId,
|
|
544
611
|
agentRef,
|
|
545
612
|
setAgentRef,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { AccountPreferencesPanel } from "../identity-account/AccountPreferencesPanel.js";
|
|
5
|
+
|
|
6
|
+
/** Settings section for editing the current user's declared preferences. */
|
|
7
|
+
export function AccountPreferencesSection() {
|
|
8
|
+
const headingId = useId();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<section aria-labelledby={headingId}>
|
|
12
|
+
<h2
|
|
13
|
+
id={headingId}
|
|
14
|
+
className="stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold"
|
|
15
|
+
>
|
|
16
|
+
Account Preferences
|
|
17
|
+
</h2>
|
|
18
|
+
<p className="stg:text-muted-foreground stg:mb-6 stg:text-xs">
|
|
19
|
+
Personal standing context shared with agents on executions you run.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<AccountPreferencesPanel />
|
|
23
|
+
</section>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { OrgPreferencesPanel } from "../organization/OrgPreferencesPanel.js";
|
|
5
|
+
import { useOrg } from "../organization/OrgProvider.js";
|
|
6
|
+
|
|
7
|
+
/** Settings section for editing the active organization's declared preferences. */
|
|
8
|
+
export function OrgPreferencesSection() {
|
|
9
|
+
const headingId = useId();
|
|
10
|
+
const { activeOrg } = useOrg();
|
|
11
|
+
const orgId = activeOrg?.metadata?.id ?? "";
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<section aria-labelledby={headingId}>
|
|
15
|
+
<h2
|
|
16
|
+
id={headingId}
|
|
17
|
+
className="stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold"
|
|
18
|
+
>
|
|
19
|
+
Organization Preferences
|
|
20
|
+
</h2>
|
|
21
|
+
<p className="stg:text-muted-foreground stg:mb-6 stg:text-xs">
|
|
22
|
+
Standing context shared with agents on every execution run by this
|
|
23
|
+
organization's members.
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
{!orgId ? (
|
|
27
|
+
<p className="stg:text-muted-foreground stg:py-4 stg:text-center stg:text-xs">
|
|
28
|
+
Select an organization to view its preferences.
|
|
29
|
+
</p>
|
|
30
|
+
) : (
|
|
31
|
+
<OrgPreferencesPanel orgId={orgId} />
|
|
32
|
+
)}
|
|
33
|
+
</section>
|
|
34
|
+
);
|
|
35
|
+
}
|