Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/opencode/src/session/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export namespace SessionCompaction {
agent: "compaction",
summary: true,
path: {
cwd: Instance.directory,
cwd: Session.directory.get(),
root: Instance.worktree,
},
cost: 0,
Expand Down
13 changes: 13 additions & 0 deletions packages/opencode/src/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,22 @@ import type { Provider } from "@/provider/provider"
import { PermissionNext } from "@/permission/next"
import { Global } from "@/global"

// Current working directory for the active session.
let _sessionDirectory: string | undefined

export namespace Session {
const log = Log.create({ service: "session" })

/** Get/set the current session's working directory. Falls back to Instance.directory if not set. */
export const directory = {
get() {
return _sessionDirectory ?? Instance.directory
},
set(value: string | undefined) {
_sessionDirectory = value
},
}

const parentTitlePrefix = "New session - "
const childTitlePrefix = "Child session - "

Expand Down
14 changes: 11 additions & 3 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ export namespace SessionPrompt {

let step = 0
const session = await Session.get(sessionID)

// Set the working directory for this session (enables session-specific directories)
Session.directory.set(session.directory)

while (true) {
SessionStatus.set(sessionID, { type: "busy" })
log.info("loop", { step, sessionID })
Expand Down Expand Up @@ -325,7 +329,7 @@ export namespace SessionPrompt {
mode: task.agent,
agent: task.agent,
path: {
cwd: Instance.directory,
cwd: Session.directory.get(),
root: Instance.worktree,
},
cost: 0,
Expand Down Expand Up @@ -525,7 +529,7 @@ export namespace SessionPrompt {
mode: agent.name,
agent: agent.name,
path: {
cwd: Instance.directory,
cwd: Session.directory.get(),
root: Instance.worktree,
},
cost: 0,
Expand Down Expand Up @@ -1350,6 +1354,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the
using _ = defer(() => cancel(input.sessionID))

const session = await Session.get(input.sessionID)

// Set the working directory for this session (enables session-specific directories)
Session.directory.set(session.directory)

if (session.revert) {
SessionRevert.cleanup(session)
}
Expand Down Expand Up @@ -1478,7 +1486,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
const args = matchingInvocation?.args

const proc = spawn(shell, args, {
cwd: Instance.directory,
cwd: Session.directory.get(),
detached: process.platform !== "win32",
stdio: ["ignore", "pipe", "pipe"],
env: {
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/session/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Filesystem } from "../util/filesystem"
import { Config } from "../config/config"

import { Instance } from "../project/instance"
import { Session } from "./index"
import path from "path"
import os from "os"

Expand Down Expand Up @@ -43,7 +44,7 @@ export namespace SystemPrompt {
[
`Here is some useful information about the environment you are running in:`,
`<env>`,
` Working directory: ${Instance.directory}`,
` Working directory: ${Session.directory.get()}`,
` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
` Platform: ${process.platform}`,
` Today's date: ${new Date().toDateString()}`,
Expand All @@ -52,7 +53,7 @@ export namespace SystemPrompt {
` ${
project.vcs === "git" && false
? await Ripgrep.tree({
cwd: Instance.directory,
cwd: Session.directory.get(),
limit: 200,
})
: ""
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/tool/apply_patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FileTime } from "../file/time"
import { Bus } from "../bus"
import { FileWatcher } from "../file/watcher"
import { Instance } from "../project/instance"
import { Session } from "../session"
import { Patch } from "../patch"
import { createTwoFilesPatch, diffLines } from "diff"
import { assertExternalDirectory } from "./external-directory"
Expand Down Expand Up @@ -57,7 +58,7 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
let totalDiff = ""

for (const hunk of hunks) {
const filePath = path.resolve(Instance.directory, hunk.path)
const filePath = path.resolve(Session.directory.get(), hunk.path)
await assertExternalDirectory(ctx, filePath)

switch (hunk.type) {
Expand Down Expand Up @@ -117,7 +118,7 @@ export const ApplyPatchTool = Tool.define("apply_patch", {
if (change.removed) deletions += change.count || 0
}

const movePath = hunk.move_path ? path.resolve(Instance.directory, hunk.move_path) : undefined
const movePath = hunk.move_path ? path.resolve(Session.directory.get(), hunk.move_path) : undefined
await assertExternalDirectory(ctx, movePath)

fileChanges.push({
Expand Down
7 changes: 4 additions & 3 deletions packages/opencode/src/tool/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import path from "path"
import DESCRIPTION from "./bash.txt"
import { Log } from "../util/log"
import { Instance } from "../project/instance"
import { Session } from "../session"
import { lazy } from "@/util/lazy"
import { Language } from "web-tree-sitter"

Expand Down Expand Up @@ -56,7 +57,7 @@ export const BashTool = Tool.define("bash", async () => {
log.info("bash tool using shell", { shell })

return {
description: DESCRIPTION.replaceAll("${directory}", Instance.directory)
description: DESCRIPTION.replaceAll("${directory}", Session.directory.get())
.replaceAll("${maxLines}", String(Truncate.MAX_LINES))
.replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)),
parameters: z.object({
Expand All @@ -65,7 +66,7 @@ export const BashTool = Tool.define("bash", async () => {
workdir: z
.string()
.describe(
`The working directory to run the command in. Defaults to ${Instance.directory}. Use this instead of 'cd' commands.`,
`The working directory to run the command in. Defaults to ${Session.directory.get()}. Use this instead of 'cd' commands.`,
)
.optional(),
description: z
Expand All @@ -75,7 +76,7 @@ export const BashTool = Tool.define("bash", async () => {
),
}),
async execute(params, ctx) {
const cwd = params.workdir || Instance.directory
const cwd = params.workdir || Session.directory.get()
if (params.timeout !== undefined && params.timeout < 0) {
throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
}
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/tool/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Bus } from "../bus"
import { FileTime } from "../file/time"
import { Filesystem } from "../util/filesystem"
import { Instance } from "../project/instance"
import { Session } from "../session"
import { Snapshot } from "@/snapshot"
import { assertExternalDirectory } from "./external-directory"

Expand All @@ -40,7 +41,7 @@ export const EditTool = Tool.define("edit", {
throw new Error("oldString and newString must be different")
}

const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Session.directory.get(), params.filePath)
await assertExternalDirectory(ctx, filePath)

let diff = ""
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/tool/glob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Tool } from "./tool"
import DESCRIPTION from "./glob.txt"
import { Ripgrep } from "../file/ripgrep"
import { Instance } from "../project/instance"
import { Session } from "../session"
import { assertExternalDirectory } from "./external-directory"

export const GlobTool = Tool.define("glob", {
Expand All @@ -28,8 +29,8 @@ export const GlobTool = Tool.define("glob", {
},
})

let search = params.path ?? Instance.directory
search = path.isAbsolute(search) ? search : path.resolve(Instance.directory, search)
let search = params.path ?? Session.directory.get()
search = path.isAbsolute(search) ? search : path.resolve(Session.directory.get(), search)
await assertExternalDirectory(ctx, search, { kind: "directory" })

const limit = 100
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/tool/grep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Ripgrep } from "../file/ripgrep"

import DESCRIPTION from "./grep.txt"
import { Instance } from "../project/instance"
import { Session } from "../session"
import path from "path"
import { assertExternalDirectory } from "./external-directory"

Expand Down Expand Up @@ -32,8 +33,8 @@ export const GrepTool = Tool.define("grep", {
},
})

let searchPath = params.path ?? Instance.directory
searchPath = path.isAbsolute(searchPath) ? searchPath : path.resolve(Instance.directory, searchPath)
let searchPath = params.path ?? Session.directory.get()
searchPath = path.isAbsolute(searchPath) ? searchPath : path.resolve(Session.directory.get(), searchPath)
await assertExternalDirectory(ctx, searchPath, { kind: "directory" })

const rgPath = await Ripgrep.filepath()
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/tool/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Tool } from "./tool"
import * as path from "path"
import DESCRIPTION from "./ls.txt"
import { Instance } from "../project/instance"
import { Session } from "../session"
import { Ripgrep } from "../file/ripgrep"
import { assertExternalDirectory } from "./external-directory"

Expand Down Expand Up @@ -42,7 +43,7 @@ export const ListTool = Tool.define("list", {
ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
}),
async execute(params, ctx) {
const searchPath = path.resolve(Instance.directory, params.path || ".")
const searchPath = path.resolve(Session.directory.get(), params.path || ".")
await assertExternalDirectory(ctx, searchPath, { kind: "directory" })

await ctx.ask({
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/tool/lsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from "path"
import { LSP } from "../lsp"
import DESCRIPTION from "./lsp.txt"
import { Instance } from "../project/instance"
import { Session } from "../session"
import { pathToFileURL } from "url"
import { assertExternalDirectory } from "./external-directory"

Expand All @@ -28,7 +29,7 @@ export const LspTool = Tool.define("lsp", {
character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"),
}),
execute: async (args, ctx) => {
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Session.directory.get(), args.filePath)
await assertExternalDirectory(ctx, file)

await ctx.ask({
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/tool/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { File } from "../file"
import { FileTime } from "../file/time"
import { Filesystem } from "../util/filesystem"
import { Instance } from "../project/instance"
import { Session } from "../session"
import { trimDiff } from "./edit"
import { assertExternalDirectory } from "./external-directory"

Expand All @@ -22,7 +23,7 @@ export const WriteTool = Tool.define("write", {
filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
}),
async execute(params, ctx) {
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Session.directory.get(), params.filePath)
await assertExternalDirectory(ctx, filepath)

const file = Bun.file(filepath)
Expand Down
23 changes: 23 additions & 0 deletions packages/opencode/test/session/directory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, test, beforeEach } from "bun:test"
import { Session } from "../../src/session"

describe("Session.directory", () => {
beforeEach(() => {
// Reset to undefined before each test
Session.directory.set(undefined)
})

test("should return custom directory when set", () => {
const customDir = "/custom/worktree/path"
Session.directory.set(customDir)
expect(Session.directory.get()).toBe(customDir)
})

test("should update when set multiple times", () => {
Session.directory.set("/first/path")
expect(Session.directory.get()).toBe("/first/path")

Session.directory.set("/second/path")
expect(Session.directory.get()).toBe("/second/path")
})
})