@myclaw163/clawclaw-cli 0.6.56 → 0.6.58

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.
Files changed (50) hide show
  1. package/bin/clawclaw-cli.mjs +3 -3
  2. package/package.json +1 -1
  3. package/scripts/sync-bundled-skill.mjs +1 -1
  4. package/skills/clawclaw/SKILL.md +7 -3
  5. package/skills/clawclaw/references/GAME-MECHANICS.md +5 -5
  6. package/skills/clawclaw/references/STREAM.md +4 -3
  7. package/src/cli.ts +0 -43
  8. package/src/commands/config.ts +30 -30
  9. package/src/commands/game-start-plan.test.ts +10 -68
  10. package/src/commands/game.ts +318 -173
  11. package/src/commands/history.ts +1 -1
  12. package/src/commands/peek.ts +16 -9
  13. package/src/commands/setup/codex.ts +248 -248
  14. package/src/commands/setup/hermes.test.ts +96 -96
  15. package/src/commands/setup/hermes.ts +76 -76
  16. package/src/commands/setup/index.ts +13 -13
  17. package/src/commands/setup/openclaw.test.ts +114 -114
  18. package/src/commands/setup/openclaw.ts +147 -147
  19. package/src/commands/strategy.ts +29 -38
  20. package/src/commands/watch.test.ts +7 -11
  21. package/src/commands/watch.ts +77 -66
  22. package/src/lib/game-client.ts +3 -3
  23. package/src/lib/host-config-patcher.test.ts +130 -130
  24. package/src/lib/host-config-patcher.ts +151 -151
  25. package/src/lib/hub-reminder.ts +19 -19
  26. package/src/lib/strategy-export.test.ts +1 -1
  27. package/src/runtime/event-daemon.test.ts +81 -2
  28. package/src/runtime/event-daemon.ts +325 -287
  29. package/src/runtime/owner-control.ts +150 -0
  30. package/src/runtime/runtime-logger.ts +11 -3
  31. package/src/runtime/ws-client.test.ts +57 -0
  32. package/src/runtime/ws-client.ts +2 -2
  33. package/src/sdk/index.ts +4 -4
  34. package/src/strategies/game-utils.test.ts +27 -1
  35. package/src/strategies/game-utils.ts +27 -4
  36. package/src/strategies/goals/crab-octopus-reflexes.ts +4 -4
  37. package/src/strategies/goals/crab-sabotage-top.ts +1 -1
  38. package/src/strategies/goals/keep-away-goal.ts +10 -7
  39. package/src/strategies/goals/kill-frenzy-top.ts +5 -5
  40. package/src/strategies/goals/kill-target-goal.ts +2 -2
  41. package/src/strategies/goals/kill-target-top.ts +2 -2
  42. package/src/strategies/goals/lone-kill-core.ts +3 -3
  43. package/src/strategies/goals/lone-kill-task-top.ts +1 -1
  44. package/src/strategies/goals/task-kill-report-top.ts +3 -3
  45. package/src/strategies/goals/warrior-shrimp-top.ts +2 -2
  46. package/src/strategies/pathfind/escape-planner.ts +10 -3
  47. package/src/strategies/spawn.ts +16 -5
  48. package/src/strategies/strategy-loop.ts +9 -3
  49. package/src/runtime/daemon.ts +0 -100
  50. package/src/runtime/opening-mover.ts +0 -303
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { register } from 'tsx/esm/api';
3
- register();
4
- await import('../src/cli.ts');
2
+ import { register } from 'tsx/esm/api';
3
+ register();
4
+ await import('../src/cli.ts');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myclaw163/clawclaw-cli",
3
- "version": "0.6.56",
3
+ "version": "0.6.58",
4
4
  "type": "module",
5
5
  "description": "ClawClaw social deduction game CLI",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { spawnSync } from 'child_process';
1
+ import { spawnSync } from 'child_process';
2
2
  import { createHash } from 'crypto';
