|
|
@@ -1,164 +0,0 @@
|
|
|
-import { describe, expect } from "bun:test"
|
|
|
-import { Effect, Layer } from "effect"
|
|
|
-import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
|
-import { Agent } from "@opencode-ai/core/agent"
|
|
|
-import { Image } from "@opencode-ai/core/image"
|
|
|
-import { Permission } from "@opencode-ai/core/permission"
|
|
|
-import { PluginRuntime } from "@opencode-ai/core/plugin/runtime"
|
|
|
-import { Session } from "@opencode-ai/core/session"
|
|
|
-import { Tool } from "@opencode-ai/core/tool"
|
|
|
-import { SessionRenameTool } from "@opencode-ai/core/tool/plugin/session-rename"
|
|
|
-import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
|
|
-import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
|
|
-import { imagePassthrough } from "./lib/image"
|
|
|
-import { testEffect } from "./lib/effect"
|
|
|
-import { executeTool, registerToolPlugin, toolDefinitions, toolIdentity } from "./lib/tool"
|
|
|
-
|
|
|
-const currentSessionID = Session.ID.make("ses_session_rename_current")
|
|
|
-const otherSessionID = Session.ID.make("ses_session_rename_other")
|
|
|
-const renamed: Array<{ sessionID: Session.ID; title: string }> = []
|
|
|
-const assertions: Permission.AssertInput[] = []
|
|
|
-let deny = false
|
|
|
-
|
|
|
-const runtime = Layer.succeed(
|
|
|
- PluginRuntime.Service,
|
|
|
- PluginRuntime.Service.of({
|
|
|
- session: {
|
|
|
- get: () => Effect.die("unused"),
|
|
|
- create: () => Effect.die("unused"),
|
|
|
- messages: () => Effect.die("unused"),
|
|
|
- prompt: () => Effect.die("unused"),
|
|
|
- generate: () => Effect.die("unused"),
|
|
|
- command: () => Effect.die("unused"),
|
|
|
- resume: () => Effect.die("unused"),
|
|
|
- interrupt: () => Effect.die("unused"),
|
|
|
- synthetic: () => Effect.die("unused"),
|
|
|
- rename: (input) => Effect.sync(() => void renamed.push(input)),
|
|
|
- wait: () => Effect.die("unused"),
|
|
|
- },
|
|
|
- job: {
|
|
|
- start: () => Effect.die("unused"),
|
|
|
- wait: () => Effect.die("unused"),
|
|
|
- block: () => Effect.die("unused"),
|
|
|
- background: () => Effect.die("unused"),
|
|
|
- cancel: () => Effect.die("unused"),
|
|
|
- },
|
|
|
- location: {
|
|
|
- agent: {
|
|
|
- list: () => Effect.die("unused"),
|
|
|
- },
|
|
|
- },
|
|
|
- }),
|
|
|
-)
|
|
|
-
|
|
|
-const permission = Layer.succeed(
|
|
|
- Permission.Service,
|
|
|
- Permission.Service.of({
|
|
|
- assert: (input) =>
|
|
|
- Effect.sync(() => assertions.push(input)).pipe(
|
|
|
- Effect.andThen(
|
|
|
- deny
|
|
|
- ? Effect.fail(
|
|
|
- new Permission.BlockedError({
|
|
|
- rules: [],
|
|
|
- permission: input.action,
|
|
|
- resources: input.resources,
|
|
|
- }),
|
|
|
- )
|
|
|
- : Effect.void,
|
|
|
- ),
|
|
|
- ),
|
|
|
- ask: () => Effect.die("unused"),
|
|
|
- reply: () => Effect.die("unused"),
|
|
|
- get: () => Effect.die("unused"),
|
|
|
- forSession: () => Effect.die("unused"),
|
|
|
- list: () => Effect.die("unused"),
|
|
|
- }),
|
|
|
-)
|
|
|
-
|
|
|
-const sessionRenameToolNode = makeLocationNode({
|
|
|
- name: "test/session-rename-tool-plugin",
|
|
|
- layer: Layer.effectDiscard(registerToolPlugin(SessionRenameTool.Plugin)),
|
|
|
- deps: [Tool.node, Permission.node, PluginRuntime.node],
|
|
|
-})
|
|
|
-
|
|
|
-const it = testEffect(
|
|
|
- AppNodeBuilder.build(LayerNode.group([Tool.node, sessionRenameToolNode]), [
|
|
|
- [Permission.node, permission],
|
|
|
- [PluginRuntime.node, runtime],
|
|
|
- [Image.node, imagePassthrough],
|
|
|
- ]),
|
|
|
-)
|
|
|
-
|
|
|
-describe("SessionRenameTool", () => {
|
|
|
- it.effect("registers directly and confines rename to the invocation session", () =>
|
|
|
- Effect.gen(function* () {
|
|
|
- renamed.length = 0
|
|
|
- assertions.length = 0
|
|
|
- deny = false
|
|
|
- const registry = yield* Tool.Service
|
|
|
- const snapshot = yield* registry.snapshot()
|
|
|
-
|
|
|
- expect(snapshot.definitions.map((tool) => tool.name)).toEqual(["sessionRename", "execute"])
|
|
|
- expect(snapshot.codeModeCatalog?.some((tool) => tool.path === "sessionRename")).toBe(false)
|
|
|
- expect(
|
|
|
- yield* executeTool(registry, {
|
|
|
- sessionID: currentSessionID,
|
|
|
- ...toolIdentity,
|
|
|
- call: {
|
|
|
- type: "tool-call",
|
|
|
- id: "call-session-rename",
|
|
|
- name: "sessionRename",
|
|
|
- input: { title: "\n Focused\n title\t", sessionID: otherSessionID },
|
|
|
- },
|
|
|
- }),
|
|
|
- ).toEqual({
|
|
|
- status: "completed",
|
|
|
- output: { title: "Focused title" },
|
|
|
- content: [{ type: "text", text: "Renamed the current session to: Focused title" }],
|
|
|
- metadata: { title: "Focused title" },
|
|
|
- })
|
|
|
- expect(renamed).toEqual([{ sessionID: currentSessionID, title: "Focused title" }])
|
|
|
- expect(assertions).toMatchObject([
|
|
|
- {
|
|
|
- action: "sessionRename",
|
|
|
- resources: ["*"],
|
|
|
- sessionID: currentSessionID,
|
|
|
- agent: Agent.ID.make("build"),
|
|
|
- source: { type: "tool", messageID: toolIdentity.messageID, callID: "call-session-rename" },
|
|
|
- },
|
|
|
- ])
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- it.effect("filters catalog denial and enforces leaf permission", () =>
|
|
|
- Effect.gen(function* () {
|
|
|
- renamed.length = 0
|
|
|
- deny = true
|
|
|
- const registry = yield* Tool.Service
|
|
|
-
|
|
|
- expect(
|
|
|
- (yield* toolDefinitions(registry, [{ action: "sessionRename", resource: "*", effect: "deny" }])).map(
|
|
|
- (tool) => tool.name,
|
|
|
- ),
|
|
|
- ).toEqual(["execute"])
|
|
|
- expect(
|
|
|
- yield* executeTool(registry, {
|
|
|
- sessionID: currentSessionID,
|
|
|
- ...toolIdentity,
|
|
|
- call: {
|
|
|
- type: "tool-call",
|
|
|
- id: "call-session-rename-denied",
|
|
|
- name: "sessionRename",
|
|
|
- input: { title: "Denied title" },
|
|
|
- },
|
|
|
- }),
|
|
|
- ).toEqual({
|
|
|
- status: "error",
|
|
|
- error: { type: "permission.rejected", message: "Permission denied: sessionRename" },
|
|
|
- })
|
|
|
- expect(renamed).toEqual([])
|
|
|
- deny = false
|
|
|
- }),
|
|
|
- )
|
|
|
-})
|