diff --git a/src/features/chat/presentation/runtime/permission-sections.ts b/src/features/chat/presentation/runtime/permission-sections.ts
index 7cc5f2a9..f538af1e 100644
--- a/src/features/chat/presentation/runtime/permission-sections.ts
+++ b/src/features/chat/presentation/runtime/permission-sections.ts
@@ -27,27 +27,31 @@ export function runtimePermissionDetails(input: RuntimePermissionSectionsInput):
const config = runtimeConfigOrDefault(input.snapshot.runtimeConfig);
const resolution = resolveRuntimeControls(input.snapshot, config);
return {
- title: permissionPanelTitle(resolution.permissions.scope),
+ title: "Permissions & Approvals",
sections: [
{
- title: "",
- rows: permissionRows(resolution.permissions.effective, resolution.approvalsReviewer.effective, input.vaultPath),
+ title: "Permissions",
+ rows: accessRows(resolution.permissions.effective, input.vaultPath),
+ },
+ {
+ title: "Approvals",
+ rows: approvalRows(resolution.permissions.effective, resolution.approvalsReviewer.effective),
},
],
};
}
-function permissionPanelTitle(scope: "new-thread" | "current-thread"): string {
- return scope === "current-thread" ? "Permissions: Current Thread" : "Permissions: New Thread";
-}
-
-function permissionRows(permissions: RuntimePermissionState, reviewer: string | null, vaultPath: string): RuntimePermissionRow[] {
+function accessRows(permissions: RuntimePermissionState, vaultPath: string): RuntimePermissionRow[] {
return [
{ label: "Profile", value: profileLabel(permissions) },
- ...extendsRows(permissions),
{ label: "Sandbox", value: sandboxLabel(permissions.sandboxPolicy) },
{ label: "Network", value: networkLabel(permissions.sandboxPolicy) },
{ label: "Extra writable roots", value: writableRootsLabel(permissions.sandboxPolicy, vaultPath) },
+ ];
+}
+
+function approvalRows(permissions: RuntimePermissionState, reviewer: string | null): RuntimePermissionRow[] {
+ return [
{ label: "Approval policy", value: approvalPolicyLabel(permissions.approvalPolicy) },
{
label: "Reviewer",
@@ -62,11 +66,6 @@ function profileLabel(permissions: RuntimePermissionState): string {
return "(not reported)";
}
-function extendsRows(permissions: RuntimePermissionState): RuntimePermissionRow[] {
- const extendsProfile = permissions.activePermissionProfile?.extends;
- return extendsProfile ? [{ label: "Extends", value: extendsProfile }] : [];
-}
-
function sandboxLabel(sandbox: RuntimeSandboxPolicy | null): string {
if (!sandbox) return "(not reported)";
switch (sandbox.type) {
diff --git a/src/features/chat/ui/toolbar.tsx b/src/features/chat/ui/toolbar.tsx
index 4bd65dd7..ca0d5e52 100644
--- a/src/features/chat/ui/toolbar.tsx
+++ b/src/features/chat/ui/toolbar.tsx
@@ -195,7 +195,7 @@ function StatusPanel({ model, actions }: { model: ToolbarViewModel; actions: Too
/>
-
+
>
diff --git a/tests/features/chat/panel/surface/projections.test.ts b/tests/features/chat/panel/surface/projections.test.ts
index a1fee6d0..d154b018 100644
--- a/tests/features/chat/panel/surface/projections.test.ts
+++ b/tests/features/chat/panel/surface/projections.test.ts
@@ -141,7 +141,9 @@ describe("chat panel surface projections", () => {
}),
);
- expect(parent.textContent).toContain("Permissions: New Thread");
+ expect(parent.textContent).toContain("Permissions & Approvals");
+ expect(parent.textContent).toContain("Permissions");
+ expect(parent.textContent).toContain("Approvals");
expect(
[...parent.querySelectorAll(".codex-panel__connection-diagnostics-section")].map((section) => section.textContent),
).not.toContain("New thread");
diff --git a/tests/features/chat/ui/toolbar.test.ts b/tests/features/chat/ui/toolbar.test.ts
index a0c998bb..9496c9bc 100644
--- a/tests/features/chat/ui/toolbar.test.ts
+++ b/tests/features/chat/ui/toolbar.test.ts
@@ -168,8 +168,8 @@ describe("Toolbar decisions", () => {
);
expect([...parent.querySelectorAll(".codex-panel__connection-diagnostics-title")].map((title) => title.textContent)).toEqual([
- "Connection",
- "Permissions: Current Thread",
+ "Connection diagnostics",
+ "Permissions & Approvals",
"Codex capabilities",
]);
expect(parent.textContent).toContain("Codex capabilities");
@@ -198,15 +198,20 @@ describe("Toolbar decisions", () => {
toolbarModel({
statusPanelOpen: true,
openPanel: "status",
- runtimePermissionsTitle: "Permissions: Current Thread",
+ runtimePermissionsTitle: "Permissions & Approvals",
runtimePermissions: [
{
- title: "",
+ title: "Permissions",
rows: [
{ label: "Profile", value: ":workspace" },
{ label: "Sandbox", value: "workspace-write" },
{ label: "Network", value: "blocked" },
{ label: "Extra writable roots", value: "Vault" },
+ ],
+ },
+ {
+ title: "Approvals",
+ rows: [
{ label: "Approval policy", value: "on-request" },
{ label: "Reviewer", value: "auto_review" },
],
@@ -217,7 +222,9 @@ describe("Toolbar decisions", () => {
toolbarActions(),
);
- expect(parent.textContent).toContain("Permissions: Current Thread");
+ expect(parent.textContent).toContain("Permissions & Approvals");
+ expect(parent.textContent).toContain("Permissions");
+ expect(parent.textContent).toContain("Approvals");
expect(
[...parent.querySelectorAll(".codex-panel__connection-diagnostics-section")].map((section) => section.textContent),
).not.toContain("Current thread");
@@ -404,8 +411,8 @@ function toolbarModel(overrides: Partial = {}): ToolbarViewMod
openPanel: null,
threads: [{ title: "Thread", threadId: "thread", selected: true, disabled: false, canArchive: true, rename: null }],
connectLabel: "Reconnect",
- runtimePermissionsTitle: "Permissions: Current Thread",
- runtimePermissions: [{ title: "", rows: [{ label: "Thread", value: "(none)" }] }],
+ runtimePermissionsTitle: "Permissions & Approvals",
+ runtimePermissions: [{ title: "Permissions", rows: [{ label: "Thread", value: "(none)" }] }],
diagnostics: [{ title: "Process", rows: [{ label: "Codex App Server", value: "codex-cli/test" }] }],
toolInventory: [{ title: "Tool providers", rows: [{ label: "Tool providers", value: "not loaded", level: "warning" }] }],
...overrides,