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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
- run: ./tests/deployment-package.sh
- uses: actions/upload-artifact@v4
with:
name: farhelm-V0.8.0-linux-x86_64
name: farhelm-V0.9.0-linux-x86_64
path: |
dist/release/farhelm-*
dist/release/SHA256SUMS
Expand Down
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ members = [
]

[workspace.package]
version = "0.8.0"
version = "0.9.0"
edition = "2024"
rust-version = "1.98"
license = "Apache-2.0"
Expand Down
12 changes: 8 additions & 4 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
<p><strong>A remote control plane for personal research and GPU training environments</strong></p>
<p>See training-host status from your phone and safely extend remote control without exposing inbound ports on training machines.</p>
<p>
<a href="https://github.com/Xiiiing/FarHelm/releases/tag/V0.8.0">V0.8.0</a> ·
<a href="https://github.com/Xiiiing/FarHelm/releases/tag/V0.9.0">V0.9.0</a> ·
<a href="./deploy/README.en.md">Deployment guide</a> ·
<a href="./README.md">简体中文</a>
</p>
</div>

> [!IMPORTANT]
> `V0.8.0` lets the Rust Agent manage your installed, authenticated Codex through one persistent App Server and an outbound WSS connection. The workspace uses Ant Design X, a shared memory cache, and virtualized history while preserving global navigation, experiments, schedules, and browser notifications.
> `V0.9.0` completes the project workspace, native Codex model and permission details, light/dark appearance, and custom accent colors. It fixes cached long-history switching, notification/experiment/audit pagination, and schedule synchronization. The Rust Agent, persistent local Codex, outbound WSS, experiment reports, and browser notifications remain in place.

## Quick install

Expand Down Expand Up @@ -121,16 +121,18 @@ A successful command returns `run_id`, `event_id`, and `stored_locally: true`: t

The [Bash example](examples/experiment-report.sh) and [Python example](examples/experiment-report.py) report failures through exit handling and preserve the training exit code, without a Python SDK. A single final call cannot infer a result if never reached, after power loss, or after SIGKILL; use PID watch for that fallback. Success follow-ups expire after 24 hours; failed and unknown reports only notify. Prompt files remain on the Agent.

The notification center provides pagination, type/Agent/result filters, synchronized unread state, and details. Settings include system/light/dark themes, experiment/Codex in-page alert switches, and a browser test notification. Keep FarHelm open to receive completion alerts and click through to details. Initial loading and reconnect history do not trigger a burst of old alerts. This release covers browser in-page notifications; iOS system notifications are planned for a later version.
The notification center provides pagination, type/Agent/result filters, synchronized unread state, and details. Notification, experiment, and audit refreshes retain loaded pages. Filters cover all registered servers, obsolete requests cannot replace current results, and failed reads can be retried. Schedule creation and cancellation reconcile the list immediately, with confirmation before cancelling a specific task. Settings include system/light/dark themes, experiment/Codex in-page alert switches, and a browser test notification. Keep FarHelm open to receive completion alerts and click through to details. Initial loading and reconnect history do not trigger a burst of old alerts. This release covers browser in-page notifications; iOS system notifications are planned for a later version.

Browser commands and schedules are acknowledged after Agent persistence. Failed submissions retain the current-page draft and operation identity for retries. On restart, saved terminal receipts reconcile completed work; running tasks without a terminal receipt become orphaned and are not replayed. Upgrade Hub before Agent; the new content relay requires the Agent's V0.7 capability.

## Codex workspace

Desktop global navigation and mobile bottom navigation remain visible in Codex. Project groups collapse, and sessions use formal names or a temporary first-user-message summary. Search covers all imported sessions in the selected scope; offline Agents are clearly marked as incomplete results. Summaries and search results never enter Hub databases, logs, or notification titles.
Desktop global navigation and mobile bottom navigation remain visible in Codex. Sessions collapse by project, with the device name beside each project; same-named projects on different devices remain separate. Sessions use formal names or a temporary first-user-message summary. Search covers all imported sessions in the selected scope; offline Agents are clearly marked as incomplete results. Summaries and search results never enter Hub databases, logs, or notification titles.

