@myclaw163/clawclaw-cli 0.6.67 → 0.6.68

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 (40) 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/references/COMMANDS.md +4 -4
  5. package/skills/clawclaw/references/KNOWLEDGE.md +14 -12
  6. package/src/commands/config.ts +30 -30
  7. package/src/commands/knowledge.test.ts +4 -10
  8. package/src/commands/knowledge.ts +10 -39
  9. package/src/commands/setup/hermes.test.ts +96 -96
  10. package/src/commands/setup/hermes.ts +76 -76
  11. package/src/commands/setup/index.ts +13 -13
  12. package/src/commands/setup/openclaw.test.ts +114 -114
  13. package/src/commands/setup/openclaw.ts +147 -147
  14. package/src/lib/host-config-patcher.test.ts +130 -130
  15. package/src/lib/host-config-patcher.ts +151 -151
  16. package/src/lib/hub-reminder.ts +19 -19
  17. package/src/lib/knowledge-store.test.ts +28 -38
  18. package/src/lib/knowledge-store.ts +52 -57
  19. package/src/sdk/index.ts +2 -3
  20. package/src/sdk/types.ts +2 -0
  21. package/src/strategies/avoid-players.knowledge.md +7 -8
  22. package/src/strategies/avoid-players.ts +1 -1
  23. package/src/strategies/corpse-patrol.ts +1 -1
  24. package/src/strategies/game-utils.ts +29 -17
  25. package/src/strategies/goals/avoid-players-top.ts +3 -3
  26. package/src/strategies/goals/corpse-patrol-top.ts +23 -1
  27. package/src/strategies/goals/leaf-goal.ts +2 -0
  28. package/src/strategies/goals/lone-kill-task-top.ts +39 -8
  29. package/src/strategies/goals/normal-shrimp-top.ts +11 -11
  30. package/src/strategies/goals/paradise-fish-top.ts +32 -15
  31. package/src/strategies/goals/warrior-shrimp-top.test.ts +4 -3
  32. package/src/strategies/goals/warrior-shrimp-top.ts +140 -80
  33. package/src/strategies/kill-lone.knowledge.md +6 -9
  34. package/src/strategies/lone-kill-task.ts +1 -1
  35. package/src/strategies/paradise-fish.knowledge.md +7 -8
  36. package/src/strategies/paradise-fish.ts +1 -1
  37. package/src/strategies/shrimp-memory.knowledge.md +7 -8
  38. package/src/strategies/shrimp-memory.ts +1 -1
  39. package/src/strategies/warrior-memory.knowledge.md +9 -10
  40. package/src/strategies/warrior-memory.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { GameState } from '../../sdk/types.js';
