fix(agent-switch): create fresh session for handoff apply
This commit is contained in:
@@ -9,218 +9,221 @@ import {
|
||||
} from "./applier"
|
||||
import { schedulePendingSwitchApply } from "./scheduler"
|
||||
|
||||
function createMockClient(overrides?: {
|
||||
onPrompt?: (input: { path: { id: string }; body: { agent: string } }) => void
|
||||
onCreate?: () => Record<string, unknown>
|
||||
onMessages?: () => Record<string, unknown>
|
||||
onStatus?: () => Record<string, unknown>
|
||||
}) {
|
||||
return {
|
||||
session: {
|
||||
create: async () => overrides?.onCreate?.() ?? { data: { id: "new-ses" } },
|
||||
promptAsync: async (input: { path: { id: string }; body: { agent: string } }) => {
|
||||
overrides?.onPrompt?.(input)
|
||||
},
|
||||
messages: async () => overrides?.onMessages?.() ?? ({ data: [] }),
|
||||
status: overrides?.onStatus ? async () => overrides.onStatus!() : undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("agent-switch applier", () => {
|
||||
beforeEach(() => {
|
||||
_resetForTesting()
|
||||
_resetApplierForTesting()
|
||||
})
|
||||
|
||||
test("scheduled apply works without idle event", async () => {
|
||||
const calls: string[] = []
|
||||
let switched = false
|
||||
const client = {
|
||||
session: {
|
||||
promptAsync: async (input: { body: { agent: string } }) => {
|
||||
calls.push(input.body.agent)
|
||||
switched = true
|
||||
describe("#given fresh session creation flow", () => {
|
||||
test("#when scheduled apply runs, #then creates new session and prompts it", async () => {
|
||||
const promptedSessions: string[] = []
|
||||
const promptedAgents: string[] = []
|
||||
const client = createMockClient({
|
||||
onCreate: () => ({ data: { id: "fresh-ses-1" } }),
|
||||
onPrompt: (input) => {
|
||||
promptedSessions.push(input.path.id)
|
||||
promptedAgents.push(input.body.agent)
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Prometheus (Plan Builder)" } }] })
|
||||
: ({ data: [] }),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
setPendingSwitch("ses-1", "prometheus", "create plan")
|
||||
schedulePendingSwitchApply({
|
||||
sessionID: "ses-1",
|
||||
client: client as any,
|
||||
setPendingSwitch("ses-1", "prometheus", "create plan")
|
||||
schedulePendingSwitchApply({
|
||||
sessionID: "ses-1",
|
||||
client: client as any,
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
|
||||
expect(promptedSessions).toEqual(["fresh-ses-1"])
|
||||
expect(promptedAgents).toEqual(["Prometheus (Plan Builder)"])
|
||||
expect(getPendingSwitch("ses-1")).toBeUndefined()
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
|
||||
expect(calls).toEqual(["Prometheus (Plan Builder)"])
|
||||
expect(getPendingSwitch("ses-1")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("normalizes pending agent to canonical prompt display name", async () => {
|
||||
const calls: string[] = []
|
||||
let switched = false
|
||||
const client = {
|
||||
session: {
|
||||
promptAsync: async (input: { body: { agent: string } }) => {
|
||||
calls.push(input.body.agent)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Prometheus (Plan Builder)" } }] })
|
||||
: ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-2", "Prometheus (Plan Builder)", "create plan")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-2",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
expect(calls).toEqual(["Prometheus (Plan Builder)"])
|
||||
expect(getPendingSwitch("ses-2")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("retries transient failures and eventually clears pending switch", async () => {
|
||||
let attempts = 0
|
||||
let switched = false
|
||||
const client = {
|
||||
session: {
|
||||
promptAsync: async () => {
|
||||
attempts += 1
|
||||
if (attempts < 3) {
|
||||
throw new Error("temporary failure")
|
||||
}
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Atlas (Plan Executor)" } }] })
|
||||
: ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-3", "atlas", "fix this")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-3",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 800))
|
||||
|
||||
expect(attempts).toBe(3)
|
||||
expect(getPendingSwitch("ses-3")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("waits for session idle before applying switch", async () => {
|
||||
let statusChecks = 0
|
||||
let promptCalls = 0
|
||||
let switched = false
|
||||
const client = {
|
||||
session: {
|
||||
status: async () => {
|
||||
statusChecks += 1
|
||||
return {
|
||||
"ses-5": { type: statusChecks < 3 ? "running" : "idle" },
|
||||
}
|
||||
},
|
||||
promptAsync: async () => {
|
||||
promptCalls += 1
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Atlas (Plan Executor)" } }] })
|
||||
: ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-5", "atlas", "fix now")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-5",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
expect(statusChecks).toBeGreaterThanOrEqual(3)
|
||||
expect(promptCalls).toBe(1)
|
||||
expect(getPendingSwitch("ses-5")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("clearPendingSwitchRuntime cancels pending retries", async () => {
|
||||
let attempts = 0
|
||||
const client = {
|
||||
session: {
|
||||
promptAsync: async () => {
|
||||
attempts += 1
|
||||
throw new Error("always failing")
|
||||
},
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-4", "atlas", "fix this")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-4",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
clearPendingSwitchRuntime("ses-4")
|
||||
|
||||
const attemptsAfterClear = attempts
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
|
||||
expect(attempts).toBe(attemptsAfterClear)
|
||||
expect(getPendingSwitch("ses-4")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("syncs CLI TUI agent selection for athena-to-atlas handoff", async () => {
|
||||
const originalClientEnv = process.env["OPENCODE_CLIENT"]
|
||||
process.env["OPENCODE_CLIENT"] = "cli"
|
||||
|
||||
try {
|
||||
const promptCalls: string[] = []
|
||||
const tuiCommands: string[] = []
|
||||
let switched = false
|
||||
test("#when apply runs directly, #then creates fresh session with parentID linking to source", async () => {
|
||||
let createInput: Record<string, unknown> | undefined
|
||||
const client = {
|
||||
session: {
|
||||
promptAsync: async (input: { body: { agent: string } }) => {
|
||||
promptCalls.push(input.body.agent)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({
|
||||
data: [
|
||||
{ info: { role: "user", agent: "Athena (Council)" } },
|
||||
{ info: { role: "user", agent: "Atlas (Plan Executor)" } },
|
||||
],
|
||||
})
|
||||
: ({
|
||||
data: [{ info: { role: "user", agent: "Athena (Council)" } }],
|
||||
}),
|
||||
},
|
||||
app: {
|
||||
agents: async () => ({
|
||||
data: [
|
||||
{ name: "Sisyphus (Ultraworker)", mode: "primary" },
|
||||
{ name: "Hephaestus (Deep Agent)", mode: "primary" },
|
||||
{ name: "Prometheus (Plan Builder)", mode: "primary" },
|
||||
{ name: "Atlas (Plan Executor)", mode: "primary" },
|
||||
{ name: "Athena (Council)", mode: "primary" },
|
||||
],
|
||||
}),
|
||||
},
|
||||
tui: {
|
||||
publish: async (input: { body: { properties: { command: string } } }) => {
|
||||
tuiCommands.push(input.body.properties.command)
|
||||
create: async (input?: { body?: Record<string, unknown> }) => {
|
||||
createInput = input?.body
|
||||
return { data: { id: "fresh-ses-2" } }
|
||||
},
|
||||
promptAsync: async () => {},
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-6", "atlas", "fix now")
|
||||
setPendingSwitch("ses-2", "atlas", "fix now")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-2",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
expect(createInput).toEqual({
|
||||
parentID: "ses-2",
|
||||
title: "atlas (handoff)",
|
||||
})
|
||||
expect(getPendingSwitch("ses-2")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given agent name normalization", () => {
|
||||
test("#when agent has canonical display name, #then normalizes for prompt", async () => {
|
||||
const promptedAgents: string[] = []
|
||||
const client = createMockClient({
|
||||
onPrompt: (input) => {
|
||||
promptedAgents.push(input.body.agent)
|
||||
},
|
||||
})
|
||||
|
||||
setPendingSwitch("ses-3", "Prometheus (Plan Builder)", "create plan")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-3",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
expect(promptedAgents).toEqual(["Prometheus (Plan Builder)"])
|
||||
expect(getPendingSwitch("ses-3")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given transient failures", () => {
|
||||
test("#when create fails transiently, #then retries and eventually succeeds", async () => {
|
||||
let createAttempts = 0
|
||||
const client = {
|
||||
session: {
|
||||
create: async () => {
|
||||
createAttempts += 1
|
||||
if (createAttempts < 3) {
|
||||
throw new Error("temporary failure")
|
||||
}
|
||||
return { data: { id: "fresh-ses-retry" } }
|
||||
},
|
||||
promptAsync: async () => {},
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-4", "atlas", "fix this")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-4",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 800))
|
||||
|
||||
expect(createAttempts).toBe(3)
|
||||
expect(getPendingSwitch("ses-4")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given session idle wait", () => {
|
||||
test("#when session is busy, #then waits for idle before creating fresh session", async () => {
|
||||
let statusChecks = 0
|
||||
let createCalled = false
|
||||
const client = {
|
||||
session: {
|
||||
status: async () => {
|
||||
statusChecks += 1
|
||||
return {
|
||||
"ses-5": { type: statusChecks < 3 ? "running" : "idle" },
|
||||
}
|
||||
},
|
||||
create: async () => {
|
||||
createCalled = true
|
||||
return { data: { id: "fresh-ses-idle" } }
|
||||
},
|
||||
promptAsync: async () => {},
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-5", "atlas", "fix now")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-5",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
expect(statusChecks).toBeGreaterThanOrEqual(3)
|
||||
expect(createCalled).toBe(true)
|
||||
expect(getPendingSwitch("ses-5")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given runtime cancellation", () => {
|
||||
test("#when clearPendingSwitchRuntime called, #then cancels pending retries", async () => {
|
||||
let attempts = 0
|
||||
const client = {
|
||||
session: {
|
||||
create: async () => {
|
||||
attempts += 1
|
||||
throw new Error("always failing")
|
||||
},
|
||||
promptAsync: async () => {},
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-6", "atlas", "fix this")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-6",
|
||||
client: client as any,
|
||||
source: "message-updated",
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
expect(promptCalls).toEqual(["Atlas (Plan Executor)"])
|
||||
expect(tuiCommands).toEqual(["agent.cycle.reverse"])
|
||||
clearPendingSwitchRuntime("ses-6")
|
||||
|
||||
const attemptsAfterClear = attempts
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
|
||||
expect(attempts).toBe(attemptsAfterClear)
|
||||
expect(getPendingSwitch("ses-6")).toBeUndefined()
|
||||
} finally {
|
||||
if (originalClientEnv === undefined) {
|
||||
delete process.env["OPENCODE_CLIENT"]
|
||||
} else {
|
||||
process.env["OPENCODE_CLIENT"] = originalClientEnv
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given create not available on client", () => {
|
||||
test("#when session.create is missing, #then enters retry path", async () => {
|
||||
const client = {
|
||||
session: {
|
||||
promptAsync: async () => {},
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
setPendingSwitch("ses-7", "atlas", "fix now")
|
||||
await applyPendingSwitch({
|
||||
sessionID: "ses-7",
|
||||
client: client as any,
|
||||
source: "idle",
|
||||
})
|
||||
|
||||
expect(getPendingSwitch("ses-7")).toBeDefined()
|
||||
|
||||
clearPendingSwitchRuntime("ses-7")
|
||||
expect(getPendingSwitch("ses-7")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,9 +2,8 @@ import { normalizeAgentForPrompt } from "../../shared/agent-display-names"
|
||||
import { log } from "../../shared/logger"
|
||||
import { clearPendingSwitch, getPendingSwitch } from "./state"
|
||||
import { waitForSessionIdle } from "./session-status"
|
||||
import { fetchMessages, shouldClearAsAlreadyApplied, verifySwitchObserved } from "./apply-verification"
|
||||
import { getLatestUserAgent } from "./message-inspection"
|
||||
import { syncCliTuiAgentSelectionAfterSwitch } from "./tui-agent-sync"
|
||||
import { shouldClearAsAlreadyApplied } from "./apply-verification"
|
||||
import { createFreshSession } from "./session-creator"
|
||||
import {
|
||||
clearInFlight,
|
||||
clearRetryState,
|
||||
@@ -24,6 +23,7 @@ type SessionClient = {
|
||||
path: { id: string }
|
||||
body: { agent: string; parts: Array<{ type: "text"; text: string }> }
|
||||
}) => Promise<unknown>
|
||||
create?: (input?: { body?: { parentID?: string; title?: string } }) => Promise<unknown>
|
||||
messages: (input: { path: { id: string } }) => Promise<unknown>
|
||||
status?: () => Promise<unknown>
|
||||
}
|
||||
@@ -135,40 +135,26 @@ export async function applyPendingSwitch(args: {
|
||||
throw new Error("session not idle before applying agent switch")
|
||||
}
|
||||
|
||||
const beforeMessages = await fetchMessages({ client, sessionID })
|
||||
const sourceUserAgent = getLatestUserAgent(beforeMessages)
|
||||
|
||||
const usedAgent = await tryPromptWithCandidates({
|
||||
const newSessionID = await createFreshSession({
|
||||
client,
|
||||
sessionID,
|
||||
sourceSessionID: sessionID,
|
||||
targetAgent: pending.agent,
|
||||
})
|
||||
|
||||
await tryPromptWithCandidates({
|
||||
client,
|
||||
sessionID: newSessionID,
|
||||
agent: pending.agent,
|
||||
context: pending.context,
|
||||
source,
|
||||
})
|
||||
|
||||
const verified = await verifySwitchObserved({
|
||||
client,
|
||||
sessionID,
|
||||
targetAgent: pending.agent,
|
||||
baselineCount: beforeMessages.length,
|
||||
})
|
||||
if (!verified) {
|
||||
throw new Error(`agent switch not observed after prompt (attempted ${usedAgent})`)
|
||||
}
|
||||
|
||||
clearPendingSwitch(sessionID)
|
||||
clearRetryState(sessionID)
|
||||
|
||||
await syncCliTuiAgentSelectionAfterSwitch({
|
||||
client,
|
||||
sessionID,
|
||||
source,
|
||||
sourceAgent: sourceUserAgent,
|
||||
targetAgent: pending.agent,
|
||||
})
|
||||
|
||||
log("[agent-switch] Pending switch applied", {
|
||||
sessionID,
|
||||
log("[agent-switch] Pending switch applied via fresh session", {
|
||||
sourceSessionID: sessionID,
|
||||
newSessionID,
|
||||
source,
|
||||
agent: pending.agent,
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ type SessionClient = {
|
||||
path: { id: string }
|
||||
body: { agent: string; parts: Array<{ type: "text"; text: string }> }
|
||||
}) => Promise<unknown>
|
||||
create?: (input?: { body?: { parentID?: string; title?: string } }) => Promise<unknown>
|
||||
messages: (input: { path: { id: string } }) => Promise<unknown>
|
||||
status?: () => Promise<unknown>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { createFreshSession } from "./session-creator"
|
||||
|
||||
describe("session-creator", () => {
|
||||
describe("#given SDK response with data wrapper", () => {
|
||||
test("#when create returns { data: { id } }, #then extracts session ID", async () => {
|
||||
const client = {
|
||||
session: {
|
||||
create: async () => ({ data: { id: "new-session-123" } }),
|
||||
},
|
||||
}
|
||||
|
||||
const result = await createFreshSession({
|
||||
client,
|
||||
sourceSessionID: "source-ses",
|
||||
targetAgent: "atlas",
|
||||
})
|
||||
|
||||
expect(result).toBe("new-session-123")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given SDK response without data wrapper", () => {
|
||||
test("#when create returns { id } directly, #then extracts session ID", async () => {
|
||||
const client = {
|
||||
session: {
|
||||
create: async () => ({ id: "direct-session-456" }),
|
||||
},
|
||||
}
|
||||
|
||||
const result = await createFreshSession({
|
||||
client,
|
||||
sourceSessionID: "source-ses",
|
||||
targetAgent: "prometheus",
|
||||
})
|
||||
|
||||
expect(result).toBe("direct-session-456")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given create not available", () => {
|
||||
test("#when session.create is undefined, #then throws", async () => {
|
||||
const client = { session: {} }
|
||||
|
||||
await expect(
|
||||
createFreshSession({
|
||||
client: client as any,
|
||||
sourceSessionID: "source-ses",
|
||||
targetAgent: "atlas",
|
||||
}),
|
||||
).rejects.toThrow("session.create not available")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given invalid response", () => {
|
||||
test("#when create returns no id, #then throws", async () => {
|
||||
const client = {
|
||||
session: {
|
||||
create: async () => ({ data: {} }),
|
||||
},
|
||||
}
|
||||
|
||||
await expect(
|
||||
createFreshSession({
|
||||
client,
|
||||
sourceSessionID: "source-ses",
|
||||
targetAgent: "atlas",
|
||||
}),
|
||||
).rejects.toThrow("failed to extract session ID")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given parentID and title", () => {
|
||||
test("#when creating session, #then passes sourceSessionID as parentID", async () => {
|
||||
let capturedInput: Record<string, unknown> | undefined
|
||||
const client = {
|
||||
session: {
|
||||
create: async (input?: { body?: Record<string, unknown> }) => {
|
||||
capturedInput = input?.body
|
||||
return { id: "new-ses" }
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await createFreshSession({
|
||||
client,
|
||||
sourceSessionID: "parent-ses-id",
|
||||
targetAgent: "atlas",
|
||||
})
|
||||
|
||||
expect(capturedInput).toEqual({
|
||||
parentID: "parent-ses-id",
|
||||
title: "atlas (handoff)",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { log } from "../../shared/logger"
|
||||
|
||||
type CreateClient = {
|
||||
session: {
|
||||
create?: (input?: { body?: { parentID?: string; title?: string } }) => Promise<unknown>
|
||||
}
|
||||
}
|
||||
|
||||
function extractSessionId(response: unknown): string | undefined {
|
||||
if (typeof response !== "object" || response === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const root = response as Record<string, unknown>
|
||||
|
||||
if (typeof root.id === "string" && root.id.length > 0) {
|
||||
return root.id
|
||||
}
|
||||
|
||||
const data = root.data
|
||||
if (typeof data === "object" && data !== null) {
|
||||
const dataRecord = data as Record<string, unknown>
|
||||
if (typeof dataRecord.id === "string" && dataRecord.id.length > 0) {
|
||||
return dataRecord.id
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export async function createFreshSession(args: {
|
||||
client: CreateClient
|
||||
sourceSessionID: string
|
||||
targetAgent: string
|
||||
}): Promise<string> {
|
||||
const { client, sourceSessionID, targetAgent } = args
|
||||
|
||||
if (!client.session.create) {
|
||||
throw new Error("session.create not available on SDK client")
|
||||
}
|
||||
|
||||
const response = await client.session.create({
|
||||
body: {
|
||||
parentID: sourceSessionID,
|
||||
title: `${targetAgent} (handoff)`,
|
||||
},
|
||||
})
|
||||
|
||||
const newSessionID = extractSessionId(response)
|
||||
if (!newSessionID) {
|
||||
throw new Error(`failed to extract session ID from create response: ${JSON.stringify(response)}`)
|
||||
}
|
||||
|
||||
log("[agent-switch] Created fresh session for handoff", {
|
||||
sourceSessionID,
|
||||
newSessionID,
|
||||
targetAgent,
|
||||
})
|
||||
|
||||
return newSessionID
|
||||
}
|
||||
@@ -21,6 +21,7 @@ type SessionClient = {
|
||||
path: { id: string }
|
||||
body: { agent: string; parts: Array<{ type: "text"; text: string }> }
|
||||
}) => Promise<unknown>
|
||||
create?: (input?: { body?: { parentID?: string; title?: string } }) => Promise<unknown>
|
||||
messages: (input: { path: { id: string } }) => Promise<unknown>
|
||||
status?: () => Promise<unknown>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user