@myclaw163/clawclaw-cli 0.6.76 → 0.6.77
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 +387 -387
- package/bin/clawclaw-cli.mjs +3 -3
- package/package.json +48 -48
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
- package/scripts/check-skill-command-surface.mjs +116 -116
- package/scripts/find-hide-spots.py +157 -157
- package/scripts/postinstall.mjs +20 -20
- package/scripts/sync-bundled-skill.mjs +254 -245
- package/scripts/sync-bundled-skill.test.mjs +152 -152
- package/skills/clawclaw/SKILL.md +248 -248
- package/skills/clawclaw/references/CHATTERBOX.md +141 -141
- package/skills/clawclaw/references/COMMANDS.md +160 -160
- package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
- package/skills/clawclaw/references/HUB.md +48 -48
- package/skills/clawclaw/references/KNOWLEDGE.md +42 -42
- package/skills/clawclaw/references/STRATEGIES.md +59 -59
- package/skills/clawclaw/references/STREAM.md +93 -93
- package/skills/clawclaw/references/TACTICS.md +65 -65
- package/src/assets/clawclaw-ascii-map.txt +40 -40
- package/src/cli.ts +112 -112
- package/src/commands/_schema.ts +124 -124
- package/src/commands/account.ts +209 -209
- package/src/commands/data.test.ts +33 -33
- package/src/commands/data.ts +22 -22
- package/src/commands/do.test.ts +84 -84
- package/src/commands/do.ts +130 -130
- package/src/commands/events.test.ts +100 -100
- package/src/commands/events.ts +250 -250
- package/src/commands/game-map.test.ts +28 -28
- package/src/commands/game-start-plan.test.ts +84 -84
- package/src/commands/game.ts +1113 -1113
- package/src/commands/history-player.test.ts +102 -102
- package/src/commands/history.ts +573 -573
- package/src/commands/hub.test.ts +96 -96
- package/src/commands/hub.ts +234 -234
- package/src/commands/knowledge.test.ts +13 -13
- package/src/commands/knowledge.ts +139 -139
- package/src/commands/load.test.ts +51 -51
- package/src/commands/load.ts +13 -13
- package/src/commands/meeting-history.test.ts +106 -106
- package/src/commands/memory.ts +40 -40
- package/src/commands/peek.ts +45 -45
- package/src/commands/persona.ts +57 -57
- package/src/commands/setup/codex.ts +266 -266
- package/src/commands/skill.ts +128 -128
- package/src/commands/state.ts +46 -46
- package/src/commands/strategy.test.ts +153 -153
- package/src/commands/strategy.ts +183 -183
- package/src/commands/tts.ts +128 -128
- package/src/commands/upgrade.test.ts +82 -82
- package/src/commands/upgrade.ts +148 -148
- package/src/commands/watch.test.ts +999 -999
- package/src/commands/watch.ts +660 -660
- package/src/lib/auth.test.ts +86 -86
- package/src/lib/auth.ts +223 -223
- package/src/lib/command-meta.ts +37 -37
- package/src/lib/game-client.ts +403 -403
- package/src/lib/game-context.ts +92 -92
- package/src/lib/http-keepalive.ts +15 -15
- package/src/lib/http-transport.test.ts +42 -42
- package/src/lib/http-transport.ts +113 -113
- package/src/lib/hub-client.test.ts +56 -56
- package/src/lib/hub-client.ts +88 -88
- package/src/lib/hub-install.test.ts +98 -98
- package/src/lib/hub-install.ts +160 -160
- package/src/lib/hub-reminder.ts +78 -78
- package/src/lib/hub-unzip.test.ts +69 -69
- package/src/lib/hub-unzip.ts +62 -62
- package/src/lib/init-command.test.ts +75 -75
- package/src/lib/init-command.ts +130 -130
- package/src/lib/knowledge-store.test.ts +170 -170
- package/src/lib/knowledge-store.ts +369 -369
- package/src/lib/load-context.test.ts +52 -52
- package/src/lib/load-context.ts +52 -52
- package/src/lib/match-state.test.ts +134 -134
- package/src/lib/match-state.ts +94 -94
- package/src/lib/netease-tts.ts +83 -83
- package/src/lib/normalize.ts +42 -42
- package/src/lib/persona.test.ts +41 -41
- package/src/lib/persona.ts +72 -72
- package/src/lib/server-registry.ts +152 -152
- package/src/lib/skill-version.test.ts +48 -48
- package/src/lib/skill-version.ts +19 -19
- package/src/lib/strategy-export.test.ts +240 -240
- package/src/lib/strategy-export.ts +247 -247
- package/src/lib/tts-keys.ts +7 -7
- package/src/lib/tts-speech.test.ts +63 -63
- package/src/lib/tts-speech.ts +76 -76
- package/src/lib/user-data.test.ts +96 -96
- package/src/lib/user-data.ts +400 -400
- package/src/lib/workspace-argv.test.ts +49 -49
- package/src/lib/workspace-argv.ts +44 -44
- package/src/perception/player-history-store.test.ts +87 -87
- package/src/perception/player-history-store.ts +194 -194
- package/src/pipeline/event-format.test.ts +243 -243
- package/src/pipeline/event-format.ts +501 -501
- package/src/pipeline/event-hints.ts +195 -195
- package/src/pipeline/event-store.test.ts +28 -28
- package/src/pipeline/event-store.ts +193 -193
- package/src/pipeline/pipeline.ts +35 -35
- package/src/pipeline/player-projection.test.ts +168 -168
- package/src/pipeline/player-projection.ts +370 -370
- package/src/runtime/auto-upgrade.test.ts +66 -66
- package/src/runtime/auto-upgrade.ts +31 -31
- package/src/runtime/event-daemon.test.ts +209 -209
- package/src/runtime/event-daemon.ts +519 -519
- package/src/runtime/owner-control.ts +150 -150
- package/src/runtime/raw-ws-log.test.ts +33 -33
- package/src/runtime/raw-ws-log.ts +32 -32
- package/src/runtime/runtime-logger.ts +107 -107
- package/src/runtime/ws-client.test.ts +125 -125
- package/src/runtime/ws-client.ts +287 -287
- package/src/sdk/action.ts +166 -166
- package/src/sdk/index.ts +110 -110
- package/src/sdk/types.ts +161 -161
- package/src/strategies/avoid-lone.ts +12 -12
- package/src/strategies/avoid-players.knowledge.md +19 -19
- package/src/strategies/avoid-players.ts +16 -16
- package/src/strategies/corpse-patrol.ts +23 -23
- package/src/strategies/crab-sabotage.ts +22 -22
- package/src/strategies/custom-module.test.ts +270 -270
- package/src/strategies/find-player.ts +17 -17
- package/src/strategies/game-utils.test.ts +242 -242
- package/src/strategies/game-utils.ts +846 -846
- package/src/strategies/goals/anchor-linger.ts +77 -77
- package/src/strategies/goals/avoid-lone-top.ts +168 -168
- package/src/strategies/goals/avoid-players-top.test.ts +83 -83
- package/src/strategies/goals/avoid-players-top.ts +121 -121
- package/src/strategies/goals/conversation-goal.ts +51 -51
- package/src/strategies/goals/corpse-patrol-top.ts +113 -113
- package/src/strategies/goals/crab-octopus-reflexes.ts +101 -101
- package/src/strategies/goals/crab-sabotage-top.ts +197 -197
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
- package/src/strategies/goals/find-player-top.ts +93 -93
- package/src/strategies/goals/flee-players-goal.ts +53 -53
- package/src/strategies/goals/follow-companion-goal.ts +106 -106
- package/src/strategies/goals/goal-manager.ts +41 -41
- package/src/strategies/goals/goal-root-strategy.ts +49 -49
- package/src/strategies/goals/goal.ts +28 -28
- package/src/strategies/goals/hide-top.ts +197 -197
- package/src/strategies/goals/keep-away-goal.ts +221 -221
- package/src/strategies/goals/kill-frenzy-top.ts +80 -80
- package/src/strategies/goals/kill-lone-top.ts +160 -160
- package/src/strategies/goals/kill-target-goal.ts +59 -59
- package/src/strategies/goals/kill-target-top.ts +109 -109
- package/src/strategies/goals/leaf-goal.ts +27 -27
- package/src/strategies/goals/linger-corpse-goal.ts +35 -35
- package/src/strategies/goals/lone-kill-core.ts +82 -82
- package/src/strategies/goals/lone-kill-goal.ts +24 -24
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
- package/src/strategies/goals/lone-kill-task-top.ts +133 -133
- package/src/strategies/goals/move-room-goal.ts +60 -60
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
- package/src/strategies/goals/normal-shrimp-top.ts +242 -242
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
- package/src/strategies/goals/paradise-fish-top.ts +224 -224
- package/src/strategies/goals/patrol-top.ts +57 -57
- package/src/strategies/goals/report-patrol-top.ts +80 -80
- package/src/strategies/goals/safe-task-goal.ts +102 -102
- package/src/strategies/goals/social-task-top.ts +161 -161
- package/src/strategies/goals/task-kill-report-top.ts +163 -163
- package/src/strategies/goals/task-only-top.ts +57 -57
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
- package/src/strategies/goals/task-report-top.ts +57 -57
- package/src/strategies/goals/wander-task-goal.ts +33 -33
- package/src/strategies/goals/warrior-shrimp-top.test.ts +87 -87
- package/src/strategies/goals/warrior-shrimp-top.ts +267 -267
- package/src/strategies/greeting.ts +53 -53
- package/src/strategies/hide-spots.ts +59 -59
- package/src/strategies/hide.ts +24 -24
- package/src/strategies/kill-frenzy.ts +13 -13
- package/src/strategies/kill-lone.knowledge.md +17 -17
- package/src/strategies/kill-lone.ts +14 -14
- package/src/strategies/kill-target.ts +19 -19
- package/src/strategies/loader.test.ts +678 -678
- package/src/strategies/loader.ts +181 -181
- package/src/strategies/lone-kill-task.ts +22 -22
- package/src/strategies/meeting-gate.test.ts +59 -59
- package/src/strategies/meeting-gate.ts +23 -23
- package/src/strategies/move-room.ts +16 -16
- package/src/strategies/new-events-backfill.ts +98 -98
- package/src/strategies/off-route-points.ts +105 -105
- package/src/strategies/paradise-fish.knowledge.md +19 -19
- package/src/strategies/paradise-fish.ts +26 -26
- package/src/strategies/pathfind/distance-field.ts +150 -150
- package/src/strategies/pathfind/escape-planner.test.ts +197 -197
- package/src/strategies/pathfind/escape-planner.ts +355 -355
- package/src/strategies/pathfind/walkable-grid.ts +117 -117
- package/src/strategies/patrol.ts +12 -12
- package/src/strategies/player-targets.ts +13 -13
- package/src/strategies/report-patrol.ts +12 -12
- package/src/strategies/shrimp-memory.knowledge.md +19 -19
- package/src/strategies/shrimp-memory.ts +26 -26
- package/src/strategies/social-task.test.ts +28 -28
- package/src/strategies/social-task.ts +50 -50
- package/src/strategies/spawn.ts +82 -82
- package/src/strategies/speech-module.ts +123 -123
- package/src/strategies/strategy-loop.test.ts +15 -15
- package/src/strategies/strategy-loop.ts +776 -776
- package/src/strategies/task-kill-report.ts +18 -18
- package/src/strategies/task-only.ts +12 -12
- package/src/strategies/task-report.ts +23 -23
- package/src/strategies/types.ts +109 -109
- package/src/strategies/warrior-memory.knowledge.md +21 -21
- package/src/strategies/warrior-memory.ts +17 -17
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { DROPPED_PLAYER_MARK_TAGS, KnowledgeStore, type KnowledgeSetInput, type PlayerMark } from '../lib/knowledge-store.js';
|
|
3
|
-
|
|
4
|
-
// 玩家只标两档:hostile(坏人)/ trusted(好人)。未标记的默认「被怀疑」,无需也无法显式标。
|
|
5
|
-
const PLAYER_MARKS = new Set<PlayerMark>(['hostile', 'trusted']);
|
|
6
|
-
const LEGACY_MARK_TAGS = ['kill_if_armed', 'cleared', 'protected', 'do_not_kill'];
|
|
7
|
-
|
|
8
|
-
export function normalizePlayerMark(mark?: string): PlayerMark | null {
|
|
9
|
-
const normalized = mark?.trim().toLowerCase() as PlayerMark | undefined;
|
|
10
|
-
return normalized && PLAYER_MARKS.has(normalized) ? normalized : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** "true"/"false"/数字 自动转类型,其余按字符串。 */
|
|
14
|
-
function coerceValue(raw: string | undefined): unknown {
|
|
15
|
-
if (raw == null) return true;
|
|
16
|
-
if (raw === 'true') return true;
|
|
17
|
-
if (raw === 'false') return false;
|
|
18
|
-
if (raw !== '' && !Number.isNaN(Number(raw))) return Number(raw);
|
|
19
|
-
return raw;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function collectTag(value: string, prev: string[]): string[] {
|
|
23
|
-
return [...prev, value];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function print(data: unknown): void {
|
|
27
|
-
console.log(JSON.stringify(data, null, 2));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function fail(error: string, message: string): never {
|
|
31
|
-
console.error(JSON.stringify({ error, message }, null, 2));
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function setPlayerMark(store: KnowledgeStore, player: string, mark: PlayerMark, opts: { note?: string }) {
|
|
36
|
-
const removeTags = [
|
|
37
|
-
...[...PLAYER_MARKS].filter(candidate => candidate !== mark),
|
|
38
|
-
...LEGACY_MARK_TAGS,
|
|
39
|
-
...DROPPED_PLAYER_MARK_TAGS, // 顺手清掉旧数据残留的 suspect/avoid raw tag,别再从 get/subject() 漏出去
|
|
40
|
-
];
|
|
41
|
-
return store.set({
|
|
42
|
-
type: 'player',
|
|
43
|
-
selector: player,
|
|
44
|
-
tags: [mark],
|
|
45
|
-
removeTags,
|
|
46
|
-
note: opts.note,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function createKnowledgeCommand(): Command {
|
|
51
|
-
const cmd = new Command('knowledge')
|
|
52
|
-
.alias('kn')
|
|
53
|
-
.description('Per-game structured knowledge that a running strategy can query (no restart needed)');
|
|
54
|
-
|
|
55
|
-
cmd
|
|
56
|
-
.command('set [type] [selector] [key] [value]', { hidden: true })
|
|
57
|
-
.description('Advanced: upsert arbitrary facts/tags. Use `knowledge mark` for player strategy behavior.')
|
|
58
|
-
.option('--tag <tag>', 'Custom tag, repeatable', collectTag, [])
|
|
59
|
-
.option('--note <text>', 'Free-text reason')
|
|
60
|
-
.option('--json <json>', 'Provide the whole input object as JSON instead of positional args')
|
|
61
|
-
.action((type: string | undefined, selector: string | undefined, key: string | undefined, value: string | undefined, opts: {
|
|
62
|
-
tag: string[]; note?: string; json?: string;
|
|
63
|
-
}) => {
|
|
64
|
-
try {
|
|
65
|
-
const store = KnowledgeStore.forActiveAccount();
|
|
66
|
-
let input: KnowledgeSetInput;
|
|
67
|
-
if (opts.json) {
|
|
68
|
-
input = JSON.parse(opts.json) as KnowledgeSetInput;
|
|
69
|
-
} else {
|
|
70
|
-
if (!type || !selector) {
|
|
71
|
-
fail('missing_args', 'Usage: ccl knowledge set <type> <selector> [key] [value] [--tag ...] — or: ccl knowledge set --json "{...}"');
|
|
72
|
-
}
|
|
73
|
-
input = {
|
|
74
|
-
type,
|
|
75
|
-
selector,
|
|
76
|
-
key,
|
|
77
|
-
value: key != null ? coerceValue(value) : undefined,
|
|
78
|
-
tags: opts.tag.length > 0 ? opts.tag : undefined,
|
|
79
|
-
note: opts.note,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const entry = store.set(input);
|
|
83
|
-
print({ ok: true, gameId: store.gameId ?? null, subject: entry });
|
|
84
|
-
} catch (e) {
|
|
85
|
-
fail('knowledge_set_failed', e instanceof Error ? e.message : String(e));
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
cmd
|
|
90
|
-
.command('mark <player> <mark>')
|
|
91
|
-
.description('Set the strategy mark for a player: hostile | trusted (unmarked players are suspected by default)')
|
|
92
|
-
.option('--note <text>', 'Free-text reason')
|
|
93
|
-
.action((player: string, rawMark: string, opts: { note?: string }) => {
|
|
94
|
-
const mark = normalizePlayerMark(rawMark);
|
|
95
|
-
if (!mark) fail('invalid_mark', `Unknown mark '${rawMark}'. Use: hostile | trusted. (Unmarked = suspected by default.)`);
|
|
96
|
-
try {
|
|
97
|
-
const store = KnowledgeStore.forActiveAccount();
|
|
98
|
-
const entry = setPlayerMark(store, player, mark, opts);
|
|
99
|
-
print({ ok: true, gameId: store.gameId ?? null, mark, subject: entry });
|
|
100
|
-
} catch (e) {
|
|
101
|
-
fail('knowledge_mark_failed', e instanceof Error ? e.message : String(e));
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
cmd
|
|
106
|
-
.command('get [type] [selector]')
|
|
107
|
-
.description('Read knowledge: all, all of a type, or one subject')
|
|
108
|
-
.action((type: string | undefined, selector: string | undefined) => {
|
|
109
|
-
const store = KnowledgeStore.forActiveAccount();
|
|
110
|
-
const file = store.list();
|
|
111
|
-
if (type && selector) {
|
|
112
|
-
print({ gameId: file.gameId ?? null, subject: file.subjects[`${type}:${selector}`] ?? null });
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const subjects = Object.values(file.subjects).filter((s) => !type || s.type === type);
|
|
116
|
-
print({ gameId: file.gameId ?? null, count: subjects.length, subjects });
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
cmd
|
|
120
|
-
.command('del <type> <selector> [key]')
|
|
121
|
-
.description('Delete a subject, or just one fact (key) / tag (--tag)')
|
|
122
|
-
.option('--tag <tag>', 'Delete only this tag')
|
|
123
|
-
.action((type: string, selector: string, key: string | undefined, opts: { tag?: string }) => {
|
|
124
|
-
const store = KnowledgeStore.forActiveAccount();
|
|
125
|
-
const removed = store.del(type, selector, { key, tag: opts.tag });
|
|
126
|
-
print({ ok: removed, gameId: store.gameId ?? null });
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
cmd
|
|
130
|
-
.command('clear')
|
|
131
|
-
.description('Clear all knowledge for the current game')
|
|
132
|
-
.action(() => {
|
|
133
|
-
const store = KnowledgeStore.forActiveAccount();
|
|
134
|
-
store.clear();
|
|
135
|
-
print({ ok: true, gameId: store.gameId ?? null });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return cmd;
|
|
139
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { DROPPED_PLAYER_MARK_TAGS, KnowledgeStore, type KnowledgeSetInput, type PlayerMark } from '../lib/knowledge-store.js';
|
|
3
|
+
|
|
4
|
+
// 玩家只标两档:hostile(坏人)/ trusted(好人)。未标记的默认「被怀疑」,无需也无法显式标。
|
|
5
|
+
const PLAYER_MARKS = new Set<PlayerMark>(['hostile', 'trusted']);
|
|
6
|
+
const LEGACY_MARK_TAGS = ['kill_if_armed', 'cleared', 'protected', 'do_not_kill'];
|
|
7
|
+
|
|
8
|
+
export function normalizePlayerMark(mark?: string): PlayerMark | null {
|
|
9
|
+
const normalized = mark?.trim().toLowerCase() as PlayerMark | undefined;
|
|
10
|
+
return normalized && PLAYER_MARKS.has(normalized) ? normalized : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** "true"/"false"/数字 自动转类型,其余按字符串。 */
|
|
14
|
+
function coerceValue(raw: string | undefined): unknown {
|
|
15
|
+
if (raw == null) return true;
|
|
16
|
+
if (raw === 'true') return true;
|
|
17
|
+
if (raw === 'false') return false;
|
|
18
|
+
if (raw !== '' && !Number.isNaN(Number(raw))) return Number(raw);
|
|
19
|
+
return raw;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function collectTag(value: string, prev: string[]): string[] {
|
|
23
|
+
return [...prev, value];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function print(data: unknown): void {
|
|
27
|
+
console.log(JSON.stringify(data, null, 2));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function fail(error: string, message: string): never {
|
|
31
|
+
console.error(JSON.stringify({ error, message }, null, 2));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function setPlayerMark(store: KnowledgeStore, player: string, mark: PlayerMark, opts: { note?: string }) {
|
|
36
|
+
const removeTags = [
|
|
37
|
+
...[...PLAYER_MARKS].filter(candidate => candidate !== mark),
|
|
38
|
+
...LEGACY_MARK_TAGS,
|
|
39
|
+
...DROPPED_PLAYER_MARK_TAGS, // 顺手清掉旧数据残留的 suspect/avoid raw tag,别再从 get/subject() 漏出去
|
|
40
|
+
];
|
|
41
|
+
return store.set({
|
|
42
|
+
type: 'player',
|
|
43
|
+
selector: player,
|
|
44
|
+
tags: [mark],
|
|
45
|
+
removeTags,
|
|
46
|
+
note: opts.note,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createKnowledgeCommand(): Command {
|
|
51
|
+
const cmd = new Command('knowledge')
|
|
52
|
+
.alias('kn')
|
|
53
|
+
.description('Per-game structured knowledge that a running strategy can query (no restart needed)');
|
|
54
|
+
|
|
55
|
+
cmd
|
|
56
|
+
.command('set [type] [selector] [key] [value]', { hidden: true })
|
|
57
|
+
.description('Advanced: upsert arbitrary facts/tags. Use `knowledge mark` for player strategy behavior.')
|
|
58
|
+
.option('--tag <tag>', 'Custom tag, repeatable', collectTag, [])
|
|
59
|
+
.option('--note <text>', 'Free-text reason')
|
|
60
|
+
.option('--json <json>', 'Provide the whole input object as JSON instead of positional args')
|
|
61
|
+
.action((type: string | undefined, selector: string | undefined, key: string | undefined, value: string | undefined, opts: {
|
|
62
|
+
tag: string[]; note?: string; json?: string;
|
|
63
|
+
}) => {
|
|
64
|
+
try {
|
|
65
|
+
const store = KnowledgeStore.forActiveAccount();
|
|
66
|
+
let input: KnowledgeSetInput;
|
|
67
|
+
if (opts.json) {
|
|
68
|
+
input = JSON.parse(opts.json) as KnowledgeSetInput;
|
|
69
|
+
} else {
|
|
70
|
+
if (!type || !selector) {
|
|
71
|
+
fail('missing_args', 'Usage: ccl knowledge set <type> <selector> [key] [value] [--tag ...] — or: ccl knowledge set --json "{...}"');
|
|
72
|
+
}
|
|
73
|
+
input = {
|
|
74
|
+
type,
|
|
75
|
+
selector,
|
|
76
|
+
key,
|
|
77
|
+
value: key != null ? coerceValue(value) : undefined,
|
|
78
|
+
tags: opts.tag.length > 0 ? opts.tag : undefined,
|
|
79
|
+
note: opts.note,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const entry = store.set(input);
|
|
83
|
+
print({ ok: true, gameId: store.gameId ?? null, subject: entry });
|
|
84
|
+
} catch (e) {
|
|
85
|
+
fail('knowledge_set_failed', e instanceof Error ? e.message : String(e));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
cmd
|
|
90
|
+
.command('mark <player> <mark>')
|
|
91
|
+
.description('Set the strategy mark for a player: hostile | trusted (unmarked players are suspected by default)')
|
|
92
|
+
.option('--note <text>', 'Free-text reason')
|
|
93
|
+
.action((player: string, rawMark: string, opts: { note?: string }) => {
|
|
94
|
+
const mark = normalizePlayerMark(rawMark);
|
|
95
|
+
if (!mark) fail('invalid_mark', `Unknown mark '${rawMark}'. Use: hostile | trusted. (Unmarked = suspected by default.)`);
|
|
96
|
+
try {
|
|
97
|
+
const store = KnowledgeStore.forActiveAccount();
|
|
98
|
+
const entry = setPlayerMark(store, player, mark, opts);
|
|
99
|
+
print({ ok: true, gameId: store.gameId ?? null, mark, subject: entry });
|
|
100
|
+
} catch (e) {
|
|
101
|
+
fail('knowledge_mark_failed', e instanceof Error ? e.message : String(e));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
cmd
|
|
106
|
+
.command('get [type] [selector]')
|
|
107
|
+
.description('Read knowledge: all, all of a type, or one subject')
|
|
108
|
+
.action((type: string | undefined, selector: string | undefined) => {
|
|
109
|
+
const store = KnowledgeStore.forActiveAccount();
|
|
110
|
+
const file = store.list();
|
|
111
|
+
if (type && selector) {
|
|
112
|
+
print({ gameId: file.gameId ?? null, subject: file.subjects[`${type}:${selector}`] ?? null });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const subjects = Object.values(file.subjects).filter((s) => !type || s.type === type);
|
|
116
|
+
print({ gameId: file.gameId ?? null, count: subjects.length, subjects });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
cmd
|
|
120
|
+
.command('del <type> <selector> [key]')
|
|
121
|
+
.description('Delete a subject, or just one fact (key) / tag (--tag)')
|
|
122
|
+
.option('--tag <tag>', 'Delete only this tag')
|
|
123
|
+
.action((type: string, selector: string, key: string | undefined, opts: { tag?: string }) => {
|
|
124
|
+
const store = KnowledgeStore.forActiveAccount();
|
|
125
|
+
const removed = store.del(type, selector, { key, tag: opts.tag });
|
|
126
|
+
print({ ok: removed, gameId: store.gameId ?? null });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
cmd
|
|
130
|
+
.command('clear')
|
|
131
|
+
.description('Clear all knowledge for the current game')
|
|
132
|
+
.action(() => {
|
|
133
|
+
const store = KnowledgeStore.forActiveAccount();
|
|
134
|
+
store.clear();
|
|
135
|
+
print({ ok: true, gameId: store.gameId ?? null });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return cmd;
|
|
139
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
vi.mock('../lib/load-context.js', () => ({
|
|
4
|
-
loadContext: vi.fn(() => ({ persona: { exists: true }, memory: { exists: false } })),
|
|
5
|
-
}));
|
|
6
|
-
|
|
7
|
-
vi.mock('../runtime/auto-upgrade.js', () => ({
|
|
8
|
-
runAutoUpgradeOnLoad: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
|
|
12
|
-
import { createLoadCommand } from './load.js';
|
|
13
|
-
|
|
14
|
-
describe('load command', () => {
|
|
15
|
-
let logSpy: ReturnType<typeof vi.spyOn>;
|
|
16
|
-
let errorSpy: ReturnType<typeof vi.spyOn>;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
vi.mocked(runAutoUpgradeOnLoad).mockClear();
|
|
20
|
-
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
21
|
-
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
logSpy.mockRestore();
|
|
26
|
-
errorSpy.mockRestore();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('runs auto-upgrade check before printing JSON load context', async () => {
|
|
30
|
-
const cmd = createLoadCommand();
|
|
31
|
-
await cmd.parseAsync(['node', 'load'], { from: 'node' });
|
|
32
|
-
|
|
33
|
-
expect(runAutoUpgradeOnLoad).toHaveBeenCalledOnce();
|
|
34
|
-
expect(logSpy).toHaveBeenCalledOnce();
|
|
35
|
-
expect(JSON.parse(String(logSpy.mock.calls[0][0]))).toEqual({
|
|
36
|
-
persona: { exists: true },
|
|
37
|
-
memory: { exists: false },
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('routes auto-upgrade notifications to stderr', async () => {
|
|
42
|
-
vi.mocked(runAutoUpgradeOnLoad).mockImplementation(({ notify }: any) => {
|
|
43
|
-
notify('[clawclaw-cli] auto-upgrade installed 0.6.39');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const cmd = createLoadCommand();
|
|
47
|
-
await cmd.parseAsync(['node', 'load'], { from: 'node' });
|
|
48
|
-
|
|
49
|
-
expect(errorSpy).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade installed 0.6.39');
|
|
50
|
-
});
|
|
51
|
-
});
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('../lib/load-context.js', () => ({
|
|
4
|
+
loadContext: vi.fn(() => ({ persona: { exists: true }, memory: { exists: false } })),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock('../runtime/auto-upgrade.js', () => ({
|
|
8
|
+
runAutoUpgradeOnLoad: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
|
|
12
|
+
import { createLoadCommand } from './load.js';
|
|
13
|
+
|
|
14
|
+
describe('load command', () => {
|
|
15
|
+
let logSpy: ReturnType<typeof vi.spyOn>;
|
|
16
|
+
let errorSpy: ReturnType<typeof vi.spyOn>;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.mocked(runAutoUpgradeOnLoad).mockClear();
|
|
20
|
+
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
21
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
logSpy.mockRestore();
|
|
26
|
+
errorSpy.mockRestore();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('runs auto-upgrade check before printing JSON load context', async () => {
|
|
30
|
+
const cmd = createLoadCommand();
|
|
31
|
+
await cmd.parseAsync(['node', 'load'], { from: 'node' });
|
|
32
|
+
|
|
33
|
+
expect(runAutoUpgradeOnLoad).toHaveBeenCalledOnce();
|
|
34
|
+
expect(logSpy).toHaveBeenCalledOnce();
|
|
35
|
+
expect(JSON.parse(String(logSpy.mock.calls[0][0]))).toEqual({
|
|
36
|
+
persona: { exists: true },
|
|
37
|
+
memory: { exists: false },
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('routes auto-upgrade notifications to stderr', async () => {
|
|
42
|
+
vi.mocked(runAutoUpgradeOnLoad).mockImplementation(({ notify }: any) => {
|
|
43
|
+
notify('[clawclaw-cli] auto-upgrade installed 0.6.39');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const cmd = createLoadCommand();
|
|
47
|
+
await cmd.parseAsync(['node', 'load'], { from: 'node' });
|
|
48
|
+
|
|
49
|
+
expect(errorSpy).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade installed 0.6.39');
|
|
50
|
+
});
|
|
51
|
+
});
|
package/src/commands/load.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { loadContext } from '../lib/load-context.js';
|
|
3
|
-
import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
|
|
4
|
-
|
|
5
|
-
export function createLoadCommand(): Command {
|
|
6
|
-
return new Command('load')
|
|
7
|
-
.description('Read active account persona and memory files')
|
|
8
|
-
.action(() => {
|
|
9
|
-
runAutoUpgradeOnLoad({ notify: (message) => console.error(message) });
|
|
10
|
-
const snapshot = loadContext();
|
|
11
|
-
console.log(JSON.stringify(snapshot, null, 2));
|
|
12
|
-
});
|
|
13
|
-
}
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { loadContext } from '../lib/load-context.js';
|
|
3
|
+
import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
|
|
4
|
+
|
|
5
|
+
export function createLoadCommand(): Command {
|
|
6
|
+
return new Command('load')
|
|
7
|
+
.description('Read active account persona and memory files')
|
|
8
|
+
.action(() => {
|
|
9
|
+
runAutoUpgradeOnLoad({ notify: (message) => console.error(message) });
|
|
10
|
+
const snapshot = loadContext();
|
|
11
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { meetingHistoryCommandInternals } from './history.js';
|
|
3
|
-
|
|
4
|
-
const allSeats = [
|
|
5
|
-
{ name: 'A', seat: 1 },
|
|
6
|
-
{ name: 'B', seat: 2 },
|
|
7
|
-
{ name: 'C', seat: 3 },
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
describe('meeting history builder', () => {
|
|
11
|
-
it('outputs the compact meeting shape with found-in-meeting deaths and ordered speeches', () => {
|
|
12
|
-
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
13
|
-
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
14
|
-
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
15
|
-
{
|
|
16
|
-
type: 'meeting_state',
|
|
17
|
-
round: 1,
|
|
18
|
-
tick: 10,
|
|
19
|
-
sub_phase: 'entry',
|
|
20
|
-
speech_order: ['A', 'B'],
|
|
21
|
-
alive_players: ['A', 'B'],
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
type: 'meeting_state',
|
|
25
|
-
round: 1,
|
|
26
|
-
tick: 20,
|
|
27
|
-
sub_phase: 'vote',
|
|
28
|
-
speech_order: ['A', 'B'],
|
|
29
|
-
speeches: { B: '我没问题', A: '' },
|
|
30
|
-
speech_skipped: ['A'],
|
|
31
|
-
votes_submitted: ['A'],
|
|
32
|
-
alive_players: ['A', 'B'],
|
|
33
|
-
},
|
|
34
|
-
{ type: 'no_exile', tick: 30, votes: { A: 'skip', B: 'skip' } },
|
|
35
|
-
{ type: 'meeting_ended', tick: 31, result: 'skip', result_target: null },
|
|
36
|
-
]);
|
|
37
|
-
const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
38
|
-
expect(Object.keys(meeting)).toEqual([
|
|
39
|
-
'round',
|
|
40
|
-
'tick',
|
|
41
|
-
'caller',
|
|
42
|
-
'corpses_found',
|
|
43
|
-
'state',
|
|
44
|
-
'exiled_player',
|
|
45
|
-
'players',
|
|
46
|
-
'votes',
|
|
47
|
-
'speeches',
|
|
48
|
-
]);
|
|
49
|
-
expect(meeting.corpses_found).toBeNull();
|
|
50
|
-
expect(meeting.state).toBe('ended');
|
|
51
|
-
expect(meeting.players).toEqual([
|
|
52
|
-
{ name: 'A', seat: 1, status: 'alive', death: null },
|
|
53
|
-
{ name: 'B', seat: 2, status: 'alive', death: null },
|
|
54
|
-
{ name: 'C', seat: 3, status: 'dead', death: { type: 'found_in_meeting', round: 1 } },
|
|
55
|
-
]);
|
|
56
|
-
expect(meeting.votes).toEqual({ A: 'skip', B: 'skip' });
|
|
57
|
-
expect(meeting.speeches).toEqual([
|
|
58
|
-
{ speaker: 'A', text: '', skipped: true },
|
|
59
|
-
{ speaker: 'B', text: '我没问题', skipped: false },
|
|
60
|
-
]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('marks exiled players and carries death status into later meetings', () => {
|
|
64
|
-
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
65
|
-
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
66
|
-
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
67
|
-
{ type: 'meeting_state', round: 1, tick: 10, sub_phase: 'vote', alive_players: ['A', 'B', 'C'] },
|
|
68
|
-
{ type: 'exile', tick: 20, actor_name: 'B', votes: { A: 'B', C: 'B' } },
|
|
69
|
-
{ type: 'meeting_ended', tick: 21, result: 'exiled', result_target: 'B' },
|
|
70
|
-
{ type: 'meeting_start', tick: 100, actor_name: 'A' },
|
|
71
|
-
{ type: 'meeting_briefing', tick: 100, all_seats: allSeats },
|
|
72
|
-
{ type: 'meeting_state', round: 2, tick: 100, sub_phase: 'entry', alive_players: ['A', 'C'] },
|
|
73
|
-
]);
|
|
74
|
-
const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
75
|
-
expect(meetings[0].players.find((player: any) => player.name === 'B')).toEqual({
|
|
76
|
-
name: 'B',
|
|
77
|
-
seat: 2,
|
|
78
|
-
status: 'dead',
|
|
79
|
-
death: { type: 'exiled', round: 1 },
|
|
80
|
-
});
|
|
81
|
-
expect(meetings[1].players.find((player: any) => player.name === 'B')?.death).toEqual({ type: 'exiled', round: 1 });
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('keeps current unfinished meeting state from meeting_state', () => {
|
|
85
|
-
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
86
|
-
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
87
|
-
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
88
|
-
{ type: 'meeting_state', round: 1, tick: 11, sub_phase: 'speech', current_speaker: 'B', alive_players: ['A', 'B', 'C'] },
|
|
89
|
-
]);
|
|
90
|
-
const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
91
|
-
expect(meeting.state).toBe('speech');
|
|
92
|
-
expect(meeting.speeches).toEqual([]);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('marks an unfinished previous meeting interrupted when a new meeting starts', () => {
|
|
96
|
-
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
97
|
-
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
98
|
-
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
99
|
-
{ type: 'meeting_start', tick: 100, actor_name: 'C' },
|
|
100
|
-
{ type: 'meeting_briefing', tick: 100, all_seats: allSeats },
|
|
101
|
-
]);
|
|
102
|
-
const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
103
|
-
expect(meetings[0].state).toBe('interrupted_or_missing_end');
|
|
104
|
-
expect(meetings[1].state).toBe('interrupted_or_missing_end');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { meetingHistoryCommandInternals } from './history.js';
|
|
3
|
+
|
|
4
|
+
const allSeats = [
|
|
5
|
+
{ name: 'A', seat: 1 },
|
|
6
|
+
{ name: 'B', seat: 2 },
|
|
7
|
+
{ name: 'C', seat: 3 },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
describe('meeting history builder', () => {
|
|
11
|
+
it('outputs the compact meeting shape with found-in-meeting deaths and ordered speeches', () => {
|
|
12
|
+
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
13
|
+
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
14
|
+
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
15
|
+
{
|
|
16
|
+
type: 'meeting_state',
|
|
17
|
+
round: 1,
|
|
18
|
+
tick: 10,
|
|
19
|
+
sub_phase: 'entry',
|
|
20
|
+
speech_order: ['A', 'B'],
|
|
21
|
+
alive_players: ['A', 'B'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'meeting_state',
|
|
25
|
+
round: 1,
|
|
26
|
+
tick: 20,
|
|
27
|
+
sub_phase: 'vote',
|
|
28
|
+
speech_order: ['A', 'B'],
|
|
29
|
+
speeches: { B: '我没问题', A: '' },
|
|
30
|
+
speech_skipped: ['A'],
|
|
31
|
+
votes_submitted: ['A'],
|
|
32
|
+
alive_players: ['A', 'B'],
|
|
33
|
+
},
|
|
34
|
+
{ type: 'no_exile', tick: 30, votes: { A: 'skip', B: 'skip' } },
|
|
35
|
+
{ type: 'meeting_ended', tick: 31, result: 'skip', result_target: null },
|
|
36
|
+
]);
|
|
37
|
+
const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
38
|
+
expect(Object.keys(meeting)).toEqual([
|
|
39
|
+
'round',
|
|
40
|
+
'tick',
|
|
41
|
+
'caller',
|
|
42
|
+
'corpses_found',
|
|
43
|
+
'state',
|
|
44
|
+
'exiled_player',
|
|
45
|
+
'players',
|
|
46
|
+
'votes',
|
|
47
|
+
'speeches',
|
|
48
|
+
]);
|
|
49
|
+
expect(meeting.corpses_found).toBeNull();
|
|
50
|
+
expect(meeting.state).toBe('ended');
|
|
51
|
+
expect(meeting.players).toEqual([
|
|
52
|
+
{ name: 'A', seat: 1, status: 'alive', death: null },
|
|
53
|
+
{ name: 'B', seat: 2, status: 'alive', death: null },
|
|
54
|
+
{ name: 'C', seat: 3, status: 'dead', death: { type: 'found_in_meeting', round: 1 } },
|
|
55
|
+
]);
|
|
56
|
+
expect(meeting.votes).toEqual({ A: 'skip', B: 'skip' });
|
|
57
|
+
expect(meeting.speeches).toEqual([
|
|
58
|
+
{ speaker: 'A', text: '', skipped: true },
|
|
59
|
+
{ speaker: 'B', text: '我没问题', skipped: false },
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('marks exiled players and carries death status into later meetings', () => {
|
|
64
|
+
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
65
|
+
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
66
|
+
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
67
|
+
{ type: 'meeting_state', round: 1, tick: 10, sub_phase: 'vote', alive_players: ['A', 'B', 'C'] },
|
|
68
|
+
{ type: 'exile', tick: 20, actor_name: 'B', votes: { A: 'B', C: 'B' } },
|
|
69
|
+
{ type: 'meeting_ended', tick: 21, result: 'exiled', result_target: 'B' },
|
|
70
|
+
{ type: 'meeting_start', tick: 100, actor_name: 'A' },
|
|
71
|
+
{ type: 'meeting_briefing', tick: 100, all_seats: allSeats },
|
|
72
|
+
{ type: 'meeting_state', round: 2, tick: 100, sub_phase: 'entry', alive_players: ['A', 'C'] },
|
|
73
|
+
]);
|
|
74
|
+
const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
75
|
+
expect(meetings[0].players.find((player: any) => player.name === 'B')).toEqual({
|
|
76
|
+
name: 'B',
|
|
77
|
+
seat: 2,
|
|
78
|
+
status: 'dead',
|
|
79
|
+
death: { type: 'exiled', round: 1 },
|
|
80
|
+
});
|
|
81
|
+
expect(meetings[1].players.find((player: any) => player.name === 'B')?.death).toEqual({ type: 'exiled', round: 1 });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('keeps current unfinished meeting state from meeting_state', () => {
|
|
85
|
+
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
86
|
+
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
87
|
+
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
88
|
+
{ type: 'meeting_state', round: 1, tick: 11, sub_phase: 'speech', current_speaker: 'B', alive_players: ['A', 'B', 'C'] },
|
|
89
|
+
]);
|
|
90
|
+
const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
91
|
+
expect(meeting.state).toBe('speech');
|
|
92
|
+
expect(meeting.speeches).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('marks an unfinished previous meeting interrupted when a new meeting starts', () => {
|
|
96
|
+
const records = meetingHistoryCommandInternals.buildMeetingRecords([
|
|
97
|
+
{ type: 'meeting_start', tick: 10, actor_name: 'A' },
|
|
98
|
+
{ type: 'meeting_briefing', tick: 10, all_seats: allSeats },
|
|
99
|
+
{ type: 'meeting_start', tick: 100, actor_name: 'C' },
|
|
100
|
+
{ type: 'meeting_briefing', tick: 100, all_seats: allSeats },
|
|
101
|
+
]);
|
|
102
|
+
const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
|
|
103
|
+
expect(meetings[0].state).toBe('interrupted_or_missing_end');
|
|
104
|
+
expect(meetings[1].state).toBe('interrupted_or_missing_end');
|
|
105
|
+
});
|
|
106
|
+
});
|