2
2
  import {
3
+ emergencyRushDecision,
3
4
  firstAvailableTask,
4
5
  killCooldownSecs,
5
6
  nonTeammatesVisible,
@@ -11,7 +12,7 @@ import { Goal } from './goal.js';
11
12
  import { LoneKillCore } from './lone-kill-core.js';
12
13
  import { LoneKillGoal } from './lone-kill-goal.js';
13
14
  import { WanderTaskGoal } from './wander-task-goal.js';
14
- import { emitLeaf, setBehavior, URGENT_GOAL_PRIORITY, WANDER_GOAL_PRIORITY } from './leaf-goal.js';
15
+ import { emitLeaf, EMERGENCY_GOAL_PRIORITY, setBehavior, URGENT_GOAL_PRIORITY, WANDER_GOAL_PRIORITY } from './leaf-goal.js';
15
16
  import { corpseReportWithNonTeammate, immediateLoneKillDecision } from './crab-octopus-reflexes.js';
16
17
 
17
18
  /**
@@ -22,7 +23,9 @@ import { corpseReportWithNonTeammate, immediateLoneKillDecision } from './crab-o
22
23
  * 立刻出刀;否则有尸体且附近有非队友 → 报警/靠近报警(不管尸体是谁造成的)。
23
24
  * 1. 落单猎杀(LoneKillGoal,URGENT):core.assess() 判 hunt(刀好且有落单目标)→ 叶子 pursue() 靠近 / 出刀。
24
25
  * 刀在冷却时 assess 返回 idle,不追不等,直接下沉到任务伪装。
25
- * 2. 任务伪装 / 巡逻(WanderTaskGoal,WANDER):做已分配的伪装任务;章鱼不能提交紧急维修任务,
26
+ * 2. 残局抢修紧急任务(EMERGENCY,仅已知存活≤6):低于落单猎杀(击杀优先),高于伪装任务——
27
+ * 紧急任务超时则蟹阵营直接获胜,中立章鱼靠活到最后取胜,故残局抢着把维修做掉、掐断蟹的破坏取胜线。
28
+ * 3. 任务伪装 / 巡逻(WanderTaskGoal,WANDER):做已分配的伪装任务(排除当前紧急任务,那个走第 2 步),
26
29
  * 跳过紧挨已知尸体(含看见过、已离开视野的)的任务,没任务就按房间巡逻,绝不停在原地。
27
30
  */
28
31
  export class LoneKillTaskTop extends Goal {
@@ -30,7 +33,14 @@ export class LoneKillTaskTop extends Goal {
30
33
  private readonly patrol = new PatrolState();
31
34
  private readonly loneKillGoal = new LoneKillGoal(this.killCore);
32
35
  private readonly wanderGoal = new WanderTaskGoal(
33
- (state, ctx) => firstAvailableTask(ctx.taskData, () => true, undefined, ctx.blockedMoveTarget, ctx.knownCorpses),
36
+ // 排除当前紧急任务:它由 tick 里的残局抢修分支按 EMERGENCY 优先级处理,不在此当普通伪装任务做。
37
+ (state, ctx) => firstAvailableTask(
38
+ ctx.taskData,
39
+ t => t.task_name !== ctx.emergency?.task_name,
40
+ undefined,
41
+ ctx.blockedMoveTarget,
42
+ ctx.knownCorpses,
43
+ ),
34
44
  () => this.killCore.patrolStopOnPlayer(),
35
45
  this.patrol,
36
46
  );
@@ -40,28 +50,49 @@ export class LoneKillTaskTop extends Goal {
40
50
  }
41
51
 
42
52
  tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
43
- this.emitProgress(state, ctx);
44
-
45
53
  const immediateKill = immediateLoneKillDecision(state, ctx);
46
- if (immediateKill) return emitLeaf(this, immediateKill, URGENT_GOAL_PRIORITY);
54
+ if (immediateKill) {
55
+ this.emitProgress(state, ctx, false);
56
+ return emitLeaf(this, immediateKill, URGENT_GOAL_PRIORITY);
57
+ }
47
58
 
48
59
  const corpseReport = corpseReportWithNonTeammate(state, ctx);
49
- if (corpseReport) return emitLeaf(this, corpseReport, URGENT_GOAL_PRIORITY);
60
+ if (corpseReport) {
61
+ this.emitProgress(state, ctx, false);
62
+ return emitLeaf(this, corpseReport, URGENT_GOAL_PRIORITY);
63
+ }
50
64
 
51
65
  const target = this.killCore.assess(state, ctx);
52
66
  if (target.kind === 'hunt') {
67
+ this.emitProgress(state, ctx, false);
53
68
  this.loneKillGoal.setTarget(target.target);
54
69
  return setBehavior(this, this.loneKillGoal, URGENT_GOAL_PRIORITY);
55
70
  }
71
+
72
+ // 残局抢修紧急任务(已知存活≤6):高于伪装任务,低于上面的落单猎杀/反射。
73
+ const emergency = emergencyRushDecision(state, ctx);
74
+ if (emergency) {
75
+ this.emitProgress(state, ctx, true);
76
+ return emitLeaf(this, [emergency], EMERGENCY_GOAL_PRIORITY);
77
+ }
78
+
79
+ this.emitProgress(state, ctx, false);
56
80
  return setBehavior(this, this.wanderGoal, WANDER_GOAL_PRIORITY);
57
81
  }
58
82
 
59
- private emitProgress(state: GameState, ctx: StrategyContext): void {
83
+ private emitProgress(state: GameState, ctx: StrategyContext, emergencyRush: boolean): void {
60
84
  const now = Date.now();
61
85
  if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
62
86
  ctx.lastProgressNotifyAt = now;
63
87
 
64
88
  const room = state.you.room ?? '未知';
89
+ if (emergencyRush) {
90
+ const name = ctx.emergency?.task_name ?? '紧急维修';
91
+ ctx.notifications.push(
92
+ `[进度] 当前在${room},残局仅剩${state.alive_count}人,抢着去做紧急任务「${name}」,阻止蟹靠破坏倒计时取胜。`,
93
+ );
94
+ return;
95
+ }
65
96
  const cd = killCooldownSecs(state);
66
97
  const killsRemaining = state.you.kills_remaining;
67
98
  const nonTeammates = nonTeammatesVisible(state, ctx);
@@ -4,7 +4,7 @@ import {
4
4
  dist,
5
5
  firstAvailableTask,
6
6
  hasKnownCorpse,
7
- isKnowledgeThreat,
7
+ isKnowledgeHostile,
8
8
  isKnowledgeTrusted,
9
9
  nonTeammatesVisible,
10
10
  PROGRESS_INTERVAL_MS,
@@ -39,8 +39,8 @@ type Tier = 'flee-bad' | 'keep-distance' | 'task';
39
39
  *
40
40
  * - P0 发现尸体:一律报警(reportCorpseDecision),必要时提示 Agent 更新尸体旁玩家记忆。
41
41
  * - P1 规避危险:
42
- * · A 视野里有威胁(知识库 suspect/hostile)→ 寻路躲避(KeepAwayGoal,不限距离)。
43
- * · B 无可信同伴且陌生人贴近(≤270px)→ 先拉开距离(KeepAwayGoal,拉开到 300px 即回任务)。
42
+ * · A 视野里有坏人(知识库 hostile)→ 寻路躲避(KeepAwayGoal,不限距离)。
43
+ * · B 无可信同伴且被怀疑者(未标记 = 默认被怀疑)贴近(≤270px)→ 先拉开距离(KeepAwayGoal,拉开到 300px 即回任务)。
44
44
  * 目击者豁免:当前见到 ≥2 个陌生人时不触发后撤(蟹不敢当众动手,独处才危险);
45
45
  * 进行中的后撤也会重新判定,只有仍是单陌生人危险时才拉满到 300px。豁免期间陌生人也不算任务威胁点。
46
46
  * - P2 紧急任务:无危险时优先处理。
@@ -54,7 +54,7 @@ export class NormalShrimpTop extends Goal {
54
54
  // 豁免期间陌生人不算威胁点(与 B 档不触发保持视野半径对齐),认定坏人始终算。
55
55
  (s, c) => nonTeammatesVisible(s, c)
56
56
  .filter(p => !this.isTrusted(p, c))
57
- .filter(p => this.isThreat(p, c) || !this.witnessExempt)
57
+ .filter(p => this.isHostile(p, c) || !this.witnessExempt)
58
58
  .map(p => ({ x: p.x, y: p.y })),
59
59
  );
60
60
  private readonly strangerSeenAt = new Map<string, number>();
@@ -91,10 +91,10 @@ export class NormalShrimpTop extends Goal {
91
91
  }
92
92
 
93
93
  // ── P1-A 视野里有认定坏人 → 寻路躲避 ──
94
- const threats = visible.filter(p => this.isThreat(p, ctx));
94
+ const threats = visible.filter(p => this.isHostile(p, ctx));
95
95
  if (threats.length > 0) {
96
96
  this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
97
- this.setKeepAway(FLEE_BAD_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => this.isThreat(p, c)), {
97
+ this.setKeepAway(FLEE_BAD_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => this.isHostile(p, c)), {
98
98
  threatRadius: Infinity,
99
99
  noun: '认定坏人',
100
100
  });
@@ -153,9 +153,9 @@ export class NormalShrimpTop extends Goal {
153
153
  this.witnessExempt = this.strangerSeenAt.size >= WITNESS_EXEMPT_COUNT;
154
154
  }
155
155
 
156
- /** 威胁:知识库 suspect/hostile,且未被标记为 trusted。 */
157
- private isThreat(p: PlayerInfo, ctx: StrategyContext): boolean {
158
- return isKnowledgeThreat(p, ctx) && !this.isTrusted(p, ctx);
156
+ /** 坏人:知识库 hostile(明确敌对)。被怀疑者(未标记)不在此列,走 B 档保持距离。 */
157
+ private isHostile(p: PlayerInfo, ctx: StrategyContext): boolean {
158
+ return isKnowledgeHostile(p, ctx);
159
159
  }
160
160
 
161
161
  /** 可信同伴:知识库 trusted。 */
@@ -185,7 +185,7 @@ export class NormalShrimpTop extends Goal {
185
185
  if (!key || this.corpseWitnessNotified.has(key)) continue;
186
186
  this.corpseWitnessNotified.add(key);
187
187
 
188
- if (this.isThreat(player, ctx)) bad.push(player.name);
188
+ if (this.isHostile(player, ctx)) bad.push(player.name);
189
189
  else if (this.isKnownParadiseFish(player, ctx)) paradise.push(player.name);
190
190
  else if (this.isTrusted(player, ctx)) trusted.push(player.name);
191
191
  else unknown.push(player.name);
@@ -217,7 +217,7 @@ export class NormalShrimpTop extends Goal {
217
217
 
218
218
  let msg = `[进度] 当前在${room}`;
219
219
  if (tier === 'flee-bad') {
220
- const names = visible.filter(p => this.isThreat(p, ctx)).map(p => p.name).join('、');
220
+ const names = visible.filter(p => this.isHostile(p, ctx)).map(p => p.name).join('、');
221
221
  msg += `,发现认定坏人${names},正在远离。`;
222
222
  } else if (tier === 'keep-distance') {
223
223
  msg += `,陌生人靠得太近,先拉开距离再做任务。`;
@@ -1,8 +1,9 @@
1
1
  import type { GameState, PlayerInfo, Position } from '../../sdk/types.js';
2
2
  import {
3
3
  corpseAtScene,
4
+ emergencyRushDecision,
4
5
  hasKnownCorpse,
5
- isKnowledgeThreat,
6
+ isKnowledgeHostile,
6
7
  isKnowledgeTrusted,
7
8
  nonTeammatesVisible,
8
9
  PatrolState,
@@ -20,7 +21,7 @@ const SUB_PRIORITY = 0.5;
20
21
  const AVOID_ROUTE_MEMORY_MS = 10_000;
21
22
  const WITNESS_EXEMPT_COUNT = 2;
22
23
 
23
- type Tier = 'corpse' | 'flee-bad' | 'keep-distance' | 'huddle' | 'wander';
24
+ type Tier = 'emergency' | 'corpse' | 'flee-bad' | 'keep-distance' | 'huddle' | 'wander';
24
25
 
25
26
  export class ParadiseFishTop extends Goal {
26
27
  private readonly linger = new LingerCorpseGoal((state, ctx) => this.visibleAvoidTargets(state, ctx));
@@ -39,16 +40,16 @@ export class ParadiseFishTop extends Goal {
39
40
 
40
41
  tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
41
42
  const visible = nonTeammatesVisible(state, ctx);
42
- const threatTargets = visible.filter(p => this.isThreat(p, ctx));
43
+ const threatTargets = visible.filter(p => this.isHostile(p, ctx));
43
44
  const trustedTargets = visible.filter(p => this.isTrusted(p, ctx));
44
45
  const strangerAvoidTargets = this.strangerAvoidTargets(visible, ctx);
45
46
  this.rememberAvoidPoints(visible, ctx, strangerAvoidTargets.length > 0);
46
- const decisions: BehaviorDecision[] = [];
47
- const greeting = this.greeting.tryGreeting(state);
48
- if (greeting) decisions.push(greeting);
49
47
 
50
48
  if (threatTargets.length > 0) {
51
- this.setKeepAway('pf-flee-bad', (s, c) => nonTeammatesVisible(s, c).filter(p => this.isThreat(p, c)), {
49
+ const decisions: BehaviorDecision[] = [];
50
+ const greeting = this.greeting.tryGreeting(state);
51
+ if (greeting) decisions.push(greeting);
52
+ this.setKeepAway('pf-flee-bad', (s, c) => nonTeammatesVisible(s, c).filter(p => this.isHostile(p, c)), {
52
53
  threatRadius: Infinity,
53
54
  noun: '认定带刀坏人',
54
55
  });
@@ -56,6 +57,18 @@ export class ParadiseFishTop extends Goal {
56
57
  return decisions;
57
58
  }
58
59
 
60
+ // 残局抢修紧急任务(已知存活≤6):仅次于躲避带刀坏人,先于保持距离/贴尸/抱团。
61
+ const emergency = emergencyRushDecision(state, ctx);
62
+ if (emergency) {
63
+ this.clearSub();
64
+ this.emitProgress(state, ctx, visible, 'emergency');
65
+ return [emergency];
66
+ }
67
+
68
+ const decisions: BehaviorDecision[] = [];
69
+ const greeting = this.greeting.tryGreeting(state);
70
+ if (greeting) decisions.push(greeting);
71
+
59
72
  if (strangerAvoidTargets.length > 0) {
60
73
  this.setKeepAway('pf-keep-distance', (s, c) => this.strangerAvoidTargets(nonTeammatesVisible(s, c), c), {
61
74
  threatRadius: Infinity,
@@ -91,8 +104,9 @@ export class ParadiseFishTop extends Goal {
91
104
  return [...this.wander(state, ctx), ...decisions];
92
105
  }
93
106
 
94
- private isThreat(p: PlayerInfo, ctx: StrategyContext): boolean {
95
- return isKnowledgeThreat(p, ctx) && !this.isTrusted(p, ctx);
107
+ /** 坏人:知识库 hostile(明确敌对带刀者)。被怀疑者(未标记)走下面的保持距离档。 */
108
+ private isHostile(p: PlayerInfo, ctx: StrategyContext): boolean {
109
+ return isKnowledgeHostile(p, ctx);
96
110
  }
97
111
 
98
112
  private isTrusted(p: PlayerInfo, ctx: StrategyContext): boolean {
@@ -101,14 +115,14 @@ export class ParadiseFishTop extends Goal {
101
115
 
102
116
  private strangerAvoidTargets(visible: PlayerInfo[], ctx: StrategyContext): PlayerInfo[] {
103
117
  if (visible.some(p => this.isTrusted(p, ctx))) return [];
104
- const nonBad = visible.filter(p => !this.isThreat(p, ctx));
118
+ const nonBad = visible.filter(p => !this.isHostile(p, ctx));
105
119
  if (nonBad.length >= WITNESS_EXEMPT_COUNT) return [];
106
- return visible.filter(p => !this.isTrusted(p, ctx) && !this.isThreat(p, ctx));
120
+ return visible.filter(p => !this.isTrusted(p, ctx) && !this.isHostile(p, ctx));
107
121
  }
108
122
 
109
123
  private visibleAvoidTargets(state: GameState, ctx: StrategyContext): PlayerInfo[] {
110
124
  const visible = nonTeammatesVisible(state, ctx);
111
- const threats = visible.filter(p => this.isThreat(p, ctx));
125
+ const threats = visible.filter(p => this.isHostile(p, ctx));
112
126
  return threats.length > 0 ? threats : this.strangerAvoidTargets(visible, ctx);
113
127
  }
114
128
 
@@ -132,7 +146,7 @@ export class ParadiseFishTop extends Goal {
132
146
  this.recentAvoidPoints.delete(key);
133
147
  continue;
134
148
  }
135
- const threat = this.isThreat(p, ctx);
149
+ const threat = this.isHostile(p, ctx);
136
150
  if (!threat && !avoidNonThreat) {
137
151
  this.recentAvoidPoints.delete(key);
138
152
  continue;
@@ -183,7 +197,10 @@ export class ParadiseFishTop extends Goal {
183
197
 
184
198
  const room = state.you.room ?? '未知';
185
199
  let msg = `[进度] 当前在${room}`;
186
- if (tier === 'corpse') {
200
+ if (tier === 'emergency') {
201
+ const name = ctx.emergency?.task_name ?? '紧急维修';
202
+ msg += `,残局仅剩${state.alive_count}人,抢着去做紧急任务「${name}」,阻止蟹靠破坏倒计时取胜。`;
203
+ } else if (tier === 'corpse') {
187
204
  // corpseAtScene=已贴近尸体(≤150px);仅凭记忆走向尸体房间、人还没到时别谎报「在尸体附近游走」。
188
205
  if (!corpseAtScene(state)) {
189
206
  msg += ',记得有尸体,正前往尸体处(不报警)。';
@@ -193,7 +210,7 @@ export class ParadiseFishTop extends Goal {
193
210
  : ',发现尸体,附近无人,贴着尸体游走制造嫌疑(不报警)。';
194
211
  }
195
212
  } else if (tier === 'flee-bad') {
196
- const names = visible.filter(p => this.isThreat(p, ctx)).map(p => p.name).join('、');
213
+ const names = visible.filter(p => this.isHostile(p, ctx)).map(p => p.name).join('、');
197
214
  msg += `,发现认定带刀坏人${names},正在远离。`;
198
215
  } else if (tier === 'keep-distance') {
199
216
  msg += `,附近有${visible.length}人但无可信同伴,保持距离中。`;
@@ -29,7 +29,7 @@ function state(): GameState {
29
29
  };
30
30
  }
31
31
 
32
- function knowledge(mark: 'suspect' | 'hostile' | 'trusted') {
32
+ function knowledge(mark: 'hostile' | 'trusted') {
33
33
  const ts = Date.now();
34
34
  const file: KnowledgeFile = {
35
35
  version: 1,
@@ -68,10 +68,11 @@ function context(overrides: Partial<StrategyContext> = {}): StrategyContext {
68
68
  }
69
69
 
70
70
  describe('WarriorShrimpTop knowledge tiers', () => {
71
- it('avoids a suspect even when the weapon is ready', () => {
71
+ it('keeps distance from an unmarked (suspected-by-default) player even when the weapon is ready', () => {
72
72
  const top = new WarriorShrimpTop();
73
73
 
74
- top.tick(state(), context({ knowledge: knowledge('suspect') }));
74
+ // 不打任何标记:未标记 = 默认被怀疑 → 只保持距离,刀好也不主动出刀。
75
+ top.tick(state(), context());
75
76
 
76
77
  expect(top.subGoal).toBeInstanceOf(KeepAwayGoal);
77
78
  });