Author SHA1 Message Date
3d58bfc8af release: v1.0.0-rc1 — feature freeze for stable release
* fix: orphan agents now grouped under main in agent tree

Agents without parentAgentId, own bot, or binding-based inference
(e.g. dynamically created sub-agents like codex-worker) were
incorrectly displayed as top-level roots. They now default to
main's subtree.

Bump version to 0.9.4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* cleanup: drop archived workflow skill defaults

* release: v1.0.0-rc1 — feature freeze for stable release

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dtzp555 <dtzp555@gmail.com>
2026-04-05 07:51:39 +10:00
d3fdbcef6a fix: improve agent tree ordering and parent inference (#15)
Root agents with their own bot (hasOwnBot) now sort to the top of the
agent tree alongside main. Agents listed in a parent's
subagents.allowAgents are automatically inferred as children even
without an explicit parentAgentId or Telegram binding.

Co-authored-by: Tao <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:33:26 +10:00
16a080e0b2 fix: add English i18n keys for Skills & Tools picker (#14)
The Skills & Tools picker added in v0.9.4 only had Chinese i18n
strings, so the panel showed Chinese text even in English mode.
Add English translations for wiz.skillsTools, wiz.skillsHint,
wiz.skillsSection, wiz.toolsSection, and the quick-action buttons.

Co-authored-by: Tao <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:21:24 +10:00
69258d8428 feat: auto-load default Skills & Tools for new agents (#12) (#13)
Add a Skills & Tools picker to the Agent/Sub-Agent creation forms so
new agents automatically get useful defaults (memory-continuity,
agent-workflow, execution-agent-dispatch, session-logs + fs/runtime/
memory/sessions_spawn/subagents tool groups).  Users can expand the
collapsible panel to customize, or leave it closed to accept defaults.

- Frontend: checkbox grid with quick-action buttons (Default/All/None)
- Backend: all 4 creation endpoints accept and persist skills/toolGroups
- Bump version to v0.9.4; update DEVLOG, CHANGELOG, README, guides

Co-authored-by: Tao <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:17:01 +10:00
a371ba6f7c docs: add related projects links (#12)
Co-authored-by: dtzp555 <dtzp555@gmail.com>
2026-03-08 16:09:47 +10:00
8ae5fdb20c bootstrap: create CURRENT_STATE for new agents (#11)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-08 11:30:36 +10:00
c3ed4c9c8f ui: add CLI copy helper + bump version to 0.9.2 (#10)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-08 10:20:34 +10:00
0a5f963b14 ui: add Dashboard first-run checklist card (#9)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-08 07:52:03 +10:00
9e3ec7b616 docs: switch README screenshots to hero layout A (#8)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-07 20:32:31 +10:00
55bfdcdbc8 ui: make CLI completion messages English (#7)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-07 20:29:28 +10:00
c6da627fed release: bump version to v0.9.1 (#6)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-07 20:19:00 +10:00
743b8acea1 docs: improve README positioning and refresh hero screenshots (#5)
* docs: rewrite README around first-run value and support positioning

* docs: add screenshot refresh brief and trim README repetition

* docs: refresh README hero screenshots

---------

Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-07 20:17:49 +10:00
e0ebe68fb5 docs/ux: startup polish and channel support follow-ups (#4)
* Fix localized startup status text

* Improve startup UX and docs positioning

* Clean up guide section numbering

* Docs: merge remaining channel support notes

---------

Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-06 21:10:12 +10:00
1c894d4ee7 v0.9.0: Discord support in Agents wizard + UI/guide updates (#3)
* feat: Agents wizard for Telegram/Discord + OCM header version

* fix: main-html-script syntax (tree toggle)

* ui: centered version badge + update Telegram/Discord setup guides

* ui: center prominent version badge + update Telegram/Discord setup guides

* fix: center OCM-only version badge + include ocmVersion in status + discord guide i18n

* fix: escape discord guide apostrophe in MAIN_HTML_SCRIPT

* release: v0.9.0

* fix: include ocmVersion in /api/status

* fix: add missing wiz.* i18n keys for Agents wizard

* fix: add wiz.* i18n keys to MAIN_HTML_SCRIPT

* docs: add Discord workflow section + new redacted screenshots (draft)

* docs: replace discord screenshots with English/redacted versions

* docs: replace discord add-subagent screenshot (English + redacted)

---------

Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-01 21:57:01 +10:00
11 changed files with 513 additions and 96 deletions
+27 -2
View File
@@ -1,10 +1,35 @@
# OpenClaw Manager — 开发日志
> 最后更新:2026-03-01
> 当前版本:v0.9.0
> 最后更新:2026-03-17
> 当前版本:v0.9.4
---
## v0.9.4 更新日志(2026-03-17
### New Features
- **Skills & Tools 自动配置**:创建 Agent / Sub-Agent 时自动加载默认 Skills 和 Tool Groups
- 默认 Skills`memory-continuity`, `agent-workflow`, `execution-agent-dispatch`, `session-logs`
- 默认 Tool Groups`group:fs`, `group:runtime`, `group:memory`, `sessions_spawn`, `subagents`
- 创建表单新增可折叠的 Skills & Tools 选择面板,支持 checkbox 多选
- 提供「仅默认」「全选」「清空」快捷按钮
- 不展开 / 不修改则自动使用默认预设,开箱即用
- 支持的可选 Skillsbrowser-use, github, gh-issues, coding-agent, execution-agent-planner, discord, weather, summarize, healthcheck
- 四个创建端点(Telegram Agent/Sub、Discord Agent/Sub)均支持 `skills` + `toolGroups` 字段
### Backend
- 新增 `applySkillsTools()` 服务端辅助函数,统一处理 skills/tools 的默认值回退逻辑
- `POST /api/agents/bot``POST /api/agents/discord``POST /api/agents/discord-sub``POST /api/agents` 均已适配
### UI
- Skills & Tools Picker:深色风格 checkbox 网格,嵌入创建表单底部
- 新增专用 CSS 样式(`.skills-picker``.sp-grid``.sp-item``.sp-actions`
- 新增 i18n 字符串支持中英文标签
---
## v0.9.0 更新日志(2026-03-01
+186 -69
View File
@@ -1,50 +1,74 @@
# OpenClaw Manager (OCM)
A zero-dependency local control panel for [OpenClaw](https://github.com/anthropics/openclaw). Manage agents, bindings, logs, commands, and rollback from your browser — no `npm install` required.
A local control panel for [OpenClaw](https://github.com/anthropics/openclaw) that makes setup checks, agent management, health inspection, and recovery much easier — without living in JSON and terminal tabs all day.
[中文说明](#中文说明) · [中文使用说明(含截图)](docs/USAGE_GUIDE.zh-CN.md) · [English Guide (with screenshots)](docs/USAGE_GUIDE.en.md)
---
## Quick Start
## Why OCM exists
```bash
# Clone
git clone https://github.com/dtzp555-max/ocm.git
cd ocm
Running OpenClaw directly is powerful, but the day-to-day workflow can get messy fast:
# Run (no npm install needed)
bash start.sh # macOS / Linux
start.bat # Windows
- `openclaw.json` is easy to break by hand
- agent / bot / channel bindings are hard to visualize
- health checks, logs, restarts, and rollback are scattered across terminal commands
- when something goes wrong, recovery is slower than it should be
# Update
git pull --ff-only
```
OCM gives you a **local control panel** for the things that become painful first:
- seeing your current setup clearly
- operating it more safely
- recovering faster when something breaks
Open [http://localhost:3333](http://localhost:3333) in your browser. For remote access, use `bash start.sh --host 0.0.0.0`.
## Docs
## Is OCM for you?
- English guide: docs/USAGE_GUIDE.en.md
- 中文使用说明: docs/USAGE_GUIDE.zh-CN.md
- FAQ: docs/FAQ.md
- Security notes: docs/SECURITY.md
- Changelog: docs/CHANGELOG.md
**OCM is a good fit if:**
- you already run OpenClaw locally
- you want a visual control panel for agents, routing, logs, health, and backups
- you are tired of editing nested config by hand
- you want safer rollback / recovery nearby
**OCM is probably not the best starting point if:**
- you have not installed OpenClaw yet
- you expect every OS / channel workflow to be equally mature today
- you want a hosted cloud dashboard instead of a local tool
## Why OCM
## What OCM helps you do
OCM is best thought of as an **OpenClaw control panel**, not just a config editor.
### 1) See your OpenClaw setup clearly
- inspect agents and sub-agents in a tree
- verify bindings and routing
- view models, auth, usage, and health in one place
It helps with three things that become painful fast in raw JSON / terminal workflows:
### 2) Operate OpenClaw with less risk
- restart gateway
- inspect logs
- run OpenClaw commands from the built-in terminal
- make config changes with rollback nearby
- **Agent topology** — see which agent is bound to which chat/channel/thread
- **Everyday operations** — run CLI commands, inspect health/logs, restart gateway
- **Safer changes** — update config with backups and rollback nearby
### 3) Recover faster when things go wrong
- browse backups / snapshots
- roll back config safely
- keep troubleshooting and operational tools close to the same UI
## Features
## See it quickly
These are the highest-value surfaces for first-time users:
<p>
<img src="docs/redacted-screenshots-refresh/dashboard-2026-03-07.jpg" width="860" />
</p>
<p>
<img src="docs/redacted-screenshots-refresh/agents-2026-03-07.jpg" width="280" />
<img src="docs/redacted-screenshots-refresh/cli-2026-03-07.jpg" width="280" />
<img src="docs/redacted-screenshots/actions.jpg" width="180" />
</p>
More screenshots: see the [full usage guide](docs/USAGE_GUIDE.en.md) or the gallery below.
<details>
<summary><b>Screenshots</b> (redacted: no personal paths, no Telegram IDs)</summary>
<summary><b>Full screenshots gallery</b> (redacted: no personal paths, no Telegram IDs)</summary>
<p>
<img src="docs/redacted-screenshots/dashboard.jpg" width="240" />
@@ -62,7 +86,77 @@ It helps with three things that become painful fast in raw JSON / terminal workf
</details>
- **Agent Management** — Add main agents and sub-agents through a guided setup flow. View all agents in a tree structure with model selection, workspace browsing, and inline configuration.
## Quick Start (recommended path)
```bash
# Clone
git clone https://github.com/dtzp555-max/ocm.git
cd ocm
# Run (no npm install needed)
bash start.sh # macOS / Linux
start.bat # Windows
```
Then open [http://localhost:3333](http://localhost:3333).
For remote access, use:
```bash
bash start.sh --host 0.0.0.0
```
## First-run checklist
For a good first experience, do these first:
1. Confirm OCM found the correct OpenClaw directory
2. Open **Dashboard** and check gateway health
3. Open **Agents** / **Channels** and inspect bindings
4. Open the built-in **Terminal** and run one OpenClaw command
5. Check **Actions** / backup / rollback so recovery is nearby before you need it
## Current support status
**Best supported today**
- macOS / Linux
- local OpenClaw installations
- local health / logs / ops / backup workflows
- Telegram-first agent management workflows
**Supported, but not equally smooth in every case**
- Discord agent / thread workflows
- more advanced routing setups
- mixed environment / service layouts
**Not the primary path today**
- fully beginner-first OpenClaw onboarding
- every OS / shell / service combination
- WhatsApp-heavy sub-agent topology workflows
## Common friction points
The most common reasons OCM feels rough are not usually “the UI is broken”, but environment mismatch:
- OpenClaw itself is not fully healthy yet
- wrong OpenClaw directory
- path / permission issues
- different service/runtime layouts across machines
- channel workflow differences
That is why OCM should be thought of as a **control panel for an existing OpenClaw setup**, not a magic replacement for all installation complexity.
## Docs
- **Start here**: docs/USAGE_GUIDE.en.md
- 中文使用说明: docs/USAGE_GUIDE.zh-CN.md
- FAQ: docs/FAQ.md
- Security notes: docs/SECURITY.md
- Changelog: docs/CHANGELOG.md
## Features by area
- **Agent Management** — Add main agents and sub-agents through a guided setup flow. View all agents in a tree structure with model selection, workspace browsing, and inline configuration. New agents automatically get default Skills and Tool Groups, or you can customize them via the built-in Skills & Tools picker.
- **Usage Statistics** — Real token usage data parsed directly from OpenClaw session files. Breakdown by model, agent, and day with a visual chart.
- **Model & Auth** — Configure models from your registered provider list. Follow built-in guides for provider authentication setup.
- **Built-in CLI** — Available from any page (top-right **Terminal** + bottom dock). Run OpenClaw commands with **real-time streaming output**, **Tab completion**, built-in presets, and your own saved favorites.
@@ -72,11 +166,13 @@ It helps with three things that become painful fast in raw JSON / terminal workf
## What's New (v0.9.x)
- **Skills & Tools auto-config**: new agents/sub-agents automatically get default Skills (`memory-continuity`, `session-logs`) and Tool Groups (`fs`, `runtime`, `memory`, `sessions_spawn`, `subagents`). A collapsible Skills & Tools picker in the creation form lets you customize per agent.
- **Discord support (v0.9.x)**: add agent/sub-agent flows for Discord (main agent binds channel; sub-agent binds thread).
- **README screenshots gallery**: all screenshots are now grouped into one collapsible section to keep the homepage short.
- **README screenshots gallery**: screenshots grouped into a collapsible section so the homepage stays shorter.
- **Built-in CLI terminal**: run OpenClaw commands from any page with streaming output, presets, favorites, and Tab completion.
- **Ops actions**: restart gateway, view logs, health check, backups (local + NAS), and cron management.
- **Telegram-first workflow**: safer sub-agent setup flow + allowlist helper + warnings for group privacy.
## Channel Support
OCM is a local UI for OpenClaw. Core features like agent trees, routing, models/auth, ops, backups, and the built-in CLI are channel-agnostic.
@@ -184,6 +280,12 @@ ocm/
- **Frontend**: Vanilla HTML/CSS/JS embedded in a template literal — no build step, no framework
- **Architecture**: Single-file, zero-dependency, runs anywhere Node.js runs
## Related Projects
- [clawkeeper](https://github.com/dtzp555-max/clawkeeper) — OpenClaw memory operations kit for doctor / provider switch / backup / restore workflows. *(archived)*
- [execution-agent-planner](https://github.com/dtzp555-max/execution-agent-planner) — archived planning skill for deciding when work should stay with one execution agent or split across specialized workers.
- [gh-pr-release-flow](https://github.com/dtzp555-max/gh-pr-release-flow) — skill for PR-first GitHub repos where release work should happen after merge.
## License
MIT
@@ -192,7 +294,38 @@ MIT
## 中文说明
OpenClaw Manager 是一个零依赖的本地 Web 管理界面,用于可视化管理 [OpenClaw](https://github.com/anthropics/openclaw) AI 智能体。所有代码在一个 `.js` 文件中,只需 Node.js 18+,不需要 `npm install`
OpenClaw ManagerOCM)是一个本地 OpenClaw 控制面板,用来更直观地管理 Agent、查看绑定关系、做健康检查、运行内置 CLI,并在改配置或排错时少踩坑
### OCM 解决什么问题
直接使用 OpenClaw 很强,但日常维护很容易变成:
-`openclaw.json` 容易手滑
- Agent / bot / 群 / 线程绑定关系很难一眼看清
- 健康检查、日志、重启、回滚散落在多个终端命令里
- 出问题时恢复链路不够顺手
OCM 的定位不是替代 OpenClaw,而是给现有 OpenClaw 环境加一个**本地控制台**。
### 适合谁
**适合:**
- 已经在本机跑通 OpenClaw 的用户
- 想更直观地管理 Agent / Routing / Health / Logs / Backups 的用户
- 不想频繁手改复杂 JSON 的用户
**暂时不太适合:**
- 还没装好 OpenClaw、希望 OCM 包办全部安装复杂度的新手
- 默认期待所有平台 / 所有 channel 路线都同样成熟的用户
- 想要云端托管产品而不是本地工具的用户
### OCM 最有价值的三件事
1. **看清你的 OpenClaw 结构**
- Agent 树、绑定关系、模型、认证、健康状态更直观
2. **更安全地做运维操作**
- 重启 gateway、看日志、跑 CLI、改配置、回滚更顺手
3. **出问题时恢复更快**
- 备份、回滚、排查入口都更近
### 快速开始
@@ -203,49 +336,33 @@ bash start.sh # macOS / Linux
start.bat # Windows
```
访问 [http://localhost:3333](http://localhost:3333)。远程访问请使用 `bash start.sh --host 0.0.0.0`
打开 [http://localhost:3333](http://localhost:3333)。
### 功能
### 第一次使用建议先做这几步
- **Agent 管理** — 添加主 Agent 和子 Agent,树状结构查看,内联切换模型,浏览工作区文件
- **使用统计** — 从 OpenClaw 会话文件解析真实 Token 用量,按模型/Agent/日期维度展示
- **模型与认证** — 管理注册模型列表,内置 Provider 认证引导
- **内置终端** — 实时流式输出,预设命令,收藏夹,Tab 补全
- **运维面板** — 重启网关、查看日志、健康检查、本地/NAS 备份、Cron 任务管理
- **双语界面** — 中英文一键切换
1. 确认 OCM 找到了正确的 OpenClaw 目录
2. 打开 **Dashboard** 看 gateway 是否健康
3. 打开 **Agents / Channels** 看绑定是否正确
4. 打开内置 **Terminal** 跑一条 OpenClaw 命令
5. 看一眼 **Actions / 备份 / 回滚**,确保恢复路径就在手边
### Telegram 场景与安全提示
### 当前支持定位
OCM 主要面向 **Telegram 场景**
**目前最好用的场景**
- macOS / Linux
- 本地 OpenClaw 环境
- 本地 health / logs / ops / backup 工作流
- Telegram-first 的 Agent 管理方式
- 通过群组绑定主 Agent,并在每条树下管理多个 Sub-Agent
- 让每个 Agent 拥有独立 `workspace``SOUL.md``MEMORY.md`
- 通过可视化方式更新 `openclaw.json`,减少手动改配置风险
- 建议使用者已具备基础 OpenClaw CLI 操作经验(如 `onboard`、认证、查看网关日志)
**支持,但不保证同样顺滑:**
- Discord workflow
- 更复杂的 routing / service 布局
- 混合环境
关键安全设置(务必确认):
### 文档
- BotFather 里 **Allow Groups = ON**
- BotFather 里 **Group Privacy = OFF**
- 每个 Agent 群组只保留“你自己 + 对应 Agent/Sub-Agent”
- 不要邀请其他人进组(会带来安全和 API 费用风险)
### 配置
启动脚本自动检测 `~/.openclaw` 目录。如需指定其他路径:
```bash
bash start.sh --dir /path/to/.openclaw --port 8080
```
也可手动创建 `manager-config.json`
```json
{ "dir": "~/.openclaw" }
```
## Discord (thread-first) notes
- Recommended: keep top-level agents in dedicated channels, and bind sub-agents to **threads** (one thread per task).
- To switch a Telegram group / Discord thread binding: go to **Routing** → remove the old binding → add a new binding with the new ID/link.
- 英文使用说明:docs/USAGE_GUIDE.en.md
- 中文使用说明:docs/USAGE_GUIDE.zh-CN.md
- FAQdocs/FAQ.md
- 安全说明:docs/SECURITY.md
- 更新记录:docs/CHANGELOG.md
+5
View File
@@ -2,6 +2,11 @@
This is a user-facing summary derived from `DEVLOG.md`.
## v0.9.4
- **Skills & Tools auto-config**: New agents/sub-agents now automatically get default Skills (memory-continuity, agent-workflow, execution-agent-dispatch, session-logs) and Tool Groups (fs, runtime, memory, sessions_spawn, subagents).
- Agent creation forms include a collapsible Skills & Tools picker with checkbox multi-select and quick-action buttons (Default / All / None).
- All 4 creation endpoints (Telegram/Discord × Agent/Sub-Agent) support `skills` and `toolGroups` fields.
## v0.9.0
- Agents: Telegram + Discord support in Add Agent / Add Sub-Agent wizard (main agent binds channel; sub-agent binds thread).
- UI: top-center OCM version badge; improved Telegram/Discord setup guides.
+79
View File
@@ -0,0 +1,79 @@
# OCM Screenshot Refresh Brief
Goal: refresh only the highest-value README screenshots first, not every screenshot.
## Redaction rules
Always hide or blur:
- Telegram / Discord IDs
- bindings / peer IDs / thread IDs
- local filesystem paths containing Tao's name
- account labels if they expose personal identifiers
- anything that looks like a secret/token
## Priority 1 screenshots
### 1) Dashboard hero
Purpose:
- show OCM as a real control panel
- support the README first-run / health story
Wanted state:
- Dashboard page open
- gateway looks healthy if possible
- cards readable
- top bar clean
- no obvious warning/noise unless it helps credibility
Frame guidance:
- browser window wide enough to show full dashboard naturally
- keep top navigation visible
- avoid cropping too tightly
### 2) Agents page
Purpose:
- show the core product value: visible agent topology / structure
Wanted state:
- main agent + several sub-agents visible
- structure easy to understand at a glance
- avoid visual clutter where possible
- keep model selectors/buttons visible, but not the focus
Frame guidance:
- enough width to show the tree clearly
- IDs and personal paths redacted
### 3) CLI / Terminal page
Purpose:
- show that OCM is not only a viewer; it can operate OpenClaw too
Wanted state:
- built-in terminal open
- a real command visible (recommended: `openclaw status` or `openclaw doctor`)
- real output visible
- do not use tutorial arrows / annotation bubbles
Frame guidance:
- include command input and some output
- avoid empty terminal state
- redact paths / personal names
## Priority 2 screenshots
- Actions menu
- Backup / rollback
- Channels
- Models
- Auth
- Cron
- Stats
## Selected refresh set (2026-03-07)
- Dashboard: `docs/redacted-screenshots-refresh/dashboard-2026-03-07.jpg`
- Agents: `docs/redacted-screenshots-refresh/agents-2026-03-07.jpg`
- CLI: `docs/redacted-screenshots-refresh/cli-2026-03-07.jpg`
- Keep using existing `docs/redacted-screenshots/actions.jpg` for now
## Notes
- Existing `actions.jpg` is already strong and can stay for now.
- The new Dashboard and Agents captures are preferred for README hero/overview use.
- The new CLI capture is preferred because it shows a real command and real output.
+3 -1
View File
@@ -138,8 +138,9 @@ Click `+ Add Agent` and fill:
- workspace path (recommended: separate folder per agent)
- model (dropdown is sourced from `openclaw models list`)
- the BotFather token
- **Skills & Tools** (optional): expand the collapsible picker at the bottom to customize. By default, new agents get: `memory-continuity`, `session-logs` as Skills, and `group:fs`, `group:runtime`, `group:memory`, `sessions_spawn`, `subagents` as Tool Groups. Use the quick-action buttons (Default / All / None) to adjust.
OCM writes the agent + Telegram account/binding into `openclaw.json`.
OCM writes the agent + Telegram account/binding + skills + tool groups into `openclaw.json`.
---
@@ -176,6 +177,7 @@ Send a message in the new group. In logs you should see something like:
- paste the Group ID
- set workspace (recommended: its own folder)
- choose model
- **Skills & Tools** (optional): same picker as Add Agent — defaults are pre-selected, expand to customize
**Step 4 — Allowlist (optional but recommended)**
+3 -1
View File
@@ -123,8 +123,9 @@ bash start.sh
- workspace 路径(建议独立目录)
- 选择模型(下拉来自 `openclaw models list`,更不容易选错)
- 填入刚拿到的 bot token
- **Skills & Tools**(可选):展开表单底部的折叠面板可自定义。默认预设:Skills 包含 `memory-continuity``session-logs`Tool Groups 包含 `group:fs``group:runtime``group:memory``sessions_spawn``subagents`。提供「仅默认 / 全选 / 清空」快捷按钮。
保存后,OCM 会把这颗主 Agent 写进 `openclaw.json`,并把 bot token 绑定到对应 `accountId`
保存后,OCM 会把主 Agent + skills + tools 写进 `openclaw.json`,并把 bot token 绑定到对应 `accountId`
---
@@ -159,6 +160,7 @@ openclaw gateway logs --follow
- 填 Group ID
- workspace(建议单独目录)
- 选择模型
- **Skills & Tools**(可选):和 Add Agent 相同的选择器,默认预设已勾选,展开可自定义
#### Step 4:写 allowlist(推荐)
如果你启用了 Telegram 的 allowlist(例如 `channels.telegram.allowFrom`),
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+209 -22
View File
@@ -24,7 +24,7 @@ const SCRIPT_DIR = __dirname;
const MANAGER_CONFIG = path.join(SCRIPT_DIR, 'manager-config.json');
let PORT = 3333;
let HOST = '0.0.0.0';
const APP_VERSION = '0.9.0';
const APP_VERSION = '1.0.0-rc1';
// --port 参数
const portIdx = process.argv.indexOf('--port');
if (portIdx !== -1 && process.argv[portIdx + 1]) PORT = parseInt(process.argv[portIdx + 1]) || 3333;
@@ -63,6 +63,17 @@ function refreshPaths() {
CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json');
}
// ── Default Skills & Tool Groups for new agents ─────────────
const DEFAULT_SKILLS = ['memory-continuity', 'session-logs'];
const DEFAULT_TOOL_GROUPS = ['group:fs', 'group:runtime', 'group:memory', 'sessions_spawn', 'subagents'];
function applySkillsTools(agentEntry, skills, toolGroups) {
const s = (Array.isArray(skills) && skills.length > 0) ? skills : DEFAULT_SKILLS;
const tg = (Array.isArray(toolGroups) && toolGroups.length > 0) ? toolGroups : DEFAULT_TOOL_GROUPS;
agentEntry.skills = s;
agentEntry.tools = { alsoAllow: tg };
}
// ── 已知模型列表 ──────────────────────────────────────────────
const KNOWN_MODELS = [
{ id: '__default__', label: '使用全局默认模型' },
@@ -315,6 +326,28 @@ ${hasMemory ? initialMemory : '> 暂无初始记录。记忆将通过日常对
`;
}
function generateCurrentStateMd() {
return `# CURRENT_STATE
_Last updated: TBD Australia/Brisbane_
## In Flight
- none
## Blocked / Waiting
- none
## Recently Finished
- none
## Next
- none
## Reset Summary
- No active summary yet.
`;
}
// ── 请求解析 ──────────────────────────────────────────────────
function parseBody(req) {
return new Promise((resolve, reject) => {
@@ -395,6 +428,12 @@ async function handleApi(req, res, urlObj, body) {
);
// For main agent without explicit binding, infer its accountId from first unclaimed telegram account
const mainInferredAccountId = telegramAccounts.find(a => !claimedAccounts.has(a)) || telegramAccounts[0] || null;
// Build map: agentId -> parentAgentId inferred from subagents.allowAgents
const allowAgentsParentMap = {};
list.forEach(parent => {
const allowed = parent.subagents?.allowAgents || [];
allowed.forEach(childId => { if (!allowAgentsParentMap[childId]) allowAgentsParentMap[childId] = parent.id; });
});
const enriched = list.map(a => {
const binding = bindings.find(b => b.agentId === a.id && b.match?.peer?.kind === 'group');
const groupId = binding?.match?.peer?.id || null;
@@ -424,7 +463,9 @@ async function handleApi(req, res, urlObj, body) {
// Workspace: explicit per-agent, or defaults.workspace for main
const workspace = a.workspace || (isMain ? defaultWorkspace : null);
return { ...a, workspace, groupId, requireMention: groupId ? (groups[groupId]?.requireMention ?? true) : null,
effectiveModel: modelVal || defaults.model?.primary || '默认', hasOwnBot, accountId, parentAccountId, parentAgentId: a.parentAgentId || null, bindings: (bindings||[]).filter(b=>b.agentId===a.id).map(b=>({
effectiveModel: modelVal || defaults.model?.primary || '默认', hasOwnBot, accountId, parentAccountId,
parentAgentId: a.parentAgentId || (!hasOwnBot ? allowAgentsParentMap[a.id] : null) || null,
bindings: (bindings||[]).filter(b=>b.agentId===a.id).map(b=>({
idx: bindings.indexOf(b),
channel: b.match?.channel || '',
accountId: b.match?.accountId || '',
@@ -439,7 +480,7 @@ async function handleApi(req, res, urlObj, body) {
// POST /api/agents/bot — create agent with its own bot token
if (method === 'POST' && pathname === '/api/agents/bot') {
const { botToken, agentId, name, model, workspace, purpose, personality } = body;
const { botToken, agentId, name, model, workspace, purpose, personality, skills, toolGroups } = body;
if (!botToken || !botToken.trim()) {
res.writeHead(400); res.end(JSON.stringify({ error: 'Bot Token is required' })); return;
}
@@ -485,6 +526,7 @@ async function handleApi(req, res, urlObj, body) {
const wsAlias = `~/.openclaw/workspaces/${workspace}`;
const agentEntry = { id: agentId, name: name || agentId, workspace: wsAlias };
if (model && model !== '__default__') agentEntry.model = { primary: model };
applySkillsTools(agentEntry, skills, toolGroups);
cfg.agents.list.push(agentEntry);
// Add binding
if (!cfg.bindings) cfg.bindings = [];
@@ -497,6 +539,7 @@ async function handleApi(req, res, urlObj, body) {
const agentName = name || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(agentName, purpose || '', personality || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(agentName, ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
// Create agents/<id>/ runtime directory (required by OpenClaw gateway)
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -506,7 +549,7 @@ async function handleApi(req, res, urlObj, body) {
ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [
'Agent created with its own bot token',
'Workspace directory created with SOUL.md and MEMORY.md',
'Workspace directory created with SOUL.md, MEMORY.md, and memory/CURRENT_STATE.md',
'Runtime directory created at agents/' + agentId + '/',
'Configuration updated and backed up',
'Restart gateway to load new bot: openclaw gateway restart'
@@ -517,7 +560,7 @@ async function handleApi(req, res, urlObj, body) {
// POST /api/agents/discord — create top-level Discord agent (single bot, channel binding)
if (method === 'POST' && pathname === '/api/agents/discord') {
const { agentId, name, workspaceFolder, model, purpose, personality, guildId, channelId } = body;
const { agentId, name, workspaceFolder, model, purpose, personality, guildId, channelId, skills, toolGroups } = body;
if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) {
res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return;
}
@@ -543,6 +586,7 @@ async function handleApi(req, res, urlObj, body) {
const wsPath = path.join(OPENCLAW_DIR, 'workspaces', folder);
const agentEntry = { id: agentId, name: name || agentId, workspace: wsAlias };
if (model && model !== '__default__') agentEntry.model = { primary: model };
applySkillsTools(agentEntry, skills, toolGroups);
cfg.agents.list.push(agentEntry);
if (!cfg.bindings) cfg.bindings = [];
@@ -565,6 +609,7 @@ async function handleApi(req, res, urlObj, body) {
await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true });
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name || agentId, purpose || '', personality || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name || agentId, ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -575,7 +620,7 @@ async function handleApi(req, res, urlObj, body) {
notes: [
'Discord agent created (single Discord bot)',
'Channel binding added',
'Workspace + runtime directories created',
'Workspace + runtime directories created (including memory/CURRENT_STATE.md)',
'Configuration updated and backed up',
'Restart gateway to apply: openclaw gateway restart'
]
@@ -585,7 +630,7 @@ async function handleApi(req, res, urlObj, body) {
// POST /api/agents/discord-sub — create Discord sub-agent (thread-only binding, grouping under parentAgentId)
if (method === 'POST' && pathname === '/api/agents/discord-sub') {
const { agentId, displayName, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, guildId, threadId } = body;
const { agentId, displayName, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, guildId, threadId, skills, toolGroups } = body;
if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) {
res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return;
}
@@ -611,6 +656,7 @@ async function handleApi(req, res, urlObj, body) {
const wsPath = path.join(OPENCLAW_DIR, 'workspaces', folder);
const agentEntry = { id: agentId, name: displayName || agentId, workspace: wsAlias, parentAgentId: String(parentAgentId).trim() };
if (model && model !== '__default__') agentEntry.model = { primary: model };
applySkillsTools(agentEntry, skills, toolGroups);
cfg.agents.list.push(agentEntry);
if (!cfg.bindings) cfg.bindings = [];
@@ -634,6 +680,7 @@ async function handleApi(req, res, urlObj, body) {
const name = displayName || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose || '', personality || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -643,7 +690,7 @@ async function handleApi(req, res, urlObj, body) {
res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [
'Discord sub-agent created (thread-only binding)',
'Workspace + runtime directories created',
'Workspace + runtime directories created (including memory/CURRENT_STATE.md)',
'Configuration updated and backed up',
'Restart gateway to apply: openclaw gateway restart'
]
@@ -654,7 +701,7 @@ async function handleApi(req, res, urlObj, body) {
// POST /api/agents — create sub-agent (shares parent bot)
if (method === 'POST' && pathname === '/api/agents') {
const { agentId, displayName, groupId, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, telegramUserId } = body;
const { agentId, displayName, groupId, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, telegramUserId, skills, toolGroups } = body;
if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) {
res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return;
}
@@ -683,6 +730,7 @@ async function handleApi(req, res, urlObj, body) {
const wsAlias= `~/.openclaw/workspaces/${folder}`;
const agentEntry = { id: agentId, name: displayName || agentId, workspace: wsAlias, parentAgentId: String(parentAgentId).trim() };
if (model && model !== '__default__') agentEntry.model = { primary: model };
applySkillsTools(agentEntry, skills, toolGroups);
cfg.agents.list.push(agentEntry);
// Binding uses parent's accountId
const parentAccountId = parentBinding.match.accountId;
@@ -707,6 +755,7 @@ async function handleApi(req, res, urlObj, body) {
const name = displayName || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose, personality), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
// Create agents/<id>/ runtime directory (required by OpenClaw gateway)
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -715,7 +764,7 @@ async function handleApi(req, res, urlObj, body) {
res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [
'Sub-agent created and shares parent bot',
'Workspace and runtime directories created',
'Workspace and runtime directories created (including memory/CURRENT_STATE.md)',
'Configuration updated and backed up',
'Restart gateway to apply: openclaw gateway restart'
],
@@ -1700,6 +1749,19 @@ header { background:var(--surface); border-bottom:1px solid var(--border); paddi
.lang-toggle { background:var(--border); border:none; color:var(--muted); border-radius:6px; padding:5px 10px; font-size:12px; cursor:pointer; }
.lang-toggle:hover { background:#3a3f5c; color:var(--text); }
/* skills/tools picker */
.skills-picker { background:var(--bg); border:1px solid var(--border); border-radius:10px; padding:12px 14px; margin-top:4px; }
.skills-picker summary { font-size:13px; font-weight:600; cursor:pointer; color:var(--text); user-select:none; }
.skills-picker summary:hover { color:var(--accent); }
.skills-picker .sp-grid { display:grid; grid-template-columns:1fr 1fr; gap:4px 16px; margin-top:8px; }
.skills-picker label.sp-item { display:flex; align-items:center; gap:6px; font-size:12.5px; color:var(--muted); cursor:pointer; padding:3px 0; }
.skills-picker label.sp-item:hover { color:var(--text); }
.skills-picker label.sp-item input[type=checkbox] { accent-color:var(--accent); width:14px; height:14px; }
.skills-picker .sp-actions { display:flex; gap:8px; margin-top:8px; padding-top:8px; border-top:1px solid var(--border); }
.skills-picker .sp-actions button { font-size:11px; padding:3px 10px; border-radius:6px; border:1px solid var(--border); background:var(--surface); color:var(--muted); cursor:pointer; }
.skills-picker .sp-actions button:hover { color:var(--text); border-color:var(--accent); }
.sp-section-title { font-size:11px; font-weight:700; color:var(--accent); text-transform:uppercase; letter-spacing:.5px; margin:8px 0 4px; grid-column:1/-1; }
/* centered version badge */
.top-version{ position:absolute; left:50%; transform:translateX(-50%); font-size:12px; font-weight:800; letter-spacing:.3px; color:var(--text); background:rgba(108,99,255,.18); border:1px solid rgba(108,99,255,.45); padding:4px 12px; border-radius:999px; box-shadow:0 6px 18px rgba(0,0,0,.25); }
.ver-old{ display:none; }
@@ -2030,6 +2092,16 @@ const MAIN_HTML_BODY = String.raw`
</div>
</div>
<div class="dash-gauges" id="dashGauges"><div class="empty">Loading...</div></div>
<div class="card dash-notice">
<h3 data-i18n="dash.firstRunTitle">🚀 First-run checklist</h3>
<ul class="dash-note-list">
<li data-i18n="dash.firstRun1">Confirm OCM is pointed at the correct OpenClaw directory.</li>
<li data-i18n="dash.firstRun2">Check this Dashboard first: make sure Gateway is running and the system looks healthy.</li>
<li data-i18n="dash.firstRun3">Open Agents / Routing and confirm the bindings you expect are actually there.</li>
<li data-i18n="dash.firstRun4">Use the built-in Terminal to run <code>openclaw doctor</code> once so you catch environment/auth issues early.</li>
<li data-i18n="dash.firstRun5">Before bigger edits, make a backup or check rollback so recovery is nearby if something goes wrong.</li>
</ul>
</div>
<div class="dash-sections">
<div class="card dash-card" id="dashSystem">
<h3>🖥 System Info</h3>
@@ -2175,6 +2247,7 @@ const MAIN_HTML_BODY = String.raw`
<select id="cliPreset" onchange="onCliPresetSelect()" style="font-size:11px;padding:3px 6px;max-width:180px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text)">
<option value="" data-i18n="cli.presets"> 常用命令 </option>
</select>
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="copyCliCommand()" data-i18n="cli.copy">复制命令</button>
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="clearCliOutput()" data-i18n="cli.clear">清空</button>
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="toggleCliPanel()" data-i18n="cli.collapse"> 收起</button>
</div>
@@ -2439,6 +2512,12 @@ const I18N = {
'models.onlyCliHint':'模型下拉仅显示 openclaw models list 返回的模型。',
'models.modelListErr':'读取 openclaw models list 失败:',
'models.modelListEmpty':'未从 openclaw models list 解析到模型。请先运行 openclaw onboard 并确认模型可用。',
'dash.firstRunTitle':'🚀 首次使用检查清单',
'dash.firstRun1':'先确认 OCM 指向的是正确的 OpenClaw 数据目录。',
'dash.firstRun2':'先看 Dashboard:确认 Gateway 正在运行,系统状态看起来正常。',
'dash.firstRun3':'打开 Agents / Routing,确认你期望的绑定确实已经存在。',
'dash.firstRun4':'用内置终端先运行一次 <code>openclaw doctor</code>,尽早发现环境或认证问题。',
'dash.firstRun5':'在做较大改动前,先做一次备份或确认回滚入口,出问题时更容易恢复。',
'dash.noticeTitle':'📌 Telegram 使用说明(重要)',
'dash.notice1':'OCM 主要面向 Telegram:通过群组绑定 Agent,让每个 Agent 拥有独立 Workspace / SOUL.md / MEMORY.md。',
'dash.notice2':'请确保你已有基本 OpenClaw 操作经验。OCM 主要负责可视化更新 openclaw.json,方便增删主 Agent 与 Sub-Agent,并支持多条 Agent 树。',
@@ -2521,6 +2600,9 @@ const I18N = {
'wiz.soul':'性格关键词','wiz.soulHint':'(逗号分隔,选填)','wiz.soulPh':'幽默、直接、有条理...',
'wiz.soulTip':'留空则使用默认成长型提示词(推荐)',
'wiz.memory':'初始记忆','wiz.memoryHint':'MEMORY.md,选填)','wiz.memoryPh':'例如:群组主要用中文交流。用户偏好简洁回复。',
'wiz.skillsTools':'Skills & Tools 配置','wiz.skillsHint':'不选择则使用默认预设(推荐)',
'wiz.skillsSection':'Skills(能力)','wiz.toolsSection':'Tool Groups(权限)',
'wiz.spSelectDefault':'仅默认','wiz.spSelectAll':'全选','wiz.spSelectNone':'清空',
'wiz.preview':'即将创建:','wiz.group':'群组','wiz.modelLabel':'模型','wiz.globalDefault':'全局默认',
'wiz.soulYes':'含性格关键词','wiz.soulDefault':'默认成长型提示词(推荐)',
'wiz.autoBackup':'自动备份当前 openclaw.json ✓',
@@ -2534,7 +2616,7 @@ const I18N = {
'ch.peerId':'Peer ID','ch.peerIdHint':'(群组 ID 或用户 ID','ch.peerIdTip':'留空则匹配所有对应类型的 Peer',
'ch.submit':'添加绑定',
'l.sub':'选择运行模式',
'cli.open':'⌨️ 终端','cli.title':'⌨️ CLI 终端','cli.clear':'清空','cli.collapse':'▼ 收起',
'cli.open':'⌨️ 终端','cli.title':'⌨️ CLI 终端','cli.copy':'复制命令','cli.clear':'清空','cli.collapse':'▼ 收起',
'cli.ready':'── 终端就绪,等待命令 ──','cli.cleared':'── 已清空 ──',
'cli.presets':'── 常用命令 ──','cli.builtins':'内置命令','cli.favs':'我的收藏',
'cli.run':'▶ 执行','cli.stop':'■ 停止','cli.star':'⭐','cli.manage':'管理',
@@ -2573,6 +2655,12 @@ const I18N = {
'models.onlyCliHint':'Model dropdowns only show IDs returned by openclaw models list.',
'models.modelListErr':'Failed to load openclaw models list: ',
'models.modelListEmpty':'No model IDs were parsed from openclaw models list. Run openclaw onboard first.',
'dash.firstRunTitle':'🚀 First-run checklist',
'dash.firstRun1':'Confirm OCM is pointed at the correct OpenClaw directory.',
'dash.firstRun2':'Check Dashboard first: make sure Gateway is running and the system looks healthy.',
'dash.firstRun3':'Open Agents / Routing and confirm the bindings you expect are actually there.',
'dash.firstRun4':'Use the built-in Terminal to run <code>openclaw doctor</code> once so you catch environment or auth issues early.',
'dash.firstRun5':'Before bigger edits, make a backup or check rollback so recovery is nearby if something goes wrong.',
'dash.noticeTitle':'📌 Telegram Usage Notes (Important)',
'dash.notice1':'OCM is primarily for Telegram workflows: bind agents to groups so each agent has isolated Workspace / SOUL.md / MEMORY.md.',
'dash.notice2':'Basic OpenClaw CLI experience is required. OCM focuses on visual openclaw.json updates for easier main-agent/sub-agent management and multiple agent trees.',
@@ -2656,6 +2744,9 @@ const I18N = {
'wiz.soul':'Personality Keywords','wiz.soulHint':'(comma-separated, optional)','wiz.soulPh':'humorous, direct, organized...',
'wiz.soulTip':'Leave blank for default growth prompt (recommended)',
'wiz.memory':'Initial Memory','wiz.memoryHint':'(MEMORY.md, optional)','wiz.memoryPh':'e.g. Group mainly uses English. Users prefer concise replies.',
'wiz.skillsTools':'Skills & Tools','wiz.skillsHint':'Leave as-is for defaults (recommended)',
'wiz.skillsSection':'Skills','wiz.toolsSection':'Tool Groups',
'wiz.spSelectDefault':'Default','wiz.spSelectAll':'All','wiz.spSelectNone':'None',
'wiz.preview':'About to create:','wiz.group':'Group','wiz.modelLabel':'Model','wiz.globalDefault':'Global Default',
'wiz.soulYes':'With personality keywords','wiz.soulDefault':'Default growth prompt (recommended)',
'wiz.autoBackup':'Auto-backup current openclaw.json ✓',
@@ -2669,7 +2760,7 @@ const I18N = {
'ch.peerId':'Peer ID','ch.peerIdHint':'(Group ID or User ID)','ch.peerIdTip':'Leave blank to match all peers of this type',
'ch.submit':'Add Binding',
'l.sub':'Select Mode',
'cli.open':'⌨️ Terminal','cli.title':'⌨️ CLI Terminal','cli.clear':'Clear','cli.collapse':'▼ Collapse',
'cli.open':'⌨️ Terminal','cli.title':'⌨️ CLI Terminal','cli.copy':'Copy cmd','cli.clear':'Clear','cli.collapse':'▼ Collapse',
'cli.ready':'── Terminal Ready ──','cli.cleared':'── Cleared ──',
'cli.presets':'── Presets ──','cli.builtins':'Built-in','cli.favs':'My Favorites',
'cli.run':'▶ Run','cli.stop':'■ Stop','cli.star':'⭐','cli.manage':'Manage',
@@ -2935,6 +3026,75 @@ function showAddForm(type) {
applyLang();
}
// ── Skills / Tools picker helper ──────────────────────────
const AVAILABLE_SKILLS = [
{ id:'memory-continuity', label:'Memory Continuity', isDefault:true },
{ id:'session-logs', label:'Session Logs', isDefault:true },
{ id:'browser-use', label:'Browser Use', isDefault:false },
{ id:'github', label:'GitHub', isDefault:false },
{ id:'gh-issues', label:'GitHub Issues', isDefault:false },
{ id:'coding-agent', label:'Coding Agent', isDefault:false },
{ id:'discord', label:'Discord', isDefault:false },
{ id:'weather', label:'Weather', isDefault:false },
{ id:'summarize', label:'Summarize', isDefault:false },
{ id:'healthcheck', label:'Healthcheck', isDefault:false },
];
const AVAILABLE_TOOL_GROUPS = [
{ id:'group:fs', label:'Filesystem (group:fs)', isDefault:true },
{ id:'group:runtime', label:'Runtime (group:runtime)', isDefault:true },
{ id:'group:memory', label:'Memory (group:memory)', isDefault:true },
{ id:'sessions_spawn', label:'Sessions Spawn', isDefault:true },
{ id:'subagents', label:'Subagents', isDefault:true },
];
function buildSkillsToolsPicker(prefix) {
let html = '<details class="skills-picker" id="'+prefix+'-sp-wrap"><summary>' + t('wiz.skillsTools') + ' <span style="color:var(--muted);font-weight:400;font-size:12px">' + t('wiz.skillsHint') + '</span></summary>';
html += '<div class="sp-section-title">' + t('wiz.skillsSection') + '</div>';
html += '<div class="sp-grid">';
AVAILABLE_SKILLS.forEach(s => {
html += '<label class="sp-item"><input type="checkbox" data-sp-type="skill" value="'+s.id+'" '+(s.isDefault?'checked':'')+'>'+s.label+'</label>';
});
html += '</div>';
html += '<div class="sp-section-title">' + t('wiz.toolsSection') + '</div>';
html += '<div class="sp-grid">';
AVAILABLE_TOOL_GROUPS.forEach(g => {
html += '<label class="sp-item"><input type="checkbox" data-sp-type="tool" value="'+g.id+'" '+(g.isDefault?'checked':'')+'>'+g.label+'</label>';
});
html += '</div>';
html += '<div class="sp-actions">';
html += '<button type="button" onclick="spAction(\\x27'+prefix+'\\x27,\\x27default\\x27)">' + t('wiz.spSelectDefault') + '</button>';
html += '<button type="button" onclick="spAction(\\x27'+prefix+'\\x27,\\x27all\\x27)">' + t('wiz.spSelectAll') + '</button>';
html += '<button type="button" onclick="spAction(\\x27'+prefix+'\\x27,\\x27none\\x27)">' + t('wiz.spSelectNone') + '</button>';
html += '</div></details>';
return html;
}
function spAction(prefix, action) {
const wrap = document.getElementById(prefix+'-sp-wrap');
if (!wrap) return;
const boxes = wrap.querySelectorAll('input[type=checkbox]');
if (action==='none') { boxes.forEach(b=>b.checked=false); return; }
if (action==='all') { boxes.forEach(b=>b.checked=true); return; }
// default
const defSkills = AVAILABLE_SKILLS.filter(s=>s.isDefault).map(s=>s.id);
const defTools = AVAILABLE_TOOL_GROUPS.filter(g=>g.isDefault).map(g=>g.id);
boxes.forEach(b=>{
if (b.dataset.spType==='skill') b.checked = defSkills.includes(b.value);
else b.checked = defTools.includes(b.value);
});
}
function collectSkillsTools(prefix) {
const wrap = document.getElementById(prefix+'-sp-wrap');
if (!wrap) return { skills:[], toolGroups:[] };
const skills = []; const toolGroups = [];
wrap.querySelectorAll('input[type=checkbox]:checked').forEach(b=>{
if (b.dataset.spType==='skill') skills.push(b.value);
else toolGroups.push(b.value);
});
return { skills, toolGroups };
}
function buildAddAgentForm() {
const modelOpts = buildModelOpts('__default__');
return '<div class="add-form">' +
@@ -2992,6 +3152,7 @@ function buildAddAgentForm() {
'<div class="form-group"><label>' + t('wiz.soul') + ' <span style="color:var(--muted);font-weight:400">' + t('wiz.soulHint') + '</span></label>' +
'<input id="fa-soul" placeholder="' + t('wiz.soulPh') + '">' +
'<span class="hint-text">' + t('wiz.soulTip') + '</span></div>' +
'<div class="form-group">' + buildSkillsToolsPicker('fa') + '</div>' +
'<div style="display:flex;gap:8px;margin-top:14px">' +
'<button class="btn-primary" onclick="submitAddAgent()">' + t('agents.addAgentSubmit') + '</button>' +
'<button class="btn-ghost" onclick="clearAddForm()">' + t('btn.cancel') + '</button>' +
@@ -3087,6 +3248,7 @@ function buildAddSubForm() {
'<input id="fs-soul" placeholder="' + t('wiz.soulPh') + '"></div>' +
'<div class="form-group"><label>' + t('wiz.memory') + ' <span style="color:var(--muted);font-weight:400">' + t('wiz.memoryHint') + '</span></label>' +
'<textarea id="fs-mem" placeholder="' + t('wiz.memoryPh') + '" rows="2"></textarea></div>' +
'<div class="form-group">' + buildSkillsToolsPicker('fs') + '</div>' +
'<div style="display:flex;gap:8px;margin-top:14px">' +
'<button class="btn-primary" onclick="submitAddSub()">' + t('agents.addSubSubmit') + '</button>' +
@@ -3120,18 +3282,20 @@ async function submitAddAgent() {
if (!name) { toast(t('agents.errName'), 'err'); return; }
if (!workspace) { toast('Workspace folder is required', 'err'); return; }
const { skills, toolGroups } = collectSkillsTools('fa');
try {
if (channel === 'telegram') {
const token = document.getElementById('fa-token').value.trim();
if (!token) { toast(t('agents.errToken'), 'err'); return; }
const payload = { botToken: token, agentId, name, workspace, model: model === '__default__' ? '' : model, purpose, personality };
const payload = { botToken: token, agentId, name, workspace, model: model === '__default__' ? '' : model, purpose, personality, skills, toolGroups };
await api('POST', '/api/agents/bot', payload);
} else {
const link = (document.getElementById('fa-discord-link')?.value||'').trim();
const parsed = parseDiscordLinkOrId(link);
if (!parsed) { toast('Discord channel link/ID is required', 'err'); return; }
const payload = { agentId, name, workspaceFolder: workspace, model: model === '__default__' ? '' : model, purpose, personality,
guildId: parsed.guildId, channelId: parsed.channelId };
guildId: parsed.guildId, channelId: parsed.channelId, skills, toolGroups };
await api('POST', '/api/agents/discord', payload);
}
toast('Agent created successfully', 'ok');
@@ -3160,6 +3324,8 @@ async function submitAddSub() {
if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) { toast(t('wiz.errIdFormat'), 'err'); return; }
if (!workspaceFolder) { toast('Workspace folder is required', 'err'); return; }
const { skills, toolGroups } = collectSkillsTools('fs');
try {
if (channel === 'telegram') {
const groupId = document.getElementById('fs-gid').value.trim();
@@ -3167,14 +3333,14 @@ async function submitAddSub() {
if (!groupId) { toast(t('wiz.errGroupId'), 'err'); return; }
if (telegramUserId && !/^\d+$/.test(telegramUserId)) { toast('Telegram User ID must be a number', 'err'); return; }
await api('POST', '/api/agents', { parentAgentId, agentId, displayName: displayName || agentId, groupId,
workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory, telegramUserId });
workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory, telegramUserId, skills, toolGroups });
} else {
const link = (document.getElementById('fs-discord-link')?.value||'').trim();
const parsed = parseDiscordLinkOrId(link);
if (!parsed) { toast('Discord thread link/ID is required', 'err'); return; }
await api('POST', '/api/agents/discord-sub', { parentAgentId, agentId, displayName: displayName || agentId,
workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory,
guildId: parsed.guildId, threadId: parsed.channelId });
guildId: parsed.guildId, threadId: parsed.channelId, skills, toolGroups });
}
toast(t('wiz.created'), 'ok');
clearAddForm();
@@ -3191,16 +3357,24 @@ function renderAgents() {
// Build grouping: prefer explicit parentAgentId; fallback to telegram bot-root grouping.
const byId = {}; (S.agents||[]).forEach(a=>{ byId[a.id]=a; });
// infer parentAgentId for legacy telegram sub-agents if missing
// infer parentAgentId for sub-agents if missing
const accountToRootId = {};
(S.agents||[]).forEach(a=>{ if(a.hasOwnBot && a.accountId) accountToRootId[a.accountId]=a.id; });
(S.agents||[]).forEach(a=>{
if(!a.parentAgentId && !a.hasOwnBot && a.parentAccountId && accountToRootId[a.parentAccountId]){
if(a.parentAgentId || a.hasOwnBot || a.id === 'main') return; // already resolved or is a root
// Case 1: legacy telegram sub-agent with parentAccountId
if(a.parentAccountId && accountToRootId[a.parentAccountId]){
a._inferParentAgentId = accountToRootId[a.parentAccountId];
return;
}
// Case 2: orphan agent (no own bot, no parentAgentId, no binding-based inference)
// These are likely dynamically created sub-agents — default to 'main'
if(byId['main']){
a._inferParentAgentId = 'main';
}
});
const roots=[]; const childrenByRoot={}; const orphans=[];
const roots=[]; const childrenByRoot={};
(S.agents||[]).forEach(a=>{
const pid = a.parentAgentId || a._inferParentAgentId || '';
if(pid && byId[pid]){
@@ -3209,6 +3383,12 @@ function renderAgents() {
roots.push(a);
}
});
// Sort roots: agents with own bot first (main always first among those), then orphans
roots.sort((a,b)=>{
if(a.id==='main') return -1; if(b.id==='main') return 1;
if(a.hasOwnBot && !b.hasOwnBot) return -1; if(!a.hasOwnBot && b.hasOwnBot) return 1;
return (a.name||a.id).localeCompare(b.name||b.id);
});
function fmtBinding(b){
const ch = (b.channel||'').toLowerCase();
@@ -3668,7 +3848,14 @@ async function deleteAuth(key){
}
function copyText(txt){
navigator.clipboard.writeText(txt).then(()=>toast('已复制','success')).catch(()=>toast('复制失败','error'));
navigator.clipboard.writeText(txt).then(()=>toast(lang==='en'?'Copied':'已复制','success')).catch(()=>toast(lang==='en'?'Copy failed':'复制失败','error'));
}
function copyCliCommand(){
const inp=document.getElementById('cliInput');
const cmd=(inp&&inp.value?inp.value:'').trim();
if(!cmd){ toast(lang==='en'?'Nothing to copy (CLI input is empty)':'没有可复制的命令(输入框为空)','error'); return; }
copyText(cmd);
}
@@ -3908,8 +4095,8 @@ function runCli(){
es.addEventListener('done',e=>{
try{
const d=JSON.parse(e.data);
if(d.code===0) cliAppend('\\n✅ 完成 (exit 0)\\n','cli-done-ok');
else cliAppend('\\n❌ 退出码 '+d.code+'\\n','cli-done-err');
if(d.code===0) cliAppend('\\n✅ Done (exit 0)\\n','cli-done-ok');
else cliAppend('\\n❌ Exit code '+d.code+'\\n','cli-done-err');
}catch(_){}
es.close(); cliEvt=null; setCliRunning(false);
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openclaw-manager",
"version": "0.9.0",
"version": "1.0.0-rc1",
"description": "A local web UI for managing OpenClaw AI agents \u2014 no npm install required",
"main": "openclaw-manager.js",
"scripts": {