Assistant replies support Markdown tables, code copying, math, and safe HTTPS links. Execution details in each turn form a collapsed group with visible failures. The composer stays visible, and reading older content does not jump to the bottom on new messages. Large-message continuation is separate from loading earlier conversations. Switching sessions immediately displays cached history and retains each draft; failed refreshes preserve visible content. Inactive body and parsing caches total at most 32 MiB, with at most 20 inactive histories, and are cleared on logout. Steer and interrupt target the visible active turn.

The system uses black and white base colors with one customizable theme color; the logo retains its original brand colors. Settings offer separate light/dark mode and theme color choices (graphite, blue, green, violet, rose, orange, or a custom color) for primary buttons, selected states, links, and focus, applied immediately and saved in the current browser. The rounded composer stays visible; locally served Manrope and Noto Sans SC fonts share a consistent reading column. Submission and expanding execution details use brief animations that respect the system’s reduced-motion preference; cached session switches do not replay the entry animation.

Upgrade Hub before Agent when moving from V0.7.1. SQLite schema remains 7. Older Agents retain HTTP compatibility; upgraded Agents use WSS without also claiming work over HTTP. Existing Python directories remain on installed machines for rollback validation and are never invoked by the new version.

## What is implemented
Expand All @@ -152,6 +154,8 @@ flowchart LR

FarHelm is one monorepo, but Hub and Agent are compiled separately and retain distinct privileges and attack surfaces. Codex communicates only over local stdio. Authentication remains local; Hub relays bodies only in bounded memory for at most 20 seconds.

The Codex composer displays the session model, reasoning effort, and permission details returned by the server. Resuming inherits native Codex settings; new browser sessions use project configuration, with an optional isolated Git worktree. Unconfirmed settings are marked unknown rather than inferred from legacy session modes. Adjust the model and permissions in Codex on the server. Browser approval prompts are not supported yet, so operations requiring user approval are declined. Upgrade Hub before Agent and verify that Agent advertises `codex.session_context`. Explicit inspect/edit creation through older APIs and the CLI remains compatible.

## Local development

You need Rust 1.98, Node.js 24, Corepack, and a locally installed Codex.
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
<p><strong>面向个人科研与 GPU 训练环境的远程控制平面</strong></p>
<p>从手机查看训练服务器状态,并在不开放训练机入站端口的前提下安全扩展远程控制能力。</p>
<p>
<a href="https://github.com/Xiiiing/FarHelm/releases/tag/V0.8.0">V0.8.0</a> ·
<a href="https://github.com/Xiiiing/FarHelm/releases/tag/V0.9.0">V0.9.0</a> ·
<a href="./deploy/README.md">部署文档</a> ·
<a href="./README.en.md">English</a>
</p>
</div>

> [!IMPORTANT]
> `V0.8.0` 由 Rust Agent 直接管理本机已安装、已登录的 Codex,通过常驻 App Server 和主动 WSS 连接传输请求与增量。工作区使用 Ant Design X、统一内存缓存和长历史虚拟化,保留系统导航、实验、调度及页面通知。
> `V0.9.0` 完善项目工作区、原生 Codex 模型与权限展示、浅深主题和自定义主题色,修复长会话缓存切换、通知/实验/审计分页刷新及定时任务同步。继续使用 Rust Agent、常驻本机 Codex 和出站 WSS,保留实验上报与页面通知。

## 快速安装

Expand Down Expand Up @@ -121,16 +121,18 @@ printf '训练失败,请登录查看详情' | farhelm-agent experiment report

[Bash 示例](examples/experiment-report.sh)和 [Python 示例](examples/experiment-report.py)通过退出捕获报告失败,保留训练退出码,不要求安装 Python SDK。脚本未运行到上报、机器断电或 SIGKILL 无法由单次上报推断结果,仍可使用 PID watch 兜底。成功续话有效期 24 小时,失败与 unknown 只通知。

