mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 16:21:50 +03:00
fix(tui): clarify interrupted task actions
This commit is contained in:
@@ -1050,6 +1050,8 @@ describe("NanobotTui layout", () => {
|
|||||||
await waitUntil(() => (app as unknown as { ready: boolean }).ready)
|
await waitUntil(() => (app as unknown as { ready: boolean }).ready)
|
||||||
await setup.renderOnce()
|
await setup.renderOnce()
|
||||||
expect(setup.captureCharFrame()).toContain("Task interrupted")
|
expect(setup.captureCharFrame()).toContain("Task interrupted")
|
||||||
|
expect(setup.captureCharFrame()).toContain("Tools will not replay automatically")
|
||||||
|
expect(ui.status.plainText).toContain("continue or dismiss")
|
||||||
expect(ui.activeTurn).toBe(false)
|
expect(ui.activeTurn).toBe(false)
|
||||||
expect(ui.composer.focused).toBe(true)
|
expect(ui.composer.focused).toBe(true)
|
||||||
|
|
||||||
@@ -1085,6 +1087,19 @@ describe("NanobotTui layout", () => {
|
|||||||
expect(ui.activeTurn).toBe(false)
|
expect(ui.activeTurn).toBe(false)
|
||||||
expect(ui.composer.focused).toBe(true)
|
expect(ui.composer.focused).toBe(true)
|
||||||
|
|
||||||
|
app.accept({
|
||||||
|
event: "recovery_state",
|
||||||
|
chat_id: "chat",
|
||||||
|
status: "awaiting_user",
|
||||||
|
recovery_id: "recovery-unavailable",
|
||||||
|
can_continue: false,
|
||||||
|
})
|
||||||
|
await setup.renderOnce()
|
||||||
|
const unavailableFrame = setup.captureCharFrame()
|
||||||
|
expect(unavailableFrame).toContain("can’t be resumed safely")
|
||||||
|
expect(unavailableFrame).not.toContain("Continue")
|
||||||
|
expect(ui.status.plainText).toContain("dismiss to start a new message")
|
||||||
|
|
||||||
app.accept({
|
app.accept({
|
||||||
event: "recovery_state",
|
event: "recovery_state",
|
||||||
chat_id: "chat",
|
chat_id: "chat",
|
||||||
|
|||||||
+3
-1
@@ -1266,7 +1266,9 @@ export class NanobotTui {
|
|||||||
? "Recovery failed"
|
? "Recovery failed"
|
||||||
: "Task interrupted")
|
: "Task interrupted")
|
||||||
this.setCurrentAction(detail)
|
this.setCurrentAction(detail)
|
||||||
this.status.content = "Waiting for recovery decision"
|
this.status.content = state.can_continue === false
|
||||||
|
? "Interrupted · dismiss to start a new message"
|
||||||
|
: "Interrupted · continue or dismiss"
|
||||||
this.host.reportState("blocked", detail)
|
this.host.reportState("blocked", detail)
|
||||||
this.composer.focus()
|
this.composer.focus()
|
||||||
return
|
return
|
||||||
|
|||||||
+31
-15
@@ -25,7 +25,8 @@ interface RecoveryNoticeOptions {
|
|||||||
/** A quiet action surface for a gateway-owned interrupted turn. */
|
/** A quiet action surface for a gateway-owned interrupted turn. */
|
||||||
export class RecoveryNotice {
|
export class RecoveryNotice {
|
||||||
readonly root: BoxRenderable
|
readonly root: BoxRenderable
|
||||||
private readonly message: TextRenderable
|
private readonly title: TextRenderable
|
||||||
|
private readonly detail: TextRenderable
|
||||||
private readonly dismiss: TextRenderable
|
private readonly dismiss: TextRenderable
|
||||||
private readonly resume: TextRenderable
|
private readonly resume: TextRenderable
|
||||||
private state: RecoveryState | null = null
|
private state: RecoveryState | null = null
|
||||||
@@ -39,18 +40,24 @@ export class RecoveryNotice {
|
|||||||
this.root = new BoxRenderable(renderer, {
|
this.root = new BoxRenderable(renderer, {
|
||||||
id: "nanobot-tui-recovery-notice",
|
id: "nanobot-tui-recovery-notice",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: 1,
|
height: 2,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
flexDirection: "row",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
|
||||||
gap: 2,
|
|
||||||
paddingLeft: 1,
|
paddingLeft: 1,
|
||||||
paddingRight: 1,
|
paddingRight: 1,
|
||||||
visible: false,
|
visible: false,
|
||||||
backgroundColor: RGBA.defaultBackground(),
|
backgroundColor: RGBA.defaultBackground(),
|
||||||
})
|
})
|
||||||
this.message = new TextRenderable(renderer, {
|
const header = new BoxRenderable(renderer, {
|
||||||
id: "nanobot-tui-recovery-message",
|
id: "nanobot-tui-recovery-header",
|
||||||
|
width: "100%",
|
||||||
|
height: 1,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 2,
|
||||||
|
})
|
||||||
|
this.title = new TextRenderable(renderer, {
|
||||||
|
id: "nanobot-tui-recovery-title",
|
||||||
width: "auto",
|
width: "auto",
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@@ -58,11 +65,20 @@ export class RecoveryNotice {
|
|||||||
truncate: true,
|
truncate: true,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
})
|
})
|
||||||
|
this.detail = new TextRenderable(renderer, {
|
||||||
|
id: "nanobot-tui-recovery-detail",
|
||||||
|
width: "100%",
|
||||||
|
height: 1,
|
||||||
|
truncate: true,
|
||||||
|
selectable: false,
|
||||||
|
})
|
||||||
this.dismiss = this.action(renderer, "dismiss", "Dismiss", options.onDismiss)
|
this.dismiss = this.action(renderer, "dismiss", "Dismiss", options.onDismiss)
|
||||||
this.resume = this.action(renderer, "continue", "Continue", options.onContinue, true)
|
this.resume = this.action(renderer, "continue", "Continue", options.onContinue, true)
|
||||||
this.root.add(this.message)
|
header.add(this.title)
|
||||||
this.root.add(this.dismiss)
|
header.add(this.dismiss)
|
||||||
this.root.add(this.resume)
|
header.add(this.resume)
|
||||||
|
this.root.add(header)
|
||||||
|
this.root.add(this.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
get visible(): boolean {
|
get visible(): boolean {
|
||||||
@@ -128,15 +144,15 @@ export class RecoveryNotice {
|
|||||||
const contextUnavailable = this.state.can_continue === false
|
const contextUnavailable = this.state.can_continue === false
|
||||||
const title = failed ? "Recovery failed" : "Task interrupted"
|
const title = failed ? "Recovery failed" : "Task interrupted"
|
||||||
const detail = failed
|
const detail = failed
|
||||||
? "Review the task before continuing"
|
? "Review the saved task before continuing."
|
||||||
: contextUnavailable
|
: contextUnavailable
|
||||||
? "Saved context unavailable"
|
? "This task can’t be resumed safely. Dismiss to start a new message."
|
||||||
: "Tools will not replay automatically"
|
: "Review the saved context. Tools will not replay automatically."
|
||||||
this.message.content = new StyledText([
|
this.title.content = new StyledText([
|
||||||
chunk("△ ", failed ? this.theme.error : this.theme.accent),
|
chunk("△ ", failed ? this.theme.error : this.theme.accent),
|
||||||
chunk(title, this.theme.text, true),
|
chunk(title, this.theme.text, true),
|
||||||
chunk(` · ${detail}`, this.theme.muted),
|
|
||||||
])
|
])
|
||||||
|
this.detail.content = new StyledText([chunk(` ${detail}`, this.theme.muted)])
|
||||||
this.dismiss.fg = RGBA.fromHex(this.busy ? this.theme.muted : this.theme.text)
|
this.dismiss.fg = RGBA.fromHex(this.busy ? this.theme.muted : this.theme.text)
|
||||||
this.resume.visible = !contextUnavailable
|
this.resume.visible = !contextUnavailable
|
||||||
this.resume.fg = RGBA.fromHex(this.busy ? this.theme.muted : this.theme.accent)
|
this.resume.fg = RGBA.fromHex(this.busy ? this.theme.muted : this.theme.accent)
|
||||||
|
|||||||
Reference in New Issue
Block a user