|
|
@@ -7,6 +7,11 @@ let createPromptSubmit: typeof import("./submit").createPromptSubmit
|
|
|
|
|
|
const createdClients: string[] = []
|
|
|
const createdSessions: string[] = []
|
|
|
+const sessionCreateInputs: Array<{
|
|
|
+ agent?: string
|
|
|
+ model?: { id: string; providerID: string; variant?: string }
|
|
|
+ location?: { directory: string }
|
|
|
+}> = []
|
|
|
const enabledAutoAccept: Array<{ server: string; sessionID: string; directory: string }> = []
|
|
|
const optimistic: Array<{
|
|
|
directory?: string
|
|
|
@@ -19,11 +24,15 @@ const optimistic: Array<{
|
|
|
}> = []
|
|
|
const optimisticSeeded: boolean[] = []
|
|
|
const storedSessions: Record<string, Array<{ id: string; title?: string }>> = {}
|
|
|
-const sessionDirectories: Record<string, string> = {}
|
|
|
const promoted: Array<{ directory: string; sessionID: string }> = []
|
|
|
-const sentShell: string[] = []
|
|
|
+const sentShell: Array<{ sessionID: string; id?: string; command: string }> = []
|
|
|
const syncedDirectories: string[] = []
|
|
|
const promotedDrafts: Array<{ draftID: string; server: string; sessionId: string }> = []
|
|
|
+const sentPrompts: string[] = []
|
|
|
+const promptInputs: unknown[] = []
|
|
|
+const sentCommands: unknown[] = []
|
|
|
+const commands: Array<{ name: string }> = []
|
|
|
+let serverSessionSyncs = 0
|
|
|
|
|
|
let params: { id?: string } = {}
|
|
|
let search: { draftId?: string } = {}
|
|
|
@@ -32,7 +41,7 @@ let variant: string | undefined
|
|
|
let permissionServer = "server-a"
|
|
|
let createSessionGate: Promise<void> | undefined
|
|
|
|
|
|
-const promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
|
|
+let promptValue: Prompt = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
|
|
const [promptStore, setPromptStore] = createStore<PromptStore>({
|
|
|
prompt: promptValue,
|
|
|
cursor: 0,
|
|
|
@@ -64,23 +73,39 @@ const prompt = {
|
|
|
const clientFor = (directory: string) => {
|
|
|
createdClients.push(directory)
|
|
|
return {
|
|
|
- session: {
|
|
|
- create: async () => {
|
|
|
- await createSessionGate
|
|
|
- createdSessions.push(directory)
|
|
|
- return {
|
|
|
- data: {
|
|
|
+ api: {
|
|
|
+ session: {
|
|
|
+ create: async (input: (typeof sessionCreateInputs)[number]) => {
|
|
|
+ await createSessionGate
|
|
|
+ const location = input.location?.directory ?? directory
|
|
|
+ createdSessions.push(location)
|
|
|
+ sessionCreateInputs.push(input)
|
|
|
+ return {
|
|
|
id: `session-${createdSessions.length}`,
|
|
|
+ projectID: "project",
|
|
|
+ agent: input.agent,
|
|
|
+ model: input.model,
|
|
|
+ cost: 0,
|
|
|
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
|
|
+ time: { created: 1, updated: 1 },
|
|
|
title: `New session ${createdSessions.length}`,
|
|
|
- },
|
|
|
- }
|
|
|
- },
|
|
|
- shell: async () => {
|
|
|
- sentShell.push(directory)
|
|
|
- return { data: undefined }
|
|
|
+ location: { directory: location },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ prompt: async (input: unknown) => {
|
|
|
+ sentPrompts.push(directory)
|
|
|
+ promptInputs.push(input)
|
|
|
+ return { data: undefined }
|
|
|
+ },
|
|
|
+ command: async (input: unknown) => {
|
|
|
+ sentCommands.push(input)
|
|
|
+ },
|
|
|
+ shell: async (input: { sessionID: string; id?: string; command: string }) => {
|
|
|
+ sentShell.push(input)
|
|
|
+ },
|
|
|
},
|
|
|
- prompt: async () => ({ data: undefined }),
|
|
|
- promptAsync: async () => ({ data: undefined }),
|
|
|
+ },
|
|
|
+ session: {
|
|
|
command: async () => ({ data: undefined }),
|
|
|
abort: async () => ({ data: undefined }),
|
|
|
},
|
|
|
@@ -90,27 +115,6 @@ const clientFor = (directory: string) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const api = {
|
|
|
- session: {
|
|
|
- async create(input: { location: { directory: string } }) {
|
|
|
- await createSessionGate
|
|
|
- createdSessions.push(input.location.directory)
|
|
|
- const session = {
|
|
|
- id: `session-${createdSessions.length}`,
|
|
|
- title: `New session ${createdSessions.length}`,
|
|
|
- }
|
|
|
- sessionDirectories[session.id] = input.location.directory
|
|
|
- return session
|
|
|
- },
|
|
|
- async shell(input: { sessionID: string }) {
|
|
|
- sentShell.push(sessionDirectories[input.sessionID] ?? "/repo/main")
|
|
|
- },
|
|
|
- async prompt() {},
|
|
|
- async command() {},
|
|
|
- async interrupt() {},
|
|
|
- },
|
|
|
-}
|
|
|
-
|
|
|
beforeAll(async () => {
|
|
|
const rootClient = clientFor("/repo/main")
|
|
|
|
|
|
@@ -193,8 +197,8 @@ beforeAll(async () => {
|
|
|
const sdk = {
|
|
|
scope: "local",
|
|
|
directory: "/repo/main",
|
|
|
- api,
|
|
|
client: rootClient,
|
|
|
+ api: rootClient.api,
|
|
|
url: "http://localhost:4096",
|
|
|
createClient(opts: any) {
|
|
|
return clientFor(opts.directory)
|
|
|
@@ -206,7 +210,7 @@ beforeAll(async () => {
|
|
|
|
|
|
mock.module("@/context/sync", () => ({
|
|
|
useSync: () => () => ({
|
|
|
- data: { command: [] },
|
|
|
+ data: { command: commands },
|
|
|
session: {
|
|
|
optimistic: {
|
|
|
add: (value: {
|
|
|
@@ -233,6 +237,9 @@ beforeAll(async () => {
|
|
|
session: {
|
|
|
remember: () => undefined,
|
|
|
set: () => undefined,
|
|
|
+ sync: async () => {
|
|
|
+ serverSessionSyncs++
|
|
|
+ },
|
|
|
},
|
|
|
child: (directory: string) => {
|
|
|
syncedDirectories.push(directory)
|
|
|
@@ -274,11 +281,17 @@ beforeAll(async () => {
|
|
|
beforeEach(() => {
|
|
|
createdClients.length = 0
|
|
|
createdSessions.length = 0
|
|
|
+ sessionCreateInputs.length = 0
|
|
|
enabledAutoAccept.length = 0
|
|
|
optimistic.length = 0
|
|
|
optimisticSeeded.length = 0
|
|
|
promoted.length = 0
|
|
|
promotedDrafts.length = 0
|
|
|
+ sentPrompts.length = 0
|
|
|
+ promptInputs.length = 0
|
|
|
+ sentCommands.length = 0
|
|
|
+ commands.length = 0
|
|
|
+ promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
|
|
params = {}
|
|
|
search = {}
|
|
|
sentShell.length = 0
|
|
|
@@ -287,8 +300,8 @@ beforeEach(() => {
|
|
|
variant = undefined
|
|
|
permissionServer = "server-a"
|
|
|
createSessionGate = undefined
|
|
|
+ serverSessionSyncs = 0
|
|
|
for (const key of Object.keys(storedSessions)) delete storedSessions[key]
|
|
|
- for (const key of Object.keys(sessionDirectories)) delete sessionDirectories[key]
|
|
|
})
|
|
|
|
|
|
describe("prompt submit worktree selection", () => {
|
|
|
@@ -321,8 +334,24 @@ describe("prompt submit worktree selection", () => {
|
|
|
|
|
|
expect(createdClients).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
|
|
|
expect(createdSessions).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
|
|
|
- expect(sentShell).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
|
|
|
+ expect(sessionCreateInputs).toEqual([
|
|
|
+ {
|
|
|
+ agent: "agent",
|
|
|
+ model: { id: "model", providerID: "provider", variant: undefined },
|
|
|
+ location: { directory: "/repo/worktree-a" },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ agent: "agent",
|
|
|
+ model: { id: "model", providerID: "provider", variant: undefined },
|
|
|
+ location: { directory: "/repo/worktree-b" },
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ expect(sentShell).toEqual([
|
|
|
+ expect.objectContaining({ sessionID: "session-1", id: expect.stringMatching(/^evt_/), command: "ls" }),
|
|
|
+ expect.objectContaining({ sessionID: "session-2", id: expect.stringMatching(/^evt_/), command: "ls" }),
|
|
|
+ ])
|
|
|
expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-a", "/repo/worktree-b", "/repo/worktree-b"])
|
|
|
+ expect(serverSessionSyncs).toBe(0)
|
|
|
expect(promoted).toEqual([
|
|
|
{ directory: "/repo/worktree-a", sessionID: "session-1" },
|
|
|
{ directory: "/repo/worktree-b", sessionID: "session-2" },
|
|
|
@@ -443,6 +472,7 @@ describe("prompt submit worktree selection", () => {
|
|
|
const event = { preventDefault: () => undefined } as unknown as Event
|
|
|
|
|
|
await submit.handleSubmit(event)
|
|
|
+ await Bun.sleep(0)
|
|
|
|
|
|
expect(optimistic).toHaveLength(1)
|
|
|
expect(optimistic[0]).toMatchObject({
|
|
|
@@ -451,6 +481,53 @@ describe("prompt submit worktree selection", () => {
|
|
|
model: { providerID: "provider", modelID: "model", variant: "high" },
|
|
|
},
|
|
|
})
|
|
|
+ expect(sentPrompts).toEqual(["/repo/main"])
|
|
|
+ expect(promptInputs[0]).toMatchObject({
|
|
|
+ sessionID: "session-1",
|
|
|
+ text: "ls",
|
|
|
+ files: [],
|
|
|
+ agents: [],
|
|
|
+ })
|
|
|
+ expect((promptInputs[0] as { id?: string }).id).toStartWith("msg_")
|
|
|
+ })
|
|
|
+
|
|
|
+ test("submits slash commands through the current session API", async () => {
|
|
|
+ params = { id: "session-1" }
|
|
|
+ variant = "high"
|
|
|
+ commands.push({ name: "review" })
|
|
|
+ promptValue = [{ type: "text", content: "/review staged changes", start: 0, end: 22 }]
|
|
|
+
|
|
|
+ const submit = createPromptSubmit({
|
|
|
+ prompt,
|
|
|
+ info: () => ({ id: "session-1" }),
|
|
|
+ imageAttachments: () => [],
|
|
|
+ commentCount: () => 0,
|
|
|
+ autoAccept: () => false,
|
|
|
+ mode: () => "normal",
|
|
|
+ working: () => false,
|
|
|
+ editor: () => undefined,
|
|
|
+ queueScroll: () => undefined,
|
|
|
+ promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
|
|
|
+ addToHistory: () => undefined,
|
|
|
+ resetHistoryNavigation: () => undefined,
|
|
|
+ setMode: () => undefined,
|
|
|
+ setPopover: () => undefined,
|
|
|
+ })
|
|
|
+
|
|
|
+ await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event)
|
|
|
+
|
|
|
+ expect(sentCommands).toEqual([
|
|
|
+ {
|
|
|
+ sessionID: "session-1",
|
|
|
+ id: expect.stringMatching(/^msg_/),
|
|
|
+ command: "review",
|
|
|
+ arguments: "staged changes",
|
|
|
+ agent: "agent",
|
|
|
+ model: { id: "model", providerID: "provider", variant: "high" },
|
|
|
+ files: [],
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ expect(serverSessionSyncs).toBe(0)
|
|
|
})
|
|
|
|
|
|
test("uses an injected model selection", async () => {
|
|
|
@@ -511,7 +588,8 @@ describe("prompt submit worktree selection", () => {
|
|
|
|
|
|
await submit.handleSubmit(event)
|
|
|
|
|
|
- expect(storedSessions["/repo/worktree-a"]).toEqual([{ id: "session-1", title: "New session 1" }])
|
|
|
+ expect(storedSessions["/repo/worktree-a"]).toHaveLength(1)
|
|
|
+ expect(storedSessions["/repo/worktree-a"]?.[0]).toMatchObject({ id: "session-1", title: "New session 1" })
|
|
|
expect(optimisticSeeded).toEqual([true])
|
|
|
})
|
|
|
})
|