通知中心支持分页、类型/Agent/结果筛选、未读同步和结果详情。设置页支持系统/浅色/深色主题、实验/Codex 页面提醒开关及页面测试通知。保持 FarHelm 页面打开即可接收完成提醒,点击提醒进入对应详情;初次进入与重连补历史不会批量弹出旧提醒。本版范围为浏览器页面通知,iOS 系统推送留待后续版本。
通知中心支持分页、类型/Agent/结果筛选、未读同步和结果详情。通知、实验、审计刷新保留已加载页;筛选覆盖全部登记服务器,过时请求不影响当前结果,读取失败可重试。定时任务创建和取消后即时核对列表,取消前确认具体任务。设置页支持系统/浅色/深色主题、实验/Codex 页面提醒开关及页面测试通知。保持 FarHelm 页面打开即可接收完成提醒,点击提醒进入对应详情;初次进入与重连补历史不会批量弹出旧提醒。本版范围为浏览器页面通知,iOS 系统推送留待后续版本。

网页指令和调度在 Agent 保存后才确认提交;失败时当前页面保留草稿并沿用操作身份重试。运行中的任务在 Agent 重启后按已保存的终态收据恢复;无终态收据时标记 orphaned,不自动重放。升级先 Hub 后 Agent,新正文交付要求 Agent 的 V0.7 能力标识。

## Codex 工作区

进入 Codex 后保留桌面全局侧栏和手机底部导航。项目分组可折叠,会话名称优先使用正式标题,没有标题时临时读取首条用户消息摘要。搜索覆盖筛选范围内全部已导入会话;离线 Agent 会明确提示结果不完整。摘要与搜索结果不写入 Hub 数据库或日志,也不用于通知标题。
进入 Codex 后保留桌面全局侧栏和手机底部导航。会话栏以项目为单位折叠,设备名显示在项目旁;不同设备的同名项目分别列出。会话名称优先使用正式标题,没有标题时临时读取首条用户消息摘要。搜索覆盖筛选范围内全部已导入会话;离线 Agent 会明确提示结果不完整。摘要与搜索结果不写入 Hub 数据库或日志,也不用于通知标题。

助手回复支持 Markdown 表格、代码复制、公式及安全 HTTPS 链接。每轮执行过程默认合并折叠,失败可直接辨认。输入区固定可见,阅读旧内容时不会被新消息拉回底部;大消息的“继续加载此消息”与“加载更早对话”分别处理。切换会话立即显示已有缓存,并保留各自草稿;刷新失败不清空已显示内容。非活动正文缓存与解析缓存合计不超过 32 MiB,最多保留 20 个非活动历史,退出登录清空。补充与中断只针对可见的活动轮次。

系统外观使用黑白基础色和一个可自定义主题色,Logo 保留品牌原色。设置中可独立选择浅深模式及主题色(石墨、蓝、绿、紫、玫瑰、橙或自定义颜色),用于主要按钮、选中状态、链接与焦点,即时生效并保存在当前浏览器。圆润的输入框固定可见;界面使用本地加载的 Manrope 与 Noto Sans SC 字体和统一阅读列。提交与展开过程提供简短动效,遵循系统“减少动态效果”设置;已缓存会话切换不重播进入动画。

从 V0.7.1 升级先更新 Hub,再更新 Agent。SQLite schema 保持 7,旧 Agent 的 HTTP 链路保留兼容;新 Agent 使用 WSS,连接期间不重复领取任务。旧 Python 目录保留在安装机供回退验证,新版本不再调用它。

## 当前实现
Expand All @@ -152,6 +154,8 @@ flowchart LR

FarHelm 是一个 monorepo,但 Hub 与 Agent 分离编译并保持不同权限与攻击面。Codex 只通过本机 stdio 通信;登录凭据保留在本机,Hub 仅在最长 20 秒的有界内存中中转正文。

