|
@@ -119,7 +119,7 @@ export function Prompt(props: PromptProps) {
|
|
|
|
|
|
|
|
const [store, setStore] = createStore<{
|
|
const [store, setStore] = createStore<{
|
|
|
prompt: PromptInfo
|
|
prompt: PromptInfo
|
|
|
- mode: "normal" | "shell"
|
|
|
|
|
|
|
+ mode: "normal" | "shell" | "handoff"
|
|
|
extmarkToPartIndex: Map<number, number>
|
|
extmarkToPartIndex: Map<number, number>
|
|
|
interrupt: number
|
|
interrupt: number
|
|
|
placeholder: number
|
|
placeholder: number
|
|
@@ -338,6 +338,20 @@ export function Prompt(props: PromptProps) {
|
|
|
))
|
|
))
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ title: "Handoff",
|
|
|
|
|
+ value: "prompt.handoff",
|
|
|
|
|
+ disabled: props.sessionID === undefined,
|
|
|
|
|
+ category: "Prompt",
|
|
|
|
|
+ slash: {
|
|
|
|
|
+ name: "handoff",
|
|
|
|
|
+ },
|
|
|
|
|
+ onSelect: () => {
|
|
|
|
|
+ input.clear()
|
|
|
|
|
+ setStore("mode", "handoff")
|
|
|
|
|
+ setStore("prompt", { input: "", parts: [] })
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
]
|
|
]
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -515,17 +529,45 @@ export function Prompt(props: PromptProps) {
|
|
|
async function submit() {
|
|
async function submit() {
|
|
|
if (props.disabled) return
|
|
if (props.disabled) return
|
|
|
if (autocomplete?.visible) return
|
|
if (autocomplete?.visible) return
|
|
|
|
|
+ const selectedModel = local.model.current()
|
|
|
|
|
+ if (!selectedModel) {
|
|
|
|
|
+ promptModelWarning()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (store.mode === "handoff") {
|
|
|
|
|
+ const result = await sdk.client.session.handoff({
|
|
|
|
|
+ sessionID: props.sessionID!,
|
|
|
|
|
+ goal: store.prompt.input,
|
|
|
|
|
+ model: {
|
|
|
|
|
+ providerID: selectedModel.providerID,
|
|
|
|
|
+ modelID: selectedModel.modelID,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ if (result.data) {
|
|
|
|
|
+ route.navigate({
|
|
|
|
|
+ type: "home",
|
|
|
|
|
+ initialPrompt: {
|
|
|
|
|
+ input: result.data.text,
|
|
|
|
|
+ parts:
|
|
|
|
|
+ result.data.files.map((file) => ({
|
|
|
|
|
+ type: "file",
|
|
|
|
|
+ url: file,
|
|
|
|
|
+ filename: file,
|
|
|
|
|
+ mime: "text/plain",
|
|
|
|
|
+ })) ?? [],
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (!store.prompt.input) return
|
|
if (!store.prompt.input) return
|
|
|
const trimmed = store.prompt.input.trim()
|
|
const trimmed = store.prompt.input.trim()
|
|
|
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
|
|
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
|
|
|
exit()
|
|
exit()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- const selectedModel = local.model.current()
|
|
|
|
|
- if (!selectedModel) {
|
|
|
|
|
- promptModelWarning()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
const sessionID = props.sessionID
|
|
const sessionID = props.sessionID
|
|
|
? props.sessionID
|
|
? props.sessionID
|
|
|
: await (async () => {
|
|
: await (async () => {
|
|
@@ -569,27 +611,6 @@ export function Prompt(props: PromptProps) {
|
|
|
command: inputText,
|
|
command: inputText,
|
|
|
})
|
|
})
|
|
|
setStore("mode", "normal")
|
|
setStore("mode", "normal")
|
|
|
- } else if (inputText.startsWith("/handoff ")) {
|
|
|
|
|
- // Handle handoff command specially - call endpoint and replace prompt
|
|
|
|
|
- const goal = inputText.slice(9).trim() // Remove "/handoff " prefix
|
|
|
|
|
- if (goal) {
|
|
|
|
|
- const result = await sdk.client.session.handoff({
|
|
|
|
|
- sessionID,
|
|
|
|
|
- goal,
|
|
|
|
|
- model: {
|
|
|
|
|
- providerID: selectedModel.providerID,
|
|
|
|
|
- modelID: selectedModel.modelID,
|
|
|
|
|
- },
|
|
|
|
|
- })
|
|
|
|
|
- if (result.data) {
|
|
|
|
|
- // Replace prompt with the handoff text
|
|
|
|
|
- const handoffText = result.data.text
|
|
|
|
|
- input.setText(handoffText)
|
|
|
|
|
- setStore("prompt", { input: handoffText, parts: [] })
|
|
|
|
|
- // Don't submit yet - let user review and submit manually
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
} else if (
|
|
} else if (
|
|
|
inputText.startsWith("/") &&
|
|
inputText.startsWith("/") &&
|
|
|
iife(() => {
|
|
iife(() => {
|
|
@@ -747,6 +768,7 @@ export function Prompt(props: PromptProps) {
|
|
|
const highlight = createMemo(() => {
|
|
const highlight = createMemo(() => {
|
|
|
if (keybind.leader) return theme.border
|
|
if (keybind.leader) return theme.border
|
|
|
if (store.mode === "shell") return theme.primary
|
|
if (store.mode === "shell") return theme.primary
|
|
|
|
|
+ if (store.mode === "handoff") return theme.warning
|
|
|
return local.agent.color(local.agent.current().name)
|
|
return local.agent.color(local.agent.current().name)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -818,7 +840,11 @@ export function Prompt(props: PromptProps) {
|
|
|
flexGrow={1}
|
|
flexGrow={1}
|
|
|
>
|
|
>
|
|
|
<textarea
|
|
<textarea
|
|
|
- placeholder={props.sessionID ? undefined : `Ask anything... "${PLACEHOLDERS[store.placeholder]}"`}
|
|
|
|
|
|
|
+ placeholder={iife(() => {
|
|
|
|
|
+ if (store.mode === "handoff") return "Goal for the new session"
|
|
|
|
|
+ if (props.sessionID) return undefined
|
|
|
|
|
+ return `Ask anything... "${PLACEHOLDERS[store.placeholder]}"`
|
|
|
|
|
+ })}
|
|
|
textColor={keybind.leader ? theme.textMuted : theme.text}
|
|
textColor={keybind.leader ? theme.textMuted : theme.text}
|
|
|
focusedTextColor={keybind.leader ? theme.textMuted : theme.text}
|
|
focusedTextColor={keybind.leader ? theme.textMuted : theme.text}
|
|
|
minHeight={1}
|
|
minHeight={1}
|
|
@@ -875,7 +901,7 @@ export function Prompt(props: PromptProps) {
|
|
|
e.preventDefault()
|
|
e.preventDefault()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- if (store.mode === "shell") {
|
|
|
|
|
|
|
+ if (store.mode === "shell" || store.mode === "handoff") {
|
|
|
if ((e.name === "backspace" && input.visualCursor.offset === 0) || e.name === "escape") {
|
|
if ((e.name === "backspace" && input.visualCursor.offset === 0) || e.name === "escape") {
|
|
|
setStore("mode", "normal")
|
|
setStore("mode", "normal")
|
|
|
e.preventDefault()
|
|
e.preventDefault()
|
|
@@ -996,7 +1022,11 @@ export function Prompt(props: PromptProps) {
|
|
|
/>
|
|
/>
|
|
|
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
|
|
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
|
|
|
<text fg={highlight()}>
|
|
<text fg={highlight()}>
|
|
|
- {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
|
|
|
|
|
|
|
+ <Switch>
|
|
|
|
|
+ <Match when={store.mode === "normal"}>{Locale.titlecase(local.agent.current().name)}</Match>
|
|
|
|
|
+ <Match when={store.mode === "shell"}>Shell</Match>
|
|
|
|
|
+ <Match when={store.mode === "handoff"}>Handoff</Match>
|
|
|
|
|
+ </Switch>
|
|
|
</text>
|
|
</text>
|
|
|
<Show when={store.mode === "normal"}>
|
|
<Show when={store.mode === "normal"}>
|
|
|
<box flexDirection="row" gap={1}>
|
|
<box flexDirection="row" gap={1}>
|
|
@@ -1143,6 +1173,11 @@ export function Prompt(props: PromptProps) {
|
|
|
esc <span style={{ fg: theme.textMuted }}>exit shell mode</span>
|
|
esc <span style={{ fg: theme.textMuted }}>exit shell mode</span>
|
|
|
</text>
|
|
</text>
|
|
|
</Match>
|
|
</Match>
|
|
|
|
|
+ <Match when={store.mode === "handoff"}>
|
|
|
|
|
+ <text fg={theme.text}>
|
|
|
|
|
+ esc <span style={{ fg: theme.textMuted }}>exit handoff mode</span>
|
|
|
|
|
+ </text>
|
|
|
|
|
+ </Match>
|
|
|
</Switch>
|
|
</Switch>
|
|
|
</box>
|
|
</box>
|
|
|
</Show>
|
|
</Show>
|