@xgjktech/xg-openclaw-harness-tools 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,144 +1,148 @@
1
- # @xgjktech/xg-openclaw-harness-tools
2
-
3
- OpenClaw 第三方工具插件:企业计划工具 `xg_plan_write` / `xg_plan_execute`。
4
-
5
- 用一份持久化的计划(SQLite 落盘)取代内置 `update_plan`(无状态、只回显、不落库),
6
- `xg_plan_write` 整张维护草稿计划,`xg_plan_execute` 只读查看计划或最近摘要。
7
-
8
- ## 架构裁决:实时事件当前处于事实性休眠(务必先读)
9
-
10
- **当前 OpenClaw 2026.6.10 版本下,实时事件推送链路休眠**:工具 execute/factory 期没有
11
- 正当途径获取真实 runId(详见 `docs/notes-plugin-entry.md` N2),且 0.3.0 已移除工具侧
12
- `notify.ts`。`xg_plan_write` 落库后不发事件,`xg_plan_execute` 全程只读。
13
-
14
- **因此 IM 侧(或任何下游消费方)不得依赖实时事件感知计划进度。** 当前唯一可靠的主感知路径是:
15
-
16
- 1. **SQLite 读取**——IM 用 `openPlanStoreReadonly(dbPath)` 按 origin 只读反查
17
- `<stateDir>/xgjktech/plans.sqlite` 并推送全量快照,这是权威状态源。
18
- 2. **agent 最终回复**——agent 完成任务后的文本回复本身会体现进度。
19
-
20
- 事件契约(`PlanEventPayload` / `isPlanEventPayload` / `planEventStream`)在 shared 中原样保留,
21
- 仅用于下游编译兼容。未来恢复实时事件需要重新实现工具侧生产与 IM 侧消费,并单独发布。
22
-
23
- ## 环境要求
24
-
25
- - **Node ≥ 22.19.0**(依赖 `node:sqlite`,与 OpenClaw 自身 `engines` 一致)。
26
- - monorepo 用 npm workspaces。
27
-
28
- ## 安装与构建
29
-
30
- 本仓库是包含 `@xgjktech/xg-openclaw-shared`(存储层+事件契约层)和
31
- `@xgjktech/xg-openclaw-harness-tools`(本插件)两个 workspace 的 monorepo。**`xg-shared` 必须先于
32
- 本插件构建**(本插件依赖它的编译产物);npm workspaces 拓扑已保证顺序,直接在仓库根执行:
33
-
34
- ```bash
35
- npm install
36
- npm run build
37
- ```
38
-
39
- 产物:`packages/xg-harness-tools/dist/`(含 `index.js` 与 `.d.ts`)。
40
-
41
- 将本插件安装到 OpenClaw 的具体命令/目录约定,请对照 OpenClaw 的第三方插件安装机制
42
- (global / workspace / config origin,本插件按非 bundled 第三方路径运行)以及
43
- `package.json` 里的 `openclaw.*` 字段块(`extensions`/`install.localPath` 等)。
44
- P1 阶段不锁死具体安装命令,避免与实际安装流程漂移(详见 `docs/05-部署文档.md` §3.3)。
45
-
46
- ## 配置
47
-
48
- ### 1. 屏蔽内置 update_plan(deny,唯一权威路径)
49
-
50
- ```jsonc
51
- {
52
- "tools": {
53
- "deny": ["update_plan"]
54
- }
55
- }
56
- ```
57
-
58
- 无论生产 profile / allowlist 取何值(包括 `allow:["*"]`),`deny` 都一票否决,
59
- 与 profile 取值解耦。
60
-
61
- ### 2. 启用本插件两个工具(allow)
62
-
63
- 本插件两个工具都以 `optional:true` 注册,**默认不启用**,必须 allowlist 显式命中才会注册:
64
-
65
- ```jsonc
66
- {
67
- "tools": {
68
- "allow": ["xg_plan_write", "xg_plan_execute"],
69
- "deny": ["update_plan"]
70
- }
71
- }
72
- ```
73
-
74
- 可直接套用的完整样例见仓库根 [`deploy/tools.config.sample.jsonc`](../../deploy/tools.config.sample.jsonc)(带注释)。
75
-
76
- ### 3. stateDir 与数据库路径
77
-
78
- 插件运行时数据库路径固定为:
79
-
80
- ```
81
- <stateDir>/xgjktech/plans.sqlite
82
- ```
83
-
84
- `stateDir` 取自 OpenClaw 全局状态目录(`api.runtime.state.resolveStateDir()`,默认
85
- `~/.openclaw/`,`OPENCLAW_STATE_DIR` 覆盖时同步),插件首次运行时通过 `resolvePlanDbPath`
86
- 自动 `mkdir -p` 出 `xgjktech/` 子目录,无需手动创建。IM 插件若要接入同一份数据,应复用
87
- 同一个 `resolvePlanDbPath` 以保证路径约定一致。
88
-
89
- ## 两个工具说明
90
-
91
- ### `xg_plan_write`
92
-
93
- 整张创建或覆盖一份草稿计划(goal + tasks[])并写入 SQLite。更新时必须重交所有要保留的
94
- task;写入 schema 不接受 steps,但读取旧计划仍兼容旧 steps 数据。
95
-
96
- ### `xg_plan_execute`
97
-
98
- 只读查看计划。带 planId 返回完整紧凑 view;不带 planId 返回最近 10 条计划摘要,用于跨会话
99
- 找回 planId。更新进度统一使用 `xg_plan_write` 整张重交。
100
-
101
- 两个工具的完整参数 schema `src/plan-write-tool.ts` / `src/plan-execute-tool.ts`
102
- 中的 `PLAN_WRITE_DESCRIPTION` / `PLAN_EXECUTE_DESCRIPTION`。
103
-
104
- ## 部署 checklist
105
-
106
- - [ ] Node ≥ 22.19.0
107
- - [ ] monorepo 构建成功(`xg-shared` 先于本插件构建,workspaces 拓扑自动保证)
108
- - [ ] 插件已安装且被 OpenClaw 识别为已加载的第三方插件(非 bundled)
109
- - [ ] `tools.deny` 含 `update_plan`
110
- - [ ] `tools.allow` `xg_plan_write` + `xg_plan_execute`(或经 `group:plugins` / `*` 放行)
111
- - [ ] 实拉一个 agent,验证工具清单**有本插件两工具、无 update_plan**(步骤见下节)
112
- - [ ] `<stateDir>/xgjktech/` 可写,首次运行能生成 `plans.sqlite`
113
- - [ ] (互通)IM 侧已依赖 `@xgjktech/xg-openclaw-shared` 并按 `docs/04-互通设计.md` 接入只读反查
114
-
115
- ## 实机验证步骤(可复现)+ 当前实际执行情况说明
116
-
117
- **验收标准要求部署方按 checklist 实拉一个 agent,确认工具清单里有 `xg_plan_write`/
118
- `xg_plan_execute`、没有 `update_plan`。以下是可复现的验证步骤:**
119
-
120
- 1. 按上文"配置"章节把 `tools.deny`/`tools.allow` 写入生产 agent/profile 配置。
121
- 2. 安装并加载本插件(确认加载日志/插件列表里出现 `xg-harness-tools`,origin 为第三方非 bundled)。
122
- 3. 拉起一个真实 agent 会话,触发一次会列出可用工具的动作(例如让 agent 描述自己有哪些工具,
123
- 或查看 gateway/日志里该次 attempt 实际注册的工具名列表)。
124
- 4. 核对该工具列表:应包含 `xg_plan_write` `xg_plan_execute`,**不应包含** `update_plan`。
125
- 5. 调用一次 `xg_plan_write` 建一份最小计划,确认返回 `planId + view`;再用该 planId 调
126
- `xg_plan_execute`,确认只读 view 一致。用 `openPlanStoreReadonly` 反查并核对落盘内容。
127
-
128
- **本项在本次交付中未在真实 OpenClaw gateway 环境实机执行**——本仓库当前开发环境没有可用的
129
- 真实 OpenClaw gateway/agent 运行环境,因此无法诚实地报告"已实拉 agent 验证通过"。以下是
130
- 已经做过、可以作为间接佐证的验证,但**明确说明这不等于实机验证**:
131
-
132
- - 当前 factory 集成测试用**手工构造的 mock `api`**捕获 2 个工具的注册工厂,并提供带
133
- `sessionKey`/`deliveryContext` `toolContext`:`xg_plan_write` 写入后通过只读存储反查
134
- `origin`,`xg_plan_execute` 校验指定计划的 view 与最近计划列表。**这验证的是插件代码本身
135
- 可以被正确加载、注册并贯通当前契约**,但 mock `api` 不是真实 OpenClaw gateway 的
136
- `tools.allow`/`deny` 策略引擎,**不能**佐证"生产 deny/allow 配置生效后工具清单精确符合
137
- 预期"这一条——那一条必须在真实 gateway 环境按上面 5 步实测。
138
- - T6 阶段(`packages/xg-harness-tools/src/integration.test.ts`)用真实的 `SqlitePlanStore`
139
- (临时目录)+ 真实工具函数(`buildPlanWriteTool`/`buildPlanExecuteTool`)跑通了完整的
140
- 写入→整张重交→execute 只读查看→SQLite 只读反查链路,但同样不经过 OpenClaw 的工具
141
- allowlist/deny 策略引擎。
142
-
143
- **结论:部署方在生产环境落实上述配置后,必须按"实机验证步骤"重新执行一次实拉 agent 验证,
144
- 不能以本仓库现有的 mock/集成测试结果代替。**
1
+ # @xgjktech/xg-openclaw-harness-tools
2
+
3
+ OpenClaw 第三方工具插件:企业计划工具 `xg_plan_write` / `xg_plan_view`。
4
+
5
+ 用一份持久化的计划(SQLite 落盘)取代内置 `update_plan`(无状态、只回显、不落库),
6
+ `xg_plan_write` 整张维护草稿计划,`xg_plan_view` 只读查看计划或最近摘要。
7
+
8
+ ## 架构裁决:实时事件当前处于事实性休眠(务必先读)
9
+
10
+ **当前 OpenClaw 2026.6.10 版本下,实时事件推送链路休眠**:工具 execute/factory 期没有
11
+ 正当途径获取真实 runId(详见 `docs/notes-plugin-entry.md` N2),且 0.3.0 已移除工具侧
12
+ `notify.ts`。`xg_plan_write` 落库后不发事件,`xg_plan_view` 全程只读。
13
+
14
+ **因此 IM 侧(或任何下游消费方)不得依赖实时事件感知计划进度。** 当前唯一可靠的主感知路径是:
15
+
16
+ 1. **SQLite 读取**——IM 用 `openPlanStoreReadonly(dbPath)` 按 origin 只读反查
17
+ `<stateDir>/xgjktech/plans.sqlite` 并推送全量快照,这是权威状态源。
18
+ 2. **agent 最终回复**——agent 完成任务后的文本回复本身会体现进度。
19
+
20
+ 事件契约(`PlanEventPayload` / `isPlanEventPayload` / `planEventStream`)在 shared 中原样保留,
21
+ 仅用于下游编译兼容。未来恢复实时事件需要重新实现工具侧生产与 IM 侧消费,并单独发布。
22
+
23
+ ## 环境要求
24
+
25
+ - **Node ≥ 22.19.0**(依赖 `node:sqlite`,与 OpenClaw 自身 `engines` 一致)。
26
+ - monorepo 用 npm workspaces。
27
+
28
+ ## 安装与构建
29
+
30
+ 本仓库是包含 `@xgjktech/xg-openclaw-shared`(存储层+事件契约层)和
31
+ `@xgjktech/xg-openclaw-harness-tools`(本插件)两个 workspace 的 monorepo。**`xg-shared` 必须先于
32
+ 本插件构建**(本插件依赖它的编译产物);npm workspaces 拓扑已保证顺序,直接在仓库根执行:
33
+
34
+ ```bash
35
+ npm install
36
+ npm run build
37
+ ```
38
+
39
+ 产物:`packages/xg-harness-tools/dist/`(含 `index.js` 与 `.d.ts`)。
40
+
41
+ 将本插件安装到 OpenClaw 的具体命令/目录约定,请对照 OpenClaw 的第三方插件安装机制
42
+ (global / workspace / config origin,本插件按非 bundled 第三方路径运行)以及
43
+ `package.json` 里的 `openclaw.*` 字段块(`extensions`/`install.localPath` 等)。
44
+ P1 阶段不锁死具体安装命令,避免与实际安装流程漂移(详见 `docs/05-部署文档.md` §3.3)。
45
+
46
+ ## 配置
47
+
48
+ ### 1. 屏蔽内置 update_plan(deny,唯一权威路径)
49
+
50
+ ```jsonc
51
+ {
52
+ "tools": {
53
+ "deny": ["update_plan"]
54
+ }
55
+ }
56
+ ```
57
+
58
+ 无论生产 profile / allowlist 取何值(包括 `allow:["*"]`),`deny` 都一票否决,
59
+ 与 profile 取值解耦。
60
+
61
+ ### 2. 启用本插件两个工具(allow)
62
+
63
+ 本插件两个工具都以 `optional:true` 注册,**默认不启用**,必须 allowlist 显式命中才会注册:
64
+
65
+ ```jsonc
66
+ {
67
+ "tools": {
68
+ "allow": ["xg_plan_write", "xg_plan_view"],
69
+ "deny": ["update_plan"]
70
+ }
71
+ }
72
+ ```
73
+
74
+ 可直接套用的完整样例见仓库根 [`deploy/tools.config.sample.jsonc`](../../deploy/tools.config.sample.jsonc)(带注释)。
75
+
76
+ ### 3. stateDir 与数据库路径
77
+
78
+ 插件运行时数据库路径固定为:
79
+
80
+ ```
81
+ <stateDir>/xgjktech/plans.sqlite
82
+ ```
83
+
84
+ `stateDir` 取自 OpenClaw 全局状态目录(`api.runtime.state.resolveStateDir()`,默认
85
+ `~/.openclaw/`,`OPENCLAW_STATE_DIR` 覆盖时同步),插件首次运行时通过 `resolvePlanDbPath`
86
+ 自动 `mkdir -p` 出 `xgjktech/` 子目录,无需手动创建。IM 插件若要接入同一份数据,应复用
87
+ 同一个 `resolvePlanDbPath` 以保证路径约定一致。
88
+
89
+ ## 两个工具说明
90
+
91
+ ### `xg_plan_write`
92
+
93
+ 整张创建或覆盖一份草稿计划(goal + tasks[])并写入 SQLite。更新时必须重交所有要保留的
94
+ task;写入 schema 不接受 steps,但读取旧计划仍兼容旧 steps 数据。
95
+
96
+ ### `xg_plan_view`
97
+
98
+ 只读查看计划,**不会执行任何任务**。带 planId 返回完整紧凑 view;不带 planId 返回最近 10
99
+ 计划摘要,用于跨会话找回 planId。更新进度统一使用 `xg_plan_write` 整张重交。
100
+
101
+ > 工具名用 `view` 而非 `execute` 是有意的:`plan_view`/`plan_write` 都只是台账,不执行、不调度、
102
+ > 无后台运行。二者 description 均明确"保存/查看 ≠ 执行",且结果带 `reminder` 字段引导模型
103
+ > 继续逐项实际执行任务——避免模型调用计划工具后误以为工作已在后台完成而停住。
104
+
105
+ 两个工具的完整参数 schema 见 `src/plan-write-tool.ts` / `src/plan-view-tool.ts`
106
+ 中的 `PLAN_WRITE_DESCRIPTION` / `PLAN_VIEW_DESCRIPTION`。
107
+
108
+ ## 部署 checklist
109
+
110
+ - [ ] Node 22.19.0
111
+ - [ ] monorepo 构建成功(`xg-shared` 先于本插件构建,workspaces 拓扑自动保证)
112
+ - [ ] 插件已安装且被 OpenClaw 识别为已加载的第三方插件(非 bundled)
113
+ - [ ] `tools.deny` `update_plan`
114
+ - [ ] `tools.allow` 含 `xg_plan_write` + `xg_plan_view`(或经 `group:plugins` / `*` 放行)
115
+ - [ ] 实拉一个 agent,验证工具清单**有本插件两工具、无 update_plan**(步骤见下节)
116
+ - [ ] `<stateDir>/xgjktech/` 可写,首次运行能生成 `plans.sqlite`
117
+ - [ ] (互通)IM 侧已依赖 `@xgjktech/xg-openclaw-shared` 并按 `docs/04-互通设计.md` 接入只读反查
118
+
119
+ ## 实机验证步骤(可复现)+ 当前实际执行情况说明
120
+
121
+ **验收标准要求部署方按 checklist 实拉一个 agent,确认工具清单里有 `xg_plan_write`/
122
+ `xg_plan_view`、没有 `update_plan`。以下是可复现的验证步骤:**
123
+
124
+ 1. 按上文"配置"章节把 `tools.deny`/`tools.allow` 写入生产 agent/profile 配置。
125
+ 2. 安装并加载本插件(确认加载日志/插件列表里出现 `xg-harness-tools`,origin 为第三方非 bundled)。
126
+ 3. 拉起一个真实 agent 会话,触发一次会列出可用工具的动作(例如让 agent 描述自己有哪些工具,
127
+ 或查看 gateway/日志里该次 attempt 实际注册的工具名列表)。
128
+ 4. 核对该工具列表:应包含 `xg_plan_write` 与 `xg_plan_view`,**不应包含** `update_plan`。
129
+ 5. 调用一次 `xg_plan_write` 建一份最小计划,确认返回 `planId + view`;再用该 planId 调
130
+ `xg_plan_view`,确认只读 view 一致。用 `openPlanStoreReadonly` 反查并核对落盘内容。
131
+
132
+ **本项在本次交付中未在真实 OpenClaw gateway 环境实机执行**——本仓库当前开发环境没有可用的
133
+ 真实 OpenClaw gateway/agent 运行环境,因此无法诚实地报告"已实拉 agent 验证通过"。以下是
134
+ 已经做过、可以作为间接佐证的验证,但**明确说明这不等于实机验证**:
135
+
136
+ - 当前 factory 集成测试用**手工构造的 mock `api`**捕获 2 个工具的注册工厂,并提供带
137
+ `sessionKey`/`deliveryContext` `toolContext`:`xg_plan_write` 写入后通过只读存储反查
138
+ `origin`,`xg_plan_view` 校验指定计划的 view 与最近计划列表。**这验证的是插件代码本身
139
+ 可以被正确加载、注册并贯通当前契约**,但 mock `api` 不是真实 OpenClaw gateway 的
140
+ `tools.allow`/`deny` 策略引擎,**不能**佐证"生产 deny/allow 配置生效后工具清单精确符合
141
+ 预期"这一条——那一条必须在真实 gateway 环境按上面 5 步实测。
142
+ - T6 阶段(`packages/xg-harness-tools/src/integration.test.ts`)用真实的 `SqlitePlanStore`
143
+ (临时目录)+ 真实工具函数(`buildPlanWriteTool`/`buildPlanViewTool`)跑通了完整的
144
+ 写入→整张重交→execute 只读查看→SQLite 只读反查链路,但同样不经过 OpenClaw 的工具
145
+ allowlist/deny 策略引擎。
146
+
147
+ **结论:部署方在生产环境落实上述配置后,必须按"实机验证步骤"重新执行一次实拉 agent 验证,
148
+ 不能以本仓库现有的 mock/集成测试结果代替。**
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
2
2
  import { resolvePlanDbPath, SqlitePlanStore, SqliteActionStore } from "@xgjktech/xg-openclaw-shared";