Codex 输入区显示服务器返回的会话模型、推理强度和权限详情。恢复会话沿用原生 Codex 设置;网页新建会话默认使用项目配置,隔离 Git 工作区为可选项。尚未确认的设置明确标为未知,不根据会话旧模式猜测权限。模型和权限在服务器上的 Codex 中调整;网页暂不支持人工审批,需要用户批准的操作会被拒绝。请先升级 Hub,再升级 Agent,确认 Agent 已提供 `codex.session_context` 能力。旧 API/CLI 显式创建的 inspect/edit 模式继续兼容。

## 本地开发

需要 Rust 1.98、Node.js 24 和 Corepack;原生联调另需已安装、已登录的 Codex。
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.0
0.9.0
2 changes: 1 addition & 1 deletion crates/farhelm-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ mod tests {
fn build_info_uses_workspace_identity() {
let info = BuildInfo::current();
assert_eq!(info.product, "FarHelm");
assert_eq!(info.version, "0.8.0");
assert_eq!(info.version, include_str!("../../../VERSION").trim());
}
}
39 changes: 38 additions & 1 deletion crates/farhelm-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl AgentHeartbeat {
"codex.session_display".to_owned(),
"codex.item_offsets".to_owned(),
"codex.native".to_owned(),
"codex.session_context".to_owned(),
"agent.live".to_owned(),
],
protocol: FARHELM_PROTOCOL.to_owned(),
Expand All @@ -74,6 +75,8 @@ pub struct AgentHeartbeatAck {

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgentSummary {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub codex: Option<live::CodexReadiness>,
pub agent_id: String,
Expand Down Expand Up @@ -354,11 +357,28 @@ pub struct CodexTranscriptPage {
pub session_id: String,
pub turns: Vec<CodexTranscriptTurn>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context: Option<CodexSessionContext>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub continuation: Option<TranscriptContinuation>,
}

/// Ephemeral configuration only. Never persist a native configuration object or paths.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CodexSessionContext {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sandbox: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approval_policy: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approvals_reviewer: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TranscriptContinuation {
pub kind: String,
Expand Down Expand Up @@ -522,6 +542,8 @@ pub struct CreateCodexSessionRequest {
pub agent_id: String,
pub project_id: String,
pub mode: CodexSessionMode,
#[serde(default)]
pub inherit_permissions: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -744,7 +766,7 @@ mod tests {
"agent_id": "gpu-a",
"hostname": "trainer-a",
"agent_version": "0.1.0",
"capabilities": ["codex.ephemeral_submit", "codex.session_display", "codex.item_offsets", "codex.native", "agent.live"]
"capabilities": ["codex.ephemeral_submit", "codex.session_display", "codex.item_offsets", "codex.native", "codex.session_context", "agent.live"]
})
);
}
Expand All @@ -770,6 +792,21 @@ mod tests {
assert_eq!(data["data"]["turn_id"], "t");
}

#[test]
fn ephemeral_native_context_is_allowlisted_and_old_requests_remain_compatible() {
let page: CodexTranscriptPage = serde_json::from_value(serde_json::json!({"session_id":"s","turns":[],"context":{"model":"gpt-5.4","sandbox":"workspace-write","cwd":"PRIVATE","writableRoots":["PRIVATE"],"api_key":"PRIVATE"}})).unwrap();
let value = serde_json::to_value(page).unwrap();
assert_eq!(value["context"]["model"], "gpt-5.4");
assert!(!value.to_string().contains("PRIVATE"));
let payload = public_event_payload("codex.session.updated", &value);
assert!(payload.get("context").is_none());
let legacy: CreateCodexSessionRequest = serde_json::from_value(
serde_json::json!({"agent_id":"a","project_id":"p","mode":"inspect"}),
)
.unwrap();
assert!(!legacy.inherit_permissions);
}

#[test]
fn command_fixture_is_stable() {
let command: AgentCommand =
Expand Down
Loading
Loading