fix(app): permissions and questions from child sessions (#15105)
Co-authored-by: adamelmore <2363879+adamdottv@users.noreply.github.com>
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import { createEffect, createMemo, Show, type ParentProps } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useNavigate, useParams } from "@solidjs/router"
|
||||
import { SDKProvider, useSDK } from "@/context/sdk"
|
||||
import { SDKProvider } from "@/context/sdk"
|
||||
import { SyncProvider, useSync } from "@/context/sync"
|
||||
import { LocalProvider } from "@/context/local"
|
||||
|
||||
import { DataProvider } from "@opencode-ai/ui/context"
|
||||
import type { QuestionAnswer } from "@opencode-ai/sdk/v2"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { useLanguage } from "@/context/language"
|
||||
@@ -15,19 +14,11 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
||||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
|
||||
return (
|
||||
<DataProvider
|
||||
data={sync.data}
|
||||
directory={props.directory}
|
||||
onPermissionRespond={(input: {
|
||||
sessionID: string
|
||||
permissionID: string
|
||||
response: "once" | "always" | "reject"
|
||||
}) => sdk.client.permission.respond(input)}
|
||||
onQuestionReply={(input: { requestID: string; answers: QuestionAnswer[] }) => sdk.client.question.reply(input)}
|
||||
onQuestionReject={(input: { requestID: string }) => sdk.client.question.reject(input)}
|
||||
onNavigateToSession={(sessionID: string) => navigate(`/${params.dir}/session/${sessionID}`)}
|
||||
onSessionHref={(sessionID: string) => `/${params.dir}/session/${sessionID}`}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
|
||||
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
||||
|
||||
const session = (input: { id: string; parentID?: string }) =>
|
||||
({
|
||||
id: input.id,
|
||||
parentID: input.parentID,
|
||||
}) as Session
|
||||
|
||||
const permission = (id: string, sessionID: string) =>
|
||||
({
|
||||
id,
|
||||
sessionID,
|
||||
}) as PermissionRequest
|
||||
|
||||
const question = (id: string, sessionID: string) =>
|
||||
({
|
||||
id,
|
||||
sessionID,
|
||||
questions: [],
|
||||
}) as QuestionRequest
|
||||
|
||||
describe("sessionPermissionRequest", () => {
|
||||
test("prefers the current session permission", () => {
|
||||
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
|
||||
const permissions = {
|
||||
root: [permission("perm-root", "root")],
|
||||
child: [permission("perm-child", "child")],
|
||||
}
|
||||
|
||||
expect(sessionPermissionRequest(sessions, permissions, "root")?.id).toBe("perm-root")
|
||||
})
|
||||
|
||||
test("returns a nested child permission", () => {
|
||||
const sessions = [
|
||||
session({ id: "root" }),
|
||||
session({ id: "child", parentID: "root" }),
|
||||
session({ id: "grand", parentID: "child" }),
|
||||
session({ id: "other" }),
|
||||
]
|
||||
const permissions = {
|
||||
grand: [permission("perm-grand", "grand")],
|
||||
other: [permission("perm-other", "other")],
|
||||
}
|
||||
|
||||
expect(sessionPermissionRequest(sessions, permissions, "root")?.id).toBe("perm-grand")
|
||||
})
|
||||
|
||||
test("returns undefined without a matching tree permission", () => {
|
||||
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
|
||||
const permissions = {
|
||||
other: [permission("perm-other", "other")],
|
||||
}
|
||||
|
||||
expect(sessionPermissionRequest(sessions, permissions, "root")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("sessionQuestionRequest", () => {
|
||||
test("prefers the current session question", () => {
|
||||
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" })]
|
||||
const questions = {
|
||||
root: [question("q-root", "root")],
|
||||
child: [question("q-child", "child")],
|
||||
}
|
||||
|
||||
expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-root")
|
||||
})
|
||||
|
||||
test("returns a nested child question", () => {
|
||||
const sessions = [
|
||||
session({ id: "root" }),
|
||||
session({ id: "child", parentID: "root" }),
|
||||
session({ id: "grand", parentID: "child" }),
|
||||
]
|
||||
const questions = {
|
||||
grand: [question("q-grand", "grand")],
|
||||
}
|
||||
|
||||
expect(sessionQuestionRequest(sessions, questions, "root")?.id).toBe("q-grand")
|
||||
})
|
||||
})
|
||||
@@ -7,14 +7,20 @@ import { useGlobalSync } from "@/context/global-sync"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
||||
|
||||
export function createSessionComposerBlocked() {
|
||||
const params = useParams()
|
||||
const sync = useSync()
|
||||
const permissionRequest = createMemo(() =>
|
||||
sessionPermissionRequest(sync.data.session, sync.data.permission, params.id),
|
||||
)
|
||||
const questionRequest = createMemo(() => sessionQuestionRequest(sync.data.session, sync.data.question, params.id))
|
||||
|
||||
return createMemo(() => {
|
||||
const id = params.id
|
||||
if (!id) return false
|
||||
return !!sync.data.permission[id]?.[0] || !!sync.data.question[id]?.[0]
|
||||
return !!permissionRequest() || !!questionRequest()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,18 +32,18 @@ export function createSessionComposerState() {
|
||||
const language = useLanguage()
|
||||
|
||||
const questionRequest = createMemo((): QuestionRequest | undefined => {
|
||||
const id = params.id
|
||||
if (!id) return
|
||||
return sync.data.question[id]?.[0]
|
||||
return sessionQuestionRequest(sync.data.session, sync.data.question, params.id)
|
||||
})
|
||||
|
||||
const permissionRequest = createMemo((): PermissionRequest | undefined => {
|
||||
const id = params.id
|
||||
if (!id) return
|
||||
return sync.data.permission[id]?.[0]
|
||||
return sessionPermissionRequest(sync.data.session, sync.data.permission, params.id)
|
||||
})
|
||||
|
||||
const blocked = createSessionComposerBlocked()
|
||||
const blocked = createMemo(() => {
|
||||
const id = params.id
|
||||
if (!id) return false
|
||||
return !!permissionRequest() || !!questionRequest()
|
||||
})
|
||||
|
||||
const todos = createMemo((): Todo[] => {
|
||||
const id = params.id
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
function sessionTreeRequest<T>(session: Session[], request: Record<string, T[] | undefined>, sessionID?: string) {
|
||||
if (!sessionID) return
|
||||
|
||||
const map = session.reduce((acc, item) => {
|
||||
if (!item.parentID) return acc
|
||||
const list = acc.get(item.parentID)
|
||||
if (list) list.push(item.id)
|
||||
if (!list) acc.set(item.parentID, [item.id])
|
||||
return acc
|
||||
}, new Map<string, string[]>())
|
||||
|
||||
const seen = new Set([sessionID])
|
||||
const ids = [sessionID]
|
||||
for (const id of ids) {
|
||||
const list = map.get(id)
|
||||
if (!list) continue
|
||||
for (const child of list) {
|
||||
if (seen.has(child)) continue
|
||||
seen.add(child)
|
||||
ids.push(child)
|
||||
}
|
||||
}
|
||||
|
||||
const id = ids.find((id) => !!request[id]?.[0])
|
||||
if (!id) return
|
||||
return request[id]?.[0]
|
||||
}
|
||||
|
||||
export function sessionPermissionRequest(
|
||||
session: Session[],
|
||||
request: Record<string, PermissionRequest[] | undefined>,
|
||||
sessionID?: string,
|
||||
) {
|
||||
return sessionTreeRequest(session, request, sessionID)
|
||||
}
|
||||
|
||||
export function sessionQuestionRequest(
|
||||
session: Session[],
|
||||
request: Record<string, QuestionRequest[] | undefined>,
|
||||
sessionID?: string,
|
||||
) {
|
||||
return sessionTreeRequest(session, request, sessionID)
|
||||
}
|
||||
Reference in New Issue
Block a user