Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

### Added
- `boss platforms` 新增 `--platform` 单平台过滤,支持 `qiancheng` 与 `51job` 别名,仅读取本地能力元数据并保持未知平台的 `INVALID_PARAM` JSON 包络。
- `boss platforms` 输出新增 `capability_status_legend`,解释 `available` / `not_supported` / `placeholder_only` / `low_risk_blocked` 的清晰语义,避免 Agent 将占位或低风险阻断误读为真实能力。

### Changed
- 补强 `boss config` 未知配置项错误路径的 stdout 单行 JSON 包络契约测试,确保 Agent 可稳定解析 `INVALID_PARAM`。
Expand Down
9 changes: 9 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ boss-agent-cli covers both the job-seeker and the recruiter side, with a pluggab
| Zhaopin (`zhilian`) | 🟡 candidate-side login + read/write flow wired | — | recruiter side is still intentionally unavailable at runtime |
| 51job (`qiancheng`) | 🚧 registered placeholder | — | returns `NOT_SUPPORTED` until the read-only research gate is satisfied |

`boss platforms` includes `capability_status_legend` in both JSON and terminal output so agents can interpret capability states clearly:

| State | Meaning |
|-------|---------|
| `available` | The local CLI has wired this capability; login requirements still follow the concrete command contract |
| `not_supported` | The current platform adapter does not implement this real workflow; the CLI returns a stable `NOT_SUPPORTED` envelope |
| `placeholder_only` | Registered only for platform identity, aliases, schema/config visibility; it does not mean a real platform capability is wired |
| `low_risk_blocked` | Write actions, sensitive data, or platform-risk boundaries are involved; default low-risk mode blocks the action and points users back to the official UI |

```bash
# pick a platform
boss --platform zhilian search "Python"
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@ boss hr candidates "Golang"
| 智联招聘 (`zhilian`) | 🟡 候选者侧登录 + 读写链路已接通 | — | 招聘者侧未接入,运行时会直接拒绝 `hr` 子命令 |
| 前程无忧 / 51job (`qiancheng`) | 🚧 已注册占位 | — | 统一返回 `NOT_SUPPORTED`,待只读研究门槛满足后再接入真实能力 |

`boss platforms` 会在 JSON 与终端输出中附带 `capability_status_legend`,用于解释能力状态:

| 状态 | 语义 |
|------|------|
| `available` | 本地 CLI 已接入该能力;是否需要登录仍以具体命令契约为准 |
| `not_supported` | 当前平台适配器没有实现该真实工作流;CLI 会稳定返回 `NOT_SUPPORTED` |
| `placeholder_only` | 仅用于平台注册、别名、schema/config 可见性;不代表真实平台能力已接入 |
| `low_risk_blocked` | 涉及写操作、敏感数据或平台风险边界;默认低风险模式阻断并提示回到官方页面手动处理 |

```bash
# 指定平台
boss --platform zhilian search "Python"
Expand Down
25 changes: 25 additions & 0 deletions src/boss_agent_cli/commands/platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@
},
}

_CAPABILITY_STATUS_LEGEND: dict[str, dict[str, str]] = {
"available": {
"label": "可用",
"description": "本地 CLI 已接入该能力;是否需要登录仍以具体命令契约为准。",
},
"not_supported": {
"label": "不支持",
"description": "当前平台适配器没有实现该真实工作流;CLI 会稳定返回 NOT_SUPPORTED。",
},
"placeholder_only": {
"label": "仅占位",
"description": "仅用于平台注册、别名、schema/config 可见性;不代表真实平台能力已接入。",
},
"low_risk_blocked": {
"label": "低风险模式阻断",
"description": "涉及写操作、敏感数据或平台风险边界;默认低风险模式阻断并提示回到官方页面手动处理。",
},
}


_PLATFORM_NOTES = {
"zhipin": "默认平台;候选者侧与招聘者侧注册表均已接入。",
"zhilian": "候选者侧只读链路已接入;招聘者侧暂不可用。",
Expand Down Expand Up @@ -96,6 +116,7 @@ def platform_capability_data(platform_name: str | None = None) -> dict[str, Any]
"count": len(platforms),
"default": "zhipin",
"aliases": {"51job": "qiancheng"},
"capability_status_legend": _CAPABILITY_STATUS_LEGEND,
"platforms": platforms,
}

Expand All @@ -106,6 +127,10 @@ def _render_platforms(data: dict[str, Any]) -> None:
candidate = "yes" if item["candidate"] else "no"
recruiter = "yes" if item["recruiter"] else "no"
lines.append(f"{item['name']}\t{item['display_name']}\t{item['status']}\t{candidate}\t{recruiter}")
lines.append("")
lines.append("capability_status_legend")
for status, meta in data["capability_status_legend"].items():
lines.append(f"{status}\t{meta['label']}\t{meta['description']}")
click.echo("\n".join(lines))


Expand Down
33 changes: 33 additions & 0 deletions tests/test_platforms_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from click.testing import CliRunner

from boss_agent_cli.commands.platforms import _render_platforms, platform_capability_data
from boss_agent_cli.main import cli


Expand All @@ -19,6 +20,11 @@ def test_platforms_outputs_local_capability_matrix() -> None:
assert payload["command"] == "platforms"
assert payload["data"]["default"] == "zhipin"
assert payload["data"]["aliases"] == {"51job": "qiancheng"}
legend = payload["data"]["capability_status_legend"]
assert set(legend) == {"available", "not_supported", "placeholder_only", "low_risk_blocked"}
assert "NOT_SUPPORTED" in legend["not_supported"]["description"]
assert "低风险模式" in legend["low_risk_blocked"]["label"]
assert "不代表真实平台能力" in legend["placeholder_only"]["description"]

platforms = {item["name"]: item for item in payload["data"]["platforms"]}
assert set(platforms) == {"qiancheng", "zhipin", "zhilian"}
Expand All @@ -30,6 +36,33 @@ def test_platforms_outputs_local_capability_matrix() -> None:
assert platforms["zhilian"]["capabilities"]["readonly"]["search"] == "available"


def test_platforms_json_payload_includes_status_legend() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["platforms"])

assert result.exit_code == 0, result.output
payload = json.loads(result.output)
legend = payload["data"]["capability_status_legend"]
assert set(legend) == {"available", "not_supported", "placeholder_only", "low_risk_blocked"}
assert legend["available"]["label"] == "可用"
assert "NOT_SUPPORTED" in legend["not_supported"]["description"]
assert "不代表真实平台能力" in legend["placeholder_only"]["description"]
assert "默认低风险模式阻断" in legend["low_risk_blocked"]["description"]


def test_platforms_terminal_render_includes_status_legend(capsys) -> None:
_render_platforms(platform_capability_data())
captured = capsys.readouterr()

rendered = captured.out + captured.err
assert "capability_status_legend" in rendered
assert "available" in rendered
assert "可用" in rendered
assert "not_supported" in rendered
assert "placeholder_only" in rendered
assert "low_risk_blocked" in rendered


def test_platforms_can_filter_single_platform_by_registered_name() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["platforms", "--platform", "qiancheng"])
Expand Down