回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复: - 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录) - tmp/(32.4 M,按接续棒命名的过程临时区) - .workbuddy/tmp/(39.5 M) - 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物) - tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留 入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与 接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、 .workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。 排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、 打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
70 lines
3.2 KiB
Python
70 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
||
# 档案 25:not_running 的浏览器导航兜底 —— 绝不吐 JSON;并发进场等待在飞的实例
|
||
# 用法: python3 patch-25.py /opt/dshs
|
||
import io
|
||
import os
|
||
import sys
|
||
|
||
root = sys.argv[1] if len(sys.argv) > 1 else '.'
|
||
p = os.path.join(root, 'src/supervisor/proxy.ts')
|
||
s = io.open(p, encoding='utf-8').read()
|
||
|
||
old = """ // 实例未运行(后台回收/崩溃/停止后刷新会话页)→ 自动重启并 302 到新实例 URL,
|
||
// 透明恢复,不必让用户重新登录。
|
||
if (access.code === 404 && access.error === 'not_running' && access.userId !== undefined) {
|
||
try {
|
||
const folderAbs = app.userFs.resolvePath(access.userId, '')
|
||
const instance = await app.supervisor.launch(access.userId, folderAbs, undefined)
|
||
const token = instance.launchToken ?? ''
|
||
if (token !== '') {
|
||
const scheme = app.config.secureCookies ? 'https' : 'http'
|
||
reply.redirect(`${scheme}://${clientHost(request.headers)}/?token=${encodeURIComponent(token)}`)
|
||
return
|
||
}
|
||
} catch {
|
||
// 启动失败(目录异常/已在启动中等)→ 落到下方 not_running 响应
|
||
}
|
||
}
|
||
reply.code(access.code).send({ error: access.error })
|
||
return"""
|
||
|
||
new = """ // 实例未运行(后台回收/崩溃/停止/熔断后刷新会话页)→ 自动重启并 302 到新实例 URL,
|
||
// 透明恢复,不必让用户重新登录。
|
||
if (access.code === 404 && access.error === 'not_running' && access.userId !== undefined) {
|
||
const navScheme = app.config.secureCookies ? 'https' : 'http'
|
||
const navHost = clientHost(request.headers) ?? app.config.baseDomain
|
||
const isNavigation =
|
||
request.raw.method === 'GET' && (request.headers.accept ?? '').includes('text/html')
|
||
let token = ''
|
||
try {
|
||
const folderAbs = app.userFs.resolvePath(access.userId, '')
|
||
const instance = await app.supervisor.launch(access.userId, folderAbs, undefined)
|
||
token = instance.launchToken ?? ''
|
||
} catch {
|
||
// 并发进场(另一个请求正在拉起 → AlreadyRunningError)或启动异常:
|
||
// 等一会儿取在飞实例的 token,而不是直接把 404 JSON 甩给浏览器(档案 25)。
|
||
try {
|
||
await app.supervisor.waitForLaunchTokenForUser(access.userId, 20000)
|
||
token = (await app.supervisor.status(access.userId)).main?.launchToken ?? ''
|
||
} catch {
|
||
token = ''
|
||
}
|
||
}
|
||
if (token !== '') {
|
||
reply.redirect(`${navScheme}://${navHost}/?token=${encodeURIComponent(token)}`)
|
||
return
|
||
}
|
||
// 仍拿不到 token:浏览器导航一律回门户(可点「进入」,避免看到裸 JSON);API 保持 JSON。
|
||
if (isNavigation) {
|
||
reply.redirect(`${navScheme}://${app.config.baseDomain}/`)
|
||
return
|
||
}
|
||
}
|
||
reply.code(access.code).send({ error: access.error })
|
||
return"""
|
||
|
||
assert old in s, 'anchor not found'
|
||
s = s.replace(old, new, 1)
|
||
io.open(p, 'w', encoding='utf-8', newline='').write(s)
|
||
print('patched: not_running 浏览器导航兜底 + 并发等待')
|