fix(core): simplify shell output notices (#50676)

This commit is contained in:
Dax
2026-09-22 11:47:07 -04:00
committed by GitHub
Unverified
parent 10aa949f43
commit ad1a4a6539
6 changed files with 32 additions and 28 deletions
+4 -2
View File
@@ -239,8 +239,10 @@ const layer = () =>
if (page.output.endsWith("\n")) lines.pop()
const truncated = latest.size > maxBytes || lines.length > maxLines
const text = lines.length > maxLines ? lines.slice(-maxLines).join("\n") : page.output
const notice = truncated ? `\n\n[output truncated; full output saved to: ${info.file}]` : ""
return { output: `${text || "(no output)"}${notice}`, truncated }
const notice = truncated
? `${text ? "\n\n" : ""}[full output saved to ${info.file}]`
: ""
return { output: `${text}${notice}`, truncated }
}).pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.succeed(undefined)))
return { info, capture }
})
+8 -4
View File
@@ -27,8 +27,8 @@ export function output(result: Result): Output {
}
export function notice(output: Pick<Output, "exit" | "timeout">) {
if (output.timeout) return "Command timed out before completion."
if (output.exit !== undefined) return `Command exited with code ${output.exit}.`
if (output.timeout) return "Timed out before completion"
if (output.exit !== undefined && output.exit !== 0) return `Exited with code ${output.exit}`
}
export function metadata(output: Output) {
@@ -62,12 +62,16 @@ export function notification(input: {
export function userNotification(result: Result) {
const captured = output(result)
const status =
result.info.status === "killed" ? "Command cancelled." : (notice(captured) ?? "Command exited with code unknown.")
result.info.status === "killed"
? "Cancelled"
: captured.exit === 0
? undefined
: (notice(captured) ?? "Exited with code unknown")
const message = notification({
shellID: result.info.id,
command: result.info.command,
state: result.info.status === "killed" ? "cancelled" : "completed",
text: `${captured.output}\n\n${status}`,
text: [captured.output, status].filter((item) => item !== undefined && item !== "").join("\n\n"),
output: captured,
})
return { ...message, text: `The following shell command was executed by the user:\n${message.text}` }
+2 -2
View File
@@ -76,7 +76,7 @@ type Output = typeof Output.Type
const resultMessages = (output: Output) => {
const notice = output.status === "running" ? BACKGROUND_INSTRUCTION : ShellResult.notice(output)
return [output.output, ...(notice ? [notice] : [])]
return [...(output.output ? [output.output] : []), ...(notice ? [notice] : [])]
}
const toolResult = (output: Output) => {
@@ -165,7 +165,7 @@ export const Plugin = {
? resultMessages(output).join("\n\n")
: info.status === "error"
? (info.error ?? "Command failed")
: "Command cancelled"
: "Cancelled"
yield* sessions.synthetic({
...(info.notificationID ? { id: info.notificationID } : {}),
sessionID,
+2 -2
View File
@@ -507,7 +507,7 @@ describe("SessionRestart background recovery", () => {
run: Deferred.await(complete),
})
yield* jobs.background("call-completed-shell")
yield* Deferred.succeed(complete, "(no output)\n\nCommand exited with code 7.")
yield* Deferred.succeed(complete, "Exited with code 7")
yield* jobs.wait({ id: "call-completed-shell" })
const scope = yield* Scope.make()
@@ -529,7 +529,7 @@ describe("SessionRestart background recovery", () => {
{
type: "synthetic",
payload: {
text: '<shell id="call-completed-shell" state="completed" command="exit 7">\n(no output)\n\nCommand exited with code 7.\n</shell>',
text: '<shell id="call-completed-shell" state="completed" command="exit 7">\nExited with code 7\n</shell>',
},
},
])
+4 -3
View File
@@ -271,7 +271,8 @@ describe("Session.shell", () => {
expect(completion.payload.text).toContain(command)
expect(completion.payload.text).toContain("user output")
expect(completion.payload.text).toContain("user error")
expect(completion.payload.text).toContain(`exited with code ${exit}`)
if (exit === 0) expect(completion.payload.text).not.toContain("Exited with code")
if (exit !== 0) expect(completion.payload.text).toContain(`Exited with code ${exit}`)
expect(fixture.control.wakes).toEqual([])
}),
)
@@ -281,10 +282,10 @@ describe("Session.shell", () => {
{
status: "killed",
state: "cancelled",
text: "Command cancelled",
text: "Cancelled",
output: "Shell command output is no longer available.",
},
{ status: "timeout", state: "completed", text: "Command timed out", output: "timeout started" },
{ status: "timeout", state: "completed", text: "Timed out before completion", output: "timeout started" },
]) {
it.live(`records a ${outcome.status} shell and admits its completion without waking the model`, () =>
Effect.gen(function* () {
+12 -15
View File
@@ -436,7 +436,7 @@ describe("ShellTool scanner permissions", () => {
value: {
status: "completed",
metadata: { exit: 0 },
content: [{ type: "text", text: `${fixture.outside}\n` }, { type: "text" }],
content: [{ type: "text", text: `${fixture.outside}\n` }],
},
})
expect(yield* Effect.promise(() => Bun.file(marker).text())).toBe("reached")
@@ -470,7 +470,7 @@ describe("ShellTool scanner permissions", () => {
value: {
status: "completed",
metadata: { exit: 0 },
content: [{ type: "text", text: `${fixture.outside}\n` }, { type: "text" }],
content: [{ type: "text", text: `${fixture.outside}\n` }],
},
})
expect(yield* Effect.promise(() => Bun.file(marker).text())).toBe("reached")
@@ -517,7 +517,7 @@ describe("ShellTool scanner permissions", () => {
value: {
status: "completed",
metadata: { exit: 0 },
content: [{ type: "text", text: `${fixture.outside}\n` }, { type: "text" }],
content: [{ type: "text", text: `${fixture.outside}\n` }],
},
})
expect(yield* Effect.promise(() => Bun.file(marker).text())).toBe("reached")
@@ -661,7 +661,7 @@ describe("ShellTool compound syntax approval compatibility", () => {
value: {
status: "completed",
metadata: { exit: 0 },
content: [{ type: "text", text: fixture.output }, { type: "text" }],
content: [{ type: "text", text: fixture.output }],
},
})
}),
@@ -729,7 +729,7 @@ describe("ShellTool ordinary shell syntax", () => {
value: {
status: "completed",
metadata: { exit: 0 },
content: [{ type: "text", text: fixture.output }, { type: "text" }],
content: [{ type: "text", text: fixture.output }],
},
})
}),
@@ -907,10 +907,7 @@ describe("ShellTool", () => {
const settled = yield* executeTool(registry, call({ command: helloCommand }))
expect(settled.status).toBe("completed")
expect(settled.metadata).toMatchObject({ exit: 0, truncated: false })
expect(settled.content?.[0]).toEqual({ type: "text", text: "hello" })
expect(settled.content?.[1]).toMatchObject(
Expected.text(expect.stringContaining("Command exited with code 0.")),
)
expect(settled.content).toEqual([{ type: "text", text: "hello" }])
expect(assertions).toMatchObject([
{
sessionID,
@@ -1313,7 +1310,7 @@ describe("ShellTool", () => {
expect(settled.metadata).toMatchObject({ exit: 7, truncated: false })
expect(settled.content?.[0]).toEqual({ type: "text", text: "body" })
expect(settled.content?.[1]).toMatchObject(
Expected.text(expect.stringContaining("Command exited with code 7")),
Expected.text(expect.stringContaining("Exited with code 7")),
)
}),
),
@@ -1342,7 +1339,7 @@ describe("ShellTool", () => {
expect(content.text.includes("output-start")).toBe(false)
expect(content.text.includes("output-end")).toBe(true)
expect(content).toMatchObject(
Expected.text(expect.stringContaining("output truncated; full output saved to:")),
Expected.text(expect.stringContaining("full output saved to ")),
)
}),
),
@@ -1374,7 +1371,7 @@ describe("ShellTool", () => {
expect(content.text).not.toContain("one")
// Windows shells emit CRLF; the assertion targets line limits, not line endings.
expect(content.text.replaceAll("\r\n", "\n")).toStartWith("two\nthree")
expect(content.text).toContain("output truncated; full output saved to:")
expect(content.text).toContain("full output saved to ")
})
},
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
@@ -1471,7 +1468,7 @@ describe("ShellTool", () => {
expect((yield* shell.list()).map((info) => info.id)).toContain(id)
expect((yield* shell.wait(id)).status).toBe("timeout")
expect((yield* Fiber.join(admitted)).valueOrUndefined?.data.item.payload).toMatchObject({
text: expect.stringContaining("Command timed out before completion."),
text: expect.stringContaining("Timed out before completion"),
description: idleCommand,
metadata: {
source: "shell",
@@ -1508,7 +1505,7 @@ describe("ShellTool", () => {
const shellID = settled.metadata?.shellID
expect(typeof shellID).toBe("string")
expect((yield* Fiber.join(admitted)).valueOrUndefined?.data.item.payload).toMatchObject({
text: expect.stringContaining("Command exited with code 7."),
text: expect.stringContaining("Exited with code 7"),
description: bodyExitCommand,
metadata: {
source: "shell",
@@ -1558,7 +1555,7 @@ describe("ShellTool", () => {
{
id: settled.metadata?.shellID,
status: "completed",
output: "(no output)\n\nCommand exited with code 7.",
output: "Exited with code 7",
},
])
}),