openclaw-plugin-grinders-farm 0.3.0 → 0.3.1

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,35 +1,54 @@
1
1
  # openclaw-plugin-grinders-farm
2
2
 
3
- OpenClaw plugin for Grinder's Farm. Registers:
3
+ `Grinder's Farm` 的 OpenClaw 插件。
4
+ 安装后你可以直接在聊天里使用 `/farm ...` 命令游玩,并开启自动推进与多频道推送。
4
5
 
5
- - `/farm` command
6
- - `grinders_farm` tool (for skill `command-dispatch: tool`)
7
-
8
- ## Install (End User)
6
+ ## 安装
9
7
 
10
8
  ```bash
11
9
  openclaw plugins install openclaw-plugin-grinders-farm --dangerously-force-unsafe-install
12
10
  ```
13
11
 
14
- No local repo clone or build is required for end users.
12
+ 安装完成后,确认已加载:
15
13
 
16
- ## Runtime Resolution
14
+ ```bash
15
+ openclaw plugins inspect grinders-farm
16
+ ```
17
17
 
18
- The plugin tries these options in order:
18
+ ## 使用流程(推荐)
19
19
 
20
- 1. `GRINDERS_FARM_ROOT` env
21
- 2. plugin config `gameRoot`
22
- 3. parent directory of this plugin
23
- 4. `grinders-farm` CLI in `PATH` (or `GRINDERS_FARM_CLI_BIN`)
20
+ 1. 在目标频道发送一次:
21
+
22
+ ```text
23
+ /farm farm
24
+ ```
24
25
 
25
- If `gameRoot` is available, plugin can auto-start:
26
+ 2. 开始自动推进:
26
27
 
27
- - local image server (`imageServerPort`)
28
- - local auto worker (fixed 20s/day)
28
+ ```text
29
+ /farm start
30
+ ```
31
+
32
+ 3. 停止自动推进:
33
+
34
+ ```text
35
+ /farm stop
36
+ ```
29
37
 
30
- If only CLI mode is available, plugin still works and can auto-run `grinders-farm start`.
38
+ 说明:
31
39
 
32
- ## Minimal OpenClaw Config
40
+ - `start` 固定每 20 秒推进一天
41
+ - 会自动推送到所有已绑定频道(先发送过 `/farm ...` 的频道)
42
+
43
+ ## 常用配置
44
+
45
+ 你可以在 OpenClaw 的 plugin 配置里设置:
46
+
47
+ - `autoStartWorkerOnGatewayBoot`:Gateway 启动时是否自动启动推进
48
+ - `imageServerPort`:本地农场图片服务端口
49
+ - `gameRoot`:当自动定位失败时,手动指定项目根目录
50
+
51
+ 最小配置示例:
33
52
 
34
53
  ```json
35
54
  {
@@ -47,18 +66,27 @@ If only CLI mode is available, plugin still works and can auto-run `grinders-far
47
66
  }
48
67
  ```
49
68
 
50
- Optional if auto-detection fails:
69
+ ## 常见问题
51
70
 
52
- ```json
53
- {
54
- "plugins": {
55
- "entries": {
56
- "grinders-farm": {
57
- "config": {
58
- "gameRoot": "/path/to/grinders-farm"
59
- }
60
- }
61
- }
62
- }
63
- }
71
+ ### 插件已安装但命令不可用
72
+
73
+ 先检查:
74
+
75
+ ```bash
76
+ openclaw plugins inspect grinders-farm
64
77
  ```
78
+
79
+ 如果不是 `loaded`,重启 Gateway 后再试。
80
+
81
+ ### 自动推送没有发到某个频道
82
+
83
+ 在该频道先执行一次 `/farm farm` 完成绑定,再执行 `/farm start`。
84
+
85
+ ### 找不到 `grinders-farm` 可执行文件
86
+
87
+ 可设置环境变量:
88
+
89
+ - `GRINDERS_FARM_ROOT`
90
+ - `GRINDERS_FARM_CLI_BIN`
91
+
92
+ 然后重启 Gateway。
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ const FARM_IMAGE_PATH = path.join(os.homedir(), ".grinders-farm", "farm.png");
11
11
  const OPENCLAW_MEDIA_DIR = path.join(os.homedir(), ".openclaw", "media", "grinders-farm");
