SDK v1.17.12: session.permission — programmatic create/fetch, more reliable auto-accept (#1982)
* docs: add SDK v1.17.12 migration plan — phase 4 (session.permission) * feat(permissions): verify pending permission before auto-accept via SDK v1.17.12 Adds createPermission() and fetchPermission() wrappers on OpencodeService for the new v2.session.permission endpoints (OpenCode SDK 1.17.12). fetchPermission() is used by the auto-accept sweep in resyncBlockingRequestsForDirectory to verify a permission is still pending before replying. The auto-accept flow now skips permissions that are already resolved, returning a null from fetchPermission() rather than blindly calling respondToPermission on a stale entry. createPermission() is exposed for future programmatic permission creation; the V1 list/reply path used by the UI is unchanged. The plan doc at plans/opencode-v1.17.12-sdk/ was rebased onto origin/main in the prior commit to keep the PR diff focused on this change. Closes #1972 * fix(permissions): drop confirmed-resolved permissions from auto-accept resync fetchPermission() now returns a tagged FetchPermissionResult so the auto-accept loop can distinguish a server-confirmed 404 (the permission is no longer pending) from a fetch failure (network error or pre-v1.17.12 server). Previously both cases collapsed to null, so a permission the server had already answered would still appear in the resync output and trigger a spurious 'Permission needed' toast. The auto-accept loop in resyncBlockingRequestsForDirectory now tracks both accepted and resolved permissions, then drops both from the 'grouped' map before it falls through to the toast path. On a pre-v1.17.12 server (no V2 endpoint) the call still returns 'unknown' and the permission stays in the resync output so the user can answer manually — fail-closed, no false-resolved signals. Adds a focused unit test for fetchPermission (4 cases: 200 ok, 404 resolved, 500 unknown, network throw) mocking the V2 SDK client shape. --------- Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
bashrusakh
parent
6d7ea82d86
commit
01a52eccab
@@ -1139,13 +1139,41 @@ export async function resyncBlockingRequestsForDirectory(
|
||||
|
||||
if (autoAcceptingSessionIds.length > 0) {
|
||||
const acceptedIdsBySession = new Map<string, Set<string>>()
|
||||
// Track server-confirmed resolved permissions separately so we can
|
||||
// remove them from `grouped` below — the V1 listPendingPermissions
|
||||
// snapshot can still contain entries the server has already answered,
|
||||
// and leaving them in place produces a spurious "Permission needed"
|
||||
// toast for a permission the user has already resolved.
|
||||
const resolvedIdsBySession = new Map<string, Set<string>>()
|
||||
await Promise.all(autoAcceptingSessionIds.flatMap((sessionId) =>
|
||||
(grouped[sessionId] ?? []).map(async (permission) => {
|
||||
try {
|
||||
await sessionActions.respondToPermission(permission.sessionID, permission.id, "once")
|
||||
const accepted = acceptedIdsBySession.get(sessionId) ?? new Set<string>()
|
||||
accepted.add(permission.id)
|
||||
acceptedIdsBySession.set(sessionId, accepted)
|
||||
// Verify the permission is still pending before auto-accepting.
|
||||
// - state: "ok" → still pending, safe to auto-accept
|
||||
// - state: "resolved" → server returned 404, drop from grouped
|
||||
// - state: "unknown" → network error / pre-1.17.12 server,
|
||||
// keep in grouped for the user to act on
|
||||
//
|
||||
// On a pre-v1.17.12 server without the V2 endpoint, every call
|
||||
// returns "unknown". This permanently disables auto-accept
|
||||
// (acknowledged scope tradeoff — project requires SDK 1.17.12)
|
||||
// but does not falsely report permissions as resolved.
|
||||
const outcome = await opencodeClient.fetchPermission(
|
||||
permission.sessionID,
|
||||
permission.id,
|
||||
)
|
||||
if (outcome.state === "ok") {
|
||||
await sessionActions.respondToPermission(permission.sessionID, permission.id, "once")
|
||||
const accepted = acceptedIdsBySession.get(sessionId) ?? new Set<string>()
|
||||
accepted.add(permission.id)
|
||||
acceptedIdsBySession.set(sessionId, accepted)
|
||||
} else if (outcome.state === "resolved") {
|
||||
const resolved = resolvedIdsBySession.get(sessionId) ?? new Set<string>()
|
||||
resolved.add(permission.id)
|
||||
resolvedIdsBySession.set(sessionId, resolved)
|
||||
}
|
||||
// state: "unknown" → keep the permission in grouped; user can
|
||||
// answer manually.
|
||||
} catch {
|
||||
// Keep failed auto-accept permissions in UI state so the user can act.
|
||||
}
|
||||
@@ -1154,8 +1182,11 @@ export async function resyncBlockingRequestsForDirectory(
|
||||
|
||||
for (const sessionId of autoAcceptingSessionIds) {
|
||||
const acceptedIds = acceptedIdsBySession.get(sessionId)
|
||||
if (!acceptedIds) continue
|
||||
const remaining = (grouped[sessionId] ?? []).filter((permission) => !acceptedIds.has(permission.id))
|
||||
const resolvedIds = resolvedIdsBySession.get(sessionId)
|
||||
if (!acceptedIds && !resolvedIds) continue
|
||||
const drop = (id: string) =>
|
||||
acceptedIds?.has(id) || resolvedIds?.has(id) || false
|
||||
const remaining = (grouped[sessionId] ?? []).filter((permission) => !drop(permission.id))
|
||||
if (remaining.length > 0) grouped[sessionId] = remaining
|
||||
else delete grouped[sessionId]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user