mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-01 22:48:16 +00:00
Apply PR #11340: feat(tui): add Claude Code-style --fork-session flag to duplicate sessions before continuing (resolves anomalyco#11137)
This commit is contained in:
@@ -50,6 +50,11 @@ export const RunCommand = cmd({
|
|||||||
describe: "session id to continue",
|
describe: "session id to continue",
|
||||||
type: "string",
|
type: "string",
|
||||||
})
|
})
|
||||||
|
.option("fork-session", {
|
||||||
|
alias: ["fork"],
|
||||||
|
describe: "fork the session before continuing (requires --continue or --session)",
|
||||||
|
type: "boolean",
|
||||||
|
})
|
||||||
.option("share", {
|
.option("share", {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
describe: "share the session",
|
describe: "share the session",
|
||||||
@@ -133,6 +138,11 @@ export const RunCommand = cmd({
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.forkSession && !args.continue && !args.session) {
|
||||||
|
UI.error("--fork-session requires --continue or --session")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
const execute = async (sdk: OpencodeClient, sessionID: string) => {
|
const execute = async (sdk: OpencodeClient, sessionID: string) => {
|
||||||
const printEvent = (color: string, type: string, title: string) => {
|
const printEvent = (color: string, type: string, title: string) => {
|
||||||
UI.println(
|
UI.println(
|
||||||
@@ -279,11 +289,16 @@ export const RunCommand = cmd({
|
|||||||
const sdk = createOpencodeClient({ baseUrl: args.attach })
|
const sdk = createOpencodeClient({ baseUrl: args.attach })
|
||||||
|
|
||||||
const sessionID = await (async () => {
|
const sessionID = await (async () => {
|
||||||
if (args.continue) {
|
const baseID = args.continue
|
||||||
const result = await sdk.session.list()
|
? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id
|
||||||
return result.data?.find((s) => !s.parentID)?.id
|
: args.session
|
||||||
|
|
||||||
|
if (baseID && args.forkSession) {
|
||||||
|
const forked = await sdk.session.fork({ sessionID: baseID })
|
||||||
|
return forked.data?.id
|
||||||
}
|
}
|
||||||
if (args.session) return args.session
|
|
||||||
|
if (baseID) return baseID
|
||||||
|
|
||||||
const title =
|
const title =
|
||||||
args.title !== undefined
|
args.title !== undefined
|
||||||
@@ -354,11 +369,16 @@ export const RunCommand = cmd({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sessionID = await (async () => {
|
const sessionID = await (async () => {
|
||||||
if (args.continue) {
|
const baseID = args.continue
|
||||||
const result = await sdk.session.list()
|
? (await sdk.session.list()).data?.find((s) => !s.parentID)?.id
|
||||||
return result.data?.find((s) => !s.parentID)?.id
|
: args.session
|
||||||
|
|
||||||
|
if (baseID && args.forkSession) {
|
||||||
|
const forked = await sdk.session.fork({ sessionID: baseID })
|
||||||
|
return forked.data?.id
|
||||||
}
|
}
|
||||||
if (args.session) return args.session
|
|
||||||
|
if (baseID) return baseID
|
||||||
|
|
||||||
const title =
|
const title =
|
||||||
args.title !== undefined
|
args.title !== undefined
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ function App() {
|
|||||||
})
|
})
|
||||||
local.model.set({ providerID, modelID }, { recent: true })
|
local.model.set({ providerID, modelID }, { recent: true })
|
||||||
}
|
}
|
||||||
if (args.sessionID) {
|
// Handle --session without --fork immediately (fork is handled in createEffect below)
|
||||||
|
if (args.sessionID && !args.fork) {
|
||||||
route.navigate({
|
route.navigate({
|
||||||
type: "session",
|
type: "session",
|
||||||
sessionID: args.sessionID,
|
sessionID: args.sessionID,
|
||||||
@@ -267,10 +268,36 @@ function App() {
|
|||||||
.find((x) => x.parentID === undefined)?.id
|
.find((x) => x.parentID === undefined)?.id
|
||||||
if (match) {
|
if (match) {
|
||||||
continued = true
|
continued = true
|
||||||
route.navigate({ type: "session", sessionID: match })
|
if (args.fork) {
|
||||||
|
sdk.client.session.fork({ sessionID: match }).then((result) => {
|
||||||
|
if (result.data?.id) {
|
||||||
|
route.navigate({ type: "session", sessionID: result.data.id })
|
||||||
|
} else {
|
||||||
|
toast.show({ message: "Failed to fork session", variant: "error" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
route.navigate({ type: "session", sessionID: match })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Handle --session with --fork: wait for sync to be fully complete before forking
|
||||||
|
// (session list loads in non-blocking phase for --session, so we must wait for "complete"
|
||||||
|
// to avoid a race where reconcile overwrites the newly forked session)
|
||||||
|
let forked = false
|
||||||
|
createEffect(() => {
|
||||||
|
if (forked || sync.status !== "complete" || !args.sessionID || !args.fork) return
|
||||||
|
forked = true
|
||||||
|
sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => {
|
||||||
|
if (result.data?.id) {
|
||||||
|
route.navigate({ type: "session", sessionID: result.data.id })
|
||||||
|
} else {
|
||||||
|
toast.show({ message: "Failed to fork session", variant: "error" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => sync.status === "complete" && sync.data.provider.length === 0,
|
() => sync.status === "complete" && sync.data.provider.length === 0,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface Args {
|
|||||||
prompt?: string
|
prompt?: string
|
||||||
continue?: boolean
|
continue?: boolean
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
|
fork?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { use: useArgs, provider: ArgsProvider } = createSimpleContext({
|
export const { use: useArgs, provider: ArgsProvider } = createSimpleContext({
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ export const TuiThreadCommand = cmd({
|
|||||||
type: "string",
|
type: "string",
|
||||||
describe: "session id to continue",
|
describe: "session id to continue",
|
||||||
})
|
})
|
||||||
|
.option("fork-session", {
|
||||||
|
alias: ["fork"],
|
||||||
|
type: "boolean",
|
||||||
|
describe: "fork the session when continuing (use with --continue or --session)",
|
||||||
|
})
|
||||||
.option("prompt", {
|
.option("prompt", {
|
||||||
type: "string",
|
type: "string",
|
||||||
describe: "prompt to use",
|
describe: "prompt to use",
|
||||||
@@ -73,6 +78,11 @@ export const TuiThreadCommand = cmd({
|
|||||||
describe: "agent to use",
|
describe: "agent to use",
|
||||||
}),
|
}),
|
||||||
handler: async (args) => {
|
handler: async (args) => {
|
||||||
|
if (args.forkSession && !args.continue && !args.session) {
|
||||||
|
UI.error("--fork-session requires --continue or --session")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
|
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
|
||||||
const baseCwd = process.env.PWD ?? process.cwd()
|
const baseCwd = process.env.PWD ?? process.cwd()
|
||||||
const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
|
const cwd = args.project ? path.resolve(baseCwd, args.project) : process.cwd()
|
||||||
@@ -150,6 +160,7 @@ export const TuiThreadCommand = cmd({
|
|||||||
agent: args.agent,
|
agent: args.agent,
|
||||||
model: args.model,
|
model: args.model,
|
||||||
prompt,
|
prompt,
|
||||||
|
fork: args.forkSession,
|
||||||
},
|
},
|
||||||
onExit: async () => {
|
onExit: async () => {
|
||||||
await client.call("shutdown", undefined)
|
await client.call("shutdown", undefined)
|
||||||
|
|||||||
Reference in New Issue
Block a user