12
12
  const ONESHOT_RELATIVE_PATH = path.join("src", "adapters", "oneshot.ts");
13
13
  const DEFAULT_START_INTERVAL_SEC = 20;
14
+ const ONESHOT_TIMEOUT_MS = 15_000;
14
15
  function resolveGameRoot(api) {
15
16
  const raw = api.pluginConfig;
16
17
  const fromEnv = process.env.GRINDERS_FARM_ROOT?.trim();
@@ -25,16 +26,42 @@ function isValidGameRoot(candidate) {
25
26
  }
26
27
  function resolveClawFarmCliBin() {
27
28
  const fromEnv = process.env.GRINDERS_FARM_CLI_BIN?.trim();
28
- if (fromEnv)
29
- return fromEnv;
29
+ if (fromEnv) {
30
+ if (fromEnv.endsWith("/dist/src/adapters/terminal.js")) {
31
+ const oneshotJs = fromEnv.replace(/terminal\.js$/, "oneshot.js");
32
+ if (fs.existsSync(oneshotJs)) {
33
+ return { bin: process.execPath, binArgsPrefix: [oneshotJs] };
34
+ }
35
+ return null;
36
+ }
37
+ return fromEnv.endsWith(".js") ? { bin: process.execPath, binArgsPrefix: [fromEnv] } : { bin: fromEnv, binArgsPrefix: [] };
38
+ }
30
39
  try {
40
+ const oneshotWhich = spawnSync("sh", ["-lc", "command -v grinders-farm-oneshot"], {
41
+ encoding: "utf8",
42
+ maxBuffer: 1024 * 1024,
43
+ });
44
+ const oneshotOut = (oneshotWhich.stdout ?? "").trim();
45
+ if (oneshotOut)
46
+ return { bin: oneshotOut, binArgsPrefix: [] };
31
47
  const which = spawnSync("sh", ["-lc", "command -v grinders-farm"], {
32
48
  encoding: "utf8",
33
49
  maxBuffer: 1024 * 1024,
34
50
  });
35
51
  const out = (which.stdout ?? "").trim();
36
- if (out)
37
- return out;
52
+ if (out) {
53
+ if (out.endsWith("/dist/src/adapters/terminal.js")) {
54
+ const oneshotJs = out.replace(/terminal\.js$/, "oneshot.js");
55
+ if (fs.existsSync(oneshotJs)) {
56
+ return { bin: process.execPath, binArgsPrefix: [oneshotJs] };
57
+ }
58
+ return null;
59
+ }
60
+ if (out.endsWith(".js")) {
61
+ return { bin: process.execPath, binArgsPrefix: [out] };
62
+ }
63
+ return { bin: out, binArgsPrefix: [] };
64
+ }
38
65
  }
39
66
  catch {
40
67
  // ignore
@@ -48,7 +75,7 @@ function resolveRuntimeMode(api) {
48
75
  }
49
76
  const bin = resolveClawFarmCliBin();
50
77
  if (bin) {
51
- return { kind: "cli-bin", bin };
78
+ return { kind: "cli-bin", bin: bin.bin, binArgsPrefix: bin.binArgsPrefix };
52
79
  }
53
80
  return null;
54
81
  }
@@ -61,29 +88,43 @@ function getPluginTiming(api) {
61
88
  function runOneshot(mode, argsLine) {
62
89
  const argv = argsLine.trim().length > 0 ? argsLine.trim().split(/\s+/) : ["farm"];
63
90
  const resolvedOpenclawBin = resolveOpenclawBin();
91
+ const env = {
92
+ ...process.env,
93
+ OPENCLAW_BIN: process.env.OPENCLAW_BIN?.trim() || resolvedOpenclawBin,
94
+ };
64
95
  const result = mode.kind === "repo-tsx"
65
96
  ? spawnSync("npx", ["tsx", path.join(mode.gameRoot, ONESHOT_RELATIVE_PATH), ...argv], {
66
97
  cwd: mode.gameRoot,
67
98
  encoding: "utf8",
68
99
  maxBuffer: 8 * 1024 * 1024,
69
- env: {
70
- ...process.env,
71
- OPENCLAW_BIN: process.env.OPENCLAW_BIN?.trim() || resolvedOpenclawBin,
72
- },
100
+ timeout: ONESHOT_TIMEOUT_MS,
101
+ env,
73
102
  })
74
- : spawnSync(mode.bin, argv, {
103
+ : spawnSync(mode.bin, [...mode.binArgsPrefix, ...argv], {
75
104
  encoding: "utf8",
76
105
  maxBuffer: 8 * 1024 * 1024,
77
- env: {
78
- ...process.env,
79
- OPENCLAW_BIN: process.env.OPENCLAW_BIN?.trim() || resolvedOpenclawBin,
80
- },
106
+ timeout: ONESHOT_TIMEOUT_MS,
107
+ env,
81
108
  });
82
- const stderr = result.stderr?.trim() ?? "";
83
- const stdout = result.stdout?.trim() ?? "";
109
+ const finalResult = result.error && result.error.code === "ENOEXEC"
110
+ ? spawnSync(process.execPath, [mode.kind === "cli-bin" ? mode.bin : "", ...(mode.kind === "cli-bin" ? mode.binArgsPrefix : []), ...argv].filter(Boolean), {
111
+ encoding: "utf8",
112
+ maxBuffer: 8 * 1024 * 1024,
113
+ timeout: ONESHOT_TIMEOUT_MS,
114
+ env,
115
+ })
116
+ : result;
117
+ const stderr = finalResult.stderr?.trim() ?? "";
118
+ const stdout = finalResult.stdout?.trim() ?? "";
84
119
  const combined = [stdout, stderr].filter(Boolean).join("\n");
85
- const code = result.status ?? (result.error ? 1 : 0);
86
- return { text: combined || (result.error ? String(result.error.message) : "(no output)"), exitCode: code };
120
+ const code = finalResult.status ?? (finalResult.error ? 1 : 0);
121
+ if (finalResult.error && finalResult.error.code === "ETIMEDOUT") {
122
+ return {
123
+ text: "grinders-farm 命令执行超时。请确认你安装的是支持 oneshot 的新版 grinders-farm(含 grinders-farm-oneshot),或在插件 config 里设置 gameRoot 指向源码仓库。",
124
+ exitCode: 1,
125
+ };
126
+ }
127
+ return { text: combined || (finalResult.error ? String(finalResult.error.message) : "(no output)"), exitCode: code };
87
128
  }
88
129
  function isTelegramChannel(ctx) {
89
130
  const surface = (ctx.channel ?? ctx.channelId ?? "").toString().trim().toLowerCase();
@@ -247,7 +288,7 @@ function createFarmTool(mode) {
247
288
  const plugin = {
248
289
  id: "grinders-farm",
249
290
  name: "Grinder's Farm",
250
- description: "Local grinders-farm subprocess only; no LLM in the game path.",
291
+ description: "Local grinders-farm subprocess for farm commands and push.",
251
292
  register(api) {
252
293
  const runtime = resolveRuntimeMode(api);
253
294
  if (!runtime) {
@@ -257,7 +298,7 @@ const plugin = {
257
298
  api.registerTool(createFarmTool(runtime));
258
299
  api.registerCommand({
259
300
  name: "farm",
260
- description: "Grinder's Farm — run a game command without invoking the model (fast path). Binds this chat for farm push.",
301
+ description: "Grinder's Farm — run a game command and bind this chat for farm push.",
261
302
  acceptsArgs: true,
262
303
  handler: async (ctx) => {
263
304
  const bound = await bindDeliveryFromPluginCommand(ctx);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "grinders-farm",
3
3
  "name": "Grinder's Farm",
4
- "description": "Local grinders-farm plugin: /farm and grinders_farm run deterministic local game logic (no LLM, no remote backend).",
4
+ "description": "Local grinders-farm plugin: /farm command and grinders_farm tool with local game state and push.",
5
5
  "configSchema": {
6
6
  "type": "object",
7
7
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-plugin-grinders-farm",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "OpenClaw plugin for Grinder's Farm: deterministic /farm command and grinders_farm tool.",
@@ -24,6 +24,8 @@
24
24
  "openclaw": ">=2026.1.0"
25
25
  },
26
26
  "openclaw": {
27
- "extensions": ["./dist/index.js"]
27
+ "extensions": [
28
+ "./dist/index.js"
29
+ ]
28
30
  }
29
31
  }