@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.
- package/bin/clawclaw-cli.mjs +3 -3
- package/package.json +1 -1
- package/scripts/sync-bundled-skill.mjs +1 -1
- package/skills/clawclaw/SKILL.md +7 -3
- package/skills/clawclaw/references/GAME-MECHANICS.md +5 -5
- package/skills/clawclaw/references/STREAM.md +4 -3
- package/src/cli.ts +0 -43
- package/src/commands/config.ts +30 -30
- package/src/commands/game-start-plan.test.ts +10 -68
- package/src/commands/game.ts +318 -173
- package/src/commands/history.ts +1 -1
- package/src/commands/peek.ts +16 -9
- package/src/commands/setup/codex.ts +248 -248
- package/src/commands/setup/hermes.test.ts +96 -96
- package/src/commands/setup/hermes.ts +76 -76
- package/src/commands/setup/index.ts +13 -13
- package/src/commands/setup/openclaw.test.ts +114 -114
- package/src/commands/setup/openclaw.ts +147 -147
- package/src/commands/strategy.ts +29 -38
- package/src/commands/watch.test.ts +7 -11
- package/src/commands/watch.ts +77 -66
- package/src/lib/game-client.ts +3 -3
- package/src/lib/host-config-patcher.test.ts +130 -130
- package/src/lib/host-config-patcher.ts +151 -151
- package/src/lib/hub-reminder.ts +19 -19
- package/src/lib/strategy-export.test.ts +1 -1
- package/src/runtime/event-daemon.test.ts +81 -2
- package/src/runtime/event-daemon.ts +325 -287
- package/src/runtime/owner-control.ts +150 -0
- package/src/runtime/runtime-logger.ts +11 -3
- package/src/runtime/ws-client.test.ts +57 -0
- package/src/runtime/ws-client.ts +2 -2
- package/src/sdk/index.ts +4 -4
- package/src/strategies/game-utils.test.ts +27 -1
- package/src/strategies/game-utils.ts +27 -4
- package/src/strategies/goals/crab-octopus-reflexes.ts +4 -4
- package/src/strategies/goals/crab-sabotage-top.ts +1 -1
- package/src/strategies/goals/keep-away-goal.ts +10 -7
- package/src/strategies/goals/kill-frenzy-top.ts +5 -5
- package/src/strategies/goals/kill-target-goal.ts +2 -2
- package/src/strategies/goals/kill-target-top.ts +2 -2
- package/src/strategies/goals/lone-kill-core.ts +3 -3
- package/src/strategies/goals/lone-kill-task-top.ts +1 -1
- package/src/strategies/goals/task-kill-report-top.ts +3 -3
- package/src/strategies/goals/warrior-shrimp-top.ts +2 -2
- package/src/strategies/pathfind/escape-planner.ts +10 -3
- package/src/strategies/spawn.ts +16 -5
- package/src/strategies/strategy-loop.ts +9 -3
- package/src/runtime/daemon.ts +0 -100
- package/src/runtime/opening-mover.ts +0 -303
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `ccl setup openclaw` — install recommended OpenClaw config snippet.
|
|
3
|
-
*
|
|
4
|
-
* Pure function `computeOpenclawPatch` is exported for tests.
|
|
5
|
-
* IO / diff / atomic write / backup is delegated to lib/host-config-patcher.ts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Command } from 'commander';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import {
|
|
12
|
-
runHostConfigSetup,
|
|
13
|
-
unionArrayField,
|
|
14
|
-
type HostPatchResult,
|
|
15
|
-
} from '../../lib/host-config-patcher.js';
|
|
16
|
-
|
|
17
|
-
export const PLUGIN_ID = 'clawclaw';
|
|
18
|
-
|
|
19
|
-
export interface ComputeOpenclawPatchOpts {
|
|
20
|
-
skipPluginsAllow?: boolean;
|
|
21
|
-
skipToolsAllow?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function resolveOpenclawConfigPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
25
|
-
const home = env.OPENCLAW_HOME?.trim();
|
|
26
|
-
if (home) return join(home, 'openclaw.json');
|
|
27
|
-
return join(homedir(), '.openclaw', 'openclaw.json');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Pure: compute the minimal additive patch to make `clawclaw` work under `coding` profile. */
|
|
31
|
-
export function computeOpenclawPatch(current: unknown, opts: ComputeOpenclawPatchOpts): HostPatchResult {
|
|
32
|
-
const changes: string[] = [];
|
|
33
|
-
const warnings: string[] = [];
|
|
34
|
-
const cfg: Record<string, unknown> =
|
|
35
|
-
current !== null && typeof current === 'object' && !Array.isArray(current)
|
|
36
|
-
? { ...(current as Record<string, unknown>) }
|
|
37
|
-
: {};
|
|
38
|
-
|
|
39
|
-
// ── plugins.allow + plugins.entries.clawclaw.enabled ──────────────────
|
|
40
|
-
if (!opts.skipPluginsAllow) {
|
|
41
|
-
const plugins: Record<string, unknown> =
|
|
42
|
-
cfg.plugins !== null && typeof cfg.plugins === 'object' && !Array.isArray(cfg.plugins)
|
|
43
|
-
? { ...(cfg.plugins as Record<string, unknown>) }
|
|
44
|
-
: {};
|
|
45
|
-
|
|
46
|
-
const allowResult = unionArrayField(plugins.allow, PLUGIN_ID);
|
|
47
|
-
if ('warning' in allowResult) {
|
|
48
|
-
warnings.push(`plugins.allow ${allowResult.warning}`);
|
|
49
|
-
} else {
|
|
50
|
-
if (allowResult.added) {
|
|
51
|
-
if (plugins.allow === undefined) {
|
|
52
|
-
changes.push(`create plugins.allow with ["${PLUGIN_ID}"]`);
|
|
53
|
-
} else {
|
|
54
|
-
changes.push(`add "${PLUGIN_ID}" to plugins.allow`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
plugins.allow = allowResult.next;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const entries: Record<string, unknown> =
|
|
61
|
-
plugins.entries !== null && typeof plugins.entries === 'object' && !Array.isArray(plugins.entries)
|
|
62
|
-
? { ...(plugins.entries as Record<string, unknown>) }
|
|
63
|
-
: {};
|
|
64
|
-
const existingEntry =
|
|
65
|
-
entries[PLUGIN_ID] !== null && typeof entries[PLUGIN_ID] === 'object' && !Array.isArray(entries[PLUGIN_ID])
|
|
66
|
-
? { ...(entries[PLUGIN_ID] as Record<string, unknown>) }
|
|
67
|
-
: {};
|
|
68
|
-
|
|
69
|
-
if (existingEntry.enabled === false) {
|
|
70
|
-
warnings.push(
|
|
71
|
-
`plugins.entries.${PLUGIN_ID}.enabled is explicitly false; not flipping it on. Remove the line manually if you want it enabled.`,
|
|
72
|
-
);
|
|
73
|
-
} else if (existingEntry.enabled !== true) {
|
|
74
|
-
existingEntry.enabled = true;
|
|
75
|
-
entries[PLUGIN_ID] = existingEntry;
|
|
76
|
-
plugins.entries = entries;
|
|
77
|
-
changes.push(`set plugins.entries.${PLUGIN_ID}.enabled = true (equivalent to: openclaw plugins enable ${PLUGIN_ID})`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
cfg.plugins = plugins;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── tools.alsoAllow ────────────────────────────────────────────────────
|
|
84
|
-
if (!opts.skipToolsAllow) {
|
|
85
|
-
const tools: Record<string, unknown> =
|
|
86
|
-
cfg.tools !== null && typeof cfg.tools === 'object' && !Array.isArray(cfg.tools)
|
|
87
|
-
? { ...(cfg.tools as Record<string, unknown>) }
|
|
88
|
-
: {};
|
|
89
|
-
|
|
90
|
-
const alsoAllowResult = unionArrayField(tools.alsoAllow, PLUGIN_ID);
|
|
91
|
-
if ('warning' in alsoAllowResult) {
|
|
92
|
-
warnings.push(`tools.alsoAllow ${alsoAllowResult.warning}`);
|
|
93
|
-
} else {
|
|
94
|
-
if (alsoAllowResult.added) {
|
|
95
|
-
if (tools.alsoAllow === undefined) {
|
|
96
|
-
changes.push(`create tools.alsoAllow with ["${PLUGIN_ID}"]`);
|
|
97
|
-
} else {
|
|
98
|
-
changes.push(`add "${PLUGIN_ID}" to tools.alsoAllow`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
tools.alsoAllow = alsoAllowResult.next;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
cfg.tools = tools;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return { next: cfg, changes, warnings };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function createSetupOpenclawSubcommand(): Command {
|
|
111
|
-
return new Command('openclaw')
|
|
112
|
-
.description('Install recommended OpenClaw config (plugins.allow + tools.alsoAllow) for the clawclaw plugin.')
|
|
113
|
-
.option('-y, --yes', 'Apply changes (default is dry-run with diff preview)')
|
|
114
|
-
.option('--print', 'Only print the recommended JSON patch; do not read or write files')
|
|
115
|
-
.option('--config <path>', 'Override openclaw.json path (default: $OPENCLAW_HOME/openclaw.json or ~/.openclaw/openclaw.json)')
|
|
116
|
-
.option('--skip-plugins-allow', "Do not touch plugins.allow / plugins.entries.clawclaw")
|
|
117
|
-
.option('--skip-tools-allow', "Do not touch tools.alsoAllow")
|
|
118
|
-
.option('--no-backup', 'Do not write a timestamped .bak.* before applying')
|
|
119
|
-
.action((opts: {
|
|
120
|
-
yes?: boolean;
|
|
121
|
-
print?: boolean;
|
|
122
|
-
config?: string;
|
|
123
|
-
skipPluginsAllow?: boolean;
|
|
124
|
-
skipToolsAllow?: boolean;
|
|
125
|
-
backup?: boolean;
|
|
126
|
-
}) => {
|
|
127
|
-
const result = runHostConfigSetup(
|
|
128
|
-
{
|
|
129
|
-
hostName: 'OpenClaw',
|
|
130
|
-
resolveConfigPath: () => resolveOpenclawConfigPath(),
|
|
131
|
-
computePatch: (current, hostOpts) => computeOpenclawPatch(current, hostOpts),
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
skipPluginsAllow: opts.skipPluginsAllow,
|
|
135
|
-
skipToolsAllow: opts.skipToolsAllow,
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
yes: opts.yes,
|
|
139
|
-
print: opts.print,
|
|
140
|
-
configPath: opts.config,
|
|
141
|
-
backup: opts.backup,
|
|
142
|
-
},
|
|
143
|
-
);
|
|
144
|
-
for (const line of result.output) console.log(line);
|
|
145
|
-
if (result.exitCode !== 0) process.exit(result.exitCode);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `ccl setup openclaw` — install recommended OpenClaw config snippet.
|
|
3
|
+
*
|
|
4
|
+
* Pure function `computeOpenclawPatch` is exported for tests.
|
|
5
|
+
* IO / diff / atomic write / backup is delegated to lib/host-config-patcher.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import {
|
|
12
|
+
runHostConfigSetup,
|
|
13
|
+
unionArrayField,
|
|
14
|
+
type HostPatchResult,
|
|
15
|
+
} from '../../lib/host-config-patcher.js';
|
|
16
|
+
|
|
17
|
+
export const PLUGIN_ID = 'clawclaw';
|
|
18
|
+
|
|
19
|
+
export interface ComputeOpenclawPatchOpts {
|
|
20
|
+
skipPluginsAllow?: boolean;
|
|
21
|
+
skipToolsAllow?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveOpenclawConfigPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
25
|
+
const home = env.OPENCLAW_HOME?.trim();
|
|
26
|
+
if (home) return join(home, 'openclaw.json');
|
|
27
|
+
return join(homedir(), '.openclaw', 'openclaw.json');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Pure: compute the minimal additive patch to make `clawclaw` work under `coding` profile. */
|
|
31
|
+
export function computeOpenclawPatch(current: unknown, opts: ComputeOpenclawPatchOpts): HostPatchResult {
|
|
32
|
+
const changes: string[] = [];
|
|
33
|
+
const warnings: string[] = [];
|
|
34
|
+
const cfg: Record<string, unknown> =
|
|
35
|
+
current !== null && typeof current === 'object' && !Array.isArray(current)
|
|
36
|
+
? { ...(current as Record<string, unknown>) }
|
|
37
|
+
: {};
|
|
38
|
+
|
|
39
|
+
// ── plugins.allow + plugins.entries.clawclaw.enabled ──────────────────
|
|
40
|
+
if (!opts.skipPluginsAllow) {
|
|
41
|
+
const plugins: Record<string, unknown> =
|
|
42
|
+
cfg.plugins !== null && typeof cfg.plugins === 'object' && !Array.isArray(cfg.plugins)
|
|
43
|
+
? { ...(cfg.plugins as Record<string, unknown>) }
|
|
44
|
+
: {};
|
|
45
|
+
|
|
46
|
+
const allowResult = unionArrayField(plugins.allow, PLUGIN_ID);
|
|
47
|
+
if ('warning' in allowResult) {
|
|
48
|
+
warnings.push(`plugins.allow ${allowResult.warning}`);
|
|
49
|
+
} else {
|
|
50
|
+
if (allowResult.added) {
|
|
51
|
+
if (plugins.allow === undefined) {
|
|
52
|
+
changes.push(`create plugins.allow with ["${PLUGIN_ID}"]`);
|
|
53
|
+
} else {
|
|
54
|
+
changes.push(`add "${PLUGIN_ID}" to plugins.allow`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
plugins.allow = allowResult.next;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const entries: Record<string, unknown> =
|
|
61
|
+
plugins.entries !== null && typeof plugins.entries === 'object' && !Array.isArray(plugins.entries)
|
|
62
|
+
? { ...(plugins.entries as Record<string, unknown>) }
|
|
63
|
+
: {};
|
|
64
|
+
const existingEntry =
|
|
65
|
+
entries[PLUGIN_ID] !== null && typeof entries[PLUGIN_ID] === 'object' && !Array.isArray(entries[PLUGIN_ID])
|
|
66
|
+
? { ...(entries[PLUGIN_ID] as Record<string, unknown>) }
|
|
67
|
+
: {};
|
|
68
|
+
|
|
69
|
+
if (existingEntry.enabled === false) {
|
|
70
|
+
warnings.push(
|
|
71
|
+
`plugins.entries.${PLUGIN_ID}.enabled is explicitly false; not flipping it on. Remove the line manually if you want it enabled.`,
|
|
72
|
+
);
|
|
73
|
+
} else if (existingEntry.enabled !== true) {
|
|
74
|
+
existingEntry.enabled = true;
|
|
75
|
+
entries[PLUGIN_ID] = existingEntry;
|
|
76
|
+
plugins.entries = entries;
|
|
77
|
+
changes.push(`set plugins.entries.${PLUGIN_ID}.enabled = true (equivalent to: openclaw plugins enable ${PLUGIN_ID})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
cfg.plugins = plugins;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── tools.alsoAllow ────────────────────────────────────────────────────
|
|
84
|
+
if (!opts.skipToolsAllow) {
|
|
85
|
+
const tools: Record<string, unknown> =
|
|
86
|
+
cfg.tools !== null && typeof cfg.tools === 'object' && !Array.isArray(cfg.tools)
|
|
87
|
+
? { ...(cfg.tools as Record<string, unknown>) }
|
|
88
|
+
: {};
|
|
89
|
+
|
|
90
|
+
const alsoAllowResult = unionArrayField(tools.alsoAllow, PLUGIN_ID);
|
|
91
|
+
if ('warning' in alsoAllowResult) {
|
|
92
|
+
warnings.push(`tools.alsoAllow ${alsoAllowResult.warning}`);
|
|
93
|
+
} else {
|
|
94
|
+
if (alsoAllowResult.added) {
|
|
95
|
+
if (tools.alsoAllow === undefined) {
|
|
96
|
+
changes.push(`create tools.alsoAllow with ["${PLUGIN_ID}"]`);
|
|
97
|
+
} else {
|
|
98
|
+
changes.push(`add "${PLUGIN_ID}" to tools.alsoAllow`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
tools.alsoAllow = alsoAllowResult.next;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
cfg.tools = tools;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { next: cfg, changes, warnings };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createSetupOpenclawSubcommand(): Command {
|
|
111
|
+
return new Command('openclaw')
|
|
112
|
+
.description('Install recommended OpenClaw config (plugins.allow + tools.alsoAllow) for the clawclaw plugin.')
|
|
113
|
+
.option('-y, --yes', 'Apply changes (default is dry-run with diff preview)')
|
|
114
|
+
.option('--print', 'Only print the recommended JSON patch; do not read or write files')
|
|
115
|
+
.option('--config <path>', 'Override openclaw.json path (default: $OPENCLAW_HOME/openclaw.json or ~/.openclaw/openclaw.json)')
|
|
116
|
+
.option('--skip-plugins-allow', "Do not touch plugins.allow / plugins.entries.clawclaw")
|
|
117
|
+
.option('--skip-tools-allow', "Do not touch tools.alsoAllow")
|
|
118
|
+
.option('--no-backup', 'Do not write a timestamped .bak.* before applying')
|
|
119
|
+
.action((opts: {
|
|
120
|
+
yes?: boolean;
|
|
121
|
+
print?: boolean;
|
|
122
|
+
config?: string;
|
|
123
|
+
skipPluginsAllow?: boolean;
|
|
124
|
+
skipToolsAllow?: boolean;
|
|
125
|
+
backup?: boolean;
|
|
126
|
+
}) => {
|
|
127
|
+
const result = runHostConfigSetup(
|
|
128
|
+
{
|
|
129
|
+
hostName: 'OpenClaw',
|
|
130
|
+
resolveConfigPath: () => resolveOpenclawConfigPath(),
|
|
131
|
+
computePatch: (current, hostOpts) => computeOpenclawPatch(current, hostOpts),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
skipPluginsAllow: opts.skipPluginsAllow,
|
|
135
|
+
skipToolsAllow: opts.skipToolsAllow,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
yes: opts.yes,
|
|
139
|
+
print: opts.print,
|
|
140
|
+
configPath: opts.config,
|
|
141
|
+
backup: opts.backup,
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
for (const line of result.output) console.log(line);
|
|
145
|
+
if (result.exitCode !== 0) process.exit(result.exitCode);
|
|
146
|
+
});
|
|
147
|
+
}
|
package/src/commands/strategy.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import { spawnStrategyLoop } from '../strategies/spawn.js';
|
|
4
3
|
import { GameClient } from '../lib/game-client.js';
|
|
5
|
-
import { getWorkspaceDir } from '../lib/init-command.js';
|
|
4
|
+
import { getProfileStateDir, getWorkspaceDir } from '../lib/init-command.js';
|
|
5
|
+
import { AuthStore } from '../lib/auth.js';
|
|
6
|
+
import { sendOwnerControlRequest } from '../runtime/owner-control.js';
|
|
6
7
|
|
|
7
8
|
const KILL_STRATEGIES = new Set([
|
|
8
9
|
'kill-frenzy', 'kill-lone', 'kill-target', 'warrior-memory',
|
|
@@ -12,32 +13,6 @@ const KILL_CAPABLE_ROLES = new Set([
|
|
|
12
13
|
'crab_generic', 'shrimp_warrior', 'shrimp_pistol', 'neutral_octopus',
|
|
13
14
|
]);
|
|
14
15
|
|
|
15
|
-
function nonEmptyString(value: unknown): string | undefined {
|
|
16
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function currentStrategyMetadata(): Promise<{ gameId?: string; role?: string }> {
|
|
20
|
-
try {
|
|
21
|
-
const client = GameClient.fromAuth();
|
|
22
|
-
const [stateResult, roleResult] = await Promise.allSettled([
|
|
23
|
-
client.getGameState(),
|
|
24
|
-
client.getRoleInfo(),
|
|
25
|
-
]);
|
|
26
|
-
const state = stateResult.status === 'fulfilled' ? stateResult.value : null;
|
|
27
|
-
const roleInfo = roleResult.status === 'fulfilled'
|
|
28
|
-
? (roleResult.value?.data ?? roleResult.value)
|
|
29
|
-
: null;
|
|
30
|
-
return {
|
|
31
|
-
gameId: nonEmptyString(state?.game_id)
|
|
32
|
-
?? nonEmptyString((state as any)?.game?.id)
|
|
33
|
-
?? nonEmptyString((state as any)?.game?.game_id),
|
|
34
|
-
role: nonEmptyString(roleInfo?.role) ?? nonEmptyString(state?.you?.role),
|
|
35
|
-
};
|
|
36
|
-
} catch {
|
|
37
|
-
return {};
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
16
|
export function createStrategyCommand(): Command {
|
|
42
17
|
const cmd = new Command('strategy')
|
|
43
18
|
.alias('stg')
|
|
@@ -118,8 +93,17 @@ Custom strategies:
|
|
|
118
93
|
}
|
|
119
94
|
|
|
120
95
|
if (opts.stop) {
|
|
121
|
-
const
|
|
122
|
-
|
|
96
|
+
const profile = new AuthStore().getActive();
|
|
97
|
+
const response = profile
|
|
98
|
+
? await sendOwnerControlRequest(getProfileStateDir(profile), 'stop_strategy')
|
|
99
|
+
: null;
|
|
100
|
+
if (!response?.ok) {
|
|
101
|
+
console.error(JSON.stringify({
|
|
102
|
+
error: 'no_game_start_owner',
|
|
103
|
+
message: 'No active ccl game start owner is available to stop strategy.',
|
|
104
|
+
}, null, 2));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
123
107
|
console.log(JSON.stringify({ message: 'Strategy stopped.' }, null, 2));
|
|
124
108
|
return;
|
|
125
109
|
}
|
|
@@ -171,17 +155,24 @@ Custom strategies:
|
|
|
171
155
|
} catch {}
|
|
172
156
|
}
|
|
173
157
|
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
158
|
+
const profile = new AuthStore().getActive();
|
|
159
|
+
const response = profile
|
|
160
|
+
? await sendOwnerControlRequest(getProfileStateDir(profile), 'switch_strategy', {
|
|
161
|
+
strategy: name,
|
|
162
|
+
args: args.length > 0 ? args : undefined,
|
|
163
|
+
})
|
|
164
|
+
: null;
|
|
165
|
+
if (!response?.ok) {
|
|
166
|
+
console.error(JSON.stringify({
|
|
167
|
+
error: 'no_game_start_owner',
|
|
168
|
+
message: 'Run `ccl game start` before switching strategy; strategy processes are owned by the game start runtime.',
|
|
169
|
+
}, null, 2));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
181
172
|
console.log(JSON.stringify({
|
|
182
173
|
message: `Strategy started: ${name}`,
|
|
183
174
|
description: entry.description,
|
|
184
|
-
pid:
|
|
175
|
+
pid: response.pid,
|
|
185
176
|
}, null, 2));
|
|
186
177
|
});
|
|
187
178
|
|
|
@@ -534,7 +534,7 @@ describe('runStreaming — emission shape', () => {
|
|
|
534
534
|
await new Promise((r) => setTimeout(r, 60));
|
|
535
535
|
const beforeTruncate = lines.length;
|
|
536
536
|
|
|
537
|
-
// Truncate the file in place (simulating
|
|
537
|
+
// Truncate the file in place (simulating runtime log rotation that keeps the inode).
|
|
538
538
|
writeFileSync(sessionPath, '');
|
|
539
539
|
// Append a fresh notable event after truncation.
|
|
540
540
|
appendFileSync(sessionPath, JSON.stringify({ type: 'killed', tick: 100, actor_name: 'b' }) + '\n');
|
|
@@ -558,7 +558,7 @@ describe('runStreaming — emission shape', () => {
|
|
|
558
558
|
sessionPath,
|
|
559
559
|
stdout: (s) => lines.push(s),
|
|
560
560
|
pollIntervalMs: 20,
|
|
561
|
-
|
|
561
|
+
runtimeWaitMs: 80,
|
|
562
562
|
}),
|
|
563
563
|
).rejects.toThrow(/not ready/i);
|
|
564
564
|
expect(lines.length).toBe(0);
|
|
@@ -575,7 +575,7 @@ describe('runStreaming — emission shape', () => {
|
|
|
575
575
|
stdout: (s) => lines.push(s),
|
|
576
576
|
signal: ctrl.signal,
|
|
577
577
|
pollIntervalMs: 20,
|
|
578
|
-
|
|
578
|
+
runtimeWaitMs: 2000,
|
|
579
579
|
});
|
|
580
580
|
setTimeout(() => {
|
|
581
581
|
writeFileSync(feedPath, JSON.stringify({ you: { name: 'me' }, phase: 'lobby', urgent: {}, meeting: null }));
|
|
@@ -608,7 +608,7 @@ describe('snapshotOnce', () => {
|
|
|
608
608
|
const lines: string[] = [];
|
|
609
609
|
expect(() =>
|
|
610
610
|
snapshotOnce({ feedPath: join(dir, 'nope.json'), stdout: (s) => lines.push(s) }),
|
|
611
|
-
).toThrow(/
|
|
611
|
+
).toThrow(/game runtime is not running/);
|
|
612
612
|
});
|
|
613
613
|
});
|
|
614
614
|
|
|
@@ -864,8 +864,8 @@ describe('runStreaming — delay buffer', () => {
|
|
|
864
864
|
});
|
|
865
865
|
|
|
866
866
|
describe('readFeedSummary', () => {
|
|
867
|
-
it('
|
|
868
|
-
const {
|
|
867
|
+
it('includes automation strategy from the feed projection without exposing pid', () => {
|
|
868
|
+
const { feedPath } = makeTmpFiles();
|
|
869
869
|
writeFileSync(
|
|
870
870
|
feedPath,
|
|
871
871
|
JSON.stringify({
|
|
@@ -874,13 +874,9 @@ describe('readFeedSummary', () => {
|
|
|
874
874
|
game: { id: 'game-1' },
|
|
875
875
|
urgent: {},
|
|
876
876
|
meeting: null,
|
|
877
|
+
automation: { strategy: 'default', running: true },
|
|
877
878
|
}),
|
|
878
879
|
);
|
|
879
|
-
writeFileSync(join(dir, 'auto.json'), JSON.stringify({
|
|
880
|
-
strategy: 'default',
|
|
881
|
-
pid: process.pid,
|
|
882
|
-
source: 'manual',
|
|
883
|
-
}));
|
|
884
880
|
|
|
885
881
|
const summary = readFeedSummary(feedPath);
|
|
886
882
|
expect(summary?.automation).toEqual({ strategy: 'default', running: true });
|