Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions checkin/xiaojuchongdian/skill/overall/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ Required:
- `DAILYHUB_XIAOJU_TOKEN_ID`
- `DAILYHUB_XIAOJU_AM_DID`

Runtime loading rule:
- prefer already-exported process env
- if isolated cron/runtime env is missing required Xiaoju variables, auto-load defaults from common local `.env` files, especially the repository `.env`
- auto-loaded defaults must not override explicitly injected env values

When auth credentials are missing or expired, switch to `xiaoju-get-params` to refresh, then continue only after the refreshed credentials have been persisted and validated through the same config source used by downstream check-in runs.
`DAILYHUB_XIAOJU_AM_DID` is a device fingerprint and is not generated by SMS login; preserve the existing value for this account, or ask the user to capture it once from real app traffic.

Expand Down
76 changes: 76 additions & 0 deletions checkin/xiaojuchongdian/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import logging
import os
import shlex
from dataclasses import dataclass
from pathlib import Path

logger = logging.getLogger(__name__)

Expand All @@ -13,14 +15,86 @@ class ConfigError(ValueError):
"""Raised when required runtime configuration is missing or invalid."""


def _parse_env_assignment(raw_line: str) -> tuple[str, str] | None:
line = raw_line.strip()
if not line or line.startswith("#"):
return None
if line.startswith("export "):
line = line[len("export ") :].strip()
if "=" not in line:
return None
key, value = line.split("=", 1)
key = key.strip()
if not key:
return None
value = value.strip()
if not value:
return key, ""
try:
parsed = shlex.split(value, comments=False, posix=True)
except ValueError:
parsed = [value]
if len(parsed) == 1:
return key, parsed[0]
return key, value


def _load_env_file(path: Path) -> bool:
if not path.is_file():
return False
loaded_any = False
for raw_line in path.read_text(encoding="utf-8").splitlines():
parsed = _parse_env_assignment(raw_line)
if not parsed:
continue
key, value = parsed
os.environ.setdefault(key, value)
loaded_any = True
if loaded_any:
logger.info("loaded Xiaoju env defaults from %s", path)
return loaded_any


def _bootstrap_env() -> None:
current_file = Path(__file__).resolve()
repo_root = current_file.parents[3]
candidates = [
Path.cwd() / ".env",
repo_root / ".env",
repo_root.parent / ".env",
Path("/root/DailyHub/.env"),
Path("/root/.env"),
]
seen: set[Path] = set()
for candidate in candidates:
resolved = candidate.resolve(strict=False)
if resolved in seen:
continue
seen.add(resolved)
_load_env_file(resolved)


_BOOTSTRAPPED_ENV = False


def _ensure_env_bootstrapped() -> None:
global _BOOTSTRAPPED_ENV
if _BOOTSTRAPPED_ENV:
return
_bootstrap_env()
_BOOTSTRAPPED_ENV = True


def _get_required_env(name: str) -> str:
_ensure_env_bootstrapped()
value = os.getenv(name, "").strip()
if not value:
raise ConfigError(f"missing required env: {name}")
return value


def _get_optional_env(name: str, default: str) -> str:
_ensure_env_bootstrapped()
raw = os.getenv(name)
if raw is None:
return default
Expand All @@ -36,6 +110,7 @@ def _get_optional_env(name: str, default: str) -> str:


def _get_int_env(name: str, default: int) -> int:
_ensure_env_bootstrapped()
raw = os.getenv(name)
if raw is None or raw.strip() == "":
return default
Expand All @@ -46,6 +121,7 @@ def _get_int_env(name: str, default: int) -> int:


def _get_float_env(name: str, default: float) -> float:
_ensure_env_bootstrapped()
raw = os.getenv(name)
if raw is None or raw.strip() == "":
return default
Expand Down
27 changes: 27 additions & 0 deletions docs/design/xiaoju-checkin-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,33 @@ class TaskModule(Protocol):
4. 失败原因语义化(认证失败、业务失败、网络失败)
5. CLI 输出 JSON,便于机器消费

