@xgjktech/xg-openclaw-harness-tools 0.4.7 → 0.4.9

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
@@ -107,10 +107,13 @@ task;写入 schema 不接受 steps,但读取旧计划仍兼容旧 steps 数
107
107
 
108
108
  ## 主动计划提示词注入(before_prompt_build)
109
109
 
110
- 插件在 `register` 时挂载 `before_prompt_build` hook,用 `appendSystemContext` 向每个 agent 的
110
+ 插件在 `register` 时挂载 `before_prompt_build` hook,用 `appendSystemContext` 向**主 agent**
111
111
  system prompt 追加一段"主动工作计划"纪律(`src/plan-prompt.ts`):多步工作先建计划、逐项整张回写、
112
112
  blocked 带 note、台账≠执行。内容与工具 description 一致,仅抬升"主动触发"的优先级。
113
113
 
114
+ - **subagent 不参与计划**:hook 通过 `isSubagentSessionKey(sessionKey)` 识别 subagent 会话,
115
+ 对 subagent 不注入纪律;同时 `xg_plan_write` / `xg_plan_view` 两个工具在 subagent 会话的
116
+ factory 阶段返回 `null` 不注册,只有主 agent 能建计划,避免 subagent 建计划污染主面板。
114
117
  - **与其他注入不冲突**:IM 通道的 `GroupSystemPrompt` 落在 base system prompt,本段追加在其后,
115
118
  纯叠加不覆盖;对内置 runtime 与 Codex runtime 均生效。
116
119
  - **如需关闭**:在部署配置里对该插件入口设 `hooks.allowPromptInjection: false`(默认放行):
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,QAAA,MAAM,WAAW,kEAqDf,CAAC;AAUH,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+BA,QAAA,MAAM,WAAW,kEA6Df,CAAC;AAaH,eAAe,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
2
+ import { isSubagentSessionKey } from "openclaw/plugin-sdk/routing";
2
3
  import { resolvePlanDbPath, SqlitePlanStore, SqliteActionStore } from "@xgjktech/xg-openclaw-shared";
3
4
  import { PLAN_WRITE_DESCRIPTION, PlanWriteParams, buildPlanWriteTool } from "./plan-write-tool.js";
4
5
  import { PLAN_VIEW_DESCRIPTION, PlanViewParams, buildPlanViewTool, } from "./plan-view-tool.js";
@@ -6,6 +7,15 @@ import { ACTION_DISPATCH_DESCRIPTION, ActionDispatchParams, buildActionDispatchT
6
7
  import { toPlanOrigin } from "./plan-origin.js";
7
8
  import { buildPlanPromptInjection } from "./plan-prompt.js";
8
9
  const PLUGIN_ID = "xg-harness-tools";
10
+ // 临时诊断日志:确认 subagent 会话的 sessionKey 是否到达 hook/tool factory 且判定正确。确认后删除。
11
+ function logPlanGate(api, tag, sessionKey) {
12
+ api.runtime?.logging
13
+ ?.getChildLogger({ plugin: PLUGIN_ID })
14
+ ?.info?.(`plan-gate:${tag}`, {
15
+ sessionKey: sessionKey ?? null,
16
+ subagent: isSubagentSessionKey(sessionKey),
17
+ });
18
+ }
9
19
  const pluginEntry = defineToolPlugin({
10
20
  id: PLUGIN_ID,
11
21
  name: "XG Harness Tools",
@@ -34,7 +44,13 @@ const pluginEntry = defineToolPlugin({
34
44
  description: PLAN_WRITE_DESCRIPTION,
35
45
  parameters: PlanWriteParams,
36
46
  optional: true,
37
- factory: ({ api, toolContext }) => buildPlanWriteTool(getPlanStore(api), toPlanOrigin(toolContext)),
47
+ factory: ({ api, toolContext }) => {
48
+ // 计划只允许主 agent 维护;subagent 会话不注册计划工具,杜绝其建计划。
49
+ logPlanGate(api, "plan_write_factory", toolContext.sessionKey);
50
+ if (isSubagentSessionKey(toolContext.sessionKey))
51
+ return null;
52
+ return buildPlanWriteTool(getPlanStore(api), toPlanOrigin(toolContext));
53
+ },
38
54
  }),
39
55
  tool({
40
56
  name: "xg_plan_view",
@@ -42,7 +58,12 @@ const pluginEntry = defineToolPlugin({
42
58
  description: PLAN_VIEW_DESCRIPTION,
43
59
  parameters: PlanViewParams,
44
60
  optional: true,
45
- factory: ({ api, toolContext }) => buildPlanViewTool({ store: getPlanStore(api), origin: toPlanOrigin(toolContext) }),
61
+ factory: ({ api, toolContext }) => {
62
+ logPlanGate(api, "plan_view_factory", toolContext.sessionKey);
63
+ if (isSubagentSessionKey(toolContext.sessionKey))
64
+ return null;
65
+ return buildPlanViewTool({ store: getPlanStore(api), origin: toPlanOrigin(toolContext) });
66
+ },
46
67
  }),
47
68
  tool({
48
69
  name: "xg_action_dispatch",
@@ -60,6 +81,9 @@ const pluginEntry = defineToolPlugin({
60
81
  const baseRegister = pluginEntry.register;
61
82
  pluginEntry.register = (api) => {
62
83
  baseRegister(api);
63
- api.on("before_prompt_build", buildPlanPromptInjection);
84
+ api.on("before_prompt_build", (event, ctx) => {
85
+ logPlanGate(api, "hook", ctx.sessionKey);
86
+ return buildPlanPromptInjection(event, ctx);
87
+ });
64
88
  };
65
89
  export default pluginEntry;
@@ -1,4 +1,4 @@
1
- import type { PluginHookBeforePromptBuildResult } from "openclaw/plugin-sdk/types";
1
+ import type { PluginHookAgentContext, PluginHookBeforePromptBuildEvent, PluginHookBeforePromptBuildResult } from "openclaw/plugin-sdk/types";
2
2
  /**
3
3
  * 追加到 agent system prompt 的"主动建计划"纪律(appendSystemContext)。
4
4
  * 与 IM 通道的 GroupSystemPrompt 纯叠加不冲突:本段拼在 base systemPrompt 之后。
@@ -6,8 +6,9 @@ import type { PluginHookBeforePromptBuildResult } from "openclaw/plugin-sdk/type
6
6
  */
7
7
  export declare const PLAN_FIRST_SYSTEM_CONTEXT: string;
8
8
  /**
9
- * before_prompt_build 钩子处理器:向 agent system prompt 追加主动建计划纪律。
10
- * 返回 void 语义由宿主管控,插件侧始终返回该静态段。
9
+ * before_prompt_build 钩子处理器:仅向主 agent system prompt 追加主动建计划纪律。
10
+ * subagent 会话(sessionKey 命中 isSubagentSessionKey)不注入,避免其主动建计划污染主面板;
11
+ * 返回 void 语义由宿主管控。
11
12
  */
12
- export declare function buildPlanPromptInjection(): PluginHookBeforePromptBuildResult;
13
+ export declare function buildPlanPromptInjection(_event: PluginHookBeforePromptBuildEvent, ctx: PluginHookAgentContext): PluginHookBeforePromptBuildResult | void;
13
14
  //# sourceMappingURL=plan-prompt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan-prompt.d.ts","sourceRoot":"","sources":["../src/plan-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,2BAA2B,CAAC;AAEnF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAQ1B,CAAC;AAEb;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,iCAAiC,CAE5E"}
1
+ {"version":3,"file":"plan-prompt.d.ts","sourceRoot":"","sources":["../src/plan-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,gCAAgC,EAChC,iCAAiC,EAClC,MAAM,2BAA2B,CAAC;AAGnC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAQ1B,CAAC;AAEb;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gCAAgC,EACxC,GAAG,EAAE,sBAAsB,GAC1B,iCAAiC,GAAG,IAAI,CAG1C"}
@@ -1,3 +1,4 @@
1
+ import { isSubagentSessionKey } from "openclaw/plugin-sdk/routing";
1
2
  /**
2
3
  * 追加到 agent system prompt 的"主动建计划"纪律(appendSystemContext)。
3
4
  * 与 IM 通道的 GroupSystemPrompt 纯叠加不冲突:本段拼在 base systemPrompt 之后。
@@ -13,9 +14,12 @@ export const PLAN_FIRST_SYSTEM_CONTEXT = [
13
14
  "- xg_plan_write / xg_plan_view 只是台账,不执行、不调度、无后台运行;保存/查看之后必须继续逐项实际执行任务。",
14
15
  ].join("\n");
15
16
  /**
16
- * before_prompt_build 钩子处理器:向 agent system prompt 追加主动建计划纪律。
17
- * 返回 void 语义由宿主管控,插件侧始终返回该静态段。
17
+ * before_prompt_build 钩子处理器:仅向主 agent system prompt 追加主动建计划纪律。
18
+ * subagent 会话(sessionKey 命中 isSubagentSessionKey)不注入,避免其主动建计划污染主面板;
19
+ * 返回 void 语义由宿主管控。
18
20
  */
19
- export function buildPlanPromptInjection() {
21
+ export function buildPlanPromptInjection(_event, ctx) {
22
+ if (isSubagentSessionKey(ctx.sessionKey))
23
+ return undefined;
20
24
  return { appendSystemContext: PLAN_FIRST_SYSTEM_CONTEXT };
21
25
  }
@@ -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;AAkCtC,eAAO,MAAM,eAAe;;;;;;;;;EAwB3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,QA4BvB,CAAC;AA0Eb,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CA2HtF"}
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;AAkCtC,eAAO,MAAM,eAAe;;;;;;;;;EAwB3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB,QA6BvB,CAAC;AA+Gb,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CA4ItF"}
@@ -62,6 +62,7 @@ export const PLAN_WRITE_DESCRIPTION = [
62
62
  "- 更新计划内容时必须重新提交 goal 和所有要保留的 tasks,包括未变化任务及其当前 status/note。只提交变化项会删除其他任务;记不清时先用 xg_plan_view 查看。",
63
63
  "- goal 用一句话;tasks 通常 3-7 项,每项是能判断是否完成的具体动作,不写 TODO、待定或内部思考。",
64
64
  "- status 为 pending、in_progress、completed 或 blocked。通常只有一项 in_progress,确实并行时可以有多项;blocked 时必须用 note 写明原因和继续所需条件。",
65
+ "- 已完成任务不可回退:一旦某项标记 completed,后续回写必须保持 completed,禁止改回 pending/in_progress/blocked。若执行中发现此前完成结论不成立或需返工,保留该 completed 项(可在 note 中说明返工原因),新增一条后续任务承接返工/补做,并相应调整其后的步骤。",
65
66
  "- 同一件工作只维护一份计划;任务方向变化时,先取消不再推进的旧计划({planId, cancel:true})再新建。",
66
67
  ].join("\n");
67
68
  function toTask(input) {
@@ -74,6 +75,32 @@ function toTask(input) {
74
75
  }
75
76
  return task;
76
77
  }
78
+ function normalizeTaskTitle(title) {
79
+ return title.trim();
80
+ }
81
+ /**
82
+ * 检测"已完成任务被回退":以规范化标题对齐旧任务与新提交任务,
83
+ * 凡旧任务为 completed 而新提交同名任务状态不再是 completed,即为回退。
84
+ * 同名任务整体删除属于整张覆盖语义(未提交即删除),不算回退。
85
+ */
86
+ function findTaskStatusRegressions(existing, nextTasks) {
87
+ const completedTitles = new Set();
88
+ for (const task of existing.tasks) {
89
+ if (task.status === "completed") {
90
+ completedTitles.add(normalizeTaskTitle(task.title));
91
+ }
92
+ }
93
+ const regressions = [];
94
+ for (const task of nextTasks) {
95
+ const nextStatus = (task.status ?? "pending");
96
+ if (nextStatus === "completed")
97
+ continue;
98
+ if (completedTitles.has(normalizeTaskTitle(task.title))) {
99
+ regressions.push({ title: task.title, from: "completed", to: nextStatus });
100
+ }
101
+ }
102
+ return regressions;
103
+ }
77
104
  function buildPlan(goal, tasks, existing, planId, origin, now) {
78
105
  const plan = {
79
106
  planId,
@@ -191,6 +218,21 @@ export function buildPlanWriteTool(store, origin) {
191
218
  return toolJsonResult(result);
192
219
  }
193
220
  }
221
+ if (existing !== undefined) {
222
+ const regressions = findTaskStatusRegressions(existing, params.tasks);
223
+ if (regressions.length > 0) {
224
+ const detail = `${regressions
225
+ .map((regression) => `「${compactText(regression.title)}」${regression.from} → ${regression.to}`)
226
+ .join(";")}。` +
227
+ "已完成任务禁止回退:若执行中发现此前完成结论不成立或需返工,请保留该 completed 项," +
228
+ "新增一条后续任务说明补救/返工动作并相应调整后续步骤,而不是把已完成项改回未完成。";
229
+ const result = {
230
+ ok: false,
231
+ error: { code: "TASK_STATUS_REGRESSION", detail },
232
+ };
233
+ return toolJsonResult(result);
234
+ }
235
+ }
194
236
  let warnings;
195
237
  if (planIdParam === undefined && scope !== undefined) {
196
238
  const warning = unfinishedPlanWarning(store.listPlansByOrigin(scope), Date.now());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xgjktech/xg-openclaw-harness-tools",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "企业工具插件:plan_write / plan_view / action_dispatch(OpenClaw 第三方工具插件)",
5
5
  "license": "MIT",
6
6
  "type": "module",