run: add shell mode to prompt (#28315)

Press `!` on an empty prompt to enter shell mode and run a command
through session.shell instead of sending a message
This commit is contained in:
Simon Klee
2026-05-20 09:09:12 +02:00
committed by GitHub
parent 11f7e5a1b0
commit 539b118690
12 changed files with 665 additions and 47 deletions
@@ -132,6 +132,8 @@ function sid(event: Event): string | undefined {
}
if (
event.type === "session.next.shell.started" ||
event.type === "session.next.shell.ended" ||
event.type === "permission.asked" ||
event.type === "permission.replied" ||
event.type === "question.asked" ||
@@ -513,6 +515,22 @@ function createLayer(input: StreamInput) {
state.footerView = current
}
const resolveShellAgent = Effect.fn("RunStreamTransport.resolveShellAgent")(function* (agent: string | undefined) {
if (agent) {
return agent
}
const list = yield* Effect.promise(() =>
input.sdk.app.agents(input.directory ? { directory: input.directory } : undefined, { throwOnError: true }),
).pipe(Effect.map((item) => item.data ?? []), Effect.orElseSucceed(() => []))
const next = list.find((item) => item.mode !== "subagent" && item.hidden !== true)?.name
if (next) {
return next
}
return yield* Effect.fail(new Error("no primary agent available for shell mode"))
})
const recoverQuestion = Effect.fn("RunStreamTransport.recoverQuestion")(function* (partID: string) {
if (recovering.has(partID)) {
return
@@ -1005,7 +1023,46 @@ function createLayer(input: StreamInput) {
],
}
const command = next.prompt.command
const send = command
const send = next.prompt.mode === "shell"
? Effect.sync(() => {
input.trace?.write("send.shell", {
sessionID: input.sessionID,
command: next.prompt.text,
})
}).pipe(
Effect.andThen(
resolveShellAgent(next.agent).pipe(
Effect.flatMap((agent) =>
Effect.promise(() =>
input.sdk.session.shell(
{
sessionID: input.sessionID,
agent,
model: next.model,
command: next.prompt.text,
},
{ signal: turn.signal, throwOnError: true },
),
),
),
).pipe(
Effect.tap(() =>
Effect.sync(() => {
input.trace?.write("send.shell.ok", {
sessionID: input.sessionID,
})
item.armed = true
item.live = true
}),
),
Effect.flatMap(() => Deferred.succeed(item.done, undefined).pipe(Effect.ignore)),
Effect.catch((error) => Deferred.fail(item.done, error).pipe(Effect.ignore)),
Effect.forkIn(scope, { startImmediately: true }),
Effect.asVoid,
),
),
)
: command
? Effect.sync(() => {
input.trace?.write("send.command", { sessionID: input.sessionID, command: command.name })
}).pipe(