mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-20 12:04:43 +00:00
fix: prevent permission reply from silently escalating "once" to "always"
Remove allow_always from the fallback chain when the user replies "once", aligning with the ACP spec which says "map by option kind first" with no fallback for allow_once. Also fix Inspector to use rawSend, revert hydration guard to accept empty configOptions, and handle respondPermission errors by rejecting the pending promise. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e8ffd78ac0
commit
6f66f92e72
4 changed files with 16 additions and 9 deletions
|
|
@ -34,6 +34,7 @@ console.log(url);
|
||||||
- Event JSON inspector
|
- Event JSON inspector
|
||||||
- Prompt testing
|
- Prompt testing
|
||||||
- Request/response debugging
|
- Request/response debugging
|
||||||
|
- Interactive permission prompts (approve, always-allow, or reject tool-use requests)
|
||||||
- Process management (create, stop, kill, delete, view logs)
|
- Process management (create, stop, kill, delete, view logs)
|
||||||
- Interactive PTY terminal for tty processes
|
- Interactive PTY terminal for tty processes
|
||||||
- One-shot command execution
|
- One-shot command execution
|
||||||
|
|
|
||||||
|
|
@ -870,7 +870,7 @@ export default function App() {
|
||||||
// Apply mode if selected
|
// Apply mode if selected
|
||||||
if (!skipPostCreateConfig && config.agentMode) {
|
if (!skipPostCreateConfig && config.agentMode) {
|
||||||
try {
|
try {
|
||||||
await session.send("session/set_mode", { modeId: config.agentMode });
|
await session.rawSend("session/set_mode", { modeId: config.agentMode });
|
||||||
} catch {
|
} catch {
|
||||||
// Mode application is best-effort
|
// Mode application is best-effort
|
||||||
}
|
}
|
||||||
|
|
@ -886,7 +886,7 @@ export default function App() {
|
||||||
(opt) => opt.category === "model" && opt.type === "select" && typeof opt.id === "string"
|
(opt) => opt.category === "model" && opt.type === "select" && typeof opt.id === "string"
|
||||||
);
|
);
|
||||||
if (modelOption && config.model !== modelOption.currentValue) {
|
if (modelOption && config.model !== modelOption.currentValue) {
|
||||||
await session.send("session/set_config_option", {
|
await session.rawSend("session/set_config_option", {
|
||||||
optionId: modelOption.id,
|
optionId: modelOption.id,
|
||||||
value: config.model,
|
value: config.model,
|
||||||
});
|
});
|
||||||
|
|
@ -1241,7 +1241,6 @@ export default function App() {
|
||||||
}));
|
}));
|
||||||
const title = params?.toolCall?.title ?? params?.toolCall?.toolCallId ?? "Permission request";
|
const title = params?.toolCall?.title ?? params?.toolCall?.toolCallId ?? "Permission request";
|
||||||
const resolved = resolvedPermissions.get(permissionId);
|
const resolved = resolvedPermissions.get(permissionId);
|
||||||
const isPending = pendingPermissionIds.has(permissionId);
|
|
||||||
entries.push({
|
entries.push({
|
||||||
id: event.id,
|
id: event.id,
|
||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
|
|
@ -1252,7 +1251,7 @@ export default function App() {
|
||||||
title,
|
title,
|
||||||
description: params?.toolCall?.description,
|
description: params?.toolCall?.description,
|
||||||
options,
|
options,
|
||||||
resolved: resolved != null || !isPending,
|
resolved: resolved != null || sdkPermissionId == null,
|
||||||
selectedOptionId: resolved,
|
selectedOptionId: resolved,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -1288,7 +1287,7 @@ export default function App() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}, [events, pendingPermissionIds, resolvedPermissions]);
|
}, [events, resolvedPermissions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
||||||
|
|
@ -1262,7 +1262,7 @@ export class SandboxAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async hydrateSessionConfigOptions(sessionId: string, snapshot: SessionRecord): Promise<SessionRecord> {
|
private async hydrateSessionConfigOptions(sessionId: string, snapshot: SessionRecord): Promise<SessionRecord> {
|
||||||
if (snapshot.configOptions !== undefined && snapshot.configOptions.length > 0) {
|
if (snapshot.configOptions !== undefined) {
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1440,7 +1440,14 @@ export class SandboxAgent {
|
||||||
throw new Error(`permission '${permissionId}' not found`);
|
throw new Error(`permission '${permissionId}' not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = permissionReplyToResponse(permissionId, pending.request, reply);
|
let response: RequestPermissionResponse;
|
||||||
|
try {
|
||||||
|
response = permissionReplyToResponse(permissionId, pending.request, reply);
|
||||||
|
} catch (error) {
|
||||||
|
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
||||||
|
this.pendingPermissionRequests.delete(permissionId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
this.resolvePendingPermission(permissionId, response);
|
this.resolvePendingPermission(permissionId, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2704,7 +2711,7 @@ function permissionReplyToResponse(
|
||||||
): RequestPermissionResponse {
|
): RequestPermissionResponse {
|
||||||
const preferredKinds: PermissionOptionKind[] =
|
const preferredKinds: PermissionOptionKind[] =
|
||||||
reply === "once"
|
reply === "once"
|
||||||
? ["allow_once", "allow_always"]
|
? ["allow_once"]
|
||||||
: reply === "always"
|
: reply === "always"
|
||||||
? ["allow_always", "allow_once"]
|
? ["allow_always", "allow_once"]
|
||||||
: ["reject_once", "reject_always"];
|
: ["reject_once", "reject_always"];
|
||||||
|
|
|
||||||
|
|
@ -512,7 +512,7 @@ describe("Integration: TypeScript SDK flat session API", () => {
|
||||||
|
|
||||||
const session = await sdk.createSession({ agent: "mock" });
|
const session = await sdk.createSession({ agent: "mock" });
|
||||||
|
|
||||||
await expect(session.send("session/cancel")).rejects.toThrow(
|
await expect(session.rawSend("session/cancel")).rejects.toThrow(
|
||||||
"Use destroySession(sessionId) instead.",
|
"Use destroySession(sessionId) instead.",
|
||||||
);
|
);
|
||||||
await expect(sdk.rawSendSessionMethod(session.id, "session/cancel", {})).rejects.toThrow(
|
await expect(sdk.rawSendSessionMethod(session.id, "session/cancel", {})).rejects.toThrow(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue