@xgjktech/xg-openclaw-harness-tools 0.1.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 +152 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/notify.d.ts +16 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +20 -0
- package/dist/plan-execute-tool.d.ts +26 -0
- package/dist/plan-execute-tool.d.ts.map +1 -0
- package/dist/plan-execute-tool.js +159 -0
- package/dist/plan-write-tool.d.ts +20 -0
- package/dist/plan-write-tool.d.ts.map +1 -0
- package/dist/plan-write-tool.js +106 -0
- package/dist/tool-json-result.d.ts +10 -0
- package/dist/tool-json-result.d.ts.map +1 -0
- package/dist/tool-json-result.js +15 -0
- package/openclaw.plugin.json +14 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# @xgjktech/xg-openclaw-harness-tools
|
|
2
|
+
|
|
3
|
+
OpenClaw 第三方工具插件:企业计划工具 `xg_plan_write` / `xg_plan_execute`。
|
|
4
|
+
|
|
5
|
+
用一份持久化的计划(SQLite 落盘)取代内置 `update_plan`(无状态、只回显、不落库),
|
|
6
|
+
并把推进进度尽力而为地通知到相关会话/群。
|
|
7
|
+
|
|
8
|
+
## 架构裁决:实时事件当前处于事实性休眠(务必先读)
|
|
9
|
+
|
|
10
|
+
**当前 OpenClaw 2026.6.10 版本下,缝隙B(实时事件推送)事实性休眠**:`xg_plan_execute`
|
|
11
|
+
的 `execute`/`factory` 期都没有正当途径获取当次 agent run 的真实 `runId`(详见
|
|
12
|
+
`docs/notes-plugin-entry.md` N2 的逐路径排查记录)。工具因此把 `routing.runId` 固定传空串,
|
|
13
|
+
`notify.emitPlanEvent` 据此恒定返回 `{emitted:false,reason:"no runId"}`,**不会真的发出事件**
|
|
14
|
+
(不伪造 runId 是硬性红线,见 `docs/04-互通设计.md` §2.4)。
|
|
15
|
+
|
|
16
|
+
**因此 IM 侧(或任何下游消费方)不得依赖实时事件感知计划进度。** 当前唯一可靠的主感知路径是:
|
|
17
|
+
|
|
18
|
+
1. **SQLite 读取**——用 `openPlanStoreReadonly(dbPath)` 只读反查 `<stateDir>/xgjktech/plans.sqlite`,
|
|
19
|
+
这是权威、始终正确的状态源(工具每次推进都先落盘、落盘成功才尝试发事件,见 `03-工具契约.md` §2.5)。
|
|
20
|
+
2. **agent 最终回复**——agent 完成任务后的文本回复本身会体现进度。
|
|
21
|
+
|
|
22
|
+
事件契约(`PlanEventPayload` / `isPlanEventPayload` / `planEventStream`)已按正式设计原样实现并保留,
|
|
23
|
+
**不是搁置未完成**,而是"契约就绪、通路等待宿主开放":一旦未来某个 OpenClaw 版本给插件暴露了
|
|
24
|
+
获取当次 `runId` 的 API,事件推送即可在**不改本插件代码**的前提下自动激活(`notify.ts` 的判断只看
|
|
25
|
+
`payload.runId` 是否非空)。
|
|
26
|
+
|
|
27
|
+
## 环境要求
|
|
28
|
+
|
|
29
|
+
- **Node ≥ 22.19.0**(依赖 `node:sqlite`,与 OpenClaw 自身 `engines` 一致)。
|
|
30
|
+
- monorepo 用 npm workspaces。
|
|
31
|
+
|
|
32
|
+
## 安装与构建
|
|
33
|
+
|
|
34
|
+
本仓库是包含 `@xgjktech/xg-openclaw-shared`(存储层+事件契约层)和
|
|
35
|
+
`@xgjktech/xg-openclaw-harness-tools`(本插件)两个 workspace 的 monorepo。**`xg-shared` 必须先于
|
|
36
|
+
本插件构建**(本插件依赖它的编译产物);npm workspaces 拓扑已保证顺序,直接在仓库根执行:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install
|
|
40
|
+
npm run build
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
产物:`packages/xg-harness-tools/dist/`(含 `index.js` 与 `.d.ts`)。
|
|
44
|
+
|
|
45
|
+
将本插件安装到 OpenClaw 的具体命令/目录约定,请对照 OpenClaw 的第三方插件安装机制
|
|
46
|
+
(global / workspace / config origin,本插件按非 bundled 第三方路径运行)以及
|
|
47
|
+
`package.json` 里的 `openclaw.*` 字段块(`extensions`/`install.localPath` 等)。
|
|
48
|
+
P1 阶段不锁死具体安装命令,避免与实际安装流程漂移(详见 `docs/05-部署文档.md` §3.3)。
|
|
49
|
+
|
|
50
|
+
## 配置
|
|
51
|
+
|
|
52
|
+
### 1. 屏蔽内置 update_plan(deny,唯一权威路径)
|
|
53
|
+
|
|
54
|
+
```jsonc
|
|
55
|
+
{
|
|
56
|
+
"tools": {
|
|
57
|
+
"deny": ["update_plan"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
无论生产 profile / allowlist 取何值(包括 `allow:["*"]`),`deny` 都一票否决,
|
|
63
|
+
与 profile 取值解耦。
|
|
64
|
+
|
|
65
|
+
### 2. 启用本插件两个工具(allow)
|
|
66
|
+
|
|
67
|
+
本插件两个工具都以 `optional:true` 注册,**默认不启用**,必须 allowlist 显式命中才会注册:
|
|
68
|
+
|
|
69
|
+
```jsonc
|
|
70
|
+
{
|
|
71
|
+
"tools": {
|
|
72
|
+
"allow": ["xg_plan_write", "xg_plan_execute"],
|
|
73
|
+
"deny": ["update_plan"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
可直接套用的完整样例见仓库根 [`deploy/tools.config.sample.jsonc`](../../deploy/tools.config.sample.jsonc)(带注释)。
|
|
79
|
+
|
|
80
|
+
### 3. stateDir 与数据库路径
|
|
81
|
+
|
|
82
|
+
插件运行时数据库路径固定为:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
<stateDir>/xgjktech/plans.sqlite
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`stateDir` 取自 OpenClaw 全局状态目录(`api.runtime.state.resolveStateDir()`,默认
|
|
89
|
+
`~/.openclaw/`,`OPENCLAW_STATE_DIR` 覆盖时同步),插件首次运行时通过 `resolvePlanDbPath`
|
|
90
|
+
自动 `mkdir -p` 出 `xgjktech/` 子目录,无需手动创建。IM 插件若要接入同一份数据,应复用
|
|
91
|
+
同一个 `resolvePlanDbPath` 以保证路径约定一致。
|
|
92
|
+
|
|
93
|
+
## 两个工具说明
|
|
94
|
+
|
|
95
|
+
### `xg_plan_write`
|
|
96
|
+
|
|
97
|
+
编写/覆盖一份完整计划(goal + tasks[],task 可含 steps[]),写入 SQLite。用于任务开始时
|
|
98
|
+
建立计划,或对既有计划做整体重写。
|
|
99
|
+
|
|
100
|
+
### `xg_plan_execute`
|
|
101
|
+
|
|
102
|
+
推进一份已保存计划的单个状态(`start_task` / `complete_task` / `block_task` /
|
|
103
|
+
`start_step` / `complete_step`),每次一步。落盘顺序:**先读最新状态 → 应用动作 →
|
|
104
|
+
不变量校验 → 落盘 → 尽力而为发事件**(先落盘、事件不影响主流程成败)。`block_task`
|
|
105
|
+
必须携带非空 `note` 说明阻塞原因。
|
|
106
|
+
|
|
107
|
+
两个工具的完整参数 schema 见 `src/plan-write-tool.ts` / `src/plan-execute-tool.ts`
|
|
108
|
+
中的 `PLAN_WRITE_DESCRIPTION` / `PLAN_EXECUTE_DESCRIPTION`。
|
|
109
|
+
|
|
110
|
+
## 部署 checklist
|
|
111
|
+
|
|
112
|
+
- [ ] Node ≥ 22.19.0
|
|
113
|
+
- [ ] monorepo 构建成功(`xg-shared` 先于本插件构建,workspaces 拓扑自动保证)
|
|
114
|
+
- [ ] 插件已安装且被 OpenClaw 识别为已加载的第三方插件(非 bundled)
|
|
115
|
+
- [ ] `tools.deny` 含 `update_plan`
|
|
116
|
+
- [ ] `tools.allow` 含 `xg_plan_write` + `xg_plan_execute`(或经 `group:plugins` / `*` 放行)
|
|
117
|
+
- [ ] 实拉一个 agent,验证工具清单**有本插件两工具、无 update_plan**(步骤见下节)
|
|
118
|
+
- [ ] `<stateDir>/xgjktech/` 可写,首次运行能生成 `plans.sqlite`
|
|
119
|
+
- [ ] (互通)IM 侧已依赖 `@xgjktech/xg-openclaw-shared` 并按 `docs/04-互通设计.md` 接入只读反查
|
|
120
|
+
|
|
121
|
+
## 实机验证步骤(可复现)+ 当前实际执行情况说明
|
|
122
|
+
|
|
123
|
+
**验收标准要求部署方按 checklist 实拉一个 agent,确认工具清单里有 `xg_plan_write`/
|
|
124
|
+
`xg_plan_execute`、没有 `update_plan`。以下是可复现的验证步骤:**
|
|
125
|
+
|
|
126
|
+
1. 按上文"配置"章节把 `tools.deny`/`tools.allow` 写入生产 agent/profile 配置。
|
|
127
|
+
2. 安装并加载本插件(确认加载日志/插件列表里出现 `xg-harness-tools`,origin 为第三方非 bundled)。
|
|
128
|
+
3. 拉起一个真实 agent 会话,触发一次会列出可用工具的动作(例如让 agent 描述自己有哪些工具,
|
|
129
|
+
或查看 gateway/日志里该次 attempt 实际注册的工具名列表)。
|
|
130
|
+
4. 核对该工具列表:应包含 `xg_plan_write` 与 `xg_plan_execute`,**不应包含** `update_plan`。
|
|
131
|
+
5. 调用一次 `xg_plan_write` 建一份最小计划,再调用 `xg_plan_execute` 推进一步,确认返回
|
|
132
|
+
`ok:true` 且 `event` 字段为 `{emitted:false,reason:"no runId"}`(与本文档"架构裁决"章节
|
|
133
|
+
一致,属预期行为非故障);用 `openPlanStoreReadonly` 反查该 planId,确认落盘内容与预期一致。
|
|
134
|
+
|
|
135
|
+
**本项在本次交付中未在真实 OpenClaw gateway 环境实机执行**——本仓库当前开发环境没有可用的
|
|
136
|
+
真实 OpenClaw gateway/agent 运行环境,因此无法诚实地报告"已实拉 agent 验证通过"。以下是
|
|
137
|
+
已经做过、可以作为间接佐证的验证,但**明确说明这不等于实机验证**:
|
|
138
|
+
|
|
139
|
+
- T2 阶段(`docs/notes-plugin-entry.md` N3.3)用**手工构造的 mock `api`**(捕获
|
|
140
|
+
`registerTool` 调用、手工构造 `toolContext.sessionKey` 等字段)驱动编译后的 `dist/index.js`
|
|
141
|
+
跑通过一次注册链路:`register(api)` → 捕获到 2 个工具的 `factory` → 调用 `factory` 拿到
|
|
142
|
+
`toolContext.sessionKey` → 调用 `execute()` → 返回结果里的 `sessionKey` 与传入值一致、
|
|
143
|
+
`planCount` 来自真实 `SqlitePlanStore.list()` 查询。**这验证的是插件代码本身可以被正确加载
|
|
144
|
+
并注册出预期的 2 个工具**,但 mock `api` 不是真实 OpenClaw gateway 的 `tools.allow`/`deny`
|
|
145
|
+
策略引擎,**不能**佐证"生产 deny/allow 配置生效后工具清单精确符合预期"这一条——那一条
|
|
146
|
+
必须在真实 gateway 环境按上面 5 步实测。
|
|
147
|
+
- T6 阶段(`packages/xg-harness-tools/src/integration.test.ts`)用真实的 `SqlitePlanStore`
|
|
148
|
+
(临时目录)+ 真实工具函数(`buildPlanWriteTool`/`buildPlanExecuteTool`)跑通了完整的
|
|
149
|
+
写入→推进→落盘→只读反查链路,但同样不经过 OpenClaw 的工具 allowlist/deny 策略引擎。
|
|
150
|
+
|
|
151
|
+
**结论:部署方在生产环境落实上述配置后,必须按"实机验证步骤"重新执行一次实拉 agent 验证,
|
|
152
|
+
不能以本仓库现有的 mock/集成测试结果代替。**
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAWA,wBAwDG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
|
|
2
|
+
import { resolvePlanDbPath, SqlitePlanStore } from "@xgjktech/xg-openclaw-shared";
|
|
3
|
+
import { PLAN_WRITE_DESCRIPTION, PlanWriteParams, buildPlanWriteTool } from "./plan-write-tool.js";
|
|
4
|
+
import { PLAN_EXECUTE_DESCRIPTION, PlanExecuteParams, buildPlanExecuteTool, } from "./plan-execute-tool.js";
|
|
5
|
+
const PLUGIN_ID = "xg-harness-tools";
|
|
6
|
+
export default defineToolPlugin({
|
|
7
|
+
id: PLUGIN_ID,
|
|
8
|
+
name: "XG Harness Tools",
|
|
9
|
+
description: "企业工具插件:计划编写与推进(plan_write / plan_execute)",
|
|
10
|
+
tools: (tool) => {
|
|
11
|
+
// store 单例:defineToolPlugin 的 tools(...) 只在插件加载时调用一次,
|
|
12
|
+
// 闭包在此建一次即可;factory 本身会在每次 agent attempt 重建工具集时重新调用。
|
|
13
|
+
let store;
|
|
14
|
+
const getStore = (api) => {
|
|
15
|
+
if (store === undefined) {
|
|
16
|
+
const dbPath = resolvePlanDbPath(api.runtime.state.resolveStateDir());
|
|
17
|
+
store = new SqlitePlanStore(dbPath);
|
|
18
|
+
}
|
|
19
|
+
return store;
|
|
20
|
+
};
|
|
21
|
+
return [
|
|
22
|
+
tool({
|
|
23
|
+
name: "xg_plan_write",
|
|
24
|
+
label: "【xg-harness】编写计划",
|
|
25
|
+
description: PLAN_WRITE_DESCRIPTION,
|
|
26
|
+
parameters: PlanWriteParams,
|
|
27
|
+
optional: true,
|
|
28
|
+
factory: ({ api }) => buildPlanWriteTool(getStore(api)),
|
|
29
|
+
}),
|
|
30
|
+
tool({
|
|
31
|
+
name: "xg_plan_execute",
|
|
32
|
+
label: "【xg-harness】推进计划",
|
|
33
|
+
description: PLAN_EXECUTE_DESCRIPTION,
|
|
34
|
+
parameters: PlanExecuteParams,
|
|
35
|
+
optional: true,
|
|
36
|
+
// T3 已实证(docs/notes-plugin-entry.md N2):execute/factory 期均无正当 runId
|
|
37
|
+
// 获取路径,routing.runId 只能留空——notify.emitPlanEvent 据此走
|
|
38
|
+
// {emitted:false,reason:"no runId"} 降级分支,不伪造 runId(04-互通设计 §2.4 红线1)。
|
|
39
|
+
factory: ({ api, toolContext }) => buildPlanExecuteTool({
|
|
40
|
+
store: getStore(api),
|
|
41
|
+
pluginId: PLUGIN_ID,
|
|
42
|
+
emitAgentEvent: (params) => api.agent.events.emitAgentEvent(params),
|
|
43
|
+
routing: {
|
|
44
|
+
runId: "",
|
|
45
|
+
...(toolContext.sessionKey !== undefined ? { sessionKey: toolContext.sessionKey } : {}),
|
|
46
|
+
...(toolContext.deliveryContext !== undefined
|
|
47
|
+
? { deliveryContext: toolContext.deliveryContext }
|
|
48
|
+
: {}),
|
|
49
|
+
...(toolContext.agentAccountId !== undefined
|
|
50
|
+
? { agentAccountId: toolContext.agentAccountId }
|
|
51
|
+
: {}),
|
|
52
|
+
...(toolContext.messageChannel !== undefined
|
|
53
|
+
? { messageChannel: toolContext.messageChannel }
|
|
54
|
+
: {}),
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
}),
|
|
58
|
+
];
|
|
59
|
+
},
|
|
60
|
+
});
|
package/dist/notify.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PluginAgentEventEmitParams, PluginAgentEventEmitResult } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { type PlanEventPayload } from "@xgjktech/xg-openclaw-shared";
|
|
3
|
+
export type EmitAgentEventFn = (params: PluginAgentEventEmitParams) => PluginAgentEventEmitResult;
|
|
4
|
+
/**
|
|
5
|
+
* 缝隙B 事件发送:尽力而为,不因失败中断主流程(04-互通设计 §2.4 契约)。
|
|
6
|
+
*
|
|
7
|
+
* 红线:禁止伪造 runId(架构裁决 2026-07-24)。T3 已实证 factory ctx / execute ctx /
|
|
8
|
+
* 插件可达的 runtime API 均无正当获取当次 run 真实 runId 的路径(见
|
|
9
|
+
* docs/notes-plugin-entry.md N2)——payload.runId 缺失时直接跳过发送,不抛异常。
|
|
10
|
+
*/
|
|
11
|
+
export declare function emitPlanEvent(params: {
|
|
12
|
+
pluginId: string;
|
|
13
|
+
emitAgentEvent: EmitAgentEventFn;
|
|
14
|
+
payload: PlanEventPayload;
|
|
15
|
+
}): PluginAgentEventEmitResult;
|
|
16
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,0BAA0B,EAE3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEtF,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,0BAA0B,KAAK,0BAA0B,CAAC;AAElG;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;IACjC,OAAO,EAAE,gBAAgB,CAAC;CAC3B,GAAG,0BAA0B,CAW7B"}
|
package/dist/notify.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { planEventStream } from "@xgjktech/xg-openclaw-shared";
|
|
2
|
+
/**
|
|
3
|
+
* 缝隙B 事件发送:尽力而为,不因失败中断主流程(04-互通设计 §2.4 契约)。
|
|
4
|
+
*
|
|
5
|
+
* 红线:禁止伪造 runId(架构裁决 2026-07-24)。T3 已实证 factory ctx / execute ctx /
|
|
6
|
+
* 插件可达的 runtime API 均无正当获取当次 run 真实 runId 的路径(见
|
|
7
|
+
* docs/notes-plugin-entry.md N2)——payload.runId 缺失时直接跳过发送,不抛异常。
|
|
8
|
+
*/
|
|
9
|
+
export function emitPlanEvent(params) {
|
|
10
|
+
const { pluginId, emitAgentEvent, payload } = params;
|
|
11
|
+
if (payload.runId.trim() === "") {
|
|
12
|
+
return { emitted: false, reason: "no runId" };
|
|
13
|
+
}
|
|
14
|
+
return emitAgentEvent({
|
|
15
|
+
runId: payload.runId,
|
|
16
|
+
stream: planEventStream(pluginId),
|
|
17
|
+
data: payload,
|
|
18
|
+
...(payload.sessionKey ? { sessionKey: payload.sessionKey } : {}),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
import { type PlanStore } from "@xgjktech/xg-openclaw-shared";
|
|
4
|
+
import { type EmitAgentEventFn } from "./notify.js";
|
|
5
|
+
export declare const PlanExecuteParams: Type.TObject<{
|
|
6
|
+
planId: Type.TString;
|
|
7
|
+
action: Type.TUnion<[Type.TLiteral<"start_task">, Type.TLiteral<"complete_task">, Type.TLiteral<"block_task">, Type.TLiteral<"start_step">, Type.TLiteral<"complete_step">]>;
|
|
8
|
+
taskIndex: Type.TInteger;
|
|
9
|
+
stepIndex: Type.TOptional<Type.TInteger>;
|
|
10
|
+
note: Type.TOptional<Type.TString>;
|
|
11
|
+
}>;
|
|
12
|
+
export type PlanExecuteParamsT = Static<typeof PlanExecuteParams>;
|
|
13
|
+
export declare const PLAN_EXECUTE_DESCRIPTION: string;
|
|
14
|
+
export declare function buildPlanExecuteTool(deps: {
|
|
15
|
+
store: PlanStore;
|
|
16
|
+
pluginId: string;
|
|
17
|
+
emitAgentEvent: EmitAgentEventFn;
|
|
18
|
+
routing: {
|
|
19
|
+
runId: string;
|
|
20
|
+
sessionKey?: string;
|
|
21
|
+
deliveryContext?: unknown;
|
|
22
|
+
agentAccountId?: string;
|
|
23
|
+
messageChannel?: string;
|
|
24
|
+
};
|
|
25
|
+
}): AnyAgentTool;
|
|
26
|
+
//# sourceMappingURL=plan-execute-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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,EAKL,KAAK,SAAS,EAEf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,eAAO,MAAM,iBAAiB;;;;;;EAsB7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,wBAAwB,QAazB,CAAC;AAiFb,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;IACjC,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH,GAAG,YAAY,CA8Df"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { assertPlanInvariants, PlanInvariantError, } from "@xgjktech/xg-openclaw-shared";
|
|
3
|
+
import { toolJsonResult } from "./tool-json-result.js";
|
|
4
|
+
import { emitPlanEvent } from "./notify.js";
|
|
5
|
+
export const PlanExecuteParams = Type.Object({
|
|
6
|
+
planId: Type.String({ minLength: 1, description: "要推进的计划 id" }),
|
|
7
|
+
action: Type.Union([
|
|
8
|
+
Type.Literal("start_task"),
|
|
9
|
+
Type.Literal("complete_task"),
|
|
10
|
+
Type.Literal("block_task"),
|
|
11
|
+
Type.Literal("start_step"),
|
|
12
|
+
Type.Literal("complete_step"),
|
|
13
|
+
], { description: "推进动作" }),
|
|
14
|
+
taskIndex: Type.Integer({ minimum: 0, description: "目标 task 在 tasks[] 的下标" }),
|
|
15
|
+
stepIndex: Type.Optional(Type.Integer({ minimum: 0, description: "start_step/complete_step 时必填,目标 step 下标" })),
|
|
16
|
+
note: Type.Optional(Type.String({ description: "block_task 时必填,说明阻塞原因;其他动作可选备注" })),
|
|
17
|
+
}, { additionalProperties: false });
|
|
18
|
+
export const PLAN_EXECUTE_DESCRIPTION = [
|
|
19
|
+
"用途:推进一份已保存的计划,一次推进一步,并把进度实时通知到相关会话/群。",
|
|
20
|
+
"",
|
|
21
|
+
"执行纪律(务必遵守):",
|
|
22
|
+
"- 一次一步:同一时刻整份计划最多一个进行中的 task,task 内最多一个进行中的 step。",
|
|
23
|
+
" 想开始新任务前,先把当前任务收尾(completed 或 blocked)。",
|
|
24
|
+
"- 验证后才 completed:只有真正做完并验证通过,才把 task/step 标 completed;",
|
|
25
|
+
" 没验证就别标完成。",
|
|
26
|
+
"- 遇阻塞就停下并记录:卡住时用 action=block_task 并在 note 写清楚卡在哪、需要谁/什么才能继续,",
|
|
27
|
+
" 不要绕过、不要假装完成。阻塞会被推送到群里等人回复——这正是本工具的价值。",
|
|
28
|
+
"",
|
|
29
|
+
"参数:planId + action + taskIndex(+ 视情况 stepIndex / note)。",
|
|
30
|
+
"每次成功推进都会落盘并发出进度事件;调用方无需手动通知。",
|
|
31
|
+
].join("\n");
|
|
32
|
+
class PlanExecuteValidationError extends Error {
|
|
33
|
+
code;
|
|
34
|
+
constructor(code, message) {
|
|
35
|
+
super(message ?? code);
|
|
36
|
+
this.name = "PlanExecuteValidationError";
|
|
37
|
+
this.code = code;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function requireTask(plan, taskIndex) {
|
|
41
|
+
const task = plan.tasks[taskIndex];
|
|
42
|
+
if (task === undefined) {
|
|
43
|
+
throw new PlanExecuteValidationError("INDEX_OUT_OF_RANGE", `taskIndex ${taskIndex} 越界`);
|
|
44
|
+
}
|
|
45
|
+
return task;
|
|
46
|
+
}
|
|
47
|
+
function requireStep(task, stepIndex) {
|
|
48
|
+
const step = task.steps?.[stepIndex];
|
|
49
|
+
if (step === undefined) {
|
|
50
|
+
throw new PlanExecuteValidationError("INDEX_OUT_OF_RANGE", `stepIndex ${stepIndex} 越界`);
|
|
51
|
+
}
|
|
52
|
+
return step;
|
|
53
|
+
}
|
|
54
|
+
function applyAction(plan, params) {
|
|
55
|
+
const task = requireTask(plan, params.taskIndex);
|
|
56
|
+
switch (params.action) {
|
|
57
|
+
case "start_task": {
|
|
58
|
+
task.status = "in_progress";
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
case "complete_task": {
|
|
62
|
+
task.status = "completed";
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
case "block_task": {
|
|
66
|
+
if (params.note === undefined || params.note.trim() === "") {
|
|
67
|
+
throw new PlanExecuteValidationError("NOTE_REQUIRED", "block_task 必须带非空 note");
|
|
68
|
+
}
|
|
69
|
+
task.status = "blocked";
|
|
70
|
+
task.note = params.note;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
case "start_step": {
|
|
74
|
+
if (params.stepIndex === undefined) {
|
|
75
|
+
throw new PlanExecuteValidationError("STEP_INDEX_REQUIRED", "start_step 必须带 stepIndex");
|
|
76
|
+
}
|
|
77
|
+
const step = requireStep(task, params.stepIndex);
|
|
78
|
+
step.status = "in_progress";
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
case "complete_step": {
|
|
82
|
+
if (params.stepIndex === undefined) {
|
|
83
|
+
throw new PlanExecuteValidationError("STEP_INDEX_REQUIRED", "complete_step 必须带 stepIndex");
|
|
84
|
+
}
|
|
85
|
+
const step = requireStep(task, params.stepIndex);
|
|
86
|
+
step.status = "completed";
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function summarize(plan) {
|
|
92
|
+
const summary = { total: 0, completed: 0, blocked: 0, inProgress: 0 };
|
|
93
|
+
for (const task of plan.tasks) {
|
|
94
|
+
summary.total += 1;
|
|
95
|
+
if (task.status === "completed")
|
|
96
|
+
summary.completed += 1;
|
|
97
|
+
if (task.status === "blocked")
|
|
98
|
+
summary.blocked += 1;
|
|
99
|
+
if (task.status === "in_progress")
|
|
100
|
+
summary.inProgress += 1;
|
|
101
|
+
}
|
|
102
|
+
return summary;
|
|
103
|
+
}
|
|
104
|
+
export function buildPlanExecuteTool(deps) {
|
|
105
|
+
const { store, pluginId, emitAgentEvent, routing } = deps;
|
|
106
|
+
return {
|
|
107
|
+
name: "xg_plan_execute",
|
|
108
|
+
label: "【xg-harness】推进计划",
|
|
109
|
+
description: PLAN_EXECUTE_DESCRIPTION,
|
|
110
|
+
parameters: PlanExecuteParams,
|
|
111
|
+
async execute(_toolCallId, rawParams) {
|
|
112
|
+
const params = rawParams;
|
|
113
|
+
try {
|
|
114
|
+
const plan = store.get(params.planId);
|
|
115
|
+
if (plan === undefined) {
|
|
116
|
+
const result = { ok: false, error: { code: "PLAN_NOT_FOUND" } };
|
|
117
|
+
return toolJsonResult(result);
|
|
118
|
+
}
|
|
119
|
+
applyAction(plan, params);
|
|
120
|
+
plan.updatedAt = new Date().toISOString();
|
|
121
|
+
assertPlanInvariants(plan);
|
|
122
|
+
store.set(plan);
|
|
123
|
+
const task = plan.tasks[params.taskIndex];
|
|
124
|
+
const payload = {
|
|
125
|
+
kind: "plan_update",
|
|
126
|
+
planId: plan.planId,
|
|
127
|
+
goal: plan.goal,
|
|
128
|
+
action: params.action,
|
|
129
|
+
taskIndex: params.taskIndex,
|
|
130
|
+
...(params.stepIndex !== undefined ? { stepIndex: params.stepIndex } : {}),
|
|
131
|
+
taskTitle: task.title,
|
|
132
|
+
taskStatus: task.status,
|
|
133
|
+
...(task.status === "blocked" && task.note !== undefined ? { blockedNote: task.note } : {}),
|
|
134
|
+
summary: summarize(plan),
|
|
135
|
+
runId: routing.runId,
|
|
136
|
+
...(routing.sessionKey !== undefined ? { sessionKey: routing.sessionKey } : {}),
|
|
137
|
+
...(routing.deliveryContext !== undefined ? { deliveryContext: routing.deliveryContext } : {}),
|
|
138
|
+
...(routing.agentAccountId !== undefined ? { agentAccountId: routing.agentAccountId } : {}),
|
|
139
|
+
...(routing.messageChannel !== undefined ? { messageChannel: routing.messageChannel } : {}),
|
|
140
|
+
};
|
|
141
|
+
const event = emitPlanEvent({ pluginId, emitAgentEvent, payload });
|
|
142
|
+
const result = { ok: true, planId: plan.planId, plan, event };
|
|
143
|
+
return toolJsonResult(result);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
const code = e instanceof PlanInvariantError
|
|
147
|
+
? "INVARIANT_VIOLATION"
|
|
148
|
+
: e instanceof PlanExecuteValidationError
|
|
149
|
+
? e.code
|
|
150
|
+
: "STORE_ERROR";
|
|
151
|
+
const result = {
|
|
152
|
+
ok: false,
|
|
153
|
+
error: { code, detail: String(e.message) },
|
|
154
|
+
};
|
|
155
|
+
return toolJsonResult(result);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
import { type PlanStore } from "@xgjktech/xg-openclaw-shared";
|
|
4
|
+
export declare const PlanWriteParams: Type.TObject<{
|
|
5
|
+
planId: Type.TOptional<Type.TString>;
|
|
6
|
+
goal: Type.TString;
|
|
7
|
+
tasks: Type.TArray<Type.TObject<{
|
|
8
|
+
title: Type.TString;
|
|
9
|
+
status: Type.TOptional<Type.TUnion<[Type.TLiteral<"pending">, Type.TLiteral<"in_progress">, Type.TLiteral<"completed">, Type.TLiteral<"blocked">]>>;
|
|
10
|
+
note: Type.TOptional<Type.TString>;
|
|
11
|
+
steps: Type.TOptional<Type.TArray<Type.TObject<{
|
|
12
|
+
text: Type.TString;
|
|
13
|
+
status: Type.TOptional<Type.TUnion<[Type.TLiteral<"pending">, Type.TLiteral<"in_progress">, Type.TLiteral<"completed">]>>;
|
|
14
|
+
}>>>;
|
|
15
|
+
}>>;
|
|
16
|
+
}>;
|
|
17
|
+
export type PlanWriteParamsT = Static<typeof PlanWriteParams>;
|
|
18
|
+
export declare const PLAN_WRITE_DESCRIPTION: string;
|
|
19
|
+
export declare function buildPlanWriteTool(store: PlanStore): AnyAgentTool;
|
|
20
|
+
//# sourceMappingURL=plan-write-tool.d.ts.map
|
|
@@ -0,0 +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,SAAS,EAKf,MAAM,8BAA8B,CAAC;AAoCtC,eAAO,MAAM,eAAe;;;;;;;;;;;;EAS3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,QAcvB,CAAC;AAsCb,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,CAwCjE"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { assertPlanInvariants, PlanInvariantError, } from "@xgjktech/xg-openclaw-shared";
|
|
4
|
+
import { toolJsonResult } from "./tool-json-result.js";
|
|
5
|
+
const StepInput = Type.Object({
|
|
6
|
+
text: Type.String({ minLength: 1, description: "单条可执行步骤,动词开头,无占位符" }),
|
|
7
|
+
status: Type.Optional(Type.Union([Type.Literal("pending"), Type.Literal("in_progress"), Type.Literal("completed")], { default: "pending" })),
|
|
8
|
+
}, { additionalProperties: false });
|
|
9
|
+
const TaskInput = Type.Object({
|
|
10
|
+
title: Type.String({ minLength: 1, description: "任务标题,bite-size,单次可完成并验证" }),
|
|
11
|
+
status: Type.Optional(Type.Union([
|
|
12
|
+
Type.Literal("pending"),
|
|
13
|
+
Type.Literal("in_progress"),
|
|
14
|
+
Type.Literal("completed"),
|
|
15
|
+
Type.Literal("blocked"),
|
|
16
|
+
], { default: "pending" })),
|
|
17
|
+
note: Type.Optional(Type.String({ description: "备注;status=blocked 时必填,说明阻塞原因" })),
|
|
18
|
+
steps: Type.Optional(Type.Array(StepInput, { description: "可选子步骤" })),
|
|
19
|
+
}, { additionalProperties: false });
|
|
20
|
+
export const PlanWriteParams = Type.Object({
|
|
21
|
+
planId: Type.Optional(Type.String({ description: "留空=新建计划并返回新 planId;填写=整体改写该计划" })),
|
|
22
|
+
goal: Type.String({ minLength: 1, description: "计划总目标,一句话说明完成态" }),
|
|
23
|
+
tasks: Type.Array(TaskInput, { minItems: 1, description: "任务清单,至少一项" }),
|
|
24
|
+
}, { additionalProperties: false });
|
|
25
|
+
export const PLAN_WRITE_DESCRIPTION = [
|
|
26
|
+
"用途:把一项工作拆成一份可执行计划并持久化保存(跨回合、跨天不丢失)。",
|
|
27
|
+
"",
|
|
28
|
+
"何时用:接到多步骤任务、需要先规划再动手时,先用本工具写下计划。",
|
|
29
|
+
"",
|
|
30
|
+
"编写纪律(务必遵守):",
|
|
31
|
+
"- Bite-size:每个 task 是一次可独立完成并验证的最小工作单元,别写成大而空的阶段。",
|
|
32
|
+
"- 无占位符:task/step 文案必须是具体动作,禁止 \"TODO/待定/等等\" 这类占位。",
|
|
33
|
+
"- 可验证:每个 task 应能明确判断\"做完没有\",把验证方式写进 title 或 note。",
|
|
34
|
+
"- 顺序清晰:tasks 按依赖顺序排列,读者不需要猜先后。",
|
|
35
|
+
"",
|
|
36
|
+
"参数:goal=总目标;tasks=任务清单(含可选 steps 子步骤)。",
|
|
37
|
+
"留空 planId=新建(返回新 planId 供后续 plan_execute 使用);填 planId=整体改写。",
|
|
38
|
+
"不要在这里推进状态——推进用 xg_plan_execute。",
|
|
39
|
+
].join("\n");
|
|
40
|
+
function toStep(input) {
|
|
41
|
+
return {
|
|
42
|
+
text: input.text,
|
|
43
|
+
status: (input.status ?? "pending"),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function toTask(input) {
|
|
47
|
+
const task = {
|
|
48
|
+
title: input.title,
|
|
49
|
+
status: (input.status ?? "pending"),
|
|
50
|
+
};
|
|
51
|
+
if (input.note !== undefined) {
|
|
52
|
+
task.note = input.note;
|
|
53
|
+
}
|
|
54
|
+
if (input.steps !== undefined) {
|
|
55
|
+
task.steps = input.steps.map(toStep);
|
|
56
|
+
}
|
|
57
|
+
return task;
|
|
58
|
+
}
|
|
59
|
+
function buildPlan(params, existing, planId) {
|
|
60
|
+
const now = new Date().toISOString();
|
|
61
|
+
return {
|
|
62
|
+
planId,
|
|
63
|
+
goal: params.goal,
|
|
64
|
+
createdAt: existing?.createdAt ?? now,
|
|
65
|
+
updatedAt: now,
|
|
66
|
+
tasks: params.tasks.map(toTask),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function buildPlanWriteTool(store) {
|
|
70
|
+
return {
|
|
71
|
+
name: "xg_plan_write",
|
|
72
|
+
label: "【xg-harness】编写计划",
|
|
73
|
+
description: PLAN_WRITE_DESCRIPTION,
|
|
74
|
+
parameters: PlanWriteParams,
|
|
75
|
+
async execute(_toolCallId, rawParams) {
|
|
76
|
+
const params = rawParams;
|
|
77
|
+
try {
|
|
78
|
+
let existing;
|
|
79
|
+
if (params.planId !== undefined) {
|
|
80
|
+
existing = store.get(params.planId);
|
|
81
|
+
if (existing === undefined) {
|
|
82
|
+
const result = {
|
|
83
|
+
ok: false,
|
|
84
|
+
error: { code: "PLAN_NOT_FOUND" },
|
|
85
|
+
};
|
|
86
|
+
return toolJsonResult(result);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const planId = params.planId ?? randomUUID();
|
|
90
|
+
const plan = buildPlan(params, existing, planId);
|
|
91
|
+
assertPlanInvariants(plan);
|
|
92
|
+
store.set(plan);
|
|
93
|
+
const result = { ok: true, planId: plan.planId, plan };
|
|
94
|
+
return toolJsonResult(result);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
const code = e instanceof PlanInvariantError ? "INVARIANT_VIOLATION" : "STORE_ERROR";
|
|
98
|
+
const result = {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: { code, detail: String(e.message) },
|
|
101
|
+
};
|
|
102
|
+
return toolJsonResult(result);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
type ToolExecuteReturn = Awaited<ReturnType<NonNullable<AnyAgentTool["execute"]>>>;
|
|
3
|
+
/**
|
|
4
|
+
* 与 `openclaw/plugin-sdk` 的 `jsonResult` 返回结构一致(AgentToolResult),供工具 execute 使用。
|
|
5
|
+
* 不直接 import SDK 的 `jsonResult`:部分打包环境下该具名导出会被打成 undefined,调用即崩
|
|
6
|
+
* (IM 插件 `tool-json-result.ts` 已踩过此坑,见 questions.md Q5)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function toolJsonResult(payload: unknown): ToolExecuteReturn;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=tool-json-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-json-result.d.ts","sourceRoot":"","sources":["../src/tool-json-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAErE,KAAK,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CASlE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 与 `openclaw/plugin-sdk` 的 `jsonResult` 返回结构一致(AgentToolResult),供工具 execute 使用。
|
|
3
|
+
* 不直接 import SDK 的 `jsonResult`:部分打包环境下该具名导出会被打成 undefined,调用即崩
|
|
4
|
+
* (IM 插件 `tool-json-result.ts` 已踩过此坑,见 questions.md Q5)。
|
|
5
|
+
*/
|
|
6
|
+
export function toolJsonResult(payload) {
|
|
7
|
+
if (payload === undefined) {
|
|
8
|
+
return { content: [{ type: "text", text: "" }] };
|
|
9
|
+
}
|
|
10
|
+
const text = typeof payload === "string" ? JSON.stringify(payload) : JSON.stringify(payload, null, 2);
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text", text }],
|
|
13
|
+
details: payload,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "xg-harness-tools",
|
|
3
|
+
"name": "XG Harness Tools",
|
|
4
|
+
"description": "企业计划工具(plan_write / plan_execute)",
|
|
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"]
|
|
13
|
+
}
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xgjktech/xg-openclaw-harness-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "企业工具插件:plan_write / plan_execute 计划工具(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.1.0"
|
|
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
|
+
}
|