mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 08:13:11 +03:00
fix(tui): close cross-platform lifecycle races
This commit is contained in:
+6
-1
@@ -22,6 +22,11 @@ function occurrences(frame: string, value: string): number {
|
|||||||
return frame.split(value).length - 1
|
return frame.split(value).length - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitUntil(predicate: () => boolean, timeout = 250): Promise<void> {
|
||||||
|
const deadline = Date.now() + timeout
|
||||||
|
while (!predicate() && Date.now() < deadline) await Bun.sleep(5)
|
||||||
|
}
|
||||||
|
|
||||||
function client(sent: string[] = []) {
|
function client(sent: string[] = []) {
|
||||||
return {
|
return {
|
||||||
activeChatId: "chat",
|
activeChatId: "chat",
|
||||||
@@ -113,7 +118,7 @@ describe("NanobotTui layout", () => {
|
|||||||
composer.setText("你")
|
composer.setText("你")
|
||||||
composer.submit()
|
composer.submit()
|
||||||
setTimeout(() => composer.setText("你好"), 0)
|
setTimeout(() => composer.setText("你好"), 0)
|
||||||
await Bun.sleep(10)
|
await waitUntil(() => sent.length > 0)
|
||||||
|
|
||||||
expect(sent).toEqual(["你好"])
|
expect(sent).toEqual(["你好"])
|
||||||
})
|
})
|
||||||
|
|||||||
+7
-1
@@ -164,6 +164,7 @@ export class NanobotTui {
|
|||||||
private shimmerFrame = 0
|
private shimmerFrame = 0
|
||||||
private shimmerTimer: ReturnType<typeof setInterval> | null = null
|
private shimmerTimer: ReturnType<typeof setInterval> | null = null
|
||||||
private submitPending = false
|
private submitPending = false
|
||||||
|
private submitGeneration = 0
|
||||||
private readonly promptHistory: string[] = []
|
private readonly promptHistory: string[] = []
|
||||||
private historyCursor = 0
|
private historyCursor = 0
|
||||||
private historyDraft = ""
|
private historyDraft = ""
|
||||||
@@ -311,14 +312,17 @@ export class NanobotTui {
|
|||||||
private deferSubmit(): void {
|
private deferSubmit(): void {
|
||||||
if (this.submitPending) return
|
if (this.submitPending) return
|
||||||
this.submitPending = true
|
this.submitPending = true
|
||||||
|
const generation = ++this.submitGeneration
|
||||||
setTimeout(() => setTimeout(() => {
|
setTimeout(() => setTimeout(() => {
|
||||||
|
if (generation !== this.submitGeneration) return
|
||||||
this.submitPending = false
|
this.submitPending = false
|
||||||
|
if (this.composer.isDestroyed) return
|
||||||
this.submit()
|
this.submit()
|
||||||
}, 0), 0)
|
}, 0), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private submit(): void {
|
private submit(): void {
|
||||||
if (this.quitting) return
|
if (this.quitting || this.composer.isDestroyed) return
|
||||||
const content = this.composer.plainText.trim()
|
const content = this.composer.plainText.trim()
|
||||||
if (!content) return
|
if (!content) return
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
@@ -658,6 +662,8 @@ export class NanobotTui {
|
|||||||
private quit(): void {
|
private quit(): void {
|
||||||
if (this.quitting) return
|
if (this.quitting) return
|
||||||
this.quitting = true
|
this.quitting = true
|
||||||
|
this.submitGeneration += 1
|
||||||
|
this.submitPending = false
|
||||||
this.client.close()
|
this.client.close()
|
||||||
this.renderer.destroy()
|
this.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -17,17 +17,20 @@ const options: AppOptions = {
|
|||||||
access: process.env.NANOBOT_TUI_ACCESS?.trim() || "workspace access",
|
access: process.env.NANOBOT_TUI_ACCESS?.trim() || "workspace access",
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await NanobotTui.create(options)
|
let app: NanobotTui | undefined
|
||||||
|
let shuttingDown = false
|
||||||
|
|
||||||
const shutdown = (code = 0) => {
|
const shutdown = (code = 0) => {
|
||||||
app.stop()
|
if (shuttingDown) return
|
||||||
|
shuttingDown = true
|
||||||
|
app?.stop()
|
||||||
process.exitCode = code
|
process.exitCode = code
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"] as const) {
|
for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"] as const) {
|
||||||
process.once(signal, () => shutdown())
|
process.once(signal, () => shutdown())
|
||||||
}
|
}
|
||||||
process.once("exit", () => app.stop())
|
process.once("exit", () => app?.stop())
|
||||||
process.once("uncaughtException", (error) => {
|
process.once("uncaughtException", (error) => {
|
||||||
shutdown(1)
|
shutdown(1)
|
||||||
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`)
|
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`)
|
||||||
@@ -37,4 +40,6 @@ process.once("unhandledRejection", (error) => {
|
|||||||
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`)
|
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.start()
|
app = await NanobotTui.create(options)
|
||||||
|
if (shuttingDown) app.stop()
|
||||||
|
else app.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user