feat(host): /window 路由与 float_window 工具共用同一段动作逻辑;smoke-host 升级为 20 条断言的映射/路由测试(真实抓包形状),实测 open 能拉起浮窗

This commit is contained in:
2026-09-27 14:50:01 +08:00
parent 5ddca41b8a
commit c3ca013d20
2 changed files with 114 additions and 39 deletions
+27 -8
View File
@@ -413,6 +413,19 @@ export function apply(ctx) {
}
return
}
// 浮窗动作(Agent 工具走同一段逻辑):open/close/pin/sizeLock/topmost/fullscreen/hide/show/status
if (url.pathname === `${ROUTE}/window` && req.method === 'POST') {
let body = {}
try {
const chunks = []
for await (const c of req) chunks.push(c)
body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}')
} catch { }
const r = await windowAction(body.action)
res.writeHead(200, { ...cors, 'Content-Type': 'application/json; charset=utf-8' })
res.end(JSON.stringify({ ...r, alive: shellAlive() }))
return
}
res.writeHead(404, { ...cors, 'Content-Type': 'text/plain; charset=utf-8' })
res.end('dsh-float-window: not found')
}
@@ -427,6 +440,18 @@ export function apply(ctx) {
return path.join(REPO, 'shell', 'run-float.cmd')
}
/** 浮窗动作的唯一入口:Agent 工具和 /window 路由都走这里 */
async function windowAction(action) {
const a = String(action || 'open')
if (a === 'status') {
const s = snapshot()
return { ok: true, text: `alive=${shellAlive()} session=${s.sessionId || 'none'} messages=${s.messages.length} turns=${s.metrics.turns ?? '?'} steps=${s.metrics.steps ?? '?'}` }
}
if (a === 'hide') broadcast({ type: 'cmd', cmd: 'hide' })
const r = await controlFloat(a)
return { ok: !!r.ok, text: r.ok ? `${a} ok (${r.via})` : `${a} failed: ${r.error}`, via: r.via, error: r.error }
}
/** 壳每 700ms 写一次心跳,据此判断浮窗是否还活着 */
function shellAlive() {
try { return Date.now() - fs.statSync(aliveFile).mtimeMs < 8000 } catch { return false }
@@ -478,14 +503,8 @@ export function apply(ctx) {
},
output: { schema: { type: 'string' }, render: (_a, v) => [{ type: 'text', text: v }] },
async execute(args) {
const action = String(args?.action || 'open')
if (action === 'status') {
const s = snapshot()
return `float-window: alive=${shellAlive()} session=${s.sessionId || 'none'} messages=${s.messages.length} turns=${s.metrics.turns ?? '?'} steps=${s.metrics.steps ?? '?'}`
}
if (action === 'hide') { broadcast({ type: 'cmd', cmd: 'hide' }); const r = await controlFloat('hide'); return `float-window: hide (${r.via || r.error})` }
const r = await controlFloat(action)
return r.ok ? `float-window: ${action} ok (${r.via})` : `float-window: ${action} failed: ${r.error}`
const r = await windowAction(args?.action)
return `float-window: ${r.text}`
},
})
status.tool = true