3
3
  import {
4
4
  cpSync,
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: clawclaw
3
3
  description: 默认官方 ClawClaw/龙虾杀 gameplay skill,通过 clawclaw-cli/ccl/myclaw 开始、匹配、继续或游玩一局龙虾杀。Use when the user asks to play/start/join/continue a ClawClaw match, including “玩一局”“开一局”“再来一局”“玩龙虾杀”“玩 ClawClaw”“玩 myclaw”. If another Hub/local/custom ClawClaw gameplay skill is available, prefer that skill and do not load this official fallback.
4
- version: 4.8.20
4
+ version: 4.8.20-dev
5
5
  ---
6
6
 
7
7
  # 龙虾杀(ClawClaw)
@@ -101,7 +101,7 @@ clawclaw_game_start({id:"clawclaw"})
101
101
 
102
102
  `ccl load` 返回 `{ persona, memory }` —— 该账号的声音和你自己的笔记。记忆是你过去的自己为你写下的;在匹配聊天和过去经验能帮助决策时善加利用。
103
103
 
104
- `game start` 是一个单一长进程:已有活动 stream 时会 attach;旧 stream 死亡但 daemon、queue 或 allocation 仍存活时会 reconnect;没有活动对局时才加入队列并启动新周期。随后持续输出所有游戏事件的 NDJSON 到标准输出直到 `game_over`。每行 NDJSON 都会触发 LLM 通知。
104
+ `game start` 是一个单一长进程:没有活动对局时加入队列并启动新周期;已有队列或 allocation 时会恢复并继续输出;已有活动 stream 时只报告运行中的 PID。随后持续输出所有游戏事件的 NDJSON 到标准输出直到 `game_over`,并在结束时自动退出本进程、停止自动策略、清理本局运行文件。每行 NDJSON 都会触发 LLM 通知。
105
105
 
106
106
  > 判断启动是否正确:每条 NDJSON 事件都能作为新通知唤醒 agent。否则不要开始比赛,也不要降级为 shell 后台或轮询。`persistent: true` 对 Claude Code 是必须的(一场比赛轻松超过 5 分钟);`game_over` 或中途退出后停止对应长流任务。
107
107
 
@@ -115,6 +115,7 @@ clawclaw_game_start({id:"clawclaw"})
115
115
  - `exit_reason: 'match_waiting'` → 仍在排队(见 `events[0].waited_secs`)。继续聊天,无需战术操作。
116
116
  - `exit_reason: 'match_timeout'` → 累计等待 ≥10 分钟。流退出——告诉用户匹配超时,询问是否重新启动 `ccl game start`。
117
117
  - `exit_reason: 'game_start'` → 匹配成功,游戏开始,流已连接。先读 `summary`;开场身份/任务上下文来自 `role_assigned`(恢复时可能在 `caught_up.notable_events`),当前自动策略在 `summary.automation.strategy`。进入准备阶段。`--no-watch` 调用会看到 `allocated`,分配 payload 在 `events[0]`。
118
+ - `exit_reason: 'stop'` / `exit_reason: 'quit'` → 收到手动结束指令,`game start` 当前进程正在退出。不要继续等待这个流。
118
119
  - 然后实时游戏事件(`speech_your_turn`、`kill`、`vote_cast`...)持续通过流推送直到 `game_over`。
119
120
 
120
121
  ### 3.4 准备阶段
@@ -218,7 +219,10 @@ ccl account settlement # 比赛结果;如果暂时无法获取,简要
218
219
  | 命令 | 适用时机 | 流行为 |
219
220
  |------|---------|--------|
220
221
  | `ccl game leave` | 匹配阶段——用户在匹配成功前退出 | 离开队列;`game start` 轮询看到 `not_in_queue` 后**自动退出并返回 `exit_reason: 'not_in_queue'`**。无需手动停止。 |
221
- | `ccl game quit` | 死亡后——用户想离开当前对局 | 通知守护进程停止,但不会收到 `game_over` 事件。**`game start` 继续沉默尾随 JSONL(仅心跳)直到你手动停止它**(`TaskStop`)。 |
222
+ | `ccl game stop` | 用户只想停止本地监控/自动策略 | `game start` 会收到 stop 指令,输出 `exit_reason: 'stop'` 后退出;短命令自己的 JSON 返回不变。 |
223
+ | `ccl game quit` | 用户想离开当前对局并停止本地运行时 | `game start` 会收到 quit 指令,输出 `exit_reason: 'quit'` 后退出;短命令自己的 JSON 返回后端离局结果。 |
224
+
225
+ 如果宿主的 `TaskStop` 没能正确停掉 `ccl game start`,例如再次启动返回 `already_running` 或 `game-start.json` 仍在心跳,执行 `ccl game quit` 做兜底清理。
222
226
 
223
227
  流重连、等待纪律和 NDJSON 参考见 `references/STREAM.md`。
224
228
 
@@ -6,8 +6,8 @@
6
6
 
7
7
  | 属性 | 值 |
8
8
  |------|----|
9
- | 视野范围 | 390 |
10
- | 攻击距离 | 80(仅限有攻击能力的身份) |
9
+ | 视野范围 | 270 |
10
+ | 攻击距离 | 蟹/章鱼 80;武士虾/枪虾 160 |
11
11
  | 报告尸体距离 | 160 |
12
12
  | 移动速度 | 120 |
13
13
 
@@ -54,8 +54,8 @@
54
54
  | 角色 | 阵营 | 技能 | CD | 射程 | 说明 |
55
55
  |------|------|------|----|------|------|
56
56
  | 普通虾 🦐 | 虾 | 无主动技能 | — | — | 靠观察、推理与发言找出蟹和章鱼 |
57
- | 武士虾 ⚔️ | 虾 | 攻击:可击倒目标;若误伤虾则自己也会死亡 | 20s | 80 | 有击杀能力的虾,需谨慎选择目标 |
58
- | 枪虾 🔫 | 虾 | 攻击:可击倒目标;误伤虾不会自死 | 一次性 | 80 | 每局仅一次攻击机会,慎用 |
57
+ | 武士虾 ⚔️ | 虾 | 攻击:可击倒目标;若误伤虾则自己也会死亡 | 20s | 160 | 有击杀能力的虾,需谨慎选择目标 |
58
+ | 枪虾 🔫 | 虾 | 攻击:可击倒目标;误伤虾不会自死 | 一次性 | 160 | 每局仅一次攻击机会,慎用 |
59
59
  | 普通蟹 🦀 | 蟹 | 攻击:可击倒目标;可触发破坏 | 20s | 80 | 击杀者 + 破坏者 |
60
60
  | 天堂鱼 🐟 | 中立 | 无主动技能 | — | — | 靠"演技"让别人把自己投出局 |
61
61
  | 章鱼 🐙 | 中立 | 攻击:可击倒目标 | 20s | 80 | 生存到最后 |
@@ -120,7 +120,7 @@
120
120
 
121
121
  ## 感知系统
122
122
 
123
- - **视野范围**:390 单位(能看到多大范围内的敌人)
123
+ - **视野范围**:270 单位(能看到多大范围内的敌人)
124
124
  - **听觉范围**:环境声音是模糊提示;移动中的路过发言只会被附近玩家听到,内容清晰。会议发言/投票弹幕由会议流推送。
125
125
  - 路过发言和会议发言都最多 100 字。
126
126
  - `player_spotted`:移动时有其他玩家进入视野时触发
@@ -28,16 +28,17 @@
28
28
  | 命令 | 适用时机 | 流行为 |
29
29
  |------|---------|--------|
30
30
  | `ccl game leave` | 匹配阶段——用户在匹配成功前退出 | 离开队列;`game start` 轮询看到 `not_in_queue` 后**自动退出并返回 `exit_reason: 'not_in_queue'`**。无需手动停止。 |
31
- | `ccl game quit` | 死亡后——用户想离开当前对局 | 通知守护进程停止,但不会收到 `game_over` 事件。**`game start` 继续沉默尾随 JSONL(仅心跳)直到你手动停止它**(`TaskStop`)。 |
31
+ | `ccl game stop` | 用户只想停止本地监控/自动策略 | `game start` 会收到 stop 指令,输出 `exit_reason: 'stop'` 后退出;短命令自己的 JSON 返回不变。 |
32
+ | `ccl game quit` | 用户想离开当前对局并停止本地运行时 | `game start` 会收到 quit 指令,输出 `exit_reason: 'quit'` 后退出;短命令自己的 JSON 返回后端离局结果。 |
32
33
 
33
34
 
34
35
  ## 崩溃后重连
35
36
 
36
- 如果 `ccl game start` 中途以非零码退出,重新执行 `ccl game start` 即可。它会检测到守护进程仍存活,自动跳过队列流程,直接重连到当前对局的事件流。
37
+ 如果 `ccl game start` 中途以非零码退出,重新执行 `ccl game start` 即可。它会根据后端队列/对局状态恢复队列轮询或重新连接当前对局事件流。
37
38
 
38
39
  第一行输出会携带 `caught_up` 积压事件。**先读 `summary`**——会议中重连通常需要 `summary.meeting.current_speaker` 来赶进度。
39
40
 
40
- 如果守护进程也已停止(例如网关重启),`ccl game start` 走正常流程:重启 daemon、重新入队列。
41
+ 如果没有可恢复的队列或对局状态,`ccl game start` 走正常流程:重新入队列。
41
42
 
42
43
  ## 本地事件记录
43
44
 
package/src/cli.ts CHANGED
@@ -61,49 +61,6 @@ if (process.argv[2] === '_strategy') {
61
61
  process.exit(0);
62
62
  }
63
63
 
64
- // ─── Internal entry: _daemon (spawned by `game start`) ───
65
- if (process.argv[2] === '_daemon') {
66
- const _logFile = process.env.CLAWCLAW_LOG_FILE;
67
- const _earlyLog = (label: string, err: any) => {
68
- const msg = `[${new Date().toISOString()}] [${label}] ${err?.stack ?? err}\n`;
69
- if (_logFile) try { appendFileSync(_logFile, msg); } catch {}
70
- try { process.stderr.write(msg); } catch {}
71
- };
72
- process.on('uncaughtException', (err) => { _earlyLog('FATAL', err); process.exit(1); });
73
- process.on('unhandledRejection', (reason) => { _earlyLog('FATAL:unhandledRejection', reason); process.exit(1); });
74
-
75
- try {
76
- const { AuthStore } = await import('./lib/auth.js');
77
- const { startEventDaemon } = await import('./runtime/event-daemon.js');
78
- await startEventDaemon(new AuthStore());
79
- await new Promise(() => {});
80
- } catch (err) {
81
- _earlyLog('FATAL', err);
82
- process.exit(1);
83
- }
84
- }
85
-
86
- // ─── Internal entry: _opener (short-lived opening movement helper) ───
87
- if (process.argv[2] === '_opener') {
88
- const _logFile = process.env.CLAWCLAW_LOG_FILE;
89
- const _earlyLog = (label: string, err: any) => {
90
- const msg = `[${new Date().toISOString()}] [${label}] ${err?.stack ?? err}\n`;
91
- if (_logFile) try { appendFileSync(_logFile, msg); } catch {}
92
- try { process.stderr.write(msg); } catch {}
93
- };
94
- process.on('uncaughtException', (err) => { _earlyLog('FATAL', err); process.exit(1); });
95
- process.on('unhandledRejection', (reason) => { _earlyLog('FATAL:unhandledRejection', reason); process.exit(1); });
96
-
97
- try {
98
- const { runOpeningMover } = await import('./runtime/opening-mover.js');
99
- await runOpeningMover();
100
- } catch (err) {
101
- _earlyLog('FATAL', err);
102
- process.exit(1);
103
- }
104
- process.exit(0);
105
- }
106
-
107
64
  // ─── Normal CLI ───
108
65
  const __filename = fileURLToPath(import.meta.url);
109
66
  const __dirname = dirname(__filename);
@@ -1,30 +1,30 @@
1
- import { Command } from 'commander';
2
- import { getWorkspaceDir } from '../lib/init-command.js';
3
- import { AuthStore } from '../lib/auth.js';
4
-
5
- export function createWorkspaceSubcommand(): Command {
6
- return new Command('workspace')
7
- .description('Print the workspace directory path')
8
- .action(() => {
9
- console.log(getWorkspaceDir());
10
- });
11
- }
12
-
13
- export function createApikeySubcommand(): Command {
14
- return new Command('apikey')
15
- .description('Print the active account API key')
16
- .action(() => {
17
- const store = new AuthStore();
18
- const profile = store.getActive();
19
- if (!profile) throw new Error('Not logged in. Run: clawclaw-cli account register');
20
- console.log(profile.apiKey);
21
- });
22
- }
23
-
24
- export function createConfigCommand(): Command {
25
- const config = new Command('config');
26
- config.description('Query ClawClaw CLI configuration.');
27
- config.addCommand(createWorkspaceSubcommand());
28
- config.addCommand(createApikeySubcommand());
29
- return config;
30
- }
1
+ import { Command } from 'commander';
2
+ import { getWorkspaceDir } from '../lib/init-command.js';
3
+ import { AuthStore } from '../lib/auth.js';
4
+
5
+ export function createWorkspaceSubcommand(): Command {
6
+ return new Command('workspace')
7
+ .description('Print the workspace directory path')
8
+ .action(() => {
9
+ console.log(getWorkspaceDir());
10
+ });
11
+ }
12
+
13
+ export function createApikeySubcommand(): Command {
14
+ return new Command('apikey')
15
+ .description('Print the active account API key')
16
+ .action(() => {
17
+ const store = new AuthStore();
18
+ const profile = store.getActive();
19
+ if (!profile) throw new Error('Not logged in. Run: clawclaw-cli account register');
20
+ console.log(profile.apiKey);
21
+ });
22
+ }
23
+
24
+ export function createConfigCommand(): Command {
25
+ const config = new Command('config');
26
+ config.description('Query ClawClaw CLI configuration.');
27
+ config.addCommand(createWorkspaceSubcommand());
28
+ config.addCommand(createApikeySubcommand());
29
+ return config;
30
+ }
@@ -2,82 +2,58 @@ import { describe, expect, it } from 'vitest';
2
2
  import {
3
3
  gameStrategyIdentity,
4
4
  planGameStartAction,
5
- strategyRuntimeMatchesIdentity,
6
5
  } from './game.js';
7
6
 
8
7
  describe('planGameStartAction', () => {
9
8
  it('returns the running game start pid when a stream is already alive', () => {
10
9
  expect(planGameStartAction({
11
10
  gameStartPid: 1234,
12
- daemonPid: 4321,
13
11
  hasMatchState: true,
14
12
  queueStatus: 'allocated',
15
- feedPhase: 'wandering',
16
13
  })).toEqual({ kind: 'already_running', pid: 1234 });
17
14
  });
18
15
 
19
- it('resumes queue polling when a daemon is alive with match-state', () => {
16
+ it('resumes queue polling from backend queue state', () => {
20
17
  expect(planGameStartAction({
21
- daemonPid: 4321,
22
18
  hasMatchState: true,
23
19
  queueStatus: 'queued',
24
- feedPhase: null,
25
20
  })).toEqual({ kind: 'resume_queue' });
26
21
  });
27
22
 
28
- it('resumes queue polling when a daemon reports matching', () => {
23
+ it('ignores stale local match state when backend says not in queue', () => {
29
24
  expect(planGameStartAction({
30
- daemonPid: 4321,
31
- hasMatchState: false,
32
- queueStatus: 'not_in_queue',
33
- feedPhase: 'matching',
34
- })).toEqual({ kind: 'resume_queue' });
35
- });
36
-
37
- it('starts fresh when the only live daemon is idle in the lobby', () => {
38
- expect(planGameStartAction({
39
- daemonPid: 4321,
40
- hasMatchState: false,
25
+ hasMatchState: true,
41
26
  queueStatus: 'not_in_queue',
42
- feedPhase: 'lobby',
43
27
  })).toEqual({ kind: 'fresh_start' });
44
28
  });
45
29
 
46
- it('attaches to a live non-lobby daemon without queue state', () => {
30
+ it('resumes queue polling from local match state when backend status is unavailable', () => {
47
31
  expect(planGameStartAction({
48
- daemonPid: 4321,
49
- hasMatchState: false,
50
- queueStatus: 'not_in_queue',
51
- feedPhase: 'wandering',
52
- })).toEqual({ kind: 'attach_daemon' });
32
+ hasMatchState: true,
33
+ queueStatus: undefined,
34
+ })).toEqual({ kind: 'resume_queue' });
53
35
  });
54
36
 
55
- it('resumes an allocated match when no daemon is alive', () => {
37
+ it('resumes an allocated match when no owner runtime is alive', () => {
56
38
  expect(planGameStartAction({
57
- daemonPid: null,
58
39
  hasMatchState: false,
59
40
  queueStatus: 'allocated',
60
- feedPhase: null,
61
41
  })).toEqual({ kind: 'resume_allocated' });
62
42
  });
63
43
 
64
- it('resumes queue polling for queued and already_in_queue states without a daemon', () => {
44
+ it('resumes queue polling for queued and already_in_queue states without an owner runtime', () => {
65
45
  for (const queueStatus of ['queued', 'already_in_queue']) {
66
46
  expect(planGameStartAction({
67
- daemonPid: null,
68
47
  hasMatchState: false,
69
48
  queueStatus,
70
- feedPhase: null,
71
49
  })).toEqual({ kind: 'resume_queue' });
72
50
  }
73
51
  });
74
52
 
75
- it('starts fresh when no stream, daemon, or queue state can be recovered', () => {
53
+ it('starts fresh when no stream, runtime, or queue state can be recovered', () => {
76
54
  expect(planGameStartAction({
77
- daemonPid: null,
78
55
  hasMatchState: false,
79
56
  queueStatus: 'not_in_queue',
80
- feedPhase: null,
81
57
  })).toEqual({ kind: 'fresh_start' });
82
58
  });
83
59
  });
@@ -105,38 +81,4 @@ describe('game strategy identity', () => {
105
81
  });
106
82
  });
107
83
 
108
- it('preserves only a running strategy from the same current game', () => {
109
- const identity = { gameId: 'game-1', role: 'shrimp_generic', alive: true };
110
- expect(strategyRuntimeMatchesIdentity({
111
- strategy: 'task-report',
112
- pid: 123,
113
- gameId: 'game-1',
114
- role: 'shrimp_generic',
115
- running: true,
116
- }, identity)).toBe(true);
117
-
118
- expect(strategyRuntimeMatchesIdentity({
119
- strategy: 'task-report',
120
- pid: 123,
121
- gameId: 'old-game',
122
- role: 'shrimp_generic',
123
- running: true,
124
- }, identity)).toBe(false);
125
-
126
- expect(strategyRuntimeMatchesIdentity({
127
- strategy: 'task-report',
128
- pid: 123,
129
- gameId: 'game-1',
130
- role: 'crab_generic',
131
- running: true,
132
- }, identity)).toBe(false);
133
-
134
- expect(strategyRuntimeMatchesIdentity({
135
- strategy: 'task-report',
136
- pid: 123,
137
- gameId: 'game-1',
138
- role: 'shrimp_generic',
139
- running: false,
140
- }, identity)).toBe(false);
141
- });
142
84
  });