chore: generate

This commit is contained in:
opencode-agent[bot]
2026-04-11 02:36:59 +00:00
parent c5fb6281f0
commit 577139c626
12 changed files with 693 additions and 549 deletions
+3 -3
View File
@@ -106,8 +106,7 @@ export namespace SessionPrompt {
const run = { const run = {
promise: <A, E>(effect: Effect.Effect<A, E>) => promise: <A, E>(effect: Effect.Effect<A, E>) =>
Effect.runPromise(effect.pipe(Effect.provide(EffectLogger.layer))), Effect.runPromise(effect.pipe(Effect.provide(EffectLogger.layer))),
fork: <A, E>(effect: Effect.Effect<A, E>) => fork: <A, E>(effect: Effect.Effect<A, E>) => Effect.runFork(effect.pipe(Effect.provide(EffectLogger.layer))),
Effect.runFork(effect.pipe(Effect.provide(EffectLogger.layer))),
} }
const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) { const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) {
@@ -638,7 +637,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}, },
} satisfies MessageV2.ToolPart) } satisfies MessageV2.ToolPart)
} }
})), }),
),
) )
const attachments = result?.attachments?.map((attachment) => ({ const attachments = result?.attachments?.map((attachment) => ({
@@ -11,7 +11,9 @@ import { Bus } from "../../src/bus"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
const runtime = ManagedRuntime.make(Layer.mergeAll(LSP.defaultLayer, AppFileSystem.defaultLayer, Format.defaultLayer, Bus.layer)) const runtime = ManagedRuntime.make(
Layer.mergeAll(LSP.defaultLayer, AppFileSystem.defaultLayer, Format.defaultLayer, Bus.layer),
)
const baseCtx = { const baseCtx = {
sessionID: SessionID.make("ses_test"), sessionID: SessionID.make("ses_test"),
@@ -57,7 +59,9 @@ const makeCtx = () => {
const ctx: ToolCtx = { const ctx: ToolCtx = {
...baseCtx, ...baseCtx,
ask: (input) => ask: (input) =>
Effect.sync(() => { calls.push(input) }), Effect.sync(() => {
calls.push(input)
}),
} }
return { ctx, calls } return { ctx, calls }
+148 -74
View File
@@ -132,13 +132,15 @@ describe("tool.bash", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: "echo test", command: "echo test",
description: "Echo test message", description: "Echo test message",
}, },
ctx, ctx,
)) ),
)
expect(result.metadata.exit).toBe(0) expect(result.metadata.exit).toBe(0)
expect(result.metadata.output).toContain("test") expect(result.metadata.output).toContain("test")
}, },
@@ -154,13 +156,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "echo hello", command: "echo hello",
description: "Echo hello", description: "Echo hello",
}, },
capture(requests), capture(requests),
)) ),
)
expect(requests.length).toBe(1) expect(requests.length).toBe(1)
expect(requests[0].permission).toBe("bash") expect(requests[0].permission).toBe("bash")
expect(requests[0].patterns).toContain("echo hello") expect(requests[0].patterns).toContain("echo hello")
@@ -175,13 +179,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "echo foo && echo bar", command: "echo foo && echo bar",
description: "Echo twice", description: "Echo twice",
}, },
capture(requests), capture(requests),
)) ),
)
expect(requests.length).toBe(1) expect(requests.length).toBe(1)
expect(requests[0].permission).toBe("bash") expect(requests[0].permission).toBe("bash")
expect(requests[0].patterns).toContain("echo foo") expect(requests[0].patterns).toContain("echo foo")
@@ -199,13 +205,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "Write-Host foo; if ($?) { Write-Host bar }", command: "Write-Host foo; if ($?) { Write-Host bar }",
description: "Check PowerShell conditional", description: "Check PowerShell conditional",
}, },
capture(requests), capture(requests),
)) ),
)
const bashReq = requests.find((r) => r.permission === "bash") const bashReq = requests.find((r) => r.permission === "bash")
expect(bashReq).toBeDefined() expect(bashReq).toBeDefined()
expect(bashReq!.patterns).toContain("Write-Host foo") expect(bashReq!.patterns).toContain("Write-Host foo")
@@ -227,13 +235,15 @@ describe("tool.bash permissions", () => {
const file = process.platform === "win32" ? `${process.env.WINDIR!.replaceAll("\\", "/")}/*` : "/etc/*" const file = process.platform === "win32" ? `${process.env.WINDIR!.replaceAll("\\", "/")}/*` : "/etc/*"
const want = process.platform === "win32" ? glob(path.join(process.env.WINDIR!, "*")) : "/etc/*" const want = process.platform === "win32" ? glob(path.join(process.env.WINDIR!, "*")) : "/etc/*"
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: `cat ${file}`, command: `cat ${file}`,
description: "Read wildcard path", description: "Read wildcard path",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -258,13 +268,15 @@ describe("tool.bash permissions", () => {
const bash = await initBash() const bash = await initBash()
const file = path.join(outerTmp.path, "outside.txt").replaceAll("\\", "/") const file = path.join(outerTmp.path, "outside.txt").replaceAll("\\", "/")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: `echo $(cat "${file}")`, command: `echo $(cat "${file}")`,
description: "Read nested bash file", description: "Read nested bash file",
}, },
capture(requests), capture(requests),
)) ),
)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
const bashReq = requests.find((r) => r.permission === "bash") const bashReq = requests.find((r) => r.permission === "bash")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -290,13 +302,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: `Copy-Item -PassThru "${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini" ./out`, command: `Copy-Item -PassThru "${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini" ./out`,
description: "Copy Windows ini", description: "Copy Windows ini",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -317,13 +331,15 @@ describe("tool.bash permissions", () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const file = `${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini` const file = `${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: `Write-Output $(Get-Content ${file})`, command: `Write-Output $(Get-Content ${file})`,
description: "Read nested PowerShell file", description: "Read nested PowerShell file",
}, },
capture(requests), capture(requests),
)) ),
)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
const bashReq = requests.find((r) => r.permission === "bash") const bashReq = requests.find((r) => r.permission === "bash")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -348,13 +364,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: 'Get-Content "C:../outside.txt"', command: 'Get-Content "C:../outside.txt"',
description: "Read drive-relative file", description: "Read drive-relative file",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]?.permission).toBe("external_directory") expect(requests[0]?.permission).toBe("external_directory")
if (requests[0]?.permission !== "external_directory") return if (requests[0]?.permission !== "external_directory") return
@@ -376,13 +394,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: 'Get-Content "$HOME/.ssh/config"', command: 'Get-Content "$HOME/.ssh/config"',
description: "Read home config", description: "Read home config",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]?.permission).toBe("external_directory") expect(requests[0]?.permission).toBe("external_directory")
if (requests[0]?.permission !== "external_directory") return if (requests[0]?.permission !== "external_directory") return
@@ -405,13 +425,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: 'Get-Content "$PWD/../outside.txt"', command: 'Get-Content "$PWD/../outside.txt"',
description: "Read pwd-relative file", description: "Read pwd-relative file",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]?.permission).toBe("external_directory") expect(requests[0]?.permission).toBe("external_directory")
if (requests[0]?.permission !== "external_directory") return if (requests[0]?.permission !== "external_directory") return
@@ -433,13 +455,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: 'Get-Content "$PSHOME/outside.txt"', command: 'Get-Content "$PSHOME/outside.txt"',
description: "Read pshome file", description: "Read pshome file",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]?.permission).toBe("external_directory") expect(requests[0]?.permission).toBe("external_directory")
if (requests[0]?.permission !== "external_directory") return if (requests[0]?.permission !== "external_directory") return
@@ -466,13 +490,15 @@ describe("tool.bash permissions", () => {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const root = path.parse(process.env.WINDIR!).root.replace(/[\\/]+$/, "") const root = path.parse(process.env.WINDIR!).root.replace(/[\\/]+$/, "")
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: `Get-Content -Path "${root}$env:${key}\\Windows\\win.ini"`, command: `Get-Content -Path "${root}$env:${key}\\Windows\\win.ini"`,
description: "Read Windows ini with missing env", description: "Read Windows ini with missing env",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -496,13 +522,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "Get-Content $env:WINDIR/win.ini", command: "Get-Content $env:WINDIR/win.ini",
description: "Read Windows ini from env", description: "Read Windows ini from env",
}, },
capture(requests), capture(requests),
)) ),
)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
expect(extDirReq!.patterns).toContain( expect(extDirReq!.patterns).toContain(
@@ -525,13 +553,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: `Get-Content -Path FileSystem::${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`, command: `Get-Content -Path FileSystem::${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`,
description: "Read Windows ini from FileSystem provider", description: "Read Windows ini from FileSystem provider",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]?.permission).toBe("external_directory") expect(requests[0]?.permission).toBe("external_directory")
if (requests[0]?.permission !== "external_directory") return if (requests[0]?.permission !== "external_directory") return
@@ -555,13 +585,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: "Get-Content ${env:WINDIR}/win.ini", command: "Get-Content ${env:WINDIR}/win.ini",
description: "Read Windows ini from braced env", description: "Read Windows ini from braced env",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]?.permission).toBe("external_directory") expect(requests[0]?.permission).toBe("external_directory")
if (requests[0]?.permission !== "external_directory") return if (requests[0]?.permission !== "external_directory") return
@@ -583,13 +615,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "Set-Location C:/Windows", command: "Set-Location C:/Windows",
description: "Change location", description: "Change location",
}, },
capture(requests), capture(requests),
)) ),
)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
const bashReq = requests.find((r) => r.permission === "bash") const bashReq = requests.find((r) => r.permission === "bash")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -612,13 +646,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "Write-Output ('a' * 3)", command: "Write-Output ('a' * 3)",
description: "Write repeated text", description: "Write repeated text",
}, },
capture(requests), capture(requests),
)) ),
)
const bashReq = requests.find((r) => r.permission === "bash") const bashReq = requests.find((r) => r.permission === "bash")
expect(bashReq).toBeDefined() expect(bashReq).toBeDefined()
expect(bashReq!.patterns).not.toContain("a * 3") expect(bashReq!.patterns).not.toContain("a * 3")
@@ -639,13 +675,15 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: "cd ../", command: "cd ../",
description: "Change to parent directory", description: "Change to parent directory",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -662,14 +700,16 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: "echo ok", command: "echo ok",
workdir: os.tmpdir(), workdir: os.tmpdir(),
description: "Echo from temp dir", description: "Echo from temp dir",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeDefined() expect(extDirReq).toBeDefined()
@@ -692,14 +732,16 @@ describe("tool.bash permissions", () => {
for (const dir of forms(outerTmp.path)) { for (const dir of forms(outerTmp.path)) {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: "echo ok", command: "echo ok",
workdir: dir, workdir: dir,
description: "Echo from external dir", description: "Echo from external dir",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
@@ -725,14 +767,16 @@ describe("tool.bash permissions", () => {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const want = glob(path.join(os.tmpdir(), "*")) const want = glob(path.join(os.tmpdir(), "*"))
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: "echo ok", command: "echo ok",
workdir: "/tmp", workdir: "/tmp",
description: "Echo from Git Bash tmp", description: "Echo from Git Bash tmp",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]).toMatchObject({ expect(requests[0]).toMatchObject({
permission: "external_directory", permission: "external_directory",
@@ -755,13 +799,15 @@ describe("tool.bash permissions", () => {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const want = glob(path.join(os.tmpdir(), "*")) const want = glob(path.join(os.tmpdir(), "*"))
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: "cat /tmp/opencode-does-not-exist", command: "cat /tmp/opencode-does-not-exist",
description: "Read Git Bash tmp file", description: "Read Git Bash tmp file",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
expect(requests[0]).toMatchObject({ expect(requests[0]).toMatchObject({
permission: "external_directory", permission: "external_directory",
@@ -790,13 +836,15 @@ describe("tool.bash permissions", () => {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const filepath = path.join(outerTmp.path, "outside.txt") const filepath = path.join(outerTmp.path, "outside.txt")
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ {
command: `cat ${filepath}`, command: `cat ${filepath}`,
description: "Read external file", description: "Read external file",
}, },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
const expected = glob(path.join(outerTmp.path, "*")) const expected = glob(path.join(outerTmp.path, "*"))
@@ -818,13 +866,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: `rm -rf ${path.join(tmp.path, "nested")}`, command: `rm -rf ${path.join(tmp.path, "nested")}`,
description: "Remove nested dir", description: "Remove nested dir",
}, },
capture(requests), capture(requests),
)) ),
)
const extDirReq = requests.find((r) => r.permission === "external_directory") const extDirReq = requests.find((r) => r.permission === "external_directory")
expect(extDirReq).toBeUndefined() expect(extDirReq).toBeUndefined()
}, },
@@ -838,13 +888,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "git log --oneline -5", command: "git log --oneline -5",
description: "Git log", description: "Git log",
}, },
capture(requests), capture(requests),
)) ),
)
expect(requests.length).toBe(1) expect(requests.length).toBe(1)
expect(requests[0].always.length).toBeGreaterThan(0) expect(requests[0].always.length).toBeGreaterThan(0)
expect(requests[0].always.some((item) => item.endsWith("*"))).toBe(true) expect(requests[0].always.some((item) => item.endsWith("*"))).toBe(true)
@@ -859,13 +911,15 @@ describe("tool.bash permissions", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await Effect.runPromise(bash.execute( await Effect.runPromise(
bash.execute(
{ {
command: "cd .", command: "cd .",
description: "Stay in current directory", description: "Stay in current directory",
}, },
capture(requests), capture(requests),
)) ),
)
const bashReq = requests.find((r) => r.permission === "bash") const bashReq = requests.find((r) => r.permission === "bash")
expect(bashReq).toBeUndefined() expect(bashReq).toBeUndefined()
}, },
@@ -881,10 +935,12 @@ describe("tool.bash permissions", () => {
const err = new Error("stop after permission") const err = new Error("stop after permission")
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
await expect( await expect(
Effect.runPromise(bash.execute( Effect.runPromise(
bash.execute(
{ command: "echo test > output.txt", description: "Redirect test output" }, { command: "echo test > output.txt", description: "Redirect test output" },
capture(requests, err), capture(requests, err),
)), ),
),
).rejects.toThrow(err.message) ).rejects.toThrow(err.message)
const bashReq = requests.find((r) => r.permission === "bash") const bashReq = requests.find((r) => r.permission === "bash")
expect(bashReq).toBeDefined() expect(bashReq).toBeDefined()
@@ -917,7 +973,8 @@ describe("tool.bash abort", () => {
const bash = await initBash() const bash = await initBash()
const controller = new AbortController() const controller = new AbortController()
const collected: string[] = [] const collected: string[] = []
const res = await Effect.runPromise(bash.execute( const res = await Effect.runPromise(
bash.execute(
{ {
command: `echo before && sleep 30`, command: `echo before && sleep 30`,
description: "Long running command", description: "Long running command",
@@ -933,7 +990,8 @@ describe("tool.bash abort", () => {
} }
}, },
}, },
)) ),
)
expect(res.output).toContain("before") expect(res.output).toContain("before")
expect(res.output).toContain("User aborted the command") expect(res.output).toContain("User aborted the command")
expect(collected.length).toBeGreaterThan(0) expect(collected.length).toBeGreaterThan(0)
@@ -946,14 +1004,16 @@ describe("tool.bash abort", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: `echo started && sleep 60`, command: `echo started && sleep 60`,
description: "Timeout test", description: "Timeout test",
timeout: 500, timeout: 500,
}, },
ctx, ctx,
)) ),
)
expect(result.output).toContain("started") expect(result.output).toContain("started")
expect(result.output).toContain("bash tool terminated command after exceeding timeout") expect(result.output).toContain("bash tool terminated command after exceeding timeout")
}, },
@@ -965,13 +1025,15 @@ describe("tool.bash abort", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: `echo stdout_msg && echo stderr_msg >&2`, command: `echo stdout_msg && echo stderr_msg >&2`,
description: "Stderr test", description: "Stderr test",
}, },
ctx, ctx,
)) ),
)
expect(result.output).toContain("stdout_msg") expect(result.output).toContain("stdout_msg")
expect(result.output).toContain("stderr_msg") expect(result.output).toContain("stderr_msg")
expect(result.metadata.exit).toBe(0) expect(result.metadata.exit).toBe(0)
@@ -984,13 +1046,15 @@ describe("tool.bash abort", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: `exit 42`, command: `exit 42`,
description: "Non-zero exit", description: "Non-zero exit",
}, },
ctx, ctx,
)) ),
)
expect(result.metadata.exit).toBe(42) expect(result.metadata.exit).toBe(42)
}, },
}) })
@@ -1002,7 +1066,8 @@ describe("tool.bash abort", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const updates: string[] = [] const updates: string[] = []
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: `echo first && sleep 0.1 && echo second`, command: `echo first && sleep 0.1 && echo second`,
description: "Streaming test", description: "Streaming test",
@@ -1014,7 +1079,8 @@ describe("tool.bash abort", () => {
if (output) updates.push(output) if (output) updates.push(output)
}, },
}, },
)) ),
)
expect(result.output).toContain("first") expect(result.output).toContain("first")
expect(result.output).toContain("second") expect(result.output).toContain("second")
expect(updates.length).toBeGreaterThan(1) expect(updates.length).toBeGreaterThan(1)
@@ -1030,13 +1096,15 @@ describe("tool.bash truncation", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const lineCount = Truncate.MAX_LINES + 500 const lineCount = Truncate.MAX_LINES + 500
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: fill("lines", lineCount), command: fill("lines", lineCount),
description: "Generate lines exceeding limit", description: "Generate lines exceeding limit",
}, },
ctx, ctx,
)) ),
)
mustTruncate(result) mustTruncate(result)
expect(result.output).toContain("truncated") expect(result.output).toContain("truncated")
expect(result.output).toContain("The tool call succeeded but the output was truncated") expect(result.output).toContain("The tool call succeeded but the output was truncated")
@@ -1050,13 +1118,15 @@ describe("tool.bash truncation", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const byteCount = Truncate.MAX_BYTES + 10000 const byteCount = Truncate.MAX_BYTES + 10000
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: fill("bytes", byteCount), command: fill("bytes", byteCount),
description: "Generate bytes exceeding limit", description: "Generate bytes exceeding limit",
}, },
ctx, ctx,
)) ),
)
mustTruncate(result) mustTruncate(result)
expect(result.output).toContain("truncated") expect(result.output).toContain("truncated")
expect(result.output).toContain("The tool call succeeded but the output was truncated") expect(result.output).toContain("The tool call succeeded but the output was truncated")
@@ -1069,13 +1139,15 @@ describe("tool.bash truncation", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: "echo hello", command: "echo hello",
description: "Echo hello", description: "Echo hello",
}, },
ctx, ctx,
)) ),
)
expect((result.metadata as { truncated?: boolean }).truncated).toBe(false) expect((result.metadata as { truncated?: boolean }).truncated).toBe(false)
expect(result.output).toContain("hello") expect(result.output).toContain("hello")
}, },
@@ -1088,13 +1160,15 @@ describe("tool.bash truncation", () => {
fn: async () => { fn: async () => {
const bash = await initBash() const bash = await initBash()
const lineCount = Truncate.MAX_LINES + 100 const lineCount = Truncate.MAX_LINES + 100
const result = await Effect.runPromise(bash.execute( const result = await Effect.runPromise(
bash.execute(
{ {
command: fill("lines", lineCount), command: fill("lines", lineCount),
description: "Generate lines for file check", description: "Generate lines for file check",
}, },
ctx, ctx,
)) ),
)
mustTruncate(result) mustTruncate(result)
const filepath = (result.metadata as { outputPath?: string }).outputPath const filepath = (result.metadata as { outputPath?: string }).outputPath
+76 -38
View File
@@ -65,14 +65,16 @@ describe("tool.edit", () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const edit = await resolve() const edit = await resolve()
const result = await Effect.runPromise(edit.execute( const result = await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "", oldString: "",
newString: "new content", newString: "new content",
}, },
ctx, ctx,
)) ),
)
expect(result.metadata.diff).toContain("new content") expect(result.metadata.diff).toContain("new content")
@@ -90,14 +92,16 @@ describe("tool.edit", () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const edit = await resolve() const edit = await resolve()
await Effect.runPromise(edit.execute( await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "", oldString: "",
newString: "nested file", newString: "nested file",
}, },
ctx, ctx,
)) ),
)
const content = await fs.readFile(filepath, "utf-8") const content = await fs.readFile(filepath, "utf-8")
expect(content).toBe("nested file") expect(content).toBe("nested file")
@@ -118,14 +122,16 @@ describe("tool.edit", () => {
const unsubUpdated = await subscribeBus(FileWatcher.Event.Updated, () => events.push("updated")) const unsubUpdated = await subscribeBus(FileWatcher.Event.Updated, () => events.push("updated"))
const edit = await resolve() const edit = await resolve()
await Effect.runPromise(edit.execute( await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "", oldString: "",
newString: "content", newString: "content",
}, },
ctx, ctx,
)) ),
)
expect(events).toContain("updated") expect(events).toContain("updated")
unsubUpdated() unsubUpdated()
@@ -146,14 +152,16 @@ describe("tool.edit", () => {
await readFileTime(ctx.sessionID, filepath) await readFileTime(ctx.sessionID, filepath)
const edit = await resolve() const edit = await resolve()
const result = await Effect.runPromise(edit.execute( const result = await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "old content", oldString: "old content",
newString: "new content", newString: "new content",
}, },
ctx, ctx,
)) ),
)
expect(result.output).toContain("Edit applied successfully") expect(result.output).toContain("Edit applied successfully")
@@ -174,14 +182,16 @@ describe("tool.edit", () => {
const edit = await resolve() const edit = await resolve()
await expect( await expect(
Effect.runPromise(edit.execute( Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "old", oldString: "old",
newString: "new", newString: "new",
}, },
ctx, ctx,
)), ),
),
).rejects.toThrow("not found") ).rejects.toThrow("not found")
}, },
}) })
@@ -197,14 +207,16 @@ describe("tool.edit", () => {
fn: async () => { fn: async () => {
const edit = await resolve() const edit = await resolve()
await expect( await expect(
Effect.runPromise(edit.execute( Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "same", oldString: "same",
newString: "same", newString: "same",
}, },
ctx, ctx,
)), ),
),
).rejects.toThrow("identical") ).rejects.toThrow("identical")
}, },
}) })
@@ -222,14 +234,16 @@ describe("tool.edit", () => {
const edit = await resolve() const edit = await resolve()
await expect( await expect(
Effect.runPromise(edit.execute( Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "not in file", oldString: "not in file",
newString: "replacement", newString: "replacement",
}, },
ctx, ctx,
)), ),
),
).rejects.toThrow() ).rejects.toThrow()
}, },
}) })
@@ -245,14 +259,16 @@ describe("tool.edit", () => {
fn: async () => { fn: async () => {
const edit = await resolve() const edit = await resolve()
await expect( await expect(
Effect.runPromise(edit.execute( Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "content", oldString: "content",
newString: "modified", newString: "modified",
}, },
ctx, ctx,
)), ),
),
).rejects.toThrow("You must read file") ).rejects.toThrow("You must read file")
}, },
}) })
@@ -277,14 +293,16 @@ describe("tool.edit", () => {
// Try to edit with the new content // Try to edit with the new content
const edit = await resolve() const edit = await resolve()
await expect( await expect(
Effect.runPromise(edit.execute( Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "modified externally", oldString: "modified externally",
newString: "edited", newString: "edited",
}, },
ctx, ctx,
)), ),
),
).rejects.toThrow("modified since it was last read") ).rejects.toThrow("modified since it was last read")
}, },
}) })
@@ -301,7 +319,8 @@ describe("tool.edit", () => {
await readFileTime(ctx.sessionID, filepath) await readFileTime(ctx.sessionID, filepath)
const edit = await resolve() const edit = await resolve()
await Effect.runPromise(edit.execute( await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "foo", oldString: "foo",
@@ -309,7 +328,8 @@ describe("tool.edit", () => {
replaceAll: true, replaceAll: true,
}, },
ctx, ctx,
)) ),
)
const content = await fs.readFile(filepath, "utf-8") const content = await fs.readFile(filepath, "utf-8")
expect(content).toBe("qux bar qux baz qux") expect(content).toBe("qux bar qux baz qux")
@@ -333,14 +353,16 @@ describe("tool.edit", () => {
const unsubUpdated = await subscribeBus(FileWatcher.Event.Updated, () => events.push("updated")) const unsubUpdated = await subscribeBus(FileWatcher.Event.Updated, () => events.push("updated"))
const edit = await resolve() const edit = await resolve()
await Effect.runPromise(edit.execute( await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "original", oldString: "original",
newString: "modified", newString: "modified",
}, },
ctx, ctx,
)) ),
)
expect(events).toContain("updated") expect(events).toContain("updated")
unsubUpdated() unsubUpdated()
@@ -361,14 +383,16 @@ describe("tool.edit", () => {
await readFileTime(ctx.sessionID, filepath) await readFileTime(ctx.sessionID, filepath)
const edit = await resolve() const edit = await resolve()
await Effect.runPromise(edit.execute( await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "line2", oldString: "line2",
newString: "new line 2\nextra line", newString: "new line 2\nextra line",
}, },
ctx, ctx,
)) ),
)
const content = await fs.readFile(filepath, "utf-8") const content = await fs.readFile(filepath, "utf-8")
expect(content).toBe("line1\nnew line 2\nextra line\nline3") expect(content).toBe("line1\nnew line 2\nextra line\nline3")
@@ -387,14 +411,16 @@ describe("tool.edit", () => {
await readFileTime(ctx.sessionID, filepath) await readFileTime(ctx.sessionID, filepath)
const edit = await resolve() const edit = await resolve()
await Effect.runPromise(edit.execute( await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "old", oldString: "old",
newString: "new", newString: "new",
}, },
ctx, ctx,
)) ),
)
const content = await fs.readFile(filepath, "utf-8") const content = await fs.readFile(filepath, "utf-8")
expect(content).toBe("line1\r\nnew\r\nline3") expect(content).toBe("line1\r\nnew\r\nline3")
@@ -412,14 +438,16 @@ describe("tool.edit", () => {
fn: async () => { fn: async () => {
const edit = await resolve() const edit = await resolve()
await expect( await expect(
Effect.runPromise(edit.execute( Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "", oldString: "",
newString: "", newString: "",
}, },
ctx, ctx,
)), ),
),
).rejects.toThrow("identical") ).rejects.toThrow("identical")
}, },
}) })
@@ -437,14 +465,16 @@ describe("tool.edit", () => {
const edit = await resolve() const edit = await resolve()
await expect( await expect(
Effect.runPromise(edit.execute( Effect.runPromise(
edit.execute(
{ {
filePath: dirpath, filePath: dirpath,
oldString: "old", oldString: "old",
newString: "new", newString: "new",
}, },
ctx, ctx,
)), ),
),
).rejects.toThrow("directory") ).rejects.toThrow("directory")
}, },
}) })
@@ -461,14 +491,16 @@ describe("tool.edit", () => {
await readFileTime(ctx.sessionID, filepath) await readFileTime(ctx.sessionID, filepath)
const edit = await resolve() const edit = await resolve()
const result = await Effect.runPromise(edit.execute( const result = await Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "line2", oldString: "line2",
newString: "new line a\nnew line b", newString: "new line a\nnew line b",
}, },
ctx, ctx,
)) ),
)
expect(result.metadata.filediff).toBeDefined() expect(result.metadata.filediff).toBeDefined()
expect(result.metadata.filediff.file).toBe(filepath) expect(result.metadata.filediff.file).toBe(filepath)
@@ -530,7 +562,8 @@ describe("tool.edit", () => {
const edit = await resolve() const edit = await resolve()
const filePath = path.join(tmp.path, "test.txt") const filePath = path.join(tmp.path, "test.txt")
await readFileTime(ctx.sessionID, filePath) await readFileTime(ctx.sessionID, filePath)
await Effect.runPromise(edit.execute( await Effect.runPromise(
edit.execute(
{ {
filePath, filePath,
oldString: input.oldString, oldString: input.oldString,
@@ -538,7 +571,8 @@ describe("tool.edit", () => {
replaceAll: input.replaceAll, replaceAll: input.replaceAll,
}, },
ctx, ctx,
)) ),
)
return await Bun.file(filePath).text() return await Bun.file(filePath).text()
}, },
}) })
@@ -675,26 +709,30 @@ describe("tool.edit", () => {
const edit = await resolve() const edit = await resolve()
// Two concurrent edits // Two concurrent edits
const promise1 = Effect.runPromise(edit.execute( const promise1 = Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "0", oldString: "0",
newString: "1", newString: "1",
}, },
ctx, ctx,
)) ),
)
// Need to read again since FileTime tracks per-session // Need to read again since FileTime tracks per-session
await readFileTime(ctx.sessionID, filepath) await readFileTime(ctx.sessionID, filepath)
const promise2 = Effect.runPromise(edit.execute( const promise2 = Effect.runPromise(
edit.execute(
{ {
filePath: filepath, filePath: filepath,
oldString: "0", oldString: "0",
newString: "2", newString: "2",
}, },
ctx, ctx,
)) ),
)
// Both should complete without error (though one might fail due to content mismatch) // Both should complete without error (though one might fail due to content mismatch)
const results = await Promise.allSettled([promise1, promise2]) const results = await Promise.allSettled([promise1, promise2])
@@ -26,7 +26,10 @@ function makeCtx() {
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = [] const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
const ctx: Tool.Context = { const ctx: Tool.Context = {
...baseCtx, ...baseCtx,
ask: (req) => Effect.sync(() => { requests.push(req) }), ask: (req) =>
Effect.sync(() => {
requests.push(req)
}),
} }
return { requests, ctx } return { requests, ctx }
} }
+12 -6
View File
@@ -32,14 +32,16 @@ describe("tool.grep", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const grep = await initGrep() const grep = await initGrep()
const result = await Effect.runPromise(grep.execute( const result = await Effect.runPromise(
grep.execute(
{ {
pattern: "export", pattern: "export",
path: path.join(projectRoot, "src/tool"), path: path.join(projectRoot, "src/tool"),
include: "*.ts", include: "*.ts",
}, },
ctx, ctx,
)) ),
)
expect(result.metadata.matches).toBeGreaterThan(0) expect(result.metadata.matches).toBeGreaterThan(0)
expect(result.output).toContain("Found") expect(result.output).toContain("Found")
}, },
@@ -56,13 +58,15 @@ describe("tool.grep", () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const grep = await initGrep() const grep = await initGrep()
const result = await Effect.runPromise(grep.execute( const result = await Effect.runPromise(
grep.execute(
{ {
pattern: "xyznonexistentpatternxyz123", pattern: "xyznonexistentpatternxyz123",
path: tmp.path, path: tmp.path,
}, },
ctx, ctx,
)) ),
)
expect(result.metadata.matches).toBe(0) expect(result.metadata.matches).toBe(0)
expect(result.output).toBe("No files found") expect(result.output).toBe("No files found")
}, },
@@ -81,13 +85,15 @@ describe("tool.grep", () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const grep = await initGrep() const grep = await initGrep()
const result = await Effect.runPromise(grep.execute( const result = await Effect.runPromise(
grep.execute(
{ {
pattern: "line", pattern: "line",
path: tmp.path, path: tmp.path,
}, },
ctx, ctx,
)) ),
)
expect(result.metadata.matches).toBeGreaterThan(0) expect(result.metadata.matches).toBeGreaterThan(0)
}, },
}) })
+3 -1
View File
@@ -96,7 +96,9 @@ const asks = () => {
next: { next: {
...ctx, ...ctx,
ask: (req: Omit<Permission.Request, "id" | "sessionID" | "tool">) => ask: (req: Omit<Permission.Request, "id" | "sessionID" | "tool">) =>
Effect.sync(() => { items.push(req) }), Effect.sync(() => {
items.push(req)
}),
}, },
} }
} }
+3 -1
View File
@@ -157,7 +157,9 @@ Use this skill.
const ctx: Tool.Context = { const ctx: Tool.Context = {
...baseCtx, ...baseCtx,
ask: (req) => ask: (req) =>
Effect.sync(() => { requests.push(req) }), Effect.sync(() => {
requests.push(req)
}),
} }
const result = await runtime.runPromise(tool.execute({ name: "tool-skill" }, ctx)) const result = await runtime.runPromise(tool.execute({ name: "tool-skill" }, ctx))
+3 -1
View File
@@ -248,7 +248,9 @@ describe("tool.task", () => {
messages: [], messages: [],
metadata() {}, metadata() {},
ask: (input) => ask: (input) =>
Effect.sync(() => { calls.push(input) }), Effect.sync(() => {
calls.push(input)
}),
}, },
) )
@@ -32,7 +32,10 @@ describe("Tool.define", () => {
test("function-defined tool returns fresh objects and is unaffected", async () => { test("function-defined tool returns fresh objects and is unaffected", async () => {
const info = await Effect.runPromise( const info = await Effect.runPromise(
Tool.define("test-fn-tool", Effect.succeed(() => Promise.resolve(makeTool("test")))), Tool.define(
"test-fn-tool",
Effect.succeed(() => Promise.resolve(makeTool("test"))),
),
) )
const first = await info.init() const first = await info.init()
+9 -6
View File
@@ -42,10 +42,9 @@ describe("tool.webfetch", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const webfetch = await initTool() const webfetch = await initTool()
const result = await Effect.runPromise(webfetch.execute( const result = await Effect.runPromise(
{ url: new URL("/image.png", url).toString(), format: "markdown" }, webfetch.execute({ url: new URL("/image.png", url).toString(), format: "markdown" }, ctx),
ctx, )
))
expect(result.output).toBe("Image fetched successfully") expect(result.output).toBe("Image fetched successfully")
expect(result.attachments).toBeDefined() expect(result.attachments).toBeDefined()
expect(result.attachments?.length).toBe(1) expect(result.attachments?.length).toBe(1)
@@ -74,7 +73,9 @@ describe("tool.webfetch", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const webfetch = await initTool() const webfetch = await initTool()
const result = await Effect.runPromise(webfetch.execute({ url: new URL("/image.svg", url).toString(), format: "html" }, ctx)) const result = await Effect.runPromise(
webfetch.execute({ url: new URL("/image.svg", url).toString(), format: "html" }, ctx),
)
expect(result.output).toContain("<svg") expect(result.output).toContain("<svg")
expect(result.attachments).toBeUndefined() expect(result.attachments).toBeUndefined()
}, },
@@ -95,7 +96,9 @@ describe("tool.webfetch", () => {
directory: projectRoot, directory: projectRoot,
fn: async () => { fn: async () => {
const webfetch = await initTool() const webfetch = await initTool()
const result = await Effect.runPromise(webfetch.execute({ url: new URL("/file.txt", url).toString(), format: "text" }, ctx)) const result = await Effect.runPromise(
webfetch.execute({ url: new URL("/file.txt", url).toString(), format: "text" }, ctx),
)
expect(result.output).toBe("hello from webfetch") expect(result.output).toBe("hello from webfetch")
expect(result.attachments).toBeUndefined() expect(result.attachments).toBeUndefined()
}, },
+8 -1
View File
@@ -31,7 +31,14 @@ afterEach(async () => {
}) })
const it = testEffect( const it = testEffect(
Layer.mergeAll(LSP.defaultLayer, AppFileSystem.defaultLayer, FileTime.defaultLayer, Bus.layer, Format.defaultLayer, CrossSpawnSpawner.defaultLayer), Layer.mergeAll(
LSP.defaultLayer,
AppFileSystem.defaultLayer,
FileTime.defaultLayer,
Bus.layer,
Format.defaultLayer,
CrossSpawnSpawner.defaultLayer,
),
) )
const init = Effect.fn("WriteToolTest.init")(function* () { const init = Effect.fn("WriteToolTest.init")(function* () {