From 7fa78244fda1e3ff03cc0754a61f091fc325d68c Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:52:03 +0200 Subject: [PATCH] fix(cli): emit full permission request ids from permit ls, allow, and deny (#5305) The permit result items stored the request id already shortened to eight characters for the table, so JSON, YAML, and quiet output serialized that display value. Every request id starts with "permission-", so every row came out as "permissi" and could not be passed back to permit allow. The items now keep the full id and only the table column shortens it. Closes #4732 Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- packages/cli/src/commands/permit/allow.ts | 25 +++++++---- packages/cli/src/commands/permit/deny.ts | 14 +++--- packages/cli/src/commands/permit/ls.ts | 36 ++++++++------- .../src/commands/permit/permit-output.test.ts | 45 +++++++++++++++++++ 4 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 packages/cli/src/commands/permit/permit-output.test.ts 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]); + }); +});