mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 04:14:42 +00:00
Say that connector Allow is for chats, not the agent's API
A connector's Allow is the in-chat permission; changes through an agent's API, widget or public link need the agent's own allowlist. The badge in "What this agent uses" now reads "Not via API", its note says where Allow applies, and for the owner it opens Access details with the allowlist unfolded. The connector permission hint says the same.
This commit is contained in:
1 parent
05bdb60956
commit
371e6d7a17
15 files changed
+279
-67
No files matched your search
@@ -39,6 +39,7 @@ const mocks = vi.hoisted(() => {
|
||||
>(() => jsonResponse({})),
|
||||
guardrailsProps: vi.fn(),
|
||||
detailsProps: vi.fn(),
|
||||
shareProps: vi.fn(),
|
||||
reconnect: vi.fn(),
|
||||
};
|
||||
});
|
||||
@@ -144,7 +145,12 @@ vi.mock('../modals/AgentDetailsModal', () => ({
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
vi.mock('../teams/ShareToTeamModal', () => ({ default: () => null }));
|
||||
vi.mock('../teams/ShareToTeamModal', () => ({
|
||||
default: (props: unknown) => {
|
||||
mocks.shareProps(props);
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
vi.mock('../modals/ConfirmationModal', () => ({
|
||||
default: ({
|
||||
modalState,
|
||||
@@ -1082,6 +1088,51 @@ describe('NewAgent gating by role', () => {
|
||||
expect(buttonByText('agents.form.buttons.cancel')).toBeUndefined();
|
||||
});
|
||||
|
||||
// "What this agent uses" in Share sends the owner to the API write
|
||||
// allowlist, which opens unfolded; opened any other way it starts folded.
|
||||
it('opens Access details on the allowlist from Share', async () => {
|
||||
await renderEdit('owner', OWNER);
|
||||
const details = () =>
|
||||
mocks.detailsProps.mock.calls.at(-1)![0] as {
|
||||
modalState: string;
|
||||
openApiWrites?: boolean;
|
||||
setModalState: (state: string) => void;
|
||||
};
|
||||
await menuLabels();
|
||||
await act(async () =>
|
||||
Array.from(document.querySelectorAll<HTMLElement>('[role="menuitem"]'))
|
||||
.find((item) => item.textContent === 'agents.shareWithTeam')!
|
||||
.click(),
|
||||
);
|
||||
const share = mocks.shareProps.mock.calls.at(-1)![0] as {
|
||||
onOpenAccessDetails?: () => void;
|
||||
};
|
||||
mocks.shareProps.mockClear();
|
||||
await act(async () => share.onOpenAccessDetails!());
|
||||
expect(mocks.shareProps).not.toHaveBeenCalled();
|
||||
expect(details().modalState).toBe('ACTIVE');
|
||||
expect(details().openApiWrites).toBe(true);
|
||||
await act(async () => details().setModalState('INACTIVE'));
|
||||
expect(details().openApiWrites).toBe(false);
|
||||
});
|
||||
|
||||
it('gives Share no way to Access details without manage_access_details', async () => {
|
||||
await renderEdit(
|
||||
'owner',
|
||||
OWNER.filter((a) => a !== 'manage_access_details'),
|
||||
);
|
||||
await menuLabels();
|
||||
await act(async () =>
|
||||
Array.from(document.querySelectorAll<HTMLElement>('[role="menuitem"]'))
|
||||
.find((item) => item.textContent === 'agents.shareWithTeam')!
|
||||
.click(),
|
||||
);
|
||||
const share = mocks.shareProps.mock.calls.at(-1)![0] as {
|
||||
onOpenAccessDetails?: () => void;
|
||||
};
|
||||
expect(share.onOpenAccessDetails).toBeUndefined();
|
||||
});
|
||||
|
||||
it('hides Access details without manage_access_details', async () => {
|
||||
await renderEdit(
|
||||
'editor',
|
||||
|
||||
@@ -221,6 +221,8 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
const [deleteConfirmation, setDeleteConfirmation] =
|
||||
useState<ActiveState>('INACTIVE');
|
||||
const [agentDetails, setAgentDetails] = useState<ActiveState>('INACTIVE');
|
||||
// Access details opened from Share to allow changes: its allowlist unfolds.
|
||||
const [detailsOnApiWrites, setDetailsOnApiWrites] = useState(false);
|
||||
const [shareModalOpen, setShareModalOpen] = useState(false);
|
||||
const [addPromptModal, setAddPromptModal] = useState<ActiveState>('INACTIVE');
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
@@ -1137,6 +1139,9 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
// Page-level actions live in the ⋯ beside the title. Until the agent is
|
||||
// published the preview can only say "Publish to preview", so Preview is a
|
||||
// menu item then and a toolbar button after.
|
||||
const canOpenAccessDetails =
|
||||
modeConfig[effectiveMode].showAccessDetails &&
|
||||
can(agent, 'manage_access_details');
|
||||
const menuOptions = [
|
||||
...(isPublished
|
||||
? []
|
||||
@@ -1147,8 +1152,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
onClick: () => setPreviewOpen(true),
|
||||
},
|
||||
]),
|
||||
...(modeConfig[effectiveMode].showAccessDetails &&
|
||||
can(agent, 'manage_access_details')
|
||||
...(canOpenAccessDetails
|
||||
? [
|
||||
{
|
||||
label: t('agents.form.buttons.accessDetails'),
|
||||
@@ -1848,7 +1852,11 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
agent={agent}
|
||||
mode={effectiveMode}
|
||||
modalState={agentDetails}
|
||||
setModalState={setAgentDetails}
|
||||
setModalState={(state) => {
|
||||
setAgentDetails(state);
|
||||
if (state === 'INACTIVE') setDetailsOnApiWrites(false);
|
||||
}}
|
||||
openApiWrites={detailsOnApiWrites}
|
||||
onKeyRegenerated={(key) => setAgent((prev) => ({ ...prev, key }))}
|
||||
onConfigChange={(config) => {
|
||||
// The allowlist is saved already: record it on the saved snapshot
|
||||
@@ -1871,6 +1879,15 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
resourceId={agent.id}
|
||||
resourceName={agent.name}
|
||||
onClose={() => setShareModalOpen(false)}
|
||||
onOpenAccessDetails={
|
||||
canOpenAccessDetails
|
||||
? () => {
|
||||
setShareModalOpen(false);
|
||||
setDetailsOnApiWrites(true);
|
||||
setAgentDetails('ACTIVE');
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{uploadModalState === 'ACTIVE' && (
|
||||
|
||||
@@ -94,10 +94,20 @@ describe('AgentUsesSection', () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
const render = async (agent: Agent | null, readerId = 'me') => {
|
||||
const render = async (
|
||||
agent: Agent | null,
|
||||
readerId = 'me',
|
||||
onOpenAccessDetails?: () => void,
|
||||
) => {
|
||||
getAgent.mockResolvedValue(agent ? ok(agent) : { ok: false });
|
||||
act(() => {
|
||||
root.render(<AgentUsesSection agentId="a1" readerId={readerId} />);
|
||||
root.render(
|
||||
<AgentUsesSection
|
||||
agentId="a1"
|
||||
readerId={readerId}
|
||||
onOpenAccessDetails={onOpenAccessDetails}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
await flush();
|
||||
};
|
||||
@@ -449,6 +459,71 @@ describe('AgentUsesSection', () => {
|
||||
expect(container.querySelector('[data-slot="alert"]')).toBeNull();
|
||||
});
|
||||
|
||||
// "Allow" on a connector is the in-chat permission; the badge is about
|
||||
// the agent's own API write allowlist, so only that list clears it.
|
||||
it('drops the badge and the note once the write is on the allowlist', async () => {
|
||||
const linear = item({
|
||||
id: 'lin',
|
||||
name: 'Linear',
|
||||
connection: { id: 'c1', connector_key: 'mcp:linear', name: 'Linear' },
|
||||
credential_mode: 'owner',
|
||||
owner_credential_writes: ['create_issue'],
|
||||
});
|
||||
await render(agentWith([linear]));
|
||||
await open();
|
||||
expect(rowOf('Linear')?.textContent).toContain(`${K}.writesOff`);
|
||||
|
||||
act(() => root.unmount());
|
||||
root = createRoot(container);
|
||||
await render(
|
||||
agentWith([linear], {
|
||||
config: { api_write_allowlist: ['LIN:create_issue'] },
|
||||
}),
|
||||
);
|
||||
await open();
|
||||
expect(rowOf('Linear')?.textContent).not.toContain(`${K}.writes`);
|
||||
expect(container.querySelector('[data-slot="alert"]')).toBeNull();
|
||||
});
|
||||
|
||||
it('opens Access details from the note for the owner', async () => {
|
||||
const openDetails = vi.fn();
|
||||
await render(
|
||||
agentWith([
|
||||
item({ id: 't1', name: 'Blocked', owner_credential_writes: ['x'] }),
|
||||
]),
|
||||
'me',
|
||||
openDetails,
|
||||
);
|
||||
await open();
|
||||
const button = Array.from(
|
||||
container.querySelectorAll<HTMLButtonElement>(
|
||||
'[data-slot="alert"] button',
|
||||
),
|
||||
).find((b) => b.textContent === `${K}.openAccessDetails`);
|
||||
expect(button).toBeDefined();
|
||||
act(() => button!.click());
|
||||
expect(openDetails).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('offers no Access details button to an editor or without a way there', async () => {
|
||||
const blocked = [
|
||||
item({ id: 't1', name: 'Blocked', owner_credential_writes: ['x'] }),
|
||||
];
|
||||
await render(
|
||||
agentWith(blocked, { access: 'editor', allowed_actions: ['edit'] }),
|
||||
'me',
|
||||
vi.fn(),
|
||||
);
|
||||
await open();
|
||||
expect(container.querySelector('[data-slot="alert"] button')).toBeNull();
|
||||
|
||||
act(() => root.unmount());
|
||||
root = createRoot(container);
|
||||
await render(agentWith(blocked));
|
||||
await open();
|
||||
expect(container.querySelector('[data-slot="alert"] button')).toBeNull();
|
||||
});
|
||||
|
||||
it('includes the resources of a workflow agent’s nodes', async () => {
|
||||
getWorkflow.mockResolvedValue(
|
||||
ok({
|
||||
|
||||
@@ -25,6 +25,12 @@ type AgentUsesSectionProps = {
|
||||
agentId: string;
|
||||
/** The reader's user id, to say "your" for what runs as them. */
|
||||
readerId?: string;
|
||||
/**
|
||||
* Opens the agent's Access details, where its owner allows changes
|
||||
* through the API, widget and public links. Without it the note only
|
||||
* says where to go.
|
||||
*/
|
||||
onOpenAccessDetails?: () => void;
|
||||
};
|
||||
|
||||
const TYPE_ICONS: Record<ResourceState['type'], typeof Wrench> = {
|
||||
@@ -56,11 +62,14 @@ function blockedWrites(item: ResourceState, allowlist: string[]): string[] {
|
||||
* writes on stored credentials that aren't in the API write allowlist is
|
||||
* marked (all or some of them), since API and widget users, and public-link
|
||||
* users on the owner's accounts, can't make those changes; so is a tool an
|
||||
* admin allows no changes through.
|
||||
* admin allows no changes through. That allowlist is not the connector's
|
||||
* Allow (the in-chat permission), so the note says so and, for the owner,
|
||||
* opens Access details where the list is set.
|
||||
*/
|
||||
export default function AgentUsesSection({
|
||||
agentId,
|
||||
readerId,
|
||||
onOpenAccessDetails,
|
||||
}: AgentUsesSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const loaded = useAgentResourceStates(agentId);
|
||||
@@ -222,6 +231,20 @@ export default function AgentUsesSection({
|
||||
<Alert variant="warning">
|
||||
<TriangleAlert />
|
||||
<AlertDescription>{writesNote}</AlertDescription>
|
||||
{/* Only the owner sets the allowlist. */}
|
||||
{ownerReads && onOpenAccessDetails && (
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
shape="pill"
|
||||
variant="outline"
|
||||
onClick={onOpenAccessDetails}
|
||||
>
|
||||
{t(`${K}.openAccessDetails`)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -270,6 +270,8 @@ function WorkflowBuilderInner() {
|
||||
const [deleteConfirmation, setDeleteConfirmation] =
|
||||
useState<ActiveState>('INACTIVE');
|
||||
const [agentDetails, setAgentDetails] = useState<ActiveState>('INACTIVE');
|
||||
// Access details opened from Share to allow changes: its allowlist unfolds.
|
||||
const [detailsOnApiWrites, setDetailsOnApiWrites] = useState(false);
|
||||
const [shareModalOpen, setShareModalOpen] = useState(false);
|
||||
const [isDeletingAgent, setIsDeletingAgent] = useState(false);
|
||||
const [currentAgent, setCurrentAgent] = useState<Agent>(
|
||||
@@ -2197,6 +2199,15 @@ function WorkflowBuilderInner() {
|
||||
resourceId={effectiveAgentId}
|
||||
resourceName={workflowName}
|
||||
onClose={() => setShareModalOpen(false)}
|
||||
onOpenAccessDetails={
|
||||
canManageAgent && can(currentAgent, 'manage_access_details')
|
||||
? () => {
|
||||
setShareModalOpen(false);
|
||||
setDetailsOnApiWrites(true);
|
||||
setAgentDetails('ACTIVE');
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{canManageAgent && (
|
||||
@@ -2204,7 +2215,11 @@ function WorkflowBuilderInner() {
|
||||
agent={agentForDetails}
|
||||
mode="edit"
|
||||
modalState={agentDetails}
|
||||
setModalState={setAgentDetails}
|
||||
setModalState={(state) => {
|
||||
setAgentDetails(state);
|
||||
if (state === 'INACTIVE') setDetailsOnApiWrites(false);
|
||||
}}
|
||||
openApiWrites={detailsOnApiWrites}
|
||||
onKeyRegenerated={(key) =>
|
||||
setCurrentAgent((prev) => ({ ...prev, key }))
|
||||
}
|
||||
|
||||
@@ -767,9 +767,9 @@
|
||||
"title": "Was dieser Agent nutzt",
|
||||
"intro": "Alle, die diesen Agenten nutzen, erhalten diese Elemente, jeweils mit dem angezeigten Zugriff.",
|
||||
"stopped": "Gestoppt",
|
||||
"writesOff": "API-Änderungen aus",
|
||||
"writesNote": "API-, Widget- und Link-Nutzer können die hier markierten Änderungen erst vornehmen, wenn du sie unter Zugangsdaten erlaubst.",
|
||||
"writesNoteEditor": "API-, Widget- und Link-Nutzer können die hier markierten Änderungen erst vornehmen, wenn der Eigentümer sie unter Zugangsdaten erlaubt.",
|
||||
"writesOff": "Nicht über API",
|
||||
"writesNote": "„Erlauben“ bei einem Connector gilt für Chats. Mit „Nicht über API“ markierte Änderungen bleiben für API-, Widget- und Link-Nutzer aus, bis du sie unter Zugangsdaten erlaubst.",
|
||||
"writesNoteEditor": "„Erlauben“ bei einem Connector gilt für Chats. Mit „Nicht über API“ markierte Änderungen bleiben für API-, Widget- und Link-Nutzer aus, bis der Eigentümer sie unter Zugangsdaten erlaubt.",
|
||||
"access": {
|
||||
"you": "Dein Zugriff",
|
||||
"person": "Zugriff von {{person}}",
|
||||
@@ -789,11 +789,12 @@
|
||||
"personCredentials": "Gespeicherte Zugangsdaten von {{person}}",
|
||||
"otherCredentials": "Gespeicherte Zugangsdaten einer anderen Person"
|
||||
},
|
||||
"writesSomeOff": "Einige API-Änderungen aus",
|
||||
"writesSomeOff": "Teils nicht über API",
|
||||
"adminOff": "Änderungen von einem Admin deaktiviert",
|
||||
"writesNoteApi": "API- und Widget-Nutzer können die hier markierten Änderungen erst vornehmen, wenn du sie unter Zugangsdaten erlaubst.",
|
||||
"writesNoteApiEditor": "API- und Widget-Nutzer können die hier markierten Änderungen erst vornehmen, wenn der Eigentümer sie unter Zugangsdaten erlaubt.",
|
||||
"writesNoteMemberTail": "Bei Werkzeugen, bei denen jede Person ihr eigenes Konto nutzt, ändern Link-Nutzer mit ihrem eigenen Konto."
|
||||
"writesNoteApi": "„Erlauben“ bei einem Connector gilt für Chats. Mit „Nicht über API“ markierte Änderungen bleiben für API- und Widget-Nutzer aus, bis du sie unter Zugangsdaten erlaubst.",
|
||||
"writesNoteApiEditor": "„Erlauben“ bei einem Connector gilt für Chats. Mit „Nicht über API“ markierte Änderungen bleiben für API- und Widget-Nutzer aus, bis der Eigentümer sie unter Zugangsdaten erlaubt.",
|
||||
"writesNoteMemberTail": "Bei Werkzeugen, bei denen jede Person ihr eigenes Konto nutzt, ändern Link-Nutzer mit ihrem eigenen Konto.",
|
||||
"openAccessDetails": "Zugangsdaten öffnen"
|
||||
}
|
||||
},
|
||||
"memberCount_one": "{{formatted}} Mitglied",
|
||||
@@ -1462,7 +1463,7 @@
|
||||
"saveFailed": "Die Berechtigung konnte nicht gespeichert werden. Versuche es erneut.",
|
||||
"groupLabel": "Berechtigung für alles unter {{group}}",
|
||||
"fold": "Aktionen ausblenden",
|
||||
"hint": "Vorher fragen: Im Chat fragt DocsGPT dich, bevor die Aktion ausgeführt wird. Über die API oder das Widget eines Agenten kann niemand antworten, daher werden diese Aktionen abgelehnt, außer die API-Schreibfreigabe des Agenten enthält sie.",
|
||||
"hint": "„Erlauben“ und „Vorher fragen“ gelten im Chat; bei „Vorher fragen“ fragt DocsGPT dich, bevor die Aktion ausgeführt wird. Änderungen über die API, das Widget oder den öffentlichen Link eines Agenten erlaubst du pro Agent unter seinen Zugangsdaten.",
|
||||
"actionCount_one": "{{formatted}} Aktion",
|
||||
"actionCount_other": "{{formatted}} Aktionen",
|
||||
"customize_one": "Aktion einzeln festlegen",
|
||||
|
||||
@@ -773,9 +773,9 @@
|
||||
"title": "What this agent uses",
|
||||
"intro": "Everyone who uses this agent gets these, each with the access shown.",
|
||||
"stopped": "Stopped",
|
||||
"writesOff": "API changes off",
|
||||
"writesNote": "API, widget and public-link users can't make the changes marked here until you allow them in Access Details.",
|
||||
"writesNoteEditor": "API, widget and public-link users can't make the changes marked here until the owner allows them in Access Details.",
|
||||
"writesOff": "Not via API",
|
||||
"writesNote": "“Allow” on a connector applies to chats. Changes marked “Not via API” stay off for API, widget and public-link users until you allow them in Access Details.",
|
||||
"writesNoteEditor": "“Allow” on a connector applies to chats. Changes marked “Not via API” stay off for API, widget and public-link users until the owner allows them in Access Details.",
|
||||
"access": {
|
||||
"you": "Your access",
|
||||
"person": "{{person}}'s access",
|
||||
@@ -795,11 +795,12 @@
|
||||
"personCredentials": "{{person}}'s saved credentials",
|
||||
"otherCredentials": "Someone else's saved credentials"
|
||||
},
|
||||
"writesSomeOff": "Some API changes off",
|
||||
"writesSomeOff": "Some not via API",
|
||||
"adminOff": "Changes off by an admin",
|
||||
"writesNoteApi": "API and widget users can't make the changes marked here until you allow them in Access Details.",
|
||||
"writesNoteApiEditor": "API and widget users can't make the changes marked here until the owner allows them in Access Details.",
|
||||
"writesNoteMemberTail": "On tools where each person uses their own account, public-link users make changes with their own account."
|
||||
"writesNoteApi": "“Allow” on a connector applies to chats. Changes marked “Not via API” stay off for API and widget users until you allow them in Access Details.",
|
||||
"writesNoteApiEditor": "“Allow” on a connector applies to chats. Changes marked “Not via API” stay off for API and widget users until the owner allows them in Access Details.",
|
||||
"writesNoteMemberTail": "On tools where each person uses their own account, public-link users make changes with their own account.",
|
||||
"openAccessDetails": "Open Access Details"
|
||||
}
|
||||
},
|
||||
"memberCount_one": "{{formatted}} member",
|
||||
@@ -1468,7 +1469,7 @@
|
||||
"saveFailed": "Could not save the permission. Try again.",
|
||||
"groupLabel": "Permission for all of {{group}}",
|
||||
"fold": "Hide actions",
|
||||
"hint": "Ask first: in chat, DocsGPT asks you before running the action. Over an agent's API or widget nobody can answer, so those actions are refused unless the agent's API write allowlist includes them.",
|
||||
"hint": "“Allow” and “Ask first” apply in chat; with “Ask first”, DocsGPT asks you before running the action. Changes through an agent's API, widget or public link are allowed per agent, in its Access Details.",
|
||||
"actionCount_one": "{{formatted}} action",
|
||||
"actionCount_other": "{{formatted}} actions",
|
||||
"customize_one": "Customize the action",
|
||||
|
||||
@@ -767,9 +767,9 @@
|
||||
"title": "Qué usa este agente",
|
||||
"intro": "Todos los que usan este agente reciben estos elementos, cada uno con el acceso indicado.",
|
||||
"stopped": "Detenido",
|
||||
"writesOff": "Cambios por API desactivados",
|
||||
"writesNote": "Los usuarios de la API, el widget y el enlace público no pueden hacer los cambios marcados aquí hasta que los permitas en Access Details.",
|
||||
"writesNoteEditor": "Los usuarios de la API, el widget y el enlace público no pueden hacer los cambios marcados aquí hasta que el propietario los permita en Access Details.",
|
||||
"writesOff": "No por API",
|
||||
"writesNote": "«Permitir» en un conector se aplica a los chats. Los cambios marcados «No por API» siguen desactivados para los usuarios de la API, el widget y el enlace público hasta que los permitas en Access Details.",
|
||||
"writesNoteEditor": "«Permitir» en un conector se aplica a los chats. Los cambios marcados «No por API» siguen desactivados para los usuarios de la API, el widget y el enlace público hasta que el propietario los permita en Access Details.",
|
||||
"access": {
|
||||
"you": "Tu acceso",
|
||||
"person": "Acceso de {{person}}",
|
||||
@@ -789,11 +789,12 @@
|
||||
"personCredentials": "Credenciales guardadas de {{person}}",
|
||||
"otherCredentials": "Credenciales guardadas de otra persona"
|
||||
},
|
||||
"writesSomeOff": "Algunos cambios por API desactivados",
|
||||
"writesSomeOff": "Algunos no por API",
|
||||
"adminOff": "Cambios desactivados por un administrador",
|
||||
"writesNoteApi": "Los usuarios de la API y el widget no pueden hacer los cambios marcados aquí hasta que los permitas en Access Details.",
|
||||
"writesNoteApiEditor": "Los usuarios de la API y el widget no pueden hacer los cambios marcados aquí hasta que el propietario los permita en Access Details.",
|
||||
"writesNoteMemberTail": "En las herramientas donde cada persona usa su propia cuenta, los usuarios del enlace público hacen cambios con su propia cuenta."
|
||||
"writesNoteApi": "«Permitir» en un conector se aplica a los chats. Los cambios marcados «No por API» siguen desactivados para los usuarios de la API y el widget hasta que los permitas en Access Details.",
|
||||
"writesNoteApiEditor": "«Permitir» en un conector se aplica a los chats. Los cambios marcados «No por API» siguen desactivados para los usuarios de la API y el widget hasta que el propietario los permita en Access Details.",
|
||||
"writesNoteMemberTail": "En las herramientas donde cada persona usa su propia cuenta, los usuarios del enlace público hacen cambios con su propia cuenta.",
|
||||
"openAccessDetails": "Abrir Access Details"
|
||||
}
|
||||
},
|
||||
"memberCount_one": "{{formatted}} miembro",
|
||||
@@ -1462,7 +1463,7 @@
|
||||
"saveFailed": "No se pudo guardar el permiso. Inténtalo de nuevo.",
|
||||
"groupLabel": "Permiso para todo en {{group}}",
|
||||
"fold": "Ocultar acciones",
|
||||
"hint": "Preguntar antes: en el chat, DocsGPT te pregunta antes de ejecutar la acción. En la API o el widget de un agente nadie puede responder, así que esas acciones se rechazan salvo que la lista de escrituras por API del agente las incluya.",
|
||||
"hint": "«Permitir» y «Preguntar antes» se aplican en el chat; con «Preguntar antes», DocsGPT te pregunta antes de ejecutar la acción. Los cambios por la API, el widget o el enlace público de un agente se permiten por agente, en su Access Details.",
|
||||
"actionCount_one": "{{formatted}} acción",
|
||||
"actionCount_other": "{{formatted}} acciones",
|
||||
"customize_one": "Ajustar la acción",
|
||||
|
||||
@@ -766,9 +766,9 @@
|
||||
"title": "このエージェントが使うもの",
|
||||
"intro": "このエージェントを使うすべての人が、表示されたアクセスでこれらを使います。",
|
||||
"stopped": "停止中",
|
||||
"writesOff": "API からの変更はオフ",
|
||||
"writesNote": "API、ウィジェット、公開リンクの利用者は、ここで示された変更を行えません。Access Details で許可すると行えます。",
|
||||
"writesNoteEditor": "API、ウィジェット、公開リンクの利用者は、ここで示された変更を行えません。オーナーが Access Details で許可すると行えます。",
|
||||
"writesOff": "API 経由は不可",
|
||||
"writesNote": "コネクタの「許可」はチャットに適用されます。「API 経由は不可」と示された変更は、Access Details で許可するまで、API、ウィジェット、公開リンクの利用者には行えません。",
|
||||
"writesNoteEditor": "コネクタの「許可」はチャットに適用されます。「API 経由は不可」と示された変更は、オーナーが Access Details で許可するまで、API、ウィジェット、公開リンクの利用者には行えません。",
|
||||
"access": {
|
||||
"you": "あなたのアクセス",
|
||||
"person": "{{person}} のアクセス",
|
||||
@@ -788,11 +788,12 @@
|
||||
"personCredentials": "{{person}} の保存済み認証情報",
|
||||
"otherCredentials": "他の人の保存済み認証情報"
|
||||
},
|
||||
"writesSomeOff": "一部の API からの変更はオフ",
|
||||
"writesSomeOff": "一部 API 経由は不可",
|
||||
"adminOff": "管理者が変更をオフにしています",
|
||||
"writesNoteApi": "API とウィジェットの利用者は、ここで示された変更を行えません。Access Details で許可すると行えます。",
|
||||
"writesNoteApiEditor": "API とウィジェットの利用者は、ここで示された変更を行えません。オーナーが Access Details で許可すると行えます。",
|
||||
"writesNoteMemberTail": "各自のアカウントを使うツールでは、公開リンクの利用者は自分のアカウントで変更します。"
|
||||
"writesNoteApi": "コネクタの「許可」はチャットに適用されます。「API 経由は不可」と示された変更は、Access Details で許可するまで、API とウィジェットの利用者には行えません。",
|
||||
"writesNoteApiEditor": "コネクタの「許可」はチャットに適用されます。「API 経由は不可」と示された変更は、オーナーが Access Details で許可するまで、API とウィジェットの利用者には行えません。",
|
||||
"writesNoteMemberTail": "各自のアカウントを使うツールでは、公開リンクの利用者は自分のアカウントで変更します。",
|
||||
"openAccessDetails": "Access Details を開く"
|
||||
}
|
||||
},
|
||||
"memberCount_one": "{{formatted}}人のメンバー",
|
||||
@@ -1461,7 +1462,7 @@
|
||||
"saveFailed": "権限を保存できませんでした。もう一度お試しください。",
|
||||
"groupLabel": "{{group}} すべての権限",
|
||||
"fold": "アクションを隠す",
|
||||
"hint": "事前に確認: チャットでは、DocsGPT がアクションの実行前に確認します。エージェントの API やウィジェットでは誰も応答できないため、エージェントの API 書き込み許可リストに含まれていない限り、これらのアクションは拒否されます。",
|
||||
"hint": "「許可」と「事前に確認」はチャットに適用されます。「事前に確認」では、DocsGPT がアクションの実行前に確認します。エージェントの API、ウィジェット、公開リンクからの変更は、エージェントごとに Access Details で許可します。",
|
||||
"actionCount_other": "{{formatted}} 件のアクション",
|
||||
"customize_other": "{{formatted}} 件を個別に設定"
|
||||
},
|
||||
|
||||
@@ -811,9 +811,9 @@
|
||||
"title": "Что использует этот агент",
|
||||
"intro": "Все, кто пользуется этим агентом, получают это, каждое — с указанным доступом.",
|
||||
"stopped": "Остановлено",
|
||||
"writesOff": "Изменения через API выключены",
|
||||
"writesNote": "Пользователи API, виджета и публичной ссылки не могут вносить отмеченные здесь изменения, пока вы не разрешите их в Access Details.",
|
||||
"writesNoteEditor": "Пользователи API, виджета и публичной ссылки не могут вносить отмеченные здесь изменения, пока владелец не разрешит их в Access Details.",
|
||||
"writesOff": "Не через API",
|
||||
"writesNote": "«Разрешать» у коннектора действует в чатах. Изменения с отметкой «Не через API» остаются выключены для пользователей API, виджета и публичной ссылки, пока вы не разрешите их в Access Details.",
|
||||
"writesNoteEditor": "«Разрешать» у коннектора действует в чатах. Изменения с отметкой «Не через API» остаются выключены для пользователей API, виджета и публичной ссылки, пока владелец не разрешит их в Access Details.",
|
||||
"access": {
|
||||
"you": "Ваш доступ",
|
||||
"person": "Доступ {{person}}",
|
||||
@@ -833,11 +833,12 @@
|
||||
"personCredentials": "Сохранённые учётные данные {{person}}",
|
||||
"otherCredentials": "Сохранённые учётные данные другого пользователя"
|
||||
},
|
||||
"writesSomeOff": "Часть изменений через API выключена",
|
||||
"writesSomeOff": "Частично не через API",
|
||||
"adminOff": "Изменения выключены администратором",
|
||||
"writesNoteApi": "Пользователи API и виджета не могут вносить отмеченные здесь изменения, пока вы не разрешите их в Access Details.",
|
||||
"writesNoteApiEditor": "Пользователи API и виджета не могут вносить отмеченные здесь изменения, пока владелец не разрешит их в Access Details.",
|
||||
"writesNoteMemberTail": "В инструментах, где у каждого свой аккаунт, пользователи публичной ссылки вносят изменения через свой аккаунт."
|
||||
"writesNoteApi": "«Разрешать» у коннектора действует в чатах. Изменения с отметкой «Не через API» остаются выключены для пользователей API и виджета, пока вы не разрешите их в Access Details.",
|
||||
"writesNoteApiEditor": "«Разрешать» у коннектора действует в чатах. Изменения с отметкой «Не через API» остаются выключены для пользователей API и виджета, пока владелец не разрешит их в Access Details.",
|
||||
"writesNoteMemberTail": "В инструментах, где у каждого свой аккаунт, пользователи публичной ссылки вносят изменения через свой аккаунт.",
|
||||
"openAccessDetails": "Открыть Access Details"
|
||||
}
|
||||
},
|
||||
"memberCount_one": "{{formatted}} участник",
|
||||
@@ -1526,7 +1527,7 @@
|
||||
"saveFailed": "Не удалось сохранить разрешение. Попробуйте ещё раз.",
|
||||
"groupLabel": "Разрешение для всего в группе «{{group}}»",
|
||||
"fold": "Скрыть действия",
|
||||
"hint": "Спрашивать: в чате DocsGPT спросит вас перед выполнением действия. Через API или виджет агента ответить некому, поэтому такие действия отклоняются, если их нет в списке разрешённых записей API агента.",
|
||||
"hint": "«Разрешать» и «Спрашивать» действуют в чате: при «Спрашивать» DocsGPT спросит вас перед выполнением действия. Изменения через API, виджет или публичную ссылку агента разрешаются для каждого агента отдельно, в его Access Details.",
|
||||
"actionCount_one": "{{formatted}} действие",
|
||||
"actionCount_few": "{{formatted}} действия",
|
||||
"actionCount_many": "{{formatted}} действий",
|
||||
|
||||
@@ -766,9 +766,9 @@
|
||||
"title": "此代理使用的內容",
|
||||
"intro": "使用此代理的每個人都會用到這些內容,各自依所示的存取權限執行。",
|
||||
"stopped": "已停止",
|
||||
"writesOff": "API 變更已關閉",
|
||||
"writesNote": "在你於 Access Details 中允許之前,API、小工具和公開連結的使用者無法進行此處標示的變更。",
|
||||
"writesNoteEditor": "在擁有者於 Access Details 中允許之前,API、小工具和公開連結的使用者無法進行此處標示的變更。",
|
||||
"writesOff": "無法透過 API",
|
||||
"writesNote": "連接器上的「允許」適用於聊天。標示為「無法透過 API」的變更,在你於 Access Details 中允許之前,API、小工具和公開連結的使用者無法進行。",
|
||||
"writesNoteEditor": "連接器上的「允許」適用於聊天。標示為「無法透過 API」的變更,在擁有者於 Access Details 中允許之前,API、小工具和公開連結的使用者無法進行。",
|
||||
"access": {
|
||||
"you": "你的存取權限",
|
||||
"person": "{{person}} 的存取權限",
|
||||
@@ -788,11 +788,12 @@
|
||||
"personCredentials": "{{person}} 儲存的憑證",
|
||||
"otherCredentials": "其他人儲存的憑證"
|
||||
},
|
||||
"writesSomeOff": "部分 API 變更已關閉",
|
||||
"writesSomeOff": "部分無法透過 API",
|
||||
"adminOff": "管理員已關閉變更",
|
||||
"writesNoteApi": "在你於 Access Details 中允許之前,API 和小工具的使用者無法進行此處標示的變更。",
|
||||
"writesNoteApiEditor": "在擁有者於 Access Details 中允許之前,API 和小工具的使用者無法進行此處標示的變更。",
|
||||
"writesNoteMemberTail": "在每個人使用自己帳號的工具上,公開連結的使用者以自己的帳號進行變更。"
|
||||
"writesNoteApi": "連接器上的「允許」適用於聊天。標示為「無法透過 API」的變更,在你於 Access Details 中允許之前,API 和小工具的使用者無法進行。",
|
||||
"writesNoteApiEditor": "連接器上的「允許」適用於聊天。標示為「無法透過 API」的變更,在擁有者於 Access Details 中允許之前,API 和小工具的使用者無法進行。",
|
||||
"writesNoteMemberTail": "在每個人使用自己帳號的工具上,公開連結的使用者以自己的帳號進行變更。",
|
||||
"openAccessDetails": "開啟 Access Details"
|
||||
}
|
||||
},
|
||||
"memberCount_one": "{{formatted}} 位成員",
|
||||
@@ -1461,7 +1462,7 @@
|
||||
"saveFailed": "無法儲存權限。請再試一次。",
|
||||
"groupLabel": "{{group}} 的全部權限",
|
||||
"fold": "隱藏動作",
|
||||
"hint": "先詢問:在聊天中,DocsGPT 會在執行動作前詢問你。透過代理的 API 或小工具時沒有人能回答,因此除非代理的 API 寫入允許清單包含這些動作,否則它們會被拒絕。",
|
||||
"hint": "「允許」和「先詢問」適用於聊天:選擇「先詢問」時,DocsGPT 會在執行動作前詢問你。透過代理的 API、小工具或公開連結進行的變更,需在該代理的 Access Details 中逐一代理允許。",
|
||||
"actionCount_other": "{{formatted}} 個動作",
|
||||
"customize_other": "分別設定 {{formatted}} 個"
|
||||
},
|
||||
|
||||
@@ -766,9 +766,9 @@
|
||||
"title": "此智能体使用的内容",
|
||||
"intro": "使用此智能体的每个人都会用到这些内容,各自按所示的访问权限运行。",
|
||||
"stopped": "已停止",
|
||||
"writesOff": "API 更改已关闭",
|
||||
"writesNote": "在你于 Access Details 中允许之前,API、小组件和公开链接的用户无法进行此处标记的更改。",
|
||||
"writesNoteEditor": "在所有者于 Access Details 中允许之前,API、小组件和公开链接的用户无法进行此处标记的更改。",
|
||||
"writesOff": "不可通过 API",
|
||||
"writesNote": "连接器上的“允许”适用于聊天。标记为“不可通过 API”的更改,在你于 Access Details 中允许之前,API、小组件和公开链接的用户无法进行。",
|
||||
"writesNoteEditor": "连接器上的“允许”适用于聊天。标记为“不可通过 API”的更改,在所有者于 Access Details 中允许之前,API、小组件和公开链接的用户无法进行。",
|
||||
"access": {
|
||||
"you": "你的访问权限",
|
||||
"person": "{{person}} 的访问权限",
|
||||
@@ -788,11 +788,12 @@
|
||||
"personCredentials": "{{person}} 保存的凭据",
|
||||
"otherCredentials": "其他人保存的凭据"
|
||||
},
|
||||
"writesSomeOff": "部分 API 更改已关闭",
|
||||
"writesSomeOff": "部分不可通过 API",
|
||||
"adminOff": "管理员已关闭更改",
|
||||
"writesNoteApi": "在你于 Access Details 中允许之前,API 和小组件的用户无法进行此处标记的更改。",
|
||||
"writesNoteApiEditor": "在所有者于 Access Details 中允许之前,API 和小组件的用户无法进行此处标记的更改。",
|
||||
"writesNoteMemberTail": "在每个人使用自己账号的工具上,公开链接的用户用自己的账号进行更改。"
|
||||
"writesNoteApi": "连接器上的“允许”适用于聊天。标记为“不可通过 API”的更改,在你于 Access Details 中允许之前,API 和小组件的用户无法进行。",
|
||||
"writesNoteApiEditor": "连接器上的“允许”适用于聊天。标记为“不可通过 API”的更改,在所有者于 Access Details 中允许之前,API 和小组件的用户无法进行。",
|
||||
"writesNoteMemberTail": "在每个人使用自己账号的工具上,公开链接的用户用自己的账号进行更改。",
|
||||
"openAccessDetails": "打开 Access Details"
|
||||
}
|
||||
},
|
||||
"memberCount_one": "{{formatted}} 名成员",
|
||||
@@ -1461,7 +1462,7 @@
|
||||
"saveFailed": "无法保存权限。请重试。",
|
||||
"groupLabel": "{{group}} 的全部权限",
|
||||
"fold": "隐藏操作",
|
||||
"hint": "先询问:在聊天中,DocsGPT 会在执行操作前询问你。通过智能体的 API 或小组件时没有人能回答,因此除非智能体的 API 写入允许列表包含这些操作,否则它们会被拒绝。",
|
||||
"hint": "“允许”和“先询问”适用于聊天:选择“先询问”时,DocsGPT 会在执行操作前询问你。通过智能体的 API、小组件或公开链接进行的更改,需在该智能体的 Access Details 中逐个智能体允许。",
|
||||
"actionCount_other": "{{formatted}} 个操作",
|
||||
"customize_other": "分别设置 {{formatted}} 个"
|
||||
},
|
||||
|
||||
@@ -20,7 +20,9 @@ vi.mock('../api/services/userService', () => ({ default: mocks }));
|
||||
// Tested on its own; it needs the store and the tool list. Here it only has
|
||||
// to show up (or not).
|
||||
vi.mock('../agents/ApiWriteAllowlist', () => ({
|
||||
default: () => <div data-testid="api-write-allowlist" />,
|
||||
default: ({ defaultOpen }: { defaultOpen?: boolean }) => (
|
||||
<div data-testid="api-write-allowlist" data-open={String(!!defaultOpen)} />
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('./ConfirmationModal', () => ({
|
||||
@@ -61,7 +63,7 @@ describe('AgentDetailsModal', () => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
const render = async (agent: Partial<Agent>) => {
|
||||
const render = async (agent: Partial<Agent>, openApiWrites?: boolean) => {
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<AgentDetailsModal
|
||||
@@ -69,6 +71,7 @@ describe('AgentDetailsModal', () => {
|
||||
mode="edit"
|
||||
modalState="ACTIVE"
|
||||
setModalState={() => undefined}
|
||||
openApiWrites={openApiWrites}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
@@ -113,6 +116,13 @@ describe('AgentDetailsModal', () => {
|
||||
expect(allowlist()).not.toBeNull();
|
||||
});
|
||||
|
||||
it('starts it folded, or open when sent to allow changes', async () => {
|
||||
await render({ status: 'published', key: 'k-1', access: 'owner' });
|
||||
expect(allowlist()?.getAttribute('data-open')).toBe('false');
|
||||
await render({ status: 'published', key: 'k-1', access: 'owner' }, true);
|
||||
expect(allowlist()?.getAttribute('data-open')).toBe('true');
|
||||
});
|
||||
|
||||
it('hides it from an editor, even with a key', async () => {
|
||||
await render({
|
||||
status: 'published',
|
||||
|
||||
@@ -41,6 +41,8 @@ type AgentDetailsModalProps = {
|
||||
onConfigChange?: (config: AgentConfig) => void;
|
||||
/** The agent's last saved config, which an allowlist change saves onto. */
|
||||
getSavedConfig?: () => AgentConfig | undefined;
|
||||
/** Open with the API write allowlist unfolded (sent here to allow changes). */
|
||||
openApiWrites?: boolean;
|
||||
};
|
||||
|
||||
export default function AgentDetailsModal({
|
||||
@@ -51,6 +53,7 @@ export default function AgentDetailsModal({
|
||||
onKeyRegenerated,
|
||||
onConfigChange,
|
||||
getSavedConfig,
|
||||
openApiWrites = false,
|
||||
}: AgentDetailsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const token = useSelector(selectToken);
|
||||
@@ -282,6 +285,7 @@ export default function AgentDetailsModal({
|
||||
agent={agent}
|
||||
onConfigChange={onConfigChange}
|
||||
getSavedConfig={getSavedConfig}
|
||||
defaultOpen={openApiWrites}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -95,6 +95,11 @@ type Props = {
|
||||
resourceName?: string;
|
||||
credentials?: ShareCredentials;
|
||||
onClose: () => void;
|
||||
/**
|
||||
* An agent's: opens its Access details (the API write allowlist) from
|
||||
* "What this agent uses". The caller closes this dialog for it.
|
||||
*/
|
||||
onOpenAccessDetails?: () => void;
|
||||
};
|
||||
|
||||
// Member subs (OIDC subs) can be long; there's no display-name endpoint, so we
|
||||
@@ -142,6 +147,7 @@ export default function ShareToTeamModal({
|
||||
resourceName,
|
||||
credentials,
|
||||
onClose,
|
||||
onOpenAccessDetails,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
@@ -1140,7 +1146,11 @@ export default function ShareToTeamModal({
|
||||
with, for the people it is shared with (owners and editors only:
|
||||
viewers never open this dialog). */}
|
||||
{resourceType === 'agent' && (
|
||||
<AgentUsesSection agentId={resourceId} readerId={currentUserId} />
|
||||
<AgentUsesSection
|
||||
agentId={resourceId}
|
||||
readerId={currentUserId}
|
||||
onOpenAccessDetails={onOpenAccessDetails}
|
||||
/>
|
||||
)}
|
||||
|
||||
{accessSettings}
|
||||
|
||||
Reference in new issue
Block a user