Metadata-Version: 2.4
Name: ennew-harness-sdk
Version: 0.1.6
Summary: Ennew Harness 业务上下文与前端消息 Python SDK
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# Ennew Harness Python SDK

适用于 Python 3.9 及以上版本，安装包名为 `ennew-harness-sdk`，导入名称为 `ennew_harness`。

从 `0.1.1` 起，导入名称由 `harness` 改为 `ennew_harness`，避免与 Worker 自身的同名包冲突。升级后请同步修改业务脚本中的导入语句；不保留旧名称别名。

```bash
python3 -m pip install --upgrade ennew-harness-sdk
```

本 SDK 依赖 Ennew Harness 运行环境，并非独立运行的公共网关客户端。普通脚本读取上下文或发送前端消息时，运行时必须提供 `CODEX_THREAD_ID`；MCP 读取上下文或发送前端消息可改为传入请求 `ctx`。读取上下文还需设置 `ENV` 或 `HARNESS_ENV`，并能访问对应业务网关。发送前端消息需要本机 Worker 服务。

读取上下文时，Worker 还需通过环境变量 `HARNESS_GATEWAY_CONTEXT_TOKEN` 注入对应环境的服务凭据。SDK 将其放入 `X-OpenClaw-Mcp-Gateway-Token` 请求头；未配置或为空时，在发送请求前报错。不要将真实凭据写入源码、镜像或发布包。

安装到运行业务脚本的 Python 环境后，可直接使用：

```python
from ennew_harness import context, gui

current = context.get_current_context()
gui.send("file_changed", {"file": "report.md"})
```

上述未传入 `ctx` 的调用都从运行时的 `CODEX_THREAD_ID` 获取当前线程身份。`gui.send` 要求传入非空的 `type` 和字典 `payload`，把 `type` 同时写入 `params.type` 与 `params.message` 中的业务 JSON，经 Worker 本机 `127.0.0.1:18080/internal/events` 发到当前线程的前端连接。只有线程正在执行且前端连接仍在时才能发送；接口返回成功表示消息已写入 WebSocket，不保证前端已处理。

MCP 回调中传入宿主提供的本次请求 `ctx`，SDK 从 `ctx.meta["threadId"]` 获取线程身份，不读取或修改 `CODEX_THREAD_ID`。元数据缺失或线程 ID 不是非空字符串时，在发送请求前报错，不回退环境变量。线程身份不要从模型填写的工具参数中获取。此方式不要求 SDK 安装 MCP 依赖，也不支持在尚无请求上下文的 MCP 启动阶段获取线程身份。

在异步 MCP 回调中，使用以下方式避免同步网关请求阻塞事件循环：

```python
import asyncio
from ennew_harness import context, gui

current = await asyncio.to_thread(context.get_current_context, ctx)
await asyncio.to_thread(gui.send, "file_changed", {"file": "report.md"}, ctx=ctx)
```

`context.get_current_context()` 优先读取系统环境变量 `ENV` 并转为大写；`ENV` 未设置或为空字符串时，再读取 `HARNESS_ENV`（保持原有大小写要求）。取到的值必须是 `FAT`、`UAT` 或 `PROD`；非空但无效的 `ENV` 会直接报错，不会回退。SDK 根据该值选择网关配置，并完整返回网关响应中的 `data`，包括 `authTokens`、`businessContext` 及后续新增字段。返回值可能包含敏感凭据，业务脚本不得记录、展示或转发完整上下文。三套网关 URL 定义在 `ennew_harness/context.py`，请求不内置 Token。安装 SDK 不会自动授予业务网关访问权限。

源码仓库中可运行上下文示例脚本：`python3 fat_context.py`。该脚本不随安装包提供。

## 文件写入后通知（0.1.6 起）

模型生成文本、JSON 时，推荐用标准输入一次同步提交完整内容，不先创建 Agent 临时输入文件。脚本的 read() 等待 EOF，之后才校验、写入和通知。下例为可执行格式示例，真实产物按业务契约填写：

```bash
python3 -m ennew_harness.write_notify --file artifacts/report.json --content-file - --json <<'ARTIFACT_EOF'
{"title":"本次报告","ready":true}
ARTIFACT_EOF
```

