Skip to content

修复看板日志不可见:自动选择正确任务数据源(多工作区)#117

Open
zhaochenxian wants to merge 1 commit intocft0808:mainfrom
zhaochenxian:fix/dashboard-task-source-mismatch-v2
Open

修复看板日志不可见:自动选择正确任务数据源(多工作区)#117
zhaochenxian wants to merge 1 commit intocft0808:mainfrom
zhaochenxian:fix/dashboard-task-source-mismatch-v2

Conversation

@zhaochenxian
Copy link

问题现象

在多工作区场景下,看板页面只能看到初始化任务 JJC-DEMO-001,但实际已经生成并完成的旨意(例如 JJC-20260311-004)及其日志不会显示。

根因分析

dashboard/server.py 原先固定读取:

  • edict/data/tasks_source.json
  • edict/data/live_status.json

而实际运行时,任务数据可能写入到:

  • ~/.openclaw/workspace-*/data/tasks_source.json
  • ~/.openclaw/workspace-*/data/live_status.json

导致看板读取源与运行时写入源不一致,出现“任务已完成但看板看不到”的问题。

修复内容

本 PR 在 dashboard/server.py 中做了以下修复:

  1. 新增任务数据目录自动探测
  • 候选目录包括:
    • edict/data
    • ~/.openclaw/workspace-*/data
  1. 新增数据源评分与选择策略
  • 优先选择包含非 DEMO 任务的数据源
  • 其次按任务数量与文件更新时间进行排序
  • 服务运行期间缓存所选目录,保证读写一致
  1. 统一任务读写与状态读取路径
  • load_tasks() 使用自动选中的数据目录
  • save_tasks() 写回同一目录
  • /api/live-status 从同一目录读取 live_status.json
  1. 刷新逻辑与健康检查同步修正
  • 刷新优先调用所选数据源对应的 scripts/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-003
    • live_status 可见任务:JJC-20260311-004, JJC-20260311-003

影响范围

  • 修复多工作区部署中看板任务与日志缺失的问题
  • 不改变单目录部署的既有行为
  • 降低“看板与实际执行状态不一致”的运维风险

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +116 to +125
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'
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的数据目录自动探测会读取真实用户目录 ~/.openclaw/workspace-*/data,这会让现有的 tests/test_server.py 通过 patch srv.DATA 的方式无法完全隔离测试环境(可能被本机 workspace 的 tasks_source.json“抢走”数据源),导致测试与本地环境耦合。建议补充/调整测试覆盖:在测试里创建 tasks_source.json 并验证 get_task_data_dir() 的选择逻辑可控(或提供可注入/可禁用 workspace 探测的开关)。

Copilot uses AI. Check for mistakes.
def load_tasks():
return atomic_json_read(DATA / 'tasks_source.json', [])
def _iter_task_data_dirs():
"""返回可用的任务数据目录候选(优先 workspace,其次本地 data)。"""
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_iter_task_data_dirs() 的 docstring 说“优先 workspace,其次本地 data”,但当前实现 dirs = [DATA] 后再 append workspace;在打分相同(或不存在 tasks_source 时回退)的情况下会更倾向选择本地 DATA,和说明不一致。建议要么调整候选顺序/加显式 tie-breaker,要么修正文档描述以匹配实际策略。

Suggested change
"""返回可用的任务数据目录候选(优先 workspace,其次本地 data)。"""
"""返回可用的任务数据目录候选(优先本地 data,其次 workspace)。"""

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +90
oclaw_home = pathlib.Path.home() / '.openclaw'
for p in sorted(oclaw_home.glob('workspace-*/data')):
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里重新计算了 oclaw_home = pathlib.Path.home() / '.openclaw',而文件顶部已经有可复用且便于测试/patch 的 OCLAW_HOME 常量。建议直接使用 OCLAW_HOME,以免后续需要在测试或部署中重定向 OpenClaw 目录时出现行为不一致。

Suggested change
oclaw_home = pathlib.Path.home() / '.openclaw'
for p in sorted(oclaw_home.glob('workspace-*/data')):
for p in sorted(OCLAW_HOME.glob('workspace-*/data')):

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +132
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

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_task_data_dir() 一旦把 _ACTIVE_TASK_DATA_DIR 缓存成某个目录,就只检查 is_dir() 并直接返回;如果服务启动时没有任何候选包含 tasks_source.json(或后续才在 workspace 生成),缓存会永久锁定到默认 DATA,导致多工作区场景下仍可能读错数据源。建议缓存前至少要求目录内存在 tasks_source.json,或在缓存命中时也验证该文件仍存在/必要时重新探测。

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +146
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)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_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 的实现。

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants