@xgjktech/xg-openclaw-harness-tools 0.3.1 → 0.3.3
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/dist/action-dispatch-tool.d.ts +15 -0
- package/dist/action-dispatch-tool.d.ts.map +1 -0
- package/dist/action-dispatch-tool.js +96 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -11
- package/dist/plan-execute-tool.d.ts +1 -1
- package/dist/plan-execute-tool.d.ts.map +1 -1
- package/dist/plan-execute-tool.js +6 -1
- package/dist/plan-view.d.ts +1 -1
- package/dist/plan-view.d.ts.map +1 -1
- package/dist/plan-view.js +3 -1
- package/dist/plan-write-tool.d.ts +4 -3
- package/dist/plan-write-tool.d.ts.map +1 -1
- package/dist/plan-write-tool.js +98 -11
- package/package.json +3 -3
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
import { type ActionStore, type PlanOrigin } from "@xgjktech/xg-openclaw-shared";
|
|
4
|
+
export declare const ActionDispatchParams: Type.TObject<{
|
|
5
|
+
action: Type.TUnion<[Type.TLiteral<"ui_refresh">, Type.TLiteral<"notification">, Type.TLiteral<"workflow_trigger">, Type.TLiteral<"form_fill">, Type.TString]>;
|
|
6
|
+
target: Type.TString;
|
|
7
|
+
topic: Type.TOptional<Type.TString>;
|
|
8
|
+
status: Type.TOptional<Type.TUnion<[Type.TLiteral<"completed">, Type.TLiteral<"running">, Type.TLiteral<"pending">, Type.TLiteral<"failed">]>>;
|
|
9
|
+
activeSessionOnly: Type.TOptional<Type.TBoolean>;
|
|
10
|
+
payloadData: Type.TRecord<"^.*$", Type.TUnknown>;
|
|
11
|
+
}>;
|
|
12
|
+
export type ActionDispatchParamsT = Static<typeof ActionDispatchParams>;
|
|
13
|
+
export declare const ACTION_DISPATCH_DESCRIPTION: string;
|
|
14
|
+
export declare function buildActionDispatchTool(store: ActionStore, origin?: PlanOrigin): AnyAgentTool;
|
|
15
|
+
//# sourceMappingURL=action-dispatch-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-dispatch-tool.d.ts","sourceRoot":"","sources":["../src/action-dispatch-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,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AAGtC,eAAO,MAAM,oBAAoB;;;;;;;EA+ChC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,2BAA2B,QAS5B,CAAC;AAMb,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CA0D7F"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { assertActionDispatchEventInvariants, } from "@xgjktech/xg-openclaw-shared";
|
|
4
|
+
import { toolJsonResult } from "./tool-json-result.js";
|
|
5
|
+
export const ActionDispatchParams = Type.Object({
|
|
6
|
+
action: Type.Union([
|
|
7
|
+
Type.Literal("ui_refresh"),
|
|
8
|
+
Type.Literal("notification"),
|
|
9
|
+
Type.Literal("workflow_trigger"),
|
|
10
|
+
Type.Literal("form_fill"),
|
|
11
|
+
Type.String({ description: "自定义扩展动作类型" }),
|
|
12
|
+
], { description: "指令动作大类" }),
|
|
13
|
+
target: Type.String({
|
|
14
|
+
minLength: 1,
|
|
15
|
+
maxLength: 128,
|
|
16
|
+
description: "目标组件或页面标识 (必须严格等于前端注册的 target 标识,如 order_detail_panel)",
|
|
17
|
+
}),
|
|
18
|
+
topic: Type.Optional(Type.String({
|
|
19
|
+
maxLength: 64,
|
|
20
|
+
description: "业务主题/模块标识 (如 order_management, user_system)",
|
|
21
|
+
})),
|
|
22
|
+
status: Type.Optional(Type.Union([
|
|
23
|
+
Type.Literal("completed"),
|
|
24
|
+
Type.Literal("running"),
|
|
25
|
+
Type.Literal("pending"),
|
|
26
|
+
Type.Literal("failed"),
|
|
27
|
+
], { default: "completed", description: "动作生命周期状态" })),
|
|
28
|
+
activeSessionOnly: Type.Optional(Type.Boolean({ default: true, description: "是否限制仅在当前激活前端窗口生效" })),
|
|
29
|
+
payloadData: Type.Record(Type.String(), Type.Unknown(), {
|
|
30
|
+
description: "传递给前端的具体业务数据键值对 (格式为 { data: { ... } })",
|
|
31
|
+
}),
|
|
32
|
+
}, { additionalProperties: false });
|
|
33
|
+
export const ACTION_DISPATCH_DESCRIPTION = [
|
|
34
|
+
"用途:分发通用动作指令(如 UI 局部刷新 ui_refresh、通知 notification、工作流触发 workflow_trigger、表单填充 form_fill 等)给前端客户端消费。",
|
|
35
|
+
"",
|
|
36
|
+
"参数要求:",
|
|
37
|
+
"- action: 动作类型枚举 (ui_refresh, notification, workflow_trigger, form_fill 或自定义扩展动作)。",
|
|
38
|
+
"- target: 目标组件或页面标识 (如 order_detail_panel)。",
|
|
39
|
+
"- payloadData: 包含要传递给前端业务数据的键值字典。",
|
|
40
|
+
"- activeSessionOnly: 默认为 true,限制仅在活跃前端窗口生效。",
|
|
41
|
+
"- status: 默认为 completed,异步长任务可用 running/pending/failed 表达状态变化。",
|
|
42
|
+
].join("\n");
|
|
43
|
+
export function buildActionDispatchTool(store, origin) {
|
|
44
|
+
return {
|
|
45
|
+
name: "xg_action_dispatch",
|
|
46
|
+
label: "【xg-harness】动作指令分发",
|
|
47
|
+
description: ACTION_DISPATCH_DESCRIPTION,
|
|
48
|
+
parameters: ActionDispatchParams,
|
|
49
|
+
async execute(_toolCallId, rawParams) {
|
|
50
|
+
const params = rawParams;
|
|
51
|
+
try {
|
|
52
|
+
const eventId = `evt_${randomUUID()}`;
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const version = now.getTime();
|
|
55
|
+
const status = params.status ?? "completed";
|
|
56
|
+
const event = {
|
|
57
|
+
eventId,
|
|
58
|
+
event: "action_dispatch",
|
|
59
|
+
action: params.action,
|
|
60
|
+
target: params.target,
|
|
61
|
+
version,
|
|
62
|
+
status,
|
|
63
|
+
scopeControl: {
|
|
64
|
+
activeSessionOnly: params.activeSessionOnly ?? true,
|
|
65
|
+
},
|
|
66
|
+
payload: {
|
|
67
|
+
data: params.payloadData,
|
|
68
|
+
},
|
|
69
|
+
timestamp: now.toISOString(),
|
|
70
|
+
};
|
|
71
|
+
if (params.topic !== undefined && params.topic.trim() !== "") {
|
|
72
|
+
event.topic = params.topic;
|
|
73
|
+
}
|
|
74
|
+
if (origin !== undefined) {
|
|
75
|
+
event.origin = origin;
|
|
76
|
+
}
|
|
77
|
+
assertActionDispatchEventInvariants(event);
|
|
78
|
+
store.set(event);
|
|
79
|
+
const result = {
|
|
80
|
+
ok: true,
|
|
81
|
+
eventId: event.eventId,
|
|
82
|
+
version: event.version,
|
|
83
|
+
event,
|
|
84
|
+
};
|
|
85
|
+
return toolJsonResult(result);
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
const result = {
|
|
89
|
+
ok: false,
|
|
90
|
+
error: { code: "ACTION_DISPATCH_ERROR", detail: String(e.message) },
|
|
91
|
+
};
|
|
92
|
+
return toolJsonResult(result);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAiBA,wBAqDG"}
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
|
|
2
|
-
import { resolvePlanDbPath, SqlitePlanStore } from "@xgjktech/xg-openclaw-shared";
|
|
2
|
+
import { resolvePlanDbPath, SqlitePlanStore, SqliteActionStore } from "@xgjktech/xg-openclaw-shared";
|
|
3
3
|
import { PLAN_WRITE_DESCRIPTION, PlanWriteParams, buildPlanWriteTool } from "./plan-write-tool.js";
|
|
4
4
|
import { PLAN_EXECUTE_DESCRIPTION, PlanExecuteParams, buildPlanExecuteTool, } from "./plan-execute-tool.js";
|
|
5
|
+
import { ACTION_DISPATCH_DESCRIPTION, ActionDispatchParams, buildActionDispatchTool, } from "./action-dispatch-tool.js";
|
|
5
6
|
import { toPlanOrigin } from "./plan-origin.js";
|
|
6
7
|
const PLUGIN_ID = "xg-harness-tools";
|
|
7
8
|
export default defineToolPlugin({
|
|
8
9
|
id: PLUGIN_ID,
|
|
9
10
|
name: "XG Harness Tools",
|
|
10
|
-
description: "
|
|
11
|
+
description: "企业工具插件:计划管理与通用动作分发(plan_write / plan_execute / action_dispatch)",
|
|
11
12
|
tools: (tool) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (store === undefined) {
|
|
13
|
+
let planStore;
|
|
14
|
+
let actionStore;
|
|
15
|
+
const getPlanStore = (api) => {
|
|
16
|
+
if (planStore === undefined) {
|
|
17
17
|
const dbPath = resolvePlanDbPath(api.runtime.state.resolveStateDir());
|
|
18
|
-
|
|
18
|
+
planStore = new SqlitePlanStore(dbPath);
|
|
19
19
|
}
|
|
20
|
-
return
|
|
20
|
+
return planStore;
|
|
21
|
+
};
|
|
22
|
+
const getActionStore = (api) => {
|
|
23
|
+
if (actionStore === undefined) {
|
|
24
|
+
const dbPath = resolvePlanDbPath(api.runtime.state.resolveStateDir());
|
|
25
|
+
actionStore = new SqliteActionStore(dbPath);
|
|
26
|
+
}
|
|
27
|
+
return actionStore;
|
|
21
28
|
};
|
|
22
29
|
return [
|
|
23
30
|
tool({
|
|
@@ -26,7 +33,7 @@ export default defineToolPlugin({
|
|
|
26
33
|
description: PLAN_WRITE_DESCRIPTION,
|
|
27
34
|
parameters: PlanWriteParams,
|
|
28
35
|
optional: true,
|
|
29
|
-
factory: ({ api, toolContext }) => buildPlanWriteTool(
|
|
36
|
+
factory: ({ api, toolContext }) => buildPlanWriteTool(getPlanStore(api), toPlanOrigin(toolContext)),
|
|
30
37
|
}),
|
|
31
38
|
tool({
|
|
32
39
|
name: "xg_plan_execute",
|
|
@@ -34,7 +41,15 @@ export default defineToolPlugin({
|
|
|
34
41
|
description: PLAN_EXECUTE_DESCRIPTION,
|
|
35
42
|
parameters: PlanExecuteParams,
|
|
36
43
|
optional: true,
|
|
37
|
-
factory: ({ api, toolContext }) => buildPlanExecuteTool({ store:
|
|
44
|
+
factory: ({ api, toolContext }) => buildPlanExecuteTool({ store: getPlanStore(api), origin: toPlanOrigin(toolContext) }),
|
|
45
|
+
}),
|
|
46
|
+
tool({
|
|
47
|
+
name: "xg_action_dispatch",
|
|
48
|
+
label: "【xg-harness】动作指令分发",
|
|
49
|
+
description: ACTION_DISPATCH_DESCRIPTION,
|
|
50
|
+
parameters: ActionDispatchParams,
|
|
51
|
+
optional: true,
|
|
52
|
+
factory: ({ api, toolContext }) => buildActionDispatchTool(getActionStore(api), toPlanOrigin(toolContext)),
|
|
38
53
|
}),
|
|
39
54
|
];
|
|
40
55
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
2
|
import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
-
import type
|
|
3
|
+
import { type PlanOrigin, type PlanStore } from "@xgjktech/xg-openclaw-shared";
|
|
4
4
|
export declare const PlanExecuteParams: Type.TObject<{
|
|
5
5
|
planId: Type.TOptional<Type.TString>;
|
|
6
6
|
}>;
|
|
@@ -1 +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,KAAK,
|
|
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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { isPlanCancelled, } from "@xgjktech/xg-openclaw-shared";
|
|
2
3
|
import { renderPlanView } from "./plan-view.js";
|
|
3
4
|
import { planInScope, scopeKey } from "./plan-scope.js";
|
|
4
5
|
import { toolJsonResult } from "./tool-json-result.js";
|
|
@@ -46,7 +47,11 @@ export function buildPlanExecuteTool(deps) {
|
|
|
46
47
|
if (params.planId === undefined) {
|
|
47
48
|
const plans = scope === undefined
|
|
48
49
|
? []
|
|
49
|
-
: store
|
|
50
|
+
: store
|
|
51
|
+
.listPlansByOrigin(scope)
|
|
52
|
+
.filter((plan) => !isPlanCancelled(plan))
|
|
53
|
+
.slice(0, 10)
|
|
54
|
+
.map(summarizePlan);
|
|
50
55
|
const result = { ok: true, plans };
|
|
51
56
|
return toolJsonResult(result);
|
|
52
57
|
}
|
package/dist/plan-view.d.ts
CHANGED
package/dist/plan-view.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-view.d.ts","sourceRoot":"","sources":["../src/plan-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
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"}
|
package/dist/plan-view.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlanCancelled, } from "@xgjktech/xg-openclaw-shared";
|
|
1
2
|
const TASK_STATUS_MARKERS = {
|
|
2
3
|
pending: "[ ]",
|
|
3
4
|
in_progress: "[>]",
|
|
@@ -14,7 +15,8 @@ function singleLine(text) {
|
|
|
14
15
|
return text.replace(/\s*\r?\n\s*/g, " ");
|
|
15
16
|
}
|
|
16
17
|
export function renderPlanView(plan) {
|
|
17
|
-
const
|
|
18
|
+
const cancelledMarker = isPlanCancelled(plan) ? "【已取消】" : "";
|
|
19
|
+
const lines = [`goal: ${cancelledMarker}${singleLine(plan.goal)}`];
|
|
18
20
|
plan.tasks.forEach((task, taskIndex) => {
|
|
19
21
|
const note = task.note !== undefined ? ` | note: ${singleLine(task.note)}` : "";
|
|
20
22
|
lines.push(`T${taskIndex} ${TASK_STATUS_MARKERS[task.status]} ${singleLine(task.title)}${note}`);
|
|
@@ -3,12 +3,13 @@ import type { AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
|
|
|
3
3
|
import { type PlanOrigin, type PlanStore } from "@xgjktech/xg-openclaw-shared";
|
|
4
4
|
export declare const PlanWriteParams: Type.TObject<{
|
|
5
5
|
planId: Type.TOptional<Type.TString>;
|
|
6
|
-
goal: Type.TString
|
|
7
|
-
tasks: Type.TArray<Type.TObject<{
|
|
6
|
+
goal: Type.TOptional<Type.TString>;
|
|
7
|
+
tasks: Type.TOptional<Type.TArray<Type.TObject<{
|
|
8
8
|
title: Type.TString;
|
|
9
9
|
status: Type.TOptional<Type.TUnion<[Type.TLiteral<"pending">, Type.TLiteral<"in_progress">, Type.TLiteral<"completed">, Type.TLiteral<"blocked">]>>;
|
|
10
10
|
note: Type.TOptional<Type.TString>;
|
|
11
|
-
}
|
|
11
|
+
}>>>;
|
|
12
|
+
cancel: Type.TOptional<Type.TBoolean>;
|
|
12
13
|
}>;
|
|
13
14
|
export type PlanWriteParamsT = Static<typeof PlanWriteParams>;
|
|
14
15
|
export declare const PLAN_WRITE_DESCRIPTION: string;
|
|
@@ -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,
|
|
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"}
|
package/dist/plan-write-tool.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { assertPlanInvariants, } from "@xgjktech/xg-openclaw-shared";
|
|
3
|
+
import { assertPlanInvariants, isPlanCancelled, } from "@xgjktech/xg-openclaw-shared";
|
|
4
4
|
import { toolJsonResult } from "./tool-json-result.js";
|
|
5
5
|
import { renderPlanView } from "./plan-view.js";
|
|
6
6
|
import { planInScope, scopeKey } from "./plan-scope.js";
|
|
@@ -20,16 +20,17 @@ const TaskInput = Type.Object({
|
|
|
20
20
|
}, { additionalProperties: false });
|
|
21
21
|
export const PlanWriteParams = Type.Object({
|
|
22
22
|
planId: Type.Optional(Type.String({ maxLength: 128, description: "留空=新建;填写=整张覆盖该计划" })),
|
|
23
|
-
goal: Type.String({
|
|
23
|
+
goal: Type.Optional(Type.String({
|
|
24
24
|
minLength: 1,
|
|
25
25
|
maxLength: 500,
|
|
26
26
|
description: "计划总目标,一句话说明完成态",
|
|
27
|
-
}),
|
|
28
|
-
tasks: Type.Array(TaskInput, {
|
|
27
|
+
})),
|
|
28
|
+
tasks: Type.Optional(Type.Array(TaskInput, {
|
|
29
29
|
minItems: 1,
|
|
30
30
|
maxItems: 20,
|
|
31
31
|
description: "完整任务清单;更新时未提交的旧任务会被删除",
|
|
32
|
-
}),
|
|
32
|
+
})),
|
|
33
|
+
cancel: Type.Optional(Type.Boolean({ description: "取消该计划;true 时只需带 planId,不带 goal/tasks" })),
|
|
33
34
|
}, { additionalProperties: false });
|
|
34
35
|
export const PLAN_WRITE_DESCRIPTION = [
|
|
35
36
|
"用途:维护一份对用户可见、可跨回合保存的任务清单。每次调用都是整张替换,不是增量更新。",
|
|
@@ -46,9 +47,10 @@ export const PLAN_WRITE_DESCRIPTION = [
|
|
|
46
47
|
"",
|
|
47
48
|
"提交规则:",
|
|
48
49
|
"- 新建时不传 planId;更新时传入原 planId。",
|
|
49
|
-
"-
|
|
50
|
+
"- 更新计划内容时必须重新提交 goal 和所有要保留的 tasks,包括未变化任务及其当前 status/note。只提交变化项会删除其他任务;记不清时先用 xg_plan_execute 查看。",
|
|
50
51
|
"- goal 用一句话;tasks 通常 3-7 项,每项是能判断是否完成的具体动作,不写 TODO、待定或内部思考。",
|
|
51
52
|
"- status 为 pending、in_progress、completed 或 blocked。通常只有一项 in_progress,确实并行时可以有多项;blocked 时必须用 note 写明原因和继续所需条件。",
|
|
53
|
+
"- 同一件工作只维护一份计划;任务方向变化时,先取消不再推进的旧计划({planId, cancel:true})再新建。",
|
|
52
54
|
].join("\n");
|
|
53
55
|
function toTask(input) {
|
|
54
56
|
const task = {
|
|
@@ -60,14 +62,13 @@ function toTask(input) {
|
|
|
60
62
|
}
|
|
61
63
|
return task;
|
|
62
64
|
}
|
|
63
|
-
function buildPlan(
|
|
64
|
-
const now = new Date().toISOString();
|
|
65
|
+
function buildPlan(goal, tasks, existing, planId, origin, now) {
|
|
65
66
|
const plan = {
|
|
66
67
|
planId,
|
|
67
|
-
goal
|
|
68
|
+
goal,
|
|
68
69
|
createdAt: existing?.createdAt ?? now,
|
|
69
70
|
updatedAt: now,
|
|
70
|
-
tasks:
|
|
71
|
+
tasks: tasks.map(toTask),
|
|
71
72
|
};
|
|
72
73
|
// 改写已有计划时保留原 origin(改写是内容编辑,不是重新发起)。
|
|
73
74
|
// 无 origin 的旧计划已在作用域校验阶段被拒绝,不会在这里被认领。
|
|
@@ -77,6 +78,34 @@ function buildPlan(params, existing, planId, origin) {
|
|
|
77
78
|
}
|
|
78
79
|
return plan;
|
|
79
80
|
}
|
|
81
|
+
function compactText(text) {
|
|
82
|
+
return text.replace(/\s*\r?\n\s*/g, " ");
|
|
83
|
+
}
|
|
84
|
+
function relativeTime(updatedAt, nowMs) {
|
|
85
|
+
const updatedAtMs = Date.parse(updatedAt);
|
|
86
|
+
if (!Number.isFinite(updatedAtMs))
|
|
87
|
+
return "时间未知";
|
|
88
|
+
const elapsedMs = Math.max(0, nowMs - updatedAtMs);
|
|
89
|
+
const minutes = Math.floor(elapsedMs / 60_000);
|
|
90
|
+
if (minutes < 1)
|
|
91
|
+
return "刚刚";
|
|
92
|
+
if (minutes < 60)
|
|
93
|
+
return `${minutes} 分钟前`;
|
|
94
|
+
const hours = Math.floor(minutes / 60);
|
|
95
|
+
if (hours < 24)
|
|
96
|
+
return `${hours} 小时前`;
|
|
97
|
+
return `${Math.floor(hours / 24)} 天前`;
|
|
98
|
+
}
|
|
99
|
+
function unfinishedPlanWarning(plans, nowMs) {
|
|
100
|
+
const unfinished = plans.filter((plan) => !isPlanCancelled(plan) && plan.tasks.some((task) => task.status !== "completed"));
|
|
101
|
+
if (unfinished.length === 0)
|
|
102
|
+
return undefined;
|
|
103
|
+
const details = unfinished.slice(0, 3).map((plan) => {
|
|
104
|
+
const completed = plan.tasks.filter((task) => task.status === "completed").length;
|
|
105
|
+
return `${plan.planId} «${compactText(plan.goal)}»(${completed}/${plan.tasks.length},最后更新 ${relativeTime(plan.updatedAt, nowMs)})`;
|
|
106
|
+
});
|
|
107
|
+
return `本会话还有 ${unfinished.length} 份未完成计划:${details.join(";")}。若已不再推进,请用 {planId, cancel:true} 取消,避免面板残留。`;
|
|
108
|
+
}
|
|
80
109
|
export function buildPlanWriteTool(store, origin) {
|
|
81
110
|
const scope = scopeKey(origin);
|
|
82
111
|
return {
|
|
@@ -87,6 +116,57 @@ export function buildPlanWriteTool(store, origin) {
|
|
|
87
116
|
async execute(_toolCallId, rawParams) {
|
|
88
117
|
const params = rawParams;
|
|
89
118
|
try {
|
|
119
|
+
if (params.cancel === true) {
|
|
120
|
+
if (params.planId === undefined ||
|
|
121
|
+
params.goal !== undefined ||
|
|
122
|
+
params.tasks !== undefined) {
|
|
123
|
+
const result = {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: { code: "CANCEL_FIELDS_CONFLICT" },
|
|
126
|
+
};
|
|
127
|
+
return toolJsonResult(result);
|
|
128
|
+
}
|
|
129
|
+
const existing = store.get(params.planId);
|
|
130
|
+
if (existing === undefined || !planInScope(existing, scope)) {
|
|
131
|
+
const result = {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: { code: "PLAN_NOT_FOUND" },
|
|
134
|
+
};
|
|
135
|
+
return toolJsonResult(result);
|
|
136
|
+
}
|
|
137
|
+
if (isPlanCancelled(existing)) {
|
|
138
|
+
const result = {
|
|
139
|
+
ok: true,
|
|
140
|
+
planId: existing.planId,
|
|
141
|
+
cancelled: true,
|
|
142
|
+
view: renderPlanView(existing),
|
|
143
|
+
};
|
|
144
|
+
return toolJsonResult(result);
|
|
145
|
+
}
|
|
146
|
+
const now = new Date().toISOString();
|
|
147
|
+
const cancelledPlan = {
|
|
148
|
+
...existing,
|
|
149
|
+
status: "cancelled",
|
|
150
|
+
cancelledAt: now,
|
|
151
|
+
updatedAt: now,
|
|
152
|
+
};
|
|
153
|
+
assertPlanInvariants(cancelledPlan);
|
|
154
|
+
store.set(cancelledPlan);
|
|
155
|
+
const result = {
|
|
156
|
+
ok: true,
|
|
157
|
+
planId: cancelledPlan.planId,
|
|
158
|
+
cancelled: true,
|
|
159
|
+
view: renderPlanView(cancelledPlan),
|
|
160
|
+
};
|
|
161
|
+
return toolJsonResult(result);
|
|
162
|
+
}
|
|
163
|
+
if (params.goal === undefined || params.tasks === undefined) {
|
|
164
|
+
const result = {
|
|
165
|
+
ok: false,
|
|
166
|
+
error: { code: "WRITE_FIELDS_REQUIRED" },
|
|
167
|
+
};
|
|
168
|
+
return toolJsonResult(result);
|
|
169
|
+
}
|
|
90
170
|
let existing;
|
|
91
171
|
if (params.planId !== undefined) {
|
|
92
172
|
existing = store.get(params.planId);
|
|
@@ -98,14 +178,21 @@ export function buildPlanWriteTool(store, origin) {
|
|
|
98
178
|
return toolJsonResult(result);
|
|
99
179
|
}
|
|
100
180
|
}
|
|
181
|
+
let warnings;
|
|
182
|
+
if (params.planId === undefined && scope !== undefined) {
|
|
183
|
+
const warning = unfinishedPlanWarning(store.listPlansByOrigin(scope), Date.now());
|
|
184
|
+
if (warning !== undefined)
|
|
185
|
+
warnings = [warning];
|
|
186
|
+
}
|
|
101
187
|
const planId = params.planId ?? randomUUID();
|
|
102
|
-
const plan = buildPlan(params, existing, planId, origin);
|
|
188
|
+
const plan = buildPlan(params.goal, params.tasks, existing, planId, origin, new Date().toISOString());
|
|
103
189
|
assertPlanInvariants(plan);
|
|
104
190
|
store.set(plan);
|
|
105
191
|
const result = {
|
|
106
192
|
ok: true,
|
|
107
193
|
planId: plan.planId,
|
|
108
194
|
view: renderPlanView(plan),
|
|
195
|
+
...(warnings !== undefined ? { warnings } : {}),
|
|
109
196
|
};
|
|
110
197
|
return toolJsonResult(result);
|
|
111
198
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xgjktech/xg-openclaw-harness-tools",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "企业工具插件:plan_write / plan_execute 计划工具(OpenClaw 第三方工具插件)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"typebox": "1.1.39",
|
|
30
|
-
"@xgjktech/xg-openclaw-shared": "^0.2.
|
|
30
|
+
"@xgjktech/xg-openclaw-shared": "^0.2.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.19.0",
|
|
@@ -52,4 +52,4 @@
|
|
|
52
52
|
"defaultChoice": "local"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
}
|
|
55
|
+
}
|