用单引号包住 heredoc 标记，避免正文中的 shell 变量和命令被展开；标记不能出现在正文独立行中，结尾独占一行。先准备完整内容再发起一次调用，不后台执行、不交互式分段输入。生成程序管道的 EOF 不代表生成成功，不建议将未检查退出码的生成程序直接管道接入。

执行顺序：读取到 EOF → 输入校验 → 目标同目录临时文件写入、flush/fsync、关闭 → os.replace 原子替换 → 回读比对 → gui.send。默认外层 type=file.changed，消息 JSON 内 type=file_changed，data.files 为项目相对路径数组，与原通知协议一致。gui.py 保持不变；脚本内部通知适配器处理外层与内层 type 不同的情况，通过现有 Worker HTTP 接口发送。默认入口不产生 Agent 输入临时文件；SDK 内部临时文件成功替换后消失，失败时清理。

### 批量标准输入

```bash
python3 -m ennew_harness.write_notify --batch-stdin <<'ARTIFACT_BATCH_EOF'
{"files":[{"file":"artifacts/report.json","content":"{\"ready\":true}","json":true},{"file":"artifacts/report.md","content":"# 本次报告\n"}]}
ARTIFACT_BATCH_EOF
```

信封只含非空 files 数组，每项 file 是目标路径、content 是完整文本字符串，可选 json=true 校验该文件 JSON。批量信封完整读取 EOF 后，解析和校验全部内容，再写入文件；全部文件回读成功后发送一次去重通知。同路径同内容去重，不同内容拒绝。--json 校验所有项；混合 Markdown 与 JSON 时用逐项 json 标志。--batch-stdin 不与 --file、--content-file、--payload-from-content 或 --cleanup-input 混用。

### 项目状态通知

```bash
python3 -m ennew_harness.write_notify --file project-state.json --content-file - --json --type sop.update --payload-from-content <<'STATE_EOF'
{"revision":1,"currentStep":"step-01","steps":[{"stepId":"step-01","stepName":"需供能力匹配","viewType":"system-page","viewUrl":"/demandSupply","status":"not_started"}]}
STATE_EOF
```

此处展示一项的格式示例，实际状态按平台步骤配置提供完整列表。通知携带状态 JSON 并附加 type=sop.update，状态文件不增加 type。--payload-from-content 仅支持单文件；自定义消息可用 --payload-file 传入 JSON 对象，与前者互斥。

### 失败处理与文件模式

退出码 0：文件已保存且 Worker 接受通知，不表示前端已处理；1：输入、写入或回读失败，未发送通知，实际文件可能部分写入，应检查后修复；2：已保存但通知失败；3：保存和通知成功，但临时输入清理失败。

退出码 2 时使用原命令加 --notify-only，并重新提供完全一致的标准输入正文或批量信封，重试一次。该模式只回读对比、不重写文件、不递增 revision；内容改变拒绝通知。退出码 3 仅清理残留，不重新通知。标准输入不落输入临时文件，重试内容须由调用方保留；SDK 不持久化重试正文。

大文件或外部程序输出保留原文件输入模式：

```bash
python3 -m ennew_harness.write_notify --file artifacts/report.pdf --content-file drafts/report.tmp.pdf --cleanup-input
```

只有外部生成程序同步成功退出、文件关闭后才执行。--cleanup-input 通知成功后删除可丢弃的输入文件，失败保留供重试；不删除目标产物、payload-file 或需长期保留的原始材料。重复输入路径去重，输入与任一目标产物相同则预先拒绝。标准输入无需清理。文件模式仍支持重复 --file 与 --content-file 按出现顺序配对，全部输入先读取再写入。

### 环境与保证范围

目标相对路径基于 --root（默认 cwd）；真实绝对路径及符号链接解析后的目标必须在 root 内。消息路径以 / 开头代表相对于项目根目录，不是服务器绝对路径。父目录自动创建。前端文件服务需访问同一存储视图，同目标写入须串行执行。

脚本校验 JSON 语法和读写一致性，不判断业务完整性；EOF 仅代表本次传输结束，不能识别内容本身遗漏或上游异常导致的完整合法短内容。批量写入不提供多文件事务回滚。当前无通知持久化、离线补发或前端处理回执。

运行环境需要更新后的 ennew-harness-sdk、CODEX_THREAD_ID 与本机 Worker。SDK 通知不产生 Codex JSONL 中 send_hidden_message 的调用记录，依赖此记录的后台记忆 Skill 归属需另行适配，不重复发送通知补记录。