### 6.1 隔离运行时配置补载设计

问题背景:
- OpenClaw 的 isolated cron session 不保证继承登录 shell 中的环境变量
- 小桔签到任务过去直接依赖 `os.getenv` 读取 `DAILYHUB_XIAOJU_*`
- 一旦 cron session 没有这些变量,就会在 `status` 前置阶段直接抛出 `missing required env`

设计要求:
1. 配置模块在首次读取环境变量前,先执行一次轻量级 bootstrap
2. bootstrap 优先保留当前进程里已存在的显式环境变量
3. 若变量缺失,则从常见本地 `.env` 位置补齐默认值
4. `.env` 解析仅支持简单 `KEY=VALUE` / `export KEY=VALUE` 赋值格式,满足当前 DailyHub 配置源即可
5. 仅使用 `os.environ.setdefault(...)` 注入,避免覆盖调用方显式传入的值
6. bootstrap 应幂等,避免在单次进程中重复加载

候选 `.env` 路径:
- 当前工作目录 `.env`
- 仓库根目录 `.env`
- 仓库父目录 `.env`
- `/root/DailyHub/.env`
- `/root/.env`

预期收益:
- 手动运行与 cron 隔离运行共享同一配置源
- 失败模式从“隐式缺 env”收敛为“补载后仍缺 env”
- 降低 cron 因环境继承差异导致的伪故障

---

## 7. 调用建议
Expand Down
22 changes: 22 additions & 0 deletions docs/requirements/xiaoju-checkin-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,29 @@
- `DAILYHUB_XIAOJU_TICKET`
- `DAILYHUB_XIAOJU_TOKEN`
- `DAILYHUB_XIAOJU_TOKEN_ID`
- `DAILYHUB_XIAOJU_AM_DID`
- `DAILYHUB_XIAOJU_APP_ID`
- `DAILYHUB_XIAOJU_AM_CHANNEL`
- `DAILYHUB_XIAOJU_SOURCE`
- `DAILYHUB_XIAOJU_TTID`

### 隔离运行时配置加载要求

为兼容 OpenClaw 的 isolated cron session,配置加载必须遵循以下顺序:

1. 优先读取当前进程已经显式导出的环境变量
2. 若关键小桔凭证缺失,则自动从本地常见 `.env` 配置源补载
3. 至少必须覆盖以下候选路径:
- 当前工作目录下的 `.env`
- DailyHub 仓库根目录下的 `.env`
- `/root/DailyHub/.env`
- `/root/.env`

补载规则:
- 仅在进程内补齐缺失变量,不覆盖已经显式注入的值
- 补载后必须继续使用同一配置源完成后续 `status/run` 校验
- 若补载后仍缺失关键变量,应明确返回缺失字段,不能伪装成成功执行

### 认证失败自愈策略

自动恢复机制覆盖两个场景:
Expand Down Expand Up @@ -119,6 +137,7 @@
3. 已签到场景重复执行不报错
4. 所有认证信息来自环境变量,无硬编码密钥
5. 日志中可看到每个步骤开始/结束与错误原因
6. 在隔离环境(未预先导出 `DAILYHUB_XIAOJU_*`)下,只要本地 `.env` 完整,`status` 仍能成功读取配置并执行

---

Expand All @@ -136,6 +155,9 @@ python3 -m checkin.xiaojuchongdian.src.main run --task xiaoju.checkin --verify-r

# 4) 仅查询状态
python3 -m checkin.xiaojuchongdian.src.main status --task xiaoju.checkin

# 5) 隔离环境回归(不继承 shell 中的小桔变量)
env -i PATH="$PATH" HOME="$HOME" bash -lc 'cd /path/to/DailyHub && python3 -m checkin.xiaojuchongdian.src.main status --task xiaoju.checkin'
```

---
Expand Down