Conversation
There was a problem hiding this comment.
Pull request overview
该 PR 旨在修复多工作区部署下看板只能看到 DEMO 任务、实际任务与日志不可见的问题:通过在 dashboard/server.py 中自动探测并选择正确的任务数据目录,实现任务读写与 live_status.json 读取的路径一致。
Changes:
- 新增任务数据目录候选探测(仓库
data/+~/.openclaw/workspace-*/data)并按任务内容/数量/更新时间评分选择。 load_tasks()/save_tasks()与/api/live-status、/healthz统一使用自动选择的数据目录。- 刷新逻辑调整为优先调用所选目录对应的
scripts/refresh_live_data.py。
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def get_task_data_dir(): | ||
| """自动选择当前任务数据目录,并缓存结果以保持一次服务期内稳定。""" | ||
| global _ACTIVE_TASK_DATA_DIR | ||
| if _ACTIVE_TASK_DATA_DIR and _ACTIVE_TASK_DATA_DIR.is_dir(): | ||
| return _ACTIVE_TASK_DATA_DIR | ||
|
|
||
| best_dir = DATA | ||
| best_score = (-1, -1, -1, -1) | ||
| for d in _iter_task_data_dirs(): | ||
| tf = d / 'tasks_source.json' |
There was a problem hiding this comment.
新增的数据目录自动探测会读取真实用户目录 ~/.openclaw/workspace-*/data,这会让现有的 tests/test_server.py 通过 patch srv.DATA 的方式无法完全隔离测试环境(可能被本机 workspace 的 tasks_source.json“抢走”数据源),导致测试与本地环境耦合。建议补充/调整测试覆盖:在测试里创建 tasks_source.json 并验证 get_task_data_dir() 的选择逻辑可控(或提供可注入/可禁用 workspace 探测的开关)。
| def load_tasks(): | ||
| return atomic_json_read(DATA / 'tasks_source.json', []) | ||
| def _iter_task_data_dirs(): | ||
| """返回可用的任务数据目录候选(优先 workspace,其次本地 data)。""" |
There was a problem hiding this comment.
_iter_task_data_dirs() 的 docstring 说“优先 workspace,其次本地 data”,但当前实现 dirs = [DATA] 后再 append workspace;在打分相同(或不存在 tasks_source 时回退)的情况下会更倾向选择本地 DATA,和说明不一致。建议要么调整候选顺序/加显式 tie-breaker,要么修正文档描述以匹配实际策略。
| """返回可用的任务数据目录候选(优先 workspace,其次本地 data)。""" | |
| """返回可用的任务数据目录候选(优先本地 data,其次 workspace)。""" |
| oclaw_home = pathlib.Path.home() / '.openclaw' | ||
| for p in sorted(oclaw_home.glob('workspace-*/data')): |
There was a problem hiding this comment.
这里重新计算了 oclaw_home = pathlib.Path.home() / '.openclaw',而文件顶部已经有可复用且便于测试/patch 的 OCLAW_HOME 常量。建议直接使用 OCLAW_HOME,以免后续需要在测试或部署中重定向 OpenClaw 目录时出现行为不一致。
| oclaw_home = pathlib.Path.home() / '.openclaw' | |
| for p in sorted(oclaw_home.glob('workspace-*/data')): | |
| for p in sorted(OCLAW_HOME.glob('workspace-*/data')): |
| if _ACTIVE_TASK_DATA_DIR and _ACTIVE_TASK_DATA_DIR.is_dir(): | ||
| return _ACTIVE_TASK_DATA_DIR | ||
|
|
||
| best_dir = DATA | ||
| best_score = (-1, -1, -1, -1) | ||
| for d in _iter_task_data_dirs(): | ||
| tf = d / 'tasks_source.json' | ||
| if not tf.exists(): | ||
| continue | ||
| score = _task_source_score(tf) | ||
| if score > best_score: | ||
| best_score = score | ||
| best_dir = d | ||
|
|
There was a problem hiding this comment.
get_task_data_dir() 一旦把 _ACTIVE_TASK_DATA_DIR 缓存成某个目录,就只检查 is_dir() 并直接返回;如果服务启动时没有任何候选包含 tasks_source.json(或后续才在 workspace 生成),缓存会永久锁定到默认 DATA,导致多工作区场景下仍可能读错数据源。建议缓存前至少要求目录内存在 tasks_source.json,或在缓存命中时也验证该文件仍存在/必要时重新探测。
| if _ACTIVE_TASK_DATA_DIR and _ACTIVE_TASK_DATA_DIR.is_dir(): | |
| return _ACTIVE_TASK_DATA_DIR | |
| best_dir = DATA | |
| best_score = (-1, -1, -1, -1) | |
| for d in _iter_task_data_dirs(): | |
| tf = d / 'tasks_source.json' | |
| if not tf.exists(): | |
| continue | |
| score = _task_source_score(tf) | |
| if score > best_score: | |
| best_score = score | |
| best_dir = d | |
| # 缓存命中时,既要求目录存在,也要求其中仍有 tasks_source.json | |
| if _ACTIVE_TASK_DATA_DIR and _ACTIVE_TASK_DATA_DIR.is_dir(): | |
| if (_ACTIVE_TASK_DATA_DIR / 'tasks_source.json').exists(): | |
| return _ACTIVE_TASK_DATA_DIR | |
| # 若缓存目录已不再包含任务源文件,则丢弃缓存并重新探测 | |
| _ACTIVE_TASK_DATA_DIR = None | |
| best_dir = DATA | |
| best_score = None | |
| for d in _iter_task_data_dirs(): | |
| tf = d / 'tasks_source.json' | |
| if not tf.exists(): | |
| continue | |
| score = _task_source_score(tf) | |
| if best_score is None or score > best_score: | |
| best_score = score | |
| best_dir = d | |
| # 仅在找到至少一个包含 tasks_source.json 的目录时才缓存; | |
| # 否则返回默认 DATA,但不缓存,以便后续新建 workspace 时能重新探测。 | |
| if best_score is None: | |
| log.info(f'任务数据源: 默认 DATA(未找到 tasks_source.json)') | |
| return best_dir |
| def _refresh_live_data_async(task_data_dir: pathlib.Path): | ||
| """触发对应数据目录的 live_status 刷新脚本。""" | ||
| script = task_data_dir.parent / 'scripts' / 'refresh_live_data.py' | ||
| if not script.exists(): | ||
| script = SCRIPTS / 'refresh_live_data.py' | ||
|
|
||
| def _refresh(): | ||
| try: | ||
| subprocess.run(['python3', str(SCRIPTS / 'refresh_live_data.py')], timeout=30) | ||
| subprocess.run(['python3', str(script)], timeout=30) |
There was a problem hiding this comment.
_refresh_live_data_async() 在 workspace 目录下找不到脚本时会回退到仓库 scripts/refresh_live_data.py;但该脚本内部写死使用自身所在目录的 DATA = BASE / 'data'(见 scripts/refresh_live_data.py),会把 live_status.json 写回仓库 data,而不是当前选中的 task_data_dir。这会造成 save_tasks 之后看板仍读取不到最新 live_status。建议让刷新脚本支持传入 dataDir(参数或环境变量),或在回退时显式传递/切换到能写入 task_data_dir 的实现。
问题现象
在多工作区场景下,看板页面只能看到初始化任务
JJC-DEMO-001,但实际已经生成并完成的旨意(例如JJC-20260311-004)及其日志不会显示。根因分析
dashboard/server.py原先固定读取:edict/data/tasks_source.jsonedict/data/live_status.json而实际运行时,任务数据可能写入到:
~/.openclaw/workspace-*/data/tasks_source.json~/.openclaw/workspace-*/data/live_status.json导致看板读取源与运行时写入源不一致,出现“任务已完成但看板看不到”的问题。
修复内容
本 PR 在
dashboard/server.py中做了以下修复:edict/data~/.openclaw/workspace-*/dataload_tasks()使用自动选中的数据目录save_tasks()写回同一目录/api/live-status从同一目录读取live_status.jsonscripts/refresh_live_data.py/healthz改为检查当前活跃数据目录的可读写状态验证结果
已完成以下验证:
语法检查通过:
python3 -m py_compile dashboard/server.py运行时数据源选择正确:
/home/zhaoc/.openclaw/workspace-taizi/data任务与看板状态一致:
tasks_source可见任务:JJC-20260311-004,JJC-20260311-003live_status可见任务:JJC-20260311-004,JJC-20260311-003影响范围