feat(client): expose agent permission responses through the public SDK (#3985)

* feat(client): expose agent permission responses

* test(client): verify permission response wire message

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
Omer Cohen
2026-09-05 14:44:24 +02:00
committed by GitHub
co-authored by paseo-bot[bot]
parent 02e611f3dd
commit 6f76a8b139
3 changed files with 67 additions and 24 deletions
+31
View File
@@ -237,6 +237,37 @@ test("createPaseoApi borrows daemon capabilities without exposing connection own
expect("skills" in paseo.agents).toBe(false);
});
test("agent handles send permission responses for their agent", async () => {
const { client, ws } = await connectClient();
await client.agents.ref("agent_sdk").respondToPermission({
requestId: "permission-request",
response: {
behavior: "deny",
selectedActionId: "deny-once",
message: "Not approved",
interrupt: true,
},
});
expect(parseSentFrame(ws.sent.at(-1))).toEqual({
type: "session",
message: {
type: "agent_permission_response",
agentId: "agent_sdk",
requestId: "permission-request",
response: {
behavior: "deny",
selectedActionId: "deny-once",
message: "Not approved",
interrupt: true,
},
},
});
await client.close();
});
test("project actions list registered projects through the existing RPC", async () => {
const { client, ws } = await connectClient();
+11
View File
@@ -1,3 +1,4 @@
import type { AgentPermissionResponse } from "@getpaseo/protocol/agent-types";
import type {
AgentSnapshotPayload,
CreateAgentRequestMessage,
@@ -238,6 +239,12 @@ export interface PaseoAgentRunOptions extends PaseoAgentSendOptions {
}
export type PaseoAgentRunResult = WaitForFinishResult;
export type PaseoAgentPermissionResponse = AgentPermissionResponse;
export interface PaseoAgentRespondToPermissionOptions {
requestId: string;
response: PaseoAgentPermissionResponse;
}
export interface PaseoAgentCommandsOptions {
requestId?: string;
@@ -291,6 +298,7 @@ export interface PaseoAgentHandle {
current(): PaseoAgent | null;
refresh(requestId?: string): Promise<PaseoAgentRefetchResult | null>;
send(text: string, options?: PaseoAgentSendOptions): Promise<void>;
respondToPermission(options: PaseoAgentRespondToPermissionOptions): Promise<void>;
/** Sends a prompt and resolves when that turn finishes or needs attention. */
run(text: string, options?: PaseoAgentRunOptions): Promise<PaseoAgentRunResult>;
/** Waits for the current turn, including one started with `prompt`. */
@@ -668,6 +676,9 @@ function createAgentHandleFactory(daemonClient: DaemonClient): AgentHandleFactor
send: async (text, options) => {
await daemonClient.sendAgentMessage(id, text, options);
},
respondToPermission: async ({ requestId, response }) => {
await daemonClient.respondToPermission(id, requestId, response);
},
run: async (text, options) => {
const { timeoutMs, ...sendOptions } = options ?? {};
await daemonClient.sendAgentMessage(id, text, sendOptions);
+25 -24
View File
@@ -72,30 +72,31 @@ Creation options include `config`, `cwd`, `parent`, `title`, `prompt`, `env`, `o
### Agent handle
| Member | Result | Behavior |
| --------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `id` | `string` | Stable daemon agent ID. |
| `workspaceId` | `string \| null` | Current workspace placement. |
| `cwd` | `string \| null` | Current working directory. |
| `status` | Agent status or `null` | Current lifecycle status. |
| `capabilities` | Capability flags or `null` | What the provider session supports. |
| `availableModes` | Agent modes or `null` | Modes the session can switch to. |
| `pendingPermissions` | Permission requests or `null` | Requests waiting on an answer. |
| `activeTurn` | Active turn or `null` | The turn in flight, with `turnId` and `startedAt`. |
| `lastUsage` | Usage or `null` | Token counts, cost, and context-window use from the last turn. |
| `lastError` | `string \| null` | Last error the daemon recorded for the agent. |
| `features` | Agent features or `null` | Provider feature toggles and selects with their current values. |
| `runtimeInfo` | Runtime info or `null` | Live provider, session ID, model, thinking option, and mode. |
| `archivedAt` | `string \| null` | Archive timestamp; `null` while the agent is active. |
| `current()` | `PaseoAgent \| null` | Current detailed value observed by this handle; never fetches. |
| `refresh(requestId?)` | `PaseoAgentRefetchResult \| null` | Fetches the current agent and project placement. |
| `send(text, options?)` | `Promise<void>` | Resolves when the daemon accepts the prompt. |
| `run(text, options?)` | `PaseoAgentRunResult` | Sends a prompt and waits for that turn. `timeoutMs` controls the wait; it defaults to 10 minutes. |
| `waitForFinish(timeoutMs?)` | `PaseoAgentRunResult` | Waits for the active turn, including an initial prompt. Default timeout: 10 minutes. |
| `commands(options?)` | `PaseoAgentCommandsResult` | Asks the live session for its slash commands and skills, including built-in ones. Options: `requestId`. |
| `subscribe(handler)` | Unsubscribe function | Filters agent-directory updates to this ID and refreshes the handle properties. |
| `archive()` | `{ archivedAt }` | Soft-deletes the agent and closes its runtime. |
| `detach()` | `Promise<void>` | Removes the parent relationship without stopping the agent. |
| Member | Result | Behavior |
| ------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `id` | `string` | Stable daemon agent ID. |
| `workspaceId` | `string \| null` | Current workspace placement. |
| `cwd` | `string \| null` | Current working directory. |
| `status` | Agent status or `null` | Current lifecycle status. |
| `capabilities` | Capability flags or `null` | What the provider session supports. |
| `availableModes` | Agent modes or `null` | Modes the session can switch to. |
| `pendingPermissions` | Permission requests or `null` | Requests waiting on an answer. |
| `activeTurn` | Active turn or `null` | The turn in flight, with `turnId` and `startedAt`. |
| `lastUsage` | Usage or `null` | Token counts, cost, and context-window use from the last turn. |
| `lastError` | `string \| null` | Last error the daemon recorded for the agent. |
| `features` | Agent features or `null` | Provider feature toggles and selects with their current values. |
| `runtimeInfo` | Runtime info or `null` | Live provider, session ID, model, thinking option, and mode. |
| `archivedAt` | `string \| null` | Archive timestamp; `null` while the agent is active. |
| `current()` | `PaseoAgent \| null` | Current detailed value observed by this handle; never fetches. |
| `refresh(requestId?)` | `PaseoAgentRefetchResult \| null` | Fetches the current agent and project placement. |
| `send(text, options?)` | `Promise<void>` | Resolves when the daemon accepts the prompt. |
| `respondToPermission(options)` | `Promise<void>` | Answers a pending permission by `requestId` with an allow or deny `response`. |
| `run(text, options?)` | `PaseoAgentRunResult` | Sends a prompt and waits for that turn. `timeoutMs` controls the wait; it defaults to 10 minutes. |
| `waitForFinish(timeoutMs?)` | `PaseoAgentRunResult` | Waits for the active turn, including an initial prompt. Default timeout: 10 minutes. |
| `commands(options?)` | `PaseoAgentCommandsResult` | Asks the live session for its slash commands and skills, including built-in ones. Options: `requestId`. |
| `subscribe(handler)` | Unsubscribe function | Filters agent-directory updates to this ID and refreshes the handle properties. |
| `archive()` | `{ archivedAt }` | Soft-deletes the agent and closes its runtime. |
| `detach()` | `Promise<void>` | Removes the parent relationship without stopping the agent. |
`workspaceId` through `archivedAt` mirror the last snapshot the handle observed. A handle from `ref()` reads `null` for all of them until `refresh()`, `run()`, `waitForFinish()`, a timeline refetch, or `subscribe()` delivers a snapshot. Optional values in an observed snapshot also read as `null`. Call `current()` when you need the whole snapshot or need to distinguish those states.