mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-19 07:04:40 +00:00
tests(app): e2e tests part 67 (#16406)
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import { test, expect } from "../fixtures"
|
import { test, expect } from "../fixtures"
|
||||||
import { promptSelector } from "../selectors"
|
import { promptSelector } from "../selectors"
|
||||||
import { sessionIDFromUrl } from "../actions"
|
import { sessionIDFromUrl, withSession } from "../actions"
|
||||||
|
|
||||||
|
const text = (value: string | null) => (value ?? "").replace(/\u200B/g, "").trim()
|
||||||
|
|
||||||
// Regression test for Issue #12453: the synchronous POST /message endpoint holds
|
// Regression test for Issue #12453: the synchronous POST /message endpoint holds
|
||||||
// the connection open while the agent works, causing "Failed to fetch" over
|
// the connection open while the agent works, causing "Failed to fetch" over
|
||||||
@@ -41,3 +43,34 @@ test("prompt succeeds when sync message endpoint is unreachable", async ({ page,
|
|||||||
await sdk.session.delete({ sessionID }).catch(() => undefined)
|
await sdk.session.delete({ sessionID }).catch(() => undefined)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("failed prompt send restores the composer input", async ({ page, sdk, gotoSession }) => {
|
||||||
|
await withSession(sdk, `e2e prompt failure ${Date.now()}`, async (session) => {
|
||||||
|
const prompt = page.locator(promptSelector)
|
||||||
|
const value = `restore ${Date.now()}`
|
||||||
|
|
||||||
|
await page.route(`**/session/${session.id}/prompt_async`, (route) =>
|
||||||
|
route.fulfill({
|
||||||
|
status: 500,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({ message: "e2e prompt failure" }),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
await gotoSession(session.id)
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type(value)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
|
||||||
|
await expect.poll(async () => text(await prompt.textContent())).toBe(value)
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const messages = await sdk.session.messages({ sessionID: session.id, limit: 50 }).then((r) => r.data ?? [])
|
||||||
|
return messages.length
|
||||||
|
},
|
||||||
|
{ timeout: 15_000 },
|
||||||
|
)
|
||||||
|
.toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
181
packages/app/e2e/prompt/prompt-history.spec.ts
Normal file
181
packages/app/e2e/prompt/prompt-history.spec.ts
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import type { ToolPart } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import type { Page } from "@playwright/test"
|
||||||
|
import { test, expect } from "../fixtures"
|
||||||
|
import { withSession } from "../actions"
|
||||||
|
import { promptSelector } from "../selectors"
|
||||||
|
|
||||||
|
const text = (value: string | null) => (value ?? "").replace(/\u200B/g, "").trim()
|
||||||
|
|
||||||
|
const isBash = (part: unknown): part is ToolPart => {
|
||||||
|
if (!part || typeof part !== "object") return false
|
||||||
|
if (!("type" in part) || part.type !== "tool") return false
|
||||||
|
if (!("tool" in part) || part.tool !== "bash") return false
|
||||||
|
return "state" in part
|
||||||
|
}
|
||||||
|
|
||||||
|
async function edge(page: Page, pos: "start" | "end") {
|
||||||
|
await page.locator(promptSelector).evaluate((el: HTMLDivElement, pos: "start" | "end") => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection) return
|
||||||
|
|
||||||
|
const walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
|
||||||
|
const nodes: Text[] = []
|
||||||
|
for (let node = walk.nextNode(); node; node = walk.nextNode()) {
|
||||||
|
nodes.push(node as Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.length === 0) {
|
||||||
|
const node = document.createTextNode("")
|
||||||
|
el.appendChild(node)
|
||||||
|
nodes.push(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = pos === "start" ? nodes[0]! : nodes[nodes.length - 1]!
|
||||||
|
const range = document.createRange()
|
||||||
|
range.setStart(node, pos === "start" ? 0 : (node.textContent ?? "").length)
|
||||||
|
range.collapse(true)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
}, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wait(page: Page, value: string) {
|
||||||
|
await expect.poll(async () => text(await page.locator(promptSelector).textContent())).toBe(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reply(sdk: Parameters<typeof withSession>[0], sessionID: string, token: string) {
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
|
||||||
|
return messages
|
||||||
|
.filter((item) => item.info.role === "assistant")
|
||||||
|
.flatMap((item) => item.parts)
|
||||||
|
.filter((item) => item.type === "text")
|
||||||
|
.map((item) => item.text)
|
||||||
|
.join("\n")
|
||||||
|
},
|
||||||
|
{ timeout: 90_000 },
|
||||||
|
)
|
||||||
|
.toContain(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shell(sdk: Parameters<typeof withSession>[0], sessionID: string, cmd: string, token: string) {
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
|
||||||
|
const part = messages
|
||||||
|
.filter((item) => item.info.role === "assistant")
|
||||||
|
.flatMap((item) => item.parts)
|
||||||
|
.filter(isBash)
|
||||||
|
.find((item) => item.state.input?.command === cmd && item.state.status === "completed")
|
||||||
|
|
||||||
|
if (!part || part.state.status !== "completed") return
|
||||||
|
return typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
|
||||||
|
},
|
||||||
|
{ timeout: 90_000 },
|
||||||
|
)
|
||||||
|
.toContain(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("prompt history restores unsent draft with arrow navigation", async ({ page, sdk, gotoSession }) => {
|
||||||
|
test.setTimeout(120_000)
|
||||||
|
|
||||||
|
await withSession(sdk, `e2e prompt history ${Date.now()}`, async (session) => {
|
||||||
|
await gotoSession(session.id)
|
||||||
|
|
||||||
|
const prompt = page.locator(promptSelector)
|
||||||
|
const firstToken = `E2E_HISTORY_ONE_${Date.now()}`
|
||||||
|
const secondToken = `E2E_HISTORY_TWO_${Date.now()}`
|
||||||
|
const first = `Reply with exactly: ${firstToken}`
|
||||||
|
const second = `Reply with exactly: ${secondToken}`
|
||||||
|
const draft = `draft ${Date.now()}`
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type(first)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
await wait(page, "")
|
||||||
|
await reply(sdk, session.id, firstToken)
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type(second)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
await wait(page, "")
|
||||||
|
await reply(sdk, session.id, secondToken)
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type(draft)
|
||||||
|
await wait(page, draft)
|
||||||
|
|
||||||
|
await edge(page, "start")
|
||||||
|
await page.keyboard.press("ArrowUp")
|
||||||
|
await wait(page, second)
|
||||||
|
|
||||||
|
await page.keyboard.press("ArrowUp")
|
||||||
|
await wait(page, first)
|
||||||
|
|
||||||
|
await page.keyboard.press("ArrowDown")
|
||||||
|
await wait(page, second)
|
||||||
|
|
||||||
|
await page.keyboard.press("ArrowDown")
|
||||||
|
await wait(page, draft)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("shell history stays separate from normal prompt history", async ({ page, sdk, gotoSession }) => {
|
||||||
|
test.setTimeout(120_000)
|
||||||
|
|
||||||
|
await withSession(sdk, `e2e shell history ${Date.now()}`, async (session) => {
|
||||||
|
await gotoSession(session.id)
|
||||||
|
|
||||||
|
const prompt = page.locator(promptSelector)
|
||||||
|
const firstToken = `E2E_SHELL_ONE_${Date.now()}`
|
||||||
|
const secondToken = `E2E_SHELL_TWO_${Date.now()}`
|
||||||
|
const normalToken = `E2E_NORMAL_${Date.now()}`
|
||||||
|
const first = `echo ${firstToken}`
|
||||||
|
const second = `echo ${secondToken}`
|
||||||
|
const normal = `Reply with exactly: ${normalToken}`
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type("!")
|
||||||
|
await page.keyboard.type(first)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
await wait(page, "")
|
||||||
|
await shell(sdk, session.id, first, firstToken)
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type("!")
|
||||||
|
await page.keyboard.type(second)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
await wait(page, "")
|
||||||
|
await shell(sdk, session.id, second, secondToken)
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type("!")
|
||||||
|
await page.keyboard.press("ArrowUp")
|
||||||
|
await wait(page, second)
|
||||||
|
|
||||||
|
await page.keyboard.press("ArrowUp")
|
||||||
|
await wait(page, first)
|
||||||
|
|
||||||
|
await page.keyboard.press("ArrowDown")
|
||||||
|
await wait(page, second)
|
||||||
|
|
||||||
|
await page.keyboard.press("ArrowDown")
|
||||||
|
await wait(page, "")
|
||||||
|
|
||||||
|
await page.keyboard.press("Escape")
|
||||||
|
await wait(page, "")
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type(normal)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
await wait(page, "")
|
||||||
|
await reply(sdk, session.id, normalToken)
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.press("ArrowUp")
|
||||||
|
await wait(page, normal)
|
||||||
|
})
|
||||||
|
})
|
||||||
61
packages/app/e2e/prompt/prompt-shell.spec.ts
Normal file
61
packages/app/e2e/prompt/prompt-shell.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import type { ToolPart } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { test, expect } from "../fixtures"
|
||||||
|
import { sessionIDFromUrl } from "../actions"
|
||||||
|
import { promptSelector } from "../selectors"
|
||||||
|
import { createSdk } from "../utils"
|
||||||
|
|
||||||
|
const isBash = (part: unknown): part is ToolPart => {
|
||||||
|
if (!part || typeof part !== "object") return false
|
||||||
|
if (!("type" in part) || part.type !== "tool") return false
|
||||||
|
if (!("tool" in part) || part.tool !== "bash") return false
|
||||||
|
return "state" in part
|
||||||
|
}
|
||||||
|
|
||||||
|
test("shell mode runs a command in the project directory", async ({ page, withProject }) => {
|
||||||
|
test.setTimeout(120_000)
|
||||||
|
|
||||||
|
await withProject(async ({ directory, gotoSession }) => {
|
||||||
|
const sdk = createSdk(directory)
|
||||||
|
const prompt = page.locator(promptSelector)
|
||||||
|
const cmd = process.platform === "win32" ? "dir" : "ls"
|
||||||
|
|
||||||
|
await gotoSession()
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type("!")
|
||||||
|
await expect(prompt).toHaveAttribute("aria-label", /enter shell command/i)
|
||||||
|
|
||||||
|
await page.keyboard.type(cmd)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
|
||||||
|
|
||||||
|
const id = sessionIDFromUrl(page.url())
|
||||||
|
if (!id) throw new Error(`Failed to parse session id from url: ${page.url()}`)
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const list = await sdk.session.messages({ sessionID: id, limit: 50 }).then((x) => x.data ?? [])
|
||||||
|
const msg = list.findLast(
|
||||||
|
(item) => item.info.role === "assistant" && "path" in item.info && item.info.path.cwd === directory,
|
||||||
|
)
|
||||||
|
if (!msg) return
|
||||||
|
|
||||||
|
const part = msg.parts
|
||||||
|
.filter(isBash)
|
||||||
|
.find((item) => item.state.input?.command === cmd && item.state.status === "completed")
|
||||||
|
|
||||||
|
if (!part || part.state.status !== "completed") return
|
||||||
|
const output =
|
||||||
|
typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
|
||||||
|
if (!output.includes("README.md")) return
|
||||||
|
|
||||||
|
return { cwd: directory, output }
|
||||||
|
},
|
||||||
|
{ timeout: 90_000 },
|
||||||
|
)
|
||||||
|
.toEqual(expect.objectContaining({ cwd: directory, output: expect.stringContaining("README.md") }))
|
||||||
|
|
||||||
|
await expect(prompt).toHaveText("")
|
||||||
|
})
|
||||||
|
})
|
||||||
64
packages/app/e2e/prompt/prompt-slash-share.spec.ts
Normal file
64
packages/app/e2e/prompt/prompt-slash-share.spec.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { test, expect } from "../fixtures"
|
||||||
|
import { promptSelector } from "../selectors"
|
||||||
|
import { withSession } from "../actions"
|
||||||
|
|
||||||
|
const shareDisabled = process.env.OPENCODE_DISABLE_SHARE === "true" || process.env.OPENCODE_DISABLE_SHARE === "1"
|
||||||
|
|
||||||
|
async function seed(sdk: Parameters<typeof withSession>[0], sessionID: string) {
|
||||||
|
await sdk.session.promptAsync({
|
||||||
|
sessionID,
|
||||||
|
noReply: true,
|
||||||
|
parts: [{ type: "text", text: "e2e share seed" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? [])
|
||||||
|
return messages.length
|
||||||
|
},
|
||||||
|
{ timeout: 30_000 },
|
||||||
|
)
|
||||||
|
.toBeGreaterThan(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("/share and /unshare update session share state", async ({ page, sdk, gotoSession }) => {
|
||||||
|
test.skip(shareDisabled, "Share is disabled in this environment (OPENCODE_DISABLE_SHARE).")
|
||||||
|
|
||||||
|
await withSession(sdk, `e2e slash share ${Date.now()}`, async (session) => {
|
||||||
|
const prompt = page.locator(promptSelector)
|
||||||
|
|
||||||
|
await seed(sdk, session.id)
|
||||||
|
await gotoSession(session.id)
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type("/share")
|
||||||
|
await expect(page.locator('[data-slash-id="session.share"]').first()).toBeVisible()
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data)
|
||||||
|
return data?.share?.url || undefined
|
||||||
|
},
|
||||||
|
{ timeout: 30_000 },
|
||||||
|
)
|
||||||
|
.not.toBeUndefined()
|
||||||
|
|
||||||
|
await prompt.click()
|
||||||
|
await page.keyboard.type("/unshare")
|
||||||
|
await expect(page.locator('[data-slash-id="session.unshare"]').first()).toBeVisible()
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data)
|
||||||
|
return data?.share?.url || undefined
|
||||||
|
},
|
||||||
|
{ timeout: 30_000 },
|
||||||
|
)
|
||||||
|
.toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
120
packages/app/e2e/terminal/terminal-tabs.spec.ts
Normal file
120
packages/app/e2e/terminal/terminal-tabs.spec.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import type { Page } from "@playwright/test"
|
||||||
|
import { test, expect } from "../fixtures"
|
||||||
|
import { terminalSelector } from "../selectors"
|
||||||
|
import { terminalToggleKey, workspacePersistKey } from "../utils"
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
active?: string
|
||||||
|
all: Array<{
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
titleNumber: number
|
||||||
|
buffer?: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
async function open(page: Page) {
|
||||||
|
const terminal = page.locator(terminalSelector)
|
||||||
|
const visible = await terminal.isVisible().catch(() => false)
|
||||||
|
if (!visible) await page.keyboard.press(terminalToggleKey)
|
||||||
|
await expect(terminal).toBeVisible()
|
||||||
|
await expect(terminal.locator("textarea")).toHaveCount(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(page: Page, cmd: string) {
|
||||||
|
const terminal = page.locator(terminalSelector)
|
||||||
|
await expect(terminal).toBeVisible()
|
||||||
|
await terminal.click()
|
||||||
|
await page.keyboard.type(cmd)
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function store(page: Page, key: string) {
|
||||||
|
return page.evaluate((key) => {
|
||||||
|
const raw = localStorage.getItem(key)
|
||||||
|
if (raw) return JSON.parse(raw) as State
|
||||||
|
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const next = localStorage.key(i)
|
||||||
|
if (!next?.endsWith(":workspace:terminal")) continue
|
||||||
|
const value = localStorage.getItem(next)
|
||||||
|
if (!value) continue
|
||||||
|
return JSON.parse(value) as State
|
||||||
|
}
|
||||||
|
}, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("terminal tab buffers persist across tab switches", async ({ page, withProject }) => {
|
||||||
|
await withProject(async ({ directory, gotoSession }) => {
|
||||||
|
const key = workspacePersistKey(directory, "terminal")
|
||||||
|
const one = `E2E_TERM_ONE_${Date.now()}`
|
||||||
|
const two = `E2E_TERM_TWO_${Date.now()}`
|
||||||
|
const tabs = page.locator('#terminal-panel [data-slot="tabs-trigger"]')
|
||||||
|
|
||||||
|
await gotoSession()
|
||||||
|
await open(page)
|
||||||
|
|
||||||
|
await run(page, `echo ${one}`)
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: /new terminal/i }).click()
|
||||||
|
await expect(tabs).toHaveCount(2)
|
||||||
|
|
||||||
|
await run(page, `echo ${two}`)
|
||||||
|
|
||||||
|
await tabs
|
||||||
|
.filter({ hasText: /Terminal 1/ })
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const state = await store(page, key)
|
||||||
|
const first = state?.all.find((item) => item.titleNumber === 1)?.buffer ?? ""
|
||||||
|
const second = state?.all.find((item) => item.titleNumber === 2)?.buffer ?? ""
|
||||||
|
return first.includes(one) && second.includes(two)
|
||||||
|
},
|
||||||
|
{ timeout: 30_000 },
|
||||||
|
)
|
||||||
|
.toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("closing the active terminal tab falls back to the previous tab", async ({ page, withProject }) => {
|
||||||
|
await withProject(async ({ directory, gotoSession }) => {
|
||||||
|
const key = workspacePersistKey(directory, "terminal")
|
||||||
|
const tabs = page.locator('#terminal-panel [data-slot="tabs-trigger"]')
|
||||||
|
|
||||||
|
await gotoSession()
|
||||||
|
await open(page)
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: /new terminal/i }).click()
|
||||||
|
await expect(tabs).toHaveCount(2)
|
||||||
|
|
||||||
|
const second = tabs.filter({ hasText: /Terminal 2/ }).first()
|
||||||
|
await second.click()
|
||||||
|
await expect(second).toHaveAttribute("aria-selected", "true")
|
||||||
|
|
||||||
|
await second.hover()
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: /close terminal/i })
|
||||||
|
.nth(1)
|
||||||
|
.click({ force: true })
|
||||||
|
|
||||||
|
const first = tabs.filter({ hasText: /Terminal 1/ }).first()
|
||||||
|
await expect(tabs).toHaveCount(1)
|
||||||
|
await expect(first).toHaveAttribute("aria-selected", "true")
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const state = await store(page, key)
|
||||||
|
return {
|
||||||
|
count: state?.all.length ?? 0,
|
||||||
|
first: state?.all.some((item) => item.titleNumber === 1) ?? false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ timeout: 15_000 },
|
||||||
|
)
|
||||||
|
.toEqual({ count: 1, first: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { base64Encode, checksum } from "@opencode-ai/util/encode"
|
||||||
|
|
||||||
export const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
|
export const serverHost = process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"
|
||||||
export const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
export const serverPort = process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"
|
||||||
@@ -33,3 +33,9 @@ export function dirPath(directory: string) {
|
|||||||
export function sessionPath(directory: string, sessionID?: string) {
|
export function sessionPath(directory: string, sessionID?: string) {
|
||||||
return `${dirPath(directory)}/session${sessionID ? `/${sessionID}` : ""}`
|
return `${dirPath(directory)}/session${sessionID ? `/${sessionID}` : ""}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function workspacePersistKey(directory: string, key: string) {
|
||||||
|
const head = directory.slice(0, 12) || "workspace"
|
||||||
|
const sum = checksum(directory) ?? "0"
|
||||||
|
return `opencode.workspace.${head}.${sum}.dat:workspace:${key}`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user