diff --git a/packages/cli/src/commands/permit/allow.ts b/packages/cli/src/commands/permit/allow.ts index d2338cffca..755bfaf4d7 100644 --- a/packages/cli/src/commands/permit/allow.ts +++ b/packages/cli/src/commands/permit/allow.ts @@ -16,7 +16,7 @@ export interface PermissionResponseItem { export const permitResponseSchema: OutputSchema = { idField: "requestId", columns: [ - { header: "REQUEST ID", field: "requestId", width: 12 }, + { header: "REQUEST ID", field: (item) => item.requestId.slice(0, 8), width: 12 }, { header: "AGENT", field: "agentShortId", width: 10 }, { header: "TOOL", field: "name", width: 20 }, { @@ -32,6 +32,21 @@ export const permitResponseSchema: OutputSchema = { ], }; +/** Transform a permission the agent answered to a response item */ +export function toPermissionResponseItem( + agentId: string, + permission: AgentPermissionRequest, + result: "allowed" | "denied", +): PermissionResponseItem { + return { + requestId: permission.id, + agentId, + agentShortId: agentId.slice(0, 7), + name: permission.name, + result, + }; +} + export type PermitAllowResult = ListResult; export interface PermitAllowOptions extends CommandOptions { @@ -118,13 +133,7 @@ export async function runAllowCommand( behavior: "allow", ...(updatedInput ? { updatedInput } : {}), }); - return { - requestId: permission.id.slice(0, 8), - agentId: resolvedAgentId, - agentShortId: resolvedAgentId.slice(0, 7), - name: permission.name, - result: "allowed", - }; + return toPermissionResponseItem(resolvedAgentId, permission, "allowed"); }), ); diff --git a/packages/cli/src/commands/permit/deny.ts b/packages/cli/src/commands/permit/deny.ts index c0d4707f35..fe6c619e32 100644 --- a/packages/cli/src/commands/permit/deny.ts +++ b/packages/cli/src/commands/permit/deny.ts @@ -2,7 +2,11 @@ import type { Command } from "commander"; import type { AgentPermissionRequest } from "@getpaseo/protocol/agent-types"; import { connectToDaemon } from "../../utils/client.js"; import type { CommandOptions, ListResult, CommandError } from "../../output/index.js"; -import { permitResponseSchema, type PermissionResponseItem } from "./allow.js"; +import { + permitResponseSchema, + toPermissionResponseItem, + type PermissionResponseItem, +} from "./allow.js"; export type PermitDenyResult = ListResult; @@ -83,13 +87,7 @@ export async function runDenyCommand( ...(options.message ? { message: options.message } : {}), ...(options.interrupt ? { interrupt: true } : {}), }); - return { - requestId: permission.id.slice(0, 8), - agentId: resolvedAgentId, - agentShortId: resolvedAgentId.slice(0, 7), - name: permission.name, - result: "denied", - }; + return toPermissionResponseItem(resolvedAgentId, permission, "denied"); }), ); diff --git a/packages/cli/src/commands/permit/ls.ts b/packages/cli/src/commands/permit/ls.ts index b04c8d9f58..ca4543d6cf 100644 --- a/packages/cli/src/commands/permit/ls.ts +++ b/packages/cli/src/commands/permit/ls.ts @@ -18,7 +18,7 @@ export const permitLsSchema: OutputSchema = { idField: "id", columns: [ { header: "AGENT", field: "agentShortId", width: 12 }, - { header: "REQ_ID", field: "id", width: 12 }, + { header: "REQ_ID", field: (item) => item.id.slice(0, 8), width: 12 }, { header: "TOOL", field: "name", width: 20 }, { header: "DESCRIPTION", field: "description", width: 50 }, ], @@ -30,7 +30,7 @@ function toListItem( permission: AgentPermissionRequest, ): PermissionListItem { return { - id: permission.id.slice(0, 8), + id: permission.id, agentId: agent.id, agentShortId: agent.id.slice(0, 7), name: permission.name, @@ -40,6 +40,22 @@ function toListItem( export type PermitLsResult = ListResult; +/** Collect the pending permissions of every agent */ +export function listPendingPermissions(agents: AgentSnapshotPayload[]): PermitLsResult { + const items: PermissionListItem[] = []; + for (const agent of agents) { + for (const permission of agent.pendingPermissions ?? []) { + items.push(toListItem(agent, permission)); + } + } + + return { + type: "list", + data: items, + schema: permitLsSchema, + }; +} + export interface PermitLsOptions extends CommandOptions { host?: string; } @@ -55,21 +71,7 @@ export async function runLsCommand( const agents = agentsPayload.entries.map((entry) => entry.agent); await client.close(); - // Collect all pending permissions from all agents - const items: PermissionListItem[] = []; - for (const agent of agents) { - if (agent.pendingPermissions && agent.pendingPermissions.length > 0) { - for (const permission of agent.pendingPermissions) { - items.push(toListItem(agent, permission)); - } - } - } - - return { - type: "list", - data: items, - schema: permitLsSchema, - }; + return listPendingPermissions(agents); } catch (err) { await client.close().catch(() => {}); const message = err instanceof Error ? err.message : String(err); diff --git a/packages/cli/src/commands/permit/permit-output.test.ts b/packages/cli/src/commands/permit/permit-output.test.ts new file mode 100644 index 0000000000..b77989f697 --- /dev/null +++ b/packages/cli/src/commands/permit/permit-output.test.ts @@ -0,0 +1,45 @@ +import type { AgentPermissionRequest } from "@getpaseo/protocol/agent-types"; +import type { AgentSnapshotPayload } from "@getpaseo/protocol/messages"; +import { describe, expect, it } from "vitest"; +import { render } from "../../output/index.js"; +import { permitResponseSchema, toPermissionResponseItem } from "./allow.js"; +import { listPendingPermissions } from "./ls.js"; + +const agentId = "2d85f428-fdaf-4d98-906f-c583de2b805f"; + +function permissionRequest(id: string): AgentPermissionRequest { + return { id, provider: "claude", name: "Write", kind: "tool" }; +} + +const writeRequest = permissionRequest("permission-cc93510e-a45d-4c4e-a0bf-fc2821f82783"); +const editRequest = permissionRequest("permission-654ff862-b312-4f6e-8aac-427421ff65eb"); + +const agent = { + id: agentId, + pendingPermissions: [writeRequest, editRequest], +} as AgentSnapshotPayload; + +describe("permit ls output", () => { + it("emits full request ids that permit allow accepts", () => { + const result = listPendingPermissions([agent]); + + const listed = JSON.parse(render(result, { format: "json" })) as Array<{ id: string }>; + expect(listed.map((item) => item.id)).toEqual([writeRequest.id, editRequest.id]); + expect(render(result, { quiet: true }).split("\n")).toEqual([writeRequest.id, editRequest.id]); + }); +}); + +describe("permit allow and deny output", () => { + it("emits the full id of the request that was answered", () => { + const result = { + type: "list" as const, + data: [toPermissionResponseItem(agentId, writeRequest, "allowed")], + schema: permitResponseSchema, + }; + + const answered = JSON.parse(render(result, { format: "json" })) as Array<{ + requestId: string; + }>; + expect(answered.map((item) => item.requestId)).toEqual([writeRequest.id]); + }); +});