Files
dsh_ai1net_server/归档/poc-dsh-local/workspace-scoped-picker/ensure-workspace-picker-patch.cjs
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 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)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

94 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* ensure-workspace-picker-patch.cjs —— 把「工作区目录选择器收敛」平台段幂等写入各用户 profile patch。
*
* 背景(档案 18 v3,2026-09-11 实测):
* · 官方 dsh-web-app 的 `directory-picker` 行(@…-auto) 在启动时**连带挂载客户端对话框**
* (@deepseek-ai/dsh-client-ui-directory-picker-browse);把它 disabled 会让对话框消失(点不动)。
* · 该 client 包自带 dsh.client 元数据,可**单独作为一行**插入 → 保留官方对话框 UI。
* · host 面(seam) 改由自建 @dsh-local/workspace-scoped-picker 提供:根固定为 <userRoot>/ws,
* 越界(/etc、..、他人目录、符号链接逃逸)一律拒绝 → 路径框里手输也出不去。
* · 另由该插件的 client 面注入 CSS,隐藏「改路径」入口(crumbEditZone/Glyph)。
*
* 用法:
* node ensure-workspace-picker-patch.cjs # 全部用户(幂等)
* node ensure-workspace-picker-patch.cjs --dry-run # 只打印计划
* node ensure-workspace-picker-patch.cjs --restart # 写后 kill 实例(由崩溃自愈拉起,读新 patch)
* node ensure-workspace-picker-patch.cjs admin guest # 指定用户
*
* 幂等:以 BEGIN/END 标记包裹平台段;已存在即跳过(不改内容)。
* 注意:本脚本**只追加平台段**,不触碰既有内容(角色 patch 等原样保留)。
*/
const { execFileSync } = require('node:child_process')
const { existsSync, readFileSync, writeFileSync } = require('node:fs')
const { join } = require('node:path')
const Database = require('/opt/dshs/node_modules/better-sqlite3')
const DB_PATH = '/var/lib/dshs/dshs.db'
const PROFILE = 'web'
const BEGIN = '# >>> platform: workspace-scoped-picker (managed by ensure-workspace-picker-patch.cjs)'
const END = '# <<< platform: workspace-scoped-picker'
const DRY = process.argv.includes('--dry-run')
const RESTART = process.argv.includes('--restart')
const only = process.argv.slice(2).filter((a) => !a.startsWith('--'))
const BLOCK = [
BEGIN,
'# 目录选择器收敛:官方对话框 UI 保留(单独插入 client 面),host 面换成受限实现(根=自有 ws)',
'- insert:',
' - id: workspace-scoped-picker',
' name: "@dsh-local/workspace-scoped-picker"',
' - id: ui-directory-picker-browse',
' name: "@deepseek-ai/dsh-client-ui-directory-picker-browse"',
'- id: directory-picker',
' name: "@deepseek-ai/dsh-host-directory-picker-auto"',
' disabled: true',
END,
'',
].join('\n')
const db = new Database(DB_PATH, { readonly: true })
const users = db
.prepare('SELECT username, uid, home_dir FROM users')
.all()
.filter((u) => only.length === 0 || only.includes(u.username))
for (const user of users) {
const patchPath = join(user.home_dir, 'profiles', PROFILE, 'cordis.patch.yml')
if (!existsSync(patchPath)) {
console.log(` ${user.username}: NO_PROFILE(用户未首登 spawn,先登录一次)`)
continue
}
const current = readFileSync(patchPath, 'utf8')
if (current.includes(BEGIN)) {
console.log(` ${user.username}: skip(平台段已存在)`)
continue
}
const body = current.trim() === '' || current.trim() === '[]' ? '' : current.trimEnd() + '\n\n'
const next = body + BLOCK
if (DRY) {
console.log(` ${user.username}: [dry-run] 将写入 ${patchPath}`)
continue
}
writeFileSync(patchPath, next, 'utf8')
try {
execFileSync('chown', [`${user.uid}:${user.uid}`, patchPath])
} catch {}
console.log(` ${user.username}: wrote(已追加平台段)${RESTART ? ' + 重启实例' : ''}`)
if (RESTART) {
try {
const out = execFileSync('ps', ['-eo', 'pid,user', '--no-headers'], { encoding: 'utf8' })
for (const line of out.split('\n')) {
const [pid, uname] = line.trim().split(/\s+/)
if (pid && uname === `dsh-${user.uid}`) {
try {
process.kill(Number(pid))
} catch {}
}
}
} catch {}
}
}
db.close()
console.log('done')