mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Add permissions slash command
This commit is contained in:
parent
b74e049360
commit
12189e5ddd
10 changed files with 102 additions and 0 deletions
|
|
@ -129,6 +129,13 @@ export const SLASH_COMMANDS = [
|
|||
surface: "diagnostic",
|
||||
detail: "Show current thread, context, and usage limits.",
|
||||
},
|
||||
{
|
||||
command: "/permissions",
|
||||
usage: "/permissions",
|
||||
argsKind: "none",
|
||||
surface: "diagnostic",
|
||||
detail: "Show current permissions and approval settings.",
|
||||
},
|
||||
{
|
||||
command: "/doctor",
|
||||
usage: "/doctor",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export interface ConversationTurnActionsContext {
|
|||
};
|
||||
runtime: {
|
||||
connectionDiagnosticDetails: () => MessageStreamNoticeSection[];
|
||||
permissionDetails: () => MessageStreamNoticeSection[];
|
||||
modelStatusLines: () => string[];
|
||||
effortStatusLines: () => string[];
|
||||
statusSummaryLines: () => string[];
|
||||
|
|
@ -105,6 +106,7 @@ export function createConversationTurnActions(
|
|||
addStructuredSystemMessage: status.addStructuredSystemMessage,
|
||||
setStatus: status.set,
|
||||
statusSummaryLines: runtime.statusSummaryLines,
|
||||
permissionDetails: runtime.permissionDetails,
|
||||
connectionDiagnosticDetails: runtime.connectionDiagnosticDetails,
|
||||
toolInventoryDetails: runtime.toolInventoryDetails,
|
||||
modelStatusLines: runtime.modelStatusLines,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export interface SlashCommandExecutionPorts {
|
|||
clear: GoalActions["clear"];
|
||||
};
|
||||
statusSummaryLines: () => string[];
|
||||
permissionDetails: () => MessageStreamNoticeSection[];
|
||||
connectionDiagnosticDetails: () => MessageStreamNoticeSection[];
|
||||
toolInventoryDetails: () => MessageStreamNoticeSection[] | Promise<MessageStreamNoticeSection[]>;
|
||||
modelStatusLines: () => string[];
|
||||
|
|
@ -183,6 +184,9 @@ export async function executeSlashCommand(
|
|||
case "status":
|
||||
context.addStructuredSystemMessage("Thread status", detailsFromLines(context.statusSummaryLines()));
|
||||
return;
|
||||
case "permissions":
|
||||
context.addStructuredSystemMessage("Permissions & Approvals", context.permissionDetails());
|
||||
return;
|
||||
case "doctor":
|
||||
context.addStructuredSystemMessage("Connection diagnostics", context.connectionDiagnosticDetails());
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export function createRuntimeBundle(
|
|||
state: () => host.stateStore.getState(),
|
||||
connected: () => input.connection.isConnected(),
|
||||
configuredCommand: () => host.environment.plugin.settingsRef.settings.codexPath(),
|
||||
vaultPath: () => host.environment.plugin.settingsRef.vaultPath,
|
||||
nowMs: () => Date.now(),
|
||||
}),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ export function createTurnBundle(host: ChatPanelTurnHost, input: ChatPanelTurnIn
|
|||
modelStatusLines: runtimeProjection.modelStatusLines,
|
||||
effortStatusLines: runtimeProjection.effortStatusLines,
|
||||
statusSummaryLines: runtimeProjection.statusSummaryLines,
|
||||
permissionDetails: runtimeProjection.permissionDetails,
|
||||
toolInventoryDetails: async () => {
|
||||
if (host.stateStore.getState().connection.serverDiagnostics.toolInventory) {
|
||||
return runtimeProjection.toolInventoryDetails();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { MessageStreamNoticeSection } from "../domain/message-stream/items"
|
|||
import { collaborationModeLabel as formatCollaborationModeLabel } from "../domain/runtime/labels";
|
||||
import type { RuntimeSnapshot } from "../domain/runtime/snapshot";
|
||||
import { appServerDiagnosticSections } from "../presentation/runtime/diagnostic-sections";
|
||||
import { runtimePermissionDetails } from "../presentation/runtime/permission-sections";
|
||||
import {
|
||||
effortStatusLines as buildEffortStatusLines,
|
||||
modelStatusLines as buildModelStatusLines,
|
||||
|
|
@ -13,6 +14,7 @@ import { toolInventoryDiagnosticSections } from "../presentation/runtime/tool-in
|
|||
|
||||
export interface ChatPanelRuntimeProjection {
|
||||
connectionDiagnosticDetails: () => MessageStreamNoticeSection[];
|
||||
permissionDetails: () => MessageStreamNoticeSection[];
|
||||
modelStatusLines: () => string[];
|
||||
effortStatusLines: () => string[];
|
||||
statusSummaryLines: () => string[];
|
||||
|
|
@ -23,12 +25,14 @@ interface ChatPanelRuntimeProjectionInput {
|
|||
state: () => ChatState;
|
||||
connected: () => boolean;
|
||||
configuredCommand: () => string;
|
||||
vaultPath: () => string;
|
||||
nowMs: () => number;
|
||||
}
|
||||
|
||||
export function createChatPanelRuntimeProjection(input: ChatPanelRuntimeProjectionInput): ChatPanelRuntimeProjection {
|
||||
return {
|
||||
connectionDiagnosticDetails: () => connectionDiagnosticDetails(input),
|
||||
permissionDetails: () => permissionDetails(input),
|
||||
modelStatusLines: () => modelStatusLines(input),
|
||||
effortStatusLines: () => effortStatusLines(input),
|
||||
statusSummaryLines: () => statusSummaryLines(input),
|
||||
|
|
@ -79,6 +83,16 @@ function toolInventoryDetails(input: ChatPanelRuntimeProjectionInput): MessageSt
|
|||
return noticeSectionsFromDiagnostics(toolInventoryDiagnosticSections(input.state().connection.serverDiagnostics));
|
||||
}
|
||||
|
||||
function permissionDetails(input: ChatPanelRuntimeProjectionInput): MessageStreamNoticeSection[] {
|
||||
const state = input.state();
|
||||
return noticeSectionsFromDiagnostics(
|
||||
runtimePermissionDetails({
|
||||
snapshot: runtimeSnapshot(state),
|
||||
vaultPath: input.vaultPath(),
|
||||
}).sections,
|
||||
);
|
||||
}
|
||||
|
||||
function noticeSectionsFromDiagnostics(
|
||||
sections: readonly { title: string; rows: readonly { label: string; value: string }[] }[],
|
||||
): MessageStreamNoticeSection[] {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ function context(overrides: Partial<SlashCommandExecutionContext> = {}): SlashCo
|
|||
clear: vi.fn().mockResolvedValue(true),
|
||||
},
|
||||
statusSummaryLines: () => ["status"],
|
||||
permissionDetails: () => [{ title: "Permissions", auditFacts: [{ key: "Profile", value: "read-only" }] }],
|
||||
connectionDiagnosticDetails: () => [{ title: "Process", rows: [{ key: "connection", value: "connected" }] }],
|
||||
toolInventoryDetails: vi.fn(() => [{ title: "Tool providers", auditFacts: [{ key: "codex_apps", value: "github" }] }]),
|
||||
modelStatusLines: () => ["model"],
|
||||
|
|
@ -559,6 +560,28 @@ describe("slash commands", () => {
|
|||
expect(ctx.addSystemMessage).toHaveBeenCalledWith("/status does not take arguments. Usage: /status");
|
||||
});
|
||||
|
||||
it("shows permissions and approvals as shared structured sections", async () => {
|
||||
const details = [
|
||||
{ title: "Permissions", auditFacts: [{ key: "Profile", value: "workspace-write" }] },
|
||||
{ title: "Approvals", auditFacts: [{ key: "Reviewer", value: "auto_review" }] },
|
||||
];
|
||||
const ctx = context({ permissionDetails: () => details });
|
||||
|
||||
await executeSlashCommand("permissions", "", ctx);
|
||||
|
||||
expect(ctx.addSystemMessage).not.toHaveBeenCalled();
|
||||
expect(ctx.addStructuredSystemMessage).toHaveBeenCalledWith("Permissions & Approvals", details);
|
||||
});
|
||||
|
||||
it("rejects /permissions arguments", async () => {
|
||||
const ctx = context();
|
||||
|
||||
await executeSlashCommand("permissions", "anything", ctx);
|
||||
|
||||
expect(ctx.addStructuredSystemMessage).not.toHaveBeenCalled();
|
||||
expect(ctx.addSystemMessage).toHaveBeenCalledWith("/permissions does not take arguments. Usage: /permissions");
|
||||
});
|
||||
|
||||
it("shows doctor diagnostics as shared structured sections", async () => {
|
||||
const details = [{ title: "Process", rows: [{ key: "connection", value: "connected" }] }];
|
||||
const ctx = context({ connectionDiagnosticDetails: () => details });
|
||||
|
|
@ -596,6 +619,7 @@ describe("slash commands", () => {
|
|||
|
||||
it("documents status and doctor as separate commands", () => {
|
||||
expect(slashCommandHelpValue("/status")).toBe("Show current thread, context, and usage limits.");
|
||||
expect(slashCommandHelpValue("/permissions")).toBe("Show current permissions and approval settings.");
|
||||
expect(slashCommandHelpValue("/doctor")).toBe("Show Codex CLI and Codex App Server diagnostics.");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ function createHost(overrides: SlashCommandExecutorHostOverrides = {}) {
|
|||
addStructuredSystemMessage: vi.fn(),
|
||||
setStatus: vi.fn(),
|
||||
statusSummaryLines: () => [],
|
||||
permissionDetails: () => [],
|
||||
connectionDiagnosticDetails: () => [],
|
||||
toolInventoryDetails: vi.fn(() => []),
|
||||
modelStatusLines: () => [],
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ function turnBundleFixture(options: { stateStore?: ReturnType<typeof createChatS
|
|||
modelStatusLines: vi.fn(() => []),
|
||||
effortStatusLines: vi.fn(() => []),
|
||||
statusSummaryLines: vi.fn(() => []),
|
||||
permissionDetails: vi.fn(() => []),
|
||||
toolInventoryDetails: vi.fn(() => [{ title: "Tool providers", auditFacts: [{ key: "codex_apps", value: "github, gmail" }] }]),
|
||||
};
|
||||
const refreshDiagnostics = vi.fn().mockResolvedValue(undefined);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ describe("createChatPanelRuntimeProjection", () => {
|
|||
state: () => state,
|
||||
connected: () => true,
|
||||
configuredCommand: () => "codex",
|
||||
vaultPath: () => "/vault",
|
||||
nowMs: () => 0,
|
||||
});
|
||||
|
||||
|
|
@ -33,6 +34,52 @@ describe("createChatPanelRuntimeProjection", () => {
|
|||
expect(projection.modelStatusLines()).toContain("Mode: Default");
|
||||
expect(projection.effortStatusLines()).toContain("Supported: high");
|
||||
});
|
||||
|
||||
it("builds slash-command permission details from chat state", () => {
|
||||
const state = chatStateWith(chatStateFixture(), {
|
||||
activeThread: { id: "thread-1" },
|
||||
runtime: {
|
||||
active: {
|
||||
activePermissionProfile: { id: "workspace-write", extends: null },
|
||||
sandboxPolicy: {
|
||||
type: "workspaceWrite",
|
||||
writableRoots: ["/vault/Notes"],
|
||||
networkAccess: false,
|
||||
excludeTmpdirEnvVar: false,
|
||||
excludeSlashTmp: false,
|
||||
},
|
||||
approvalPolicy: "on-request",
|
||||
approvalsReviewer: "auto_review",
|
||||
},
|
||||
},
|
||||
});
|
||||
const projection = createChatPanelRuntimeProjection({
|
||||
state: () => state,
|
||||
connected: () => true,
|
||||
configuredCommand: () => "codex",
|
||||
vaultPath: () => "/vault",
|
||||
nowMs: () => 0,
|
||||
});
|
||||
|
||||
expect(projection.permissionDetails()).toEqual([
|
||||
{
|
||||
title: "Permissions",
|
||||
auditFacts: [
|
||||
{ key: "Profile", value: "workspace-write" },
|
||||
{ key: "Sandbox", value: "workspace-write" },
|
||||
{ key: "Network", value: "blocked" },
|
||||
{ key: "Extra writable roots", value: "Vault/Notes" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Approvals",
|
||||
auditFacts: [
|
||||
{ key: "Approval policy", value: "on-request" },
|
||||
{ key: "Reviewer", value: "auto_review" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
function runtimeConfigFixture(config: Record<string, unknown>): RuntimeConfigSnapshot {
|
||||
|
|
|
|||
Loading…
Reference in a new issue