3
3
  import { PLAN_WRITE_DESCRIPTION, PlanWriteParams, buildPlanWriteTool } from "./plan-write-tool.js";
4
- import { PLAN_EXECUTE_DESCRIPTION, PlanExecuteParams, buildPlanExecuteTool, } from "./plan-execute-tool.js";
4
+ import { PLAN_VIEW_DESCRIPTION, PlanViewParams, buildPlanViewTool, } from "./plan-view-tool.js";
5
5
  import { ACTION_DISPATCH_DESCRIPTION, ActionDispatchParams, buildActionDispatchTool, } from "./action-dispatch-tool.js";
6
6
  import { toPlanOrigin } from "./plan-origin.js";
7
7
  const PLUGIN_ID = "xg-harness-tools";
8
8
  export default defineToolPlugin({
9
9
  id: PLUGIN_ID,
10
10
  name: "XG Harness Tools",
11
- description: "企业工具插件:计划管理与通用动作分发(plan_write / plan_execute / action_dispatch)",
11
+ description: "企业工具插件:计划管理与通用动作分发(plan_write / plan_view / action_dispatch)",
12
12
  tools: (tool) => {
13
13
  let planStore;
14
14
  let actionStore;
@@ -36,12 +36,12 @@ export default defineToolPlugin({
36
36
  factory: ({ api, toolContext }) => buildPlanWriteTool(getPlanStore(api), toPlanOrigin(toolContext)),
37
37
  }),
38
38
  tool({
39
- name: "xg_plan_execute",
39
+ name: "xg_plan_view",
40
40
  label: "【xg-harness】查看计划",
41
- description: PLAN_EXECUTE_DESCRIPTION,
42
- parameters: PlanExecuteParams,
41
+ description: PLAN_VIEW_DESCRIPTION,
42
+ parameters: PlanViewParams,
43
43
  optional: true,
44
- factory: ({ api, toolContext }) => buildPlanExecuteTool({ store: getPlanStore(api), origin: toPlanOrigin(toolContext) }),
44
+ factory: ({ api, toolContext }) => buildPlanViewTool({ store: getPlanStore(api), origin: toPlanOrigin(toolContext) }),
45
45
  }),
46
46
  tool({
47
47
  name: "xg_action_dispatch",
@@ -1,13 +1,13 @@
1
1
  import { Type, type Static } from "typebox";
2
2
  import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
3
3
  import { type PlanOrigin, type PlanStore } from "@xgjktech/xg-openclaw-shared";
4
- export declare const PlanExecuteParams: Type.TObject<{
4
+ export declare const PlanViewParams: Type.TObject<{
5
5
  planId: Type.TOptional<Type.TString>;
6
6
  }>;
7
- export type PlanExecuteParamsT = Static<typeof PlanExecuteParams>;
8
- export declare const PLAN_EXECUTE_DESCRIPTION: string;
9
- export declare function buildPlanExecuteTool(deps: {
7
+ export type PlanViewParamsT = Static<typeof PlanViewParams>;
8
+ export declare const PLAN_VIEW_DESCRIPTION: string;
9
+ export declare function buildPlanViewTool(deps: {
10
10
  store: PlanStore;
11
11
  origin?: PlanOrigin;
12
12
  }): AnyAgentTool;
13
- //# sourceMappingURL=plan-execute-tool.d.ts.map
13
+ //# sourceMappingURL=plan-view-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-view-tool.d.ts","sourceRoot":"","sources":["../src/plan-view-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,SAAS,EAEf,MAAM,8BAA8B,CAAC;AAKtC,eAAO,MAAM,cAAc;;EAK1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,qBAAqB,QAQtB,CAAC;AAuBb,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,YAAY,CAmD/F"}
@@ -1,19 +1,19 @@
1
1
  import { Type } from "typebox";
2
2
  import { isPlanCancelled, } from "@xgjktech/xg-openclaw-shared";
3
- import { renderPlanView } from "./plan-view.js";
3
+ import { buildPlanReminder, renderPlanView } from "./plan-view.js";
4
4
  import { planInScope, scopeKey } from "./plan-scope.js";
5
5
  import { toolJsonResult } from "./tool-json-result.js";
6
- export const PlanExecuteParams = Type.Object({
6
+ export const PlanViewParams = Type.Object({
7
7
  planId: Type.Optional(Type.String({ description: "要查看的计划 id;留空则列出最近计划" })),
8
8
  }, { additionalProperties: false });
9
- export const PLAN_EXECUTE_DESCRIPTION = [
10
- "用途:只读查看已保存的计划;不执行任务,也不修改计划。",
9
+ export const PLAN_VIEW_DESCRIPTION = [
10
+ "用途:只读查看已保存的计划;不会执行任何任务,也不会修改计划。",
11
11
  "只能看到当前会话来源的计划。",
12
12
  "",
13
13
  "- 带 planId:返回该计划当前的完整清单。",
14
14
  "- 不带 planId:返回最近计划的摘要列表,用于忘记或跨会话找回 planId。",
15
15
  "",
16
- "需要更新进度时,用 xg_plan_write 重新提交整张计划。",
16
+ "重要:查看不等于执行。若计划存在未完成任务,你必须继续实际执行它们,完成一项后用 xg_plan_write 回写状态。",
17
17
  ].join("\n");
18
18
  function summarizePlan(plan) {
19
19
  const taskCounts = { total: 0, completed: 0, blocked: 0, inProgress: 0 };
@@ -33,14 +33,14 @@ function summarizePlan(plan) {
33
33
  taskCounts,
34
34
  };
35
35
  }
36
- export function buildPlanExecuteTool(deps) {
36
+ export function buildPlanViewTool(deps) {
37
37
  const { store, origin } = deps;
38
38
  const scope = scopeKey(origin);
39
39
  return {
40
- name: "xg_plan_execute",
40
+ name: "xg_plan_view",
41
41
  label: "【xg-harness】查看计划",
42
- description: PLAN_EXECUTE_DESCRIPTION,
43
- parameters: PlanExecuteParams,
42
+ description: PLAN_VIEW_DESCRIPTION,
43
+ parameters: PlanViewParams,
44
44
  async execute(_toolCallId, rawParams) {
45
45
  const params = rawParams;
46
46
  try {
@@ -60,10 +60,14 @@ export function buildPlanExecuteTool(deps) {
60
60
  const result = { ok: false, error: { code: "PLAN_NOT_FOUND" } };
61
61
  return toolJsonResult(result);
62
62
  }
63
+ const reminder = buildPlanReminder(plan);
63
64
  const result = {
64
65
  ok: true,
65
66
  planId: plan.planId,
66
67
  view: renderPlanView(plan),
68
+ ...(reminder !== undefined
69
+ ? { reminder: `该计划有未完成任务,查看不等于执行。${reminder}` }
70
+ : {}),
67
71
  };
68
72
  return toolJsonResult(result);
69
73
  }
@@ -1,3 +1,9 @@
1
1
  import { type Plan } from "@xgjktech/xg-openclaw-shared";
2
2
  export declare function renderPlanView(plan: Plan): string;
3
+ /**
4
+ * 生成"下一步该做什么"的引导句,喂给模型防止其只记录不执行。
5
+ * 规则:优先 in_progress(继续手头任务)→ 其次 pending(开新任务)→ 只剩 blocked 时提示等待;
6
+ * 全部完成或已取消则返回 undefined。
7
+ */
8
+ export declare function buildPlanReminder(plan: Plan): string | undefined;
3
9
  //# sourceMappingURL=plan-view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan-view.d.ts","sourceRoot":"","sources":["../src/plan-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,IAAI,EAGV,MAAM,8BAA8B,CAAC;AAoBtC,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAgBjD"}
1
+ {"version":3,"file":"plan-view.d.ts","sourceRoot":"","sources":["../src/plan-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,IAAI,EAGV,MAAM,8BAA8B,CAAC;AAqBtC,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAgBjD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAsBhE"}
package/dist/plan-view.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import { isPlanCancelled, } from "@xgjktech/xg-openclaw-shared";
2
+ // 状态标记直接用枚举原文,模型可零歧义对账(写读一致),不用符号猜测。
2
3
  const TASK_STATUS_MARKERS = {
3
- pending: "[ ]",
4
- in_progress: "[>]",
5
- completed: "[x]",
6
- blocked: "[!]",
4
+ pending: "[pending]",
5
+ in_progress: "[in_progress]",
6
+ completed: "[completed]",
7
+ blocked: "[blocked]",
7
8
  };
8
9
  const STEP_STATUS_MARKERS = {
9
- pending: "[ ]",
10
- in_progress: "[>]",
11
- completed: "[x]",
10
+ pending: "[pending]",
11
+ in_progress: "[in_progress]",
12
+ completed: "[completed]",
12
13
  };
13
14
  /** view 依赖"一行一项"的格式约定;字段内容里的换行统一压成空格,防止伪造出多余的行。 */
14
15
  function singleLine(text) {
@@ -26,3 +27,28 @@ export function renderPlanView(plan) {
26
27
  });
27
28
  return lines.join("\n");
28
29
  }
30
+ /**
31
+ * 生成"下一步该做什么"的引导句,喂给模型防止其只记录不执行。
32
+ * 规则:优先 in_progress(继续手头任务)→ 其次 pending(开新任务)→ 只剩 blocked 时提示等待;
33
+ * 全部完成或已取消则返回 undefined。
34
+ */
35
+ export function buildPlanReminder(plan) {
36
+ if (isPlanCancelled(plan))
37
+ return undefined;
38
+ const unfinished = plan.tasks.filter((task) => task.status !== "completed");
39
+ if (unfinished.length === 0)
40
+ return undefined;
41
+ const inProgress = unfinished.find((task) => task.status === "in_progress");
42
+ if (inProgress !== undefined) {
43
+ return `下一步:继续执行「${singleLine(inProgress.title)}」并回写状态`;
44
+ }
45
+ const pending = unfinished.find((task) => task.status === "pending");
46
+ if (pending !== undefined) {
47
+ return `下一步:执行「${singleLine(pending.title)}」并回写状态`;
48
+ }
49
+ const blockedCount = unfinished.filter((task) => task.status === "blocked").length;
50
+ if (blockedCount > 0) {
51
+ return `计划中有 ${blockedCount} 项 blocked 等待中:请先处理阻塞条件,或与用户确认后调整/取消计划`;
52
+ }
53
+ return undefined;
54
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"plan-write-tool.d.ts","sourceRoot":"","sources":["../src/plan-write-tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,SAAS,EAGf,MAAM,8BAA8B,CAAC;AA8BtC,eAAO,MAAM,eAAe;;;;;;;;;EAwB3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,QAmBvB,CAAC;AA0Eb,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAsHtF"}
1
+ {"version":3,"file":"plan-write-tool.d.ts","sourceRoot":"","sources":["../src/plan-write-tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,SAAS,EAGf,MAAM,8BAA8B,CAAC;AA8BtC,eAAO,MAAM,eAAe;;;;;;;;;EAwB3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,QAwBvB,CAAC;AA0Eb,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CA0HtF"}
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { Type } from "typebox";
3
3
  import { assertPlanInvariants, isPlanCancelled, } from "@xgjktech/xg-openclaw-shared";
4
4
  import { toolJsonResult } from "./tool-json-result.js";
5
- import { renderPlanView } from "./plan-view.js";
5
+ import { buildPlanReminder, renderPlanView } from "./plan-view.js";
6
6
  import { planInScope, scopeKey } from "./plan-scope.js";
7
7
  const TaskInput = Type.Object({
8
8
  title: Type.String({
@@ -45,9 +45,14 @@ export const PLAN_WRITE_DESCRIPTION = [
45
45
  "- 需要等待人的回复、审批或外部系统,即使任务较少。",
46
46
  "- 用户明确要求任务清单或进度追踪。",
47
47
  "",
48
+ "执行纪律(必须遵守):",
49
+ "- 本工具只把计划清单落库:不执行、不调度、无后台运行。保存计划 ≠ 完成工作。",
50
+ "- 保存后必须立即继续逐个实际执行 tasks:完成一项 → 回写其状态 → 继续下一项,直至全部 completed。",
51
+ "- 无法当场完成或需等待外部条件的项,标 in_progress(进行中)或 blocked(note 写明原因),后续回合继续。",
52
+ "",
48
53
  "提交规则:",
49
54
  "- 新建时不传 planId;更新时传入原 planId。",
50
- "- 更新计划内容时必须重新提交 goal 和所有要保留的 tasks,包括未变化任务及其当前 status/note。只提交变化项会删除其他任务;记不清时先用 xg_plan_execute 查看。",
55
+ "- 更新计划内容时必须重新提交 goal 和所有要保留的 tasks,包括未变化任务及其当前 status/note。只提交变化项会删除其他任务;记不清时先用 xg_plan_view 查看。",
51
56
  "- goal 用一句话;tasks 通常 3-7 项,每项是能判断是否完成的具体动作,不写 TODO、待定或内部思考。",
52
57
  "- status 为 pending、in_progress、completed 或 blocked。通常只有一项 in_progress,确实并行时可以有多项;blocked 时必须用 note 写明原因和继续所需条件。",
53
58
  "- 同一件工作只维护一份计划;任务方向变化时,先取消不再推进的旧计划({planId, cancel:true})再新建。",
@@ -188,11 +193,15 @@ export function buildPlanWriteTool(store, origin) {
188
193
  const plan = buildPlan(params.goal, params.tasks, existing, planId, origin, new Date().toISOString());
189
194
  assertPlanInvariants(plan);
190
195
  store.set(plan);
196
+ const reminder = buildPlanReminder(plan);
191
197
  const result = {
192
198
  ok: true,
193
199
  planId: plan.planId,
194
200
  view: renderPlanView(plan),
195
201
  ...(warnings !== undefined ? { warnings } : {}),
202
+ ...(reminder !== undefined
203
+ ? { reminder: `计划已保存(仅记录,未执行)。${reminder}` }
204
+ : {}),
196
205
  };
197
206
  return toolJsonResult(result);
198
207
  }
@@ -1,14 +1,14 @@
1
- {
2
- "id": "xg-harness-tools",
3
- "name": "XG Harness Tools",
4
- "description": "企业工具插件:计划管理与通用动作分发(plan_write / plan_execute / action_dispatch)",
5
- "version": "0.1.0",
6
- "configSchema": {
7
- "type": "object",
8
- "additionalProperties": true,
9
- "properties": {}
10
- },
11
- "contracts": {
12
- "tools": ["xg_plan_write", "xg_plan_execute", "xg_action_dispatch"]
13
- }
14
- }
1
+ {
2
+ "id": "xg-harness-tools",
3
+ "name": "XG Harness Tools",
4
+ "description": "企业工具插件:计划管理与通用动作分发(plan_write / plan_view / action_dispatch)",
5
+ "version": "0.1.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": true,
9
+ "properties": {}
10
+ },
11
+ "contracts": {
12
+ "tools": ["xg_plan_write", "xg_plan_view", "xg_action_dispatch"]
13
+ }
14
+ }
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
- {
2
- "name": "@xgjktech/xg-openclaw-harness-tools",
3
- "version": "0.3.5",
4
- "description": "企业工具插件:plan_write / plan_execute / action_dispatch(OpenClaw 第三方工具插件)",
5
- "license": "MIT",
6
- "type": "module",
7
- "engines": {
8
- "node": ">=22.19.0"
9
- },
10
- "main": "dist/index.js",
11
- "types": "dist/index.d.ts",
12
- "files": [
13
- "dist/**/*",
14
- "!dist/*.test.*",
15
- "openclaw.plugin.json"
16
- ],
17
- "publishConfig": {
18
- "access": "public",
19
- "registry": "https://registry.npmjs.org/"
20
- },
21
- "scripts": {
22
- "build": "tsc -p tsconfig.json",
23
- "typecheck": "tsc -p tsconfig.json --noEmit",
24
- "pretest": "npm run build",
25
- "test": "node --test dist/*.test.js",
26
- "prepublishOnly": "npm run build && npm run test"
27
- },
28
- "dependencies": {
29
- "typebox": "1.1.39",
30
- "@xgjktech/xg-openclaw-shared": "^0.2.2"
31
- },
32
- "devDependencies": {
33
- "@types/node": "^22.19.0",
34
- "openclaw": "2026.6.10",
35
- "typescript": "^5.6.0"
36
- },
37
- "peerDependencies": {
38
- "openclaw": ">=2026.6.10"
39
- },
40
- "peerDependenciesMeta": {
41
- "openclaw": {
42
- "optional": true
43
- }
44
- },
45
- "openclaw": {
46
- "extensions": [
47
- "./dist/index.js"
48
- ],
49
- "installDependencies": false,
50
- "install": {
51
- "localPath": ".",
52
- "defaultChoice": "local"
53
- }
54
- }
1
+ {
2
+ "name": "@xgjktech/xg-openclaw-harness-tools",
3
+ "version": "0.4.0",
4
+ "description": "企业工具插件:plan_write / plan_view / action_dispatch(OpenClaw 第三方工具插件)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.19.0"
9
+ },
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "files": [
13
+ "dist/**/*",
14
+ "!dist/*.test.*",
15
+ "openclaw.plugin.json"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org/"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "pretest": "npm run build",
25
+ "test": "node --test dist/*.test.js",
26
+ "prepublishOnly": "npm run build && npm run test"
27
+ },
28
+ "dependencies": {
29
+ "typebox": "1.1.39",
30
+ "@xgjktech/xg-openclaw-shared": "^0.2.2"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^22.19.0",
34
+ "openclaw": "2026.6.10",
35
+ "typescript": "^5.6.0"
36
+ },
37
+ "peerDependencies": {
38
+ "openclaw": ">=2026.6.10"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "openclaw": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "openclaw": {
46
+ "extensions": [
47
+ "./dist/index.js"
48
+ ],
49
+ "installDependencies": false,
50
+ "install": {
51
+ "localPath": ".",
52
+ "defaultChoice": "local"
53
+ }
54
+ }
55
55
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"plan-execute-tool.d.ts","sourceRoot":"","sources":["../src/plan-execute-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,SAAS,EAEf,MAAM,8BAA8B,CAAC;AAKtC,eAAO,MAAM,iBAAiB;;EAK7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,wBAAwB,QAQzB,CAAC;AAuBb,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,YAAY,CA+ClG"}