diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index 952451c8e0..3327bef396 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -43,7 +43,6 @@ import { QuestionTool } from "../tool/plugin/question" import { ReadToolFileSystem } from "../tool/read-filesystem" import { ReadTool } from "../tool/plugin/read" import { ShellTool } from "../tool/plugin/shell" -import { SessionRenameTool } from "../tool/plugin/session-rename" import { SkillTool } from "../tool/plugin/skill" import { SubagentTool } from "../tool/plugin/subagent" import { Tool } from "../tool" @@ -150,7 +149,6 @@ const pre = [ QuestionTool.Plugin, ReadTool.Plugin, ShellTool.Plugin, - SessionRenameTool.Plugin, SkillTool.Plugin, SubagentTool.Plugin, WebFetchTool.Plugin, diff --git a/packages/core/src/tool/plugin/session-rename.ts b/packages/core/src/tool/plugin/session-rename.ts deleted file mode 100644 index d82f3c20f1..0000000000 --- a/packages/core/src/tool/plugin/session-rename.ts +++ /dev/null @@ -1,77 +0,0 @@ -export * as SessionRenameTool from "./session-rename" - -import type { Context as PluginContext } from "@opencode-ai/plugin/effect/plugin" -import { ToolFailure } from "@opencode-ai/ai" -import { Effect, Schema } from "effect" -import { Permission } from "../../permission" -import { PluginRuntime } from "../../plugin/runtime" - -export const name = "sessionRename" - -export const description = - "Rename the current session. Use a short, specific title that summarizes the work being done. This tool can only rename the session you are currently working in." - -export const Input = Schema.Struct({ - title: Schema.String.check( - Schema.isMinLength(1, { message: "Title must not be empty" }), - Schema.isMaxLength(100, { message: "Title must be 100 characters or fewer" }), - ).annotate({ description: "New title for the current session" }), -}) - -export const Output = Schema.Struct({ - title: Schema.String, -}) - -export const Plugin = { - id: "opencode.tool.session-rename", - effect: Effect.fn("SessionRenameTool.Plugin")(function* (ctx: PluginContext) { - const runtime = yield* PluginRuntime.Service - const permission = yield* Permission.Service - - yield* ctx.tool - .transform((draft) => - draft.add( - ({ - name, - options: { codemode: false }, - description, - input: Input, - output: Output, - execute: (input, context) => - Effect.gen(function* () { - const title = input.title.replace(/\s+/g, " ").trim() - if (!title) return yield* new ToolFailure({ message: "Session title must not be empty" }) - yield* permission - .assert({ - action: name, - resources: ["*"], - save: ["*"], - metadata: { title }, - sessionID: context.sessionID, - agent: context.agent, - source: { type: "tool", messageID: context.messageID, callID: context.callID }, - }) - .pipe( - Effect.mapError( - (error) => new ToolFailure({ message: "Permission denied: sessionRename", error }), - ), - ) - yield* runtime.session - .rename({ sessionID: context.sessionID, title }) - .pipe( - Effect.mapError( - (error) => new ToolFailure({ message: "Unable to rename the current session", error }), - ), - ) - return { - output: { title }, - content: `Renamed the current session to: ${title}`, - metadata: { title }, - } - }), - }), - ), - ) - .pipe(Effect.orDie) - }), -} diff --git a/packages/core/test/location-layer.test.ts b/packages/core/test/location-layer.test.ts index c7ea33c49f..bf897a6b1a 100644 --- a/packages/core/test/location-layer.test.ts +++ b/packages/core/test/location-layer.test.ts @@ -552,9 +552,8 @@ describe("LocationServiceMap", () => { "grep", "question", "read", - "shell", - "sessionRename", - "skill", + "shell", + "skill", "subagent", "webfetch", "websearch", @@ -586,7 +585,6 @@ describe("LocationServiceMap", () => { "patch", "question", "read", - "sessionRename", "shell", "skill", "subagent", @@ -606,7 +604,6 @@ describe("LocationServiceMap", () => { "patch", "question", "read", - "sessionRename", "shell", "skill", "subagent", diff --git a/packages/core/test/tool-session-rename.test.ts b/packages/core/test/tool-session-rename.test.ts deleted file mode 100644 index 2f67f0ecd2..0000000000 --- a/packages/core/test/tool-session-rename.test.ts +++ /dev/null @@ -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 - }), - ) -})