chesspal 0.2.3 → 0.3.0
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 +11 -12
- package/dist/cli/cli.js +83 -3
- package/dist/cli/command-line.js +26 -9
- package/dist/cli/icon.d.ts +25 -0
- package/dist/cli/icon.js +80 -0
- package/dist/commands/agents.d.ts +19 -2
- package/dist/commands/agents.js +41 -14
- package/dist/commands/ai-say.d.ts +2 -1
- package/dist/commands/ai-say.js +3 -4
- package/dist/commands/analyse-game.d.ts +6 -0
- package/dist/commands/analyse-game.js +51 -15
- package/dist/commands/annotate-game.d.ts +4 -0
- package/dist/commands/annotate-game.js +60 -20
- package/dist/commands/answer-lines.d.ts +10 -4
- package/dist/commands/answer-lines.js +30 -4
- package/dist/commands/coach.js +6 -4
- package/dist/commands/context.d.ts +11 -1
- package/dist/commands/context.js +5 -0
- package/dist/commands/delete-review.js +9 -4
- package/dist/commands/game-summary.d.ts +9 -2
- package/dist/commands/game-summary.js +11 -0
- package/dist/commands/index.d.ts +4 -3
- package/dist/commands/index.js +8 -5
- package/dist/commands/init.js +2 -4
- package/dist/commands/instructions.d.ts +9 -0
- package/dist/commands/instructions.js +30 -0
- package/dist/commands/lead-input.d.ts +1 -1
- package/dist/commands/lead-input.js +7 -4
- package/dist/commands/look.d.ts +9 -5
- package/dist/commands/look.js +19 -9
- package/dist/commands/page-link.js +6 -5
- package/dist/commands/past-games.d.ts +1 -1
- package/dist/commands/past-games.js +12 -5
- package/dist/commands/player-move.js +2 -3
- package/dist/commands/ponder.js +1 -1
- package/dist/commands/position.js +2 -2
- package/dist/commands/profiles.d.ts +23 -9
- package/dist/commands/profiles.js +76 -35
- package/dist/commands/ratings.d.ts +8 -0
- package/dist/commands/ratings.js +45 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +3 -2
- package/dist/commands/say.d.ts +11 -6
- package/dist/commands/say.js +21 -13
- package/dist/commands/status.d.ts +6 -2
- package/dist/commands/status.js +70 -26
- package/dist/commands/study.d.ts +11 -1
- package/dist/commands/study.js +39 -7
- package/dist/commands/view.d.ts +17 -5
- package/dist/commands/view.js +67 -10
- package/dist/core/facts.d.ts +14 -2
- package/dist/core/facts.js +32 -7
- package/dist/core/game.d.ts +5 -0
- package/dist/core/game.js +9 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +1 -1
- package/dist/core/placement.d.ts +63 -0
- package/dist/core/placement.js +259 -0
- package/dist/core/possibilities.d.ts +38 -0
- package/dist/core/possibilities.js +254 -0
- package/dist/core/review.d.ts +6 -1
- package/dist/core/review.js +21 -6
- package/dist/games/ai-model.d.ts +4 -2
- package/dist/games/ai-model.js +10 -1
- package/dist/games/analysis.d.ts +31 -4
- package/dist/games/analysis.js +44 -5
- package/dist/games/archive.d.ts +6 -3
- package/dist/games/archive.js +13 -5
- package/dist/games/current-game.d.ts +26 -2
- package/dist/games/current-game.js +57 -21
- package/dist/games/files.d.ts +7 -0
- package/dist/games/files.js +48 -1
- package/dist/games/index.d.ts +4 -4
- package/dist/games/index.js +4 -4
- package/dist/games/table-talk.d.ts +2 -0
- package/dist/games/table-talk.js +5 -1
- package/dist/games/talk.d.ts +4 -3
- package/dist/games/talk.js +3 -3
- package/dist/home/data-folder.d.ts +2 -0
- package/dist/home/data-folder.js +5 -1
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/host-files/agents.d.ts +2 -2
- package/dist/host-files/agents.js +12 -6
- package/dist/host-files/host-files.d.ts +5 -3
- package/dist/host-files/host-files.js +15 -8
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +2 -2
- package/dist/page/app/app.js +59 -22
- package/dist/page/app/keys.js +16 -2
- package/dist/page/app/served.js +2 -2
- package/dist/page/app/title.js +17 -0
- package/dist/page/avatars/torretta.js +4 -4
- package/dist/page/board/board.js +2 -1
- package/dist/page/board/marks.js +4 -24
- package/dist/page/board/signs.js +23 -0
- package/dist/page/dialogs/analyses.js +4 -4
- package/dist/page/dialogs/dialog.js +4 -1
- package/dist/page/dialogs/dialogs.css +84 -10
- package/dist/page/dialogs/profiles.js +95 -42
- package/dist/page/game/column.js +6 -2
- package/dist/page/game/game.css +12 -0
- package/dist/page/game/move-list.js +2 -2
- package/dist/page/game/plies.js +11 -0
- package/dist/page/guide/blocks.js +67 -0
- package/dist/page/guide/en.md +66 -0
- package/dist/page/guide/foot.js +18 -0
- package/dist/page/guide/guide.css +200 -0
- package/dist/page/guide/guide.js +89 -0
- package/dist/page/guide/it.md +66 -0
- package/dist/page/guide/markdown.js +67 -0
- package/dist/page/helps/helps.css +10 -11
- package/dist/page/helps/helps.js +15 -15
- package/dist/page/layout/favicon.svg +13 -0
- package/dist/page/layout/header.js +3 -3
- package/dist/page/layout/icon.icns +0 -0
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/icon.png +0 -0
- package/dist/page/layout/layout.css +140 -9
- package/dist/page/layout/window.js +45 -0
- package/dist/page/legend/legend.css +203 -0
- package/dist/page/legend/legend.js +169 -0
- package/dist/page/legend/view.js +54 -0
- package/dist/page/main.js +4 -1
- package/dist/page/players/card.js +1 -1
- package/dist/page/profile/profile.js +14 -9
- package/dist/page/profile/settings.js +9 -0
- package/dist/page/style.css +4 -0
- package/dist/page/table/opener.css +65 -0
- package/dist/page/table/opener.js +59 -0
- package/dist/page/talk/chat.css +97 -0
- package/dist/page/talk/talk.css +38 -26
- package/dist/page/talk/talk.js +31 -7
- package/dist/page/text.js +120 -18
- package/dist/page/theme.css +7 -0
- package/dist/profiles/index.d.ts +2 -1
- package/dist/profiles/index.js +2 -1
- package/dist/profiles/profiles.d.ts +10 -15
- package/dist/profiles/profiles.js +30 -15
- package/dist/profiles/ratings.d.ts +35 -0
- package/dist/profiles/ratings.js +61 -0
- package/dist/server/bell.d.ts +24 -3
- package/dist/server/bell.js +84 -10
- package/dist/server/caller.d.ts +2 -1
- package/dist/server/caller.js +3 -2
- package/dist/server/mcp.d.ts +7 -3
- package/dist/server/mcp.js +106 -56
- package/dist/server/page/analyses.d.ts +2 -0
- package/dist/server/page/analyses.js +20 -5
- package/dist/server/page/attach.d.ts +4 -0
- package/dist/server/page/attach.js +112 -0
- package/dist/server/page/chat.d.ts +60 -0
- package/dist/server/page/chat.js +79 -0
- package/dist/server/page/events.d.ts +7 -0
- package/dist/server/page/events.js +20 -0
- package/dist/server/page/files.js +10 -1
- package/dist/server/page/game.js +62 -29
- package/dist/server/page/page.d.ts +47 -7
- package/dist/server/page/page.js +74 -51
- package/dist/server/page/profiles.d.ts +5 -0
- package/dist/server/page/profiles.js +26 -10
- package/dist/server/page/table.d.ts +39 -0
- package/dist/server/page/table.js +102 -0
- package/dist/server/table/browser.d.ts +6 -0
- package/dist/server/table/browser.js +48 -0
- package/dist/server/table/hosts/chats.d.ts +7 -0
- package/dist/server/table/hosts/chats.js +31 -0
- package/dist/server/table/hosts/find.d.ts +2 -0
- package/dist/server/table/hosts/find.js +20 -0
- package/dist/server/table/hosts/hosts.d.ts +34 -0
- package/dist/server/table/hosts/hosts.js +49 -0
- package/dist/server/table/hosts/index.d.ts +3 -0
- package/dist/server/table/hosts/index.js +4 -0
- package/dist/server/table/hosts/play.d.ts +25 -0
- package/dist/server/table/hosts/play.js +152 -0
- package/dist/server/table/hosts/seat.d.ts +25 -0
- package/dist/server/table/hosts/seat.js +74 -0
- package/dist/server/table/hosts/terminal.d.ts +6 -0
- package/dist/server/table/hosts/terminal.js +63 -0
- package/dist/server/table/icon/index.d.ts +32 -0
- package/dist/server/table/icon/index.js +83 -0
- package/dist/server/table/icon/linux.d.ts +5 -0
- package/dist/server/table/icon/linux.js +47 -0
- package/dist/server/table/icon/macos.d.ts +3 -0
- package/dist/server/table/icon/macos.js +38 -0
- package/dist/server/table/icon/windows.d.ts +3 -0
- package/dist/server/table/icon/windows.js +48 -0
- package/dist/server/table/index.d.ts +6 -0
- package/dist/server/table/index.js +7 -0
- package/dist/server/table/launch.d.ts +19 -0
- package/dist/server/table/launch.js +78 -0
- package/dist/server/table/link.d.ts +10 -0
- package/dist/server/table/link.js +230 -0
- package/dist/server/table/table.d.ts +35 -0
- package/dist/server/table/table.js +94 -0
- package/dist/sites/chesscom.d.ts +4 -0
- package/dist/sites/chesscom.js +25 -0
- package/dist/sites/choice.d.ts +17 -0
- package/dist/sites/choice.js +13 -0
- package/dist/sites/index.d.ts +2 -0
- package/dist/sites/index.js +4 -0
- package/dist/sites/lichess.d.ts +4 -0
- package/dist/sites/lichess.js +32 -0
- package/dist/sites/request.d.ts +11 -0
- package/dist/sites/request.js +15 -0
- package/dist/sites/sites.d.ts +6 -0
- package/dist/sites/sites.js +33 -0
- package/instructions/analysis.md +1 -1
- package/instructions/analyst-agent.md +1 -1
- package/instructions/coach.md +2 -1
- package/instructions/opponent.md +6 -6
- package/instructions/ponder.md +1 -0
- package/instructions/start.md +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The player's ratings read again from the sites (D-105, D-106): when their names there change, and when the page opens a day after the last reading;
|
|
2
|
+
// never from a tool of the AI's.
|
|
3
|
+
import { listProfiles, profileFolder, saveReadings } from "../profiles/index.js";
|
|
4
|
+
import { readingsStale, readSites } from "../sites/index.js";
|
|
5
|
+
/** A day, in milliseconds: the page reads the ratings again once they are older. */
|
|
6
|
+
export const ratingsAge = 24 * 60 * 60 * 1000;
|
|
7
|
+
/**
|
|
8
|
+
* Reads a profile's ratings again when its names on the sites changed, a site did not answer ten minutes ago or more, or the last reading is older than `olderThan`
|
|
9
|
+
* milliseconds: whether new readings were kept. One reading at a time of the same names in this process.
|
|
10
|
+
*/
|
|
11
|
+
export function refreshRatings(context, id, olderThan = Infinity) {
|
|
12
|
+
const home = context.home();
|
|
13
|
+
const profile = listProfiles(home).find((one) => one.id === id);
|
|
14
|
+
if (profile === undefined || context.fetch === undefined) {
|
|
15
|
+
return Promise.resolve(false);
|
|
16
|
+
}
|
|
17
|
+
const key = [home, id, profile.accounts?.lichess, profile.accounts?.chesscom].join("\n");
|
|
18
|
+
const going = reading.get(key);
|
|
19
|
+
if (going !== undefined) {
|
|
20
|
+
return going;
|
|
21
|
+
}
|
|
22
|
+
const read = readAgain(context, profile, context.fetch, olderThan).finally(() => reading.delete(key));
|
|
23
|
+
reading.set(key, read);
|
|
24
|
+
return read;
|
|
25
|
+
}
|
|
26
|
+
const reading = new Map();
|
|
27
|
+
async function readAgain(context, { id, accounts, sites }, fetch, olderThan) {
|
|
28
|
+
const home = context.home();
|
|
29
|
+
const age = sites === undefined ? Infinity : context.now().getTime() - Date.parse(sites.read);
|
|
30
|
+
if (!readingsStale(sites, accounts, context.now()) && age < olderThan) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (accounts === undefined) {
|
|
34
|
+
saveReadings(profileFolder(home, id), undefined);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const read = await readSites(accounts, fetch, sites, context.now());
|
|
38
|
+
// The names may have changed while the sites answered: what they said of the names before is not kept, and the change reads its own.
|
|
39
|
+
const now = listProfiles(home).find((one) => one.id === id);
|
|
40
|
+
if (now?.accounts?.lichess !== accounts.lichess || now?.accounts?.chesscom !== accounts.chesscom) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
saveReadings(profileFolder(home, id), read);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
@@ -55,5 +55,5 @@ export declare function isBoolean(field: z.ZodType): boolean;
|
|
|
55
55
|
export declare function isList(field: z.ZodType): boolean;
|
|
56
56
|
/** The type of a field of a command's input, past optional, default and nullable: string, boolean, array… */
|
|
57
57
|
export declare function baseType(field: z.ZodType): string;
|
|
58
|
-
/** Whether a field takes numbers, alone
|
|
58
|
+
/** Whether a field takes numbers, alone, in a list or as one of its choices: the command line reads them as text and turns them into numbers. */
|
|
59
59
|
export declare function takesNumbers(field: z.ZodType): boolean;
|
|
@@ -37,10 +37,11 @@ export function isList(field) {
|
|
|
37
37
|
export function baseType(field) {
|
|
38
38
|
return baseDefinition(field).type;
|
|
39
39
|
}
|
|
40
|
-
/** Whether a field takes numbers, alone
|
|
40
|
+
/** Whether a field takes numbers, alone, in a list or as one of its choices: the command line reads them as text and turns them into numbers. */
|
|
41
41
|
export function takesNumbers(field) {
|
|
42
42
|
const definition = baseDefinition(field);
|
|
43
|
-
|
|
43
|
+
const numbers = (one) => one !== undefined && baseType(one) === "number";
|
|
44
|
+
return definition.type === "number" || (definition.type === "array" && numbers(definition.element)) || (definition.type === "union" && (definition.options ?? []).some(numbers));
|
|
44
45
|
}
|
|
45
46
|
function inputIssue(command, issue, given, written) {
|
|
46
47
|
const path = issue.path.map(String).join(".");
|
package/dist/commands/say.d.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { type Mood } from "../core/index.ts";
|
|
2
|
-
import { type Sayer, type Talk, type Words } from "../games/index.ts";
|
|
2
|
+
import { type Sayer, type Shown, type Talk, type Words } from "../games/index.ts";
|
|
3
3
|
import type { Context } from "./context.ts";
|
|
4
4
|
/**
|
|
5
5
|
* Adds a line to the talk of the current game, going on or just over, and saves it; the AI's mood once the game is over, which the archive keeps too (D-70).
|
|
6
6
|
* With no game the line goes in the talk before it (D-77). A line refused changes nothing: it throws, in words for whoever spoke.
|
|
7
7
|
*/
|
|
8
|
-
export declare function say(folder: string, who: Sayer, text: string, at: Date, mood?: Mood): Talk;
|
|
8
|
+
export declare function say(folder: string, who: Sayer, text: string, at: Date, mood?: Mood, shown?: Shown): Talk;
|
|
9
|
+
/** Words of the player's as the AI is given them: their text, and in analysis the move the board page showed as they wrote, 52...♚f6 (D-101). */
|
|
10
|
+
export interface Written {
|
|
11
|
+
readonly text: string;
|
|
12
|
+
readonly place?: string;
|
|
13
|
+
}
|
|
9
14
|
/** What the player wrote on the board page that the AI has not been given in this chat, given now: each game tool gives it once; never to an agent (D-89). */
|
|
10
|
-
export declare function newWords(context: Context):
|
|
15
|
+
export declare function newWords(context: Context): Written[];
|
|
11
16
|
/** Every word of the player's the AI has not answered, given again: status gives them all, for a chat that lost them in a summary. */
|
|
12
|
-
export declare function unansweredWords(context: Context):
|
|
17
|
+
export declare function unansweredWords(context: Context): Written[];
|
|
13
18
|
/** What the player wrote on the board page that the AI has not been given in this chat, still to give: the AI's move would count as its answer. */
|
|
14
19
|
export declare function waitingWords(context: Context, given?: ReadonlySet<string>): Words[];
|
|
15
|
-
/** Gives the words to the AI: from now on they count as given in this chat. Their text. */
|
|
16
|
-
export declare function give(context: Context, words: readonly Words[]):
|
|
20
|
+
/** Gives the words to the AI: from now on they count as given in this chat. Their text, with the move shown in analysis. */
|
|
21
|
+
export declare function give(context: Context, words: readonly Words[]): Written[];
|
package/dist/commands/say.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
// The talk on the board page (D-51): a line of the player's or the AI's, saved with the game, and the player's words the AI has not been given yet.
|
|
2
2
|
import { endWithMood } from "../core/index.js";
|
|
3
|
-
import { archiveEnded, endOf, loadGame, loadTableTalk, saveGame, saveTableTalk, talkLimits, talkLine, unheardWords } from "../games/index.js";
|
|
3
|
+
import { archiveEnded, endOf, gameInAnalysis, loadGame, loadTableTalk, saveGame, saveTableTalk, talkLimits, talkLine, unheardWords } from "../games/index.js";
|
|
4
4
|
import { gameFolder, helpsIn } from "../profiles/index.js";
|
|
5
|
+
import { shownMove } from "./answer-lines.js";
|
|
5
6
|
import { gameAt } from "./clock.js";
|
|
6
7
|
/**
|
|
7
8
|
* Adds a line to the talk of the current game, going on or just over, and saves it; the AI's mood once the game is over, which the archive keeps too (D-70).
|
|
8
9
|
* With no game the line goes in the talk before it (D-77). A line refused changes nothing: it throws, in words for whoever spoke.
|
|
9
10
|
*/
|
|
10
|
-
export function say(folder, who, text, at, mood) {
|
|
11
|
+
export function say(folder, who, text, at, mood, shown) {
|
|
11
12
|
const current = gameAt(folder, at);
|
|
12
13
|
if (current === undefined) {
|
|
13
|
-
return sayAtTable(folder, who, text, at, mood);
|
|
14
|
+
return sayAtTable(folder, who, text, at, mood, shown);
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
const going = endOf(current.game) === undefined;
|
|
17
|
+
if (mood !== undefined && going) {
|
|
16
18
|
throw new Error("While the game goes on your mood comes with ai_move: say it without mood. Nothing changed.");
|
|
17
19
|
}
|
|
18
|
-
// With the helps on the AI writes as long as the analyst (D-78).
|
|
19
|
-
current.talk.push(talkLine(current, who, text, at, undefined, aiMost(folder, who, text)));
|
|
20
|
+
// With the helps on the AI writes as long as the analyst (D-78), and once the game is over as long, to talk it over (D-101).
|
|
21
|
+
current.talk.push(talkLine(current, who, text, at, undefined, aiMost(folder, who, text, going), shown));
|
|
20
22
|
if (mood !== undefined) {
|
|
21
23
|
endWithMood(current.game, mood);
|
|
22
24
|
}
|
|
@@ -45,7 +47,7 @@ export function waitingWords(context, given = context.given) {
|
|
|
45
47
|
return [];
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
|
-
/** Gives the words to the AI: from now on they count as given in this chat. Their text. */
|
|
50
|
+
/** Gives the words to the AI: from now on they count as given in this chat. Their text, with the move shown in analysis. */
|
|
49
51
|
export function give(context, words) {
|
|
50
52
|
const before = context.given.size;
|
|
51
53
|
for (const { key } of words) {
|
|
@@ -54,14 +56,19 @@ export function give(context, words) {
|
|
|
54
56
|
if (context.given.size > before) {
|
|
55
57
|
context.heard?.();
|
|
56
58
|
}
|
|
57
|
-
|
|
59
|
+
const analysed = words.some((word) => word.shown !== undefined) ? gameInAnalysis(gameFolder(context.home()))?.game : undefined;
|
|
60
|
+
return words.map(({ text, shown }) => shown === undefined ? { text } : { text, place: placeOf(shown, analysed) });
|
|
58
61
|
}
|
|
59
|
-
/**
|
|
60
|
-
function
|
|
62
|
+
/** Where the page stood: the move shown, as the player saw it, or the start; with the game's name when the page shows another now. */
|
|
63
|
+
function placeOf({ game, move }, analysed) {
|
|
64
|
+
return `${move === undefined ? "the start" : shownMove(move)}${game === analysed ? "" : ` of ${game}`}`;
|
|
65
|
+
}
|
|
66
|
+
/** The most a line of the AI's may hold in the games of `folder`: an analyst's note with the helps on (D-78), or with no game going on (D-101); a longer one is refused. */
|
|
67
|
+
function aiMost(folder, who, text, going) {
|
|
61
68
|
if (who !== "ai") {
|
|
62
69
|
return talkLimits.ai;
|
|
63
70
|
}
|
|
64
|
-
if (helpsIn(folder)) {
|
|
71
|
+
if (!going || helpsIn(folder)) {
|
|
65
72
|
return talkLimits.aiHelps;
|
|
66
73
|
}
|
|
67
74
|
if (text.trim().length > talkLimits.ai) {
|
|
@@ -70,7 +77,7 @@ function aiMost(folder, who, text) {
|
|
|
70
77
|
return talkLimits.ai;
|
|
71
78
|
}
|
|
72
79
|
/** A line of the talk with no game, which starts it the first time; no mood, which only the end of a game takes. */
|
|
73
|
-
function sayAtTable(folder, who, text, at, mood) {
|
|
80
|
+
function sayAtTable(folder, who, text, at, mood, shown) {
|
|
74
81
|
if (who === "agent") {
|
|
75
82
|
throw new Error("There is no game to think on. Nothing changed.");
|
|
76
83
|
}
|
|
@@ -78,7 +85,8 @@ function sayAtTable(folder, who, text, at, mood) {
|
|
|
78
85
|
throw new Error("There is no game: a mood goes with a game's end. Say it without mood. Nothing changed.");
|
|
79
86
|
}
|
|
80
87
|
const before = loadTableTalk(folder);
|
|
81
|
-
const
|
|
88
|
+
const line = talkLine(before, who, text, at, undefined, aiMost(folder, who, text, false), shown);
|
|
89
|
+
const table = { started: before.started === "" ? at.toISOString() : before.started, talk: [...before.talk, line] };
|
|
82
90
|
saveTableTalk(folder, table);
|
|
83
91
|
return table;
|
|
84
92
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type GameSummary } from "./game-summary.ts";
|
|
3
|
+
import { type Written } from "./say.ts";
|
|
3
4
|
import { type Speech } from "./speech.ts";
|
|
4
5
|
interface Status {
|
|
5
6
|
version: string;
|
|
@@ -16,12 +17,13 @@ interface Status {
|
|
|
16
17
|
/** Why the game on disk cannot be read, when it cannot. */
|
|
17
18
|
problem?: string;
|
|
18
19
|
/** What the player wrote on the board page that the AI has not answered yet, all of it. */
|
|
19
|
-
words?:
|
|
20
|
+
words?: Written[];
|
|
20
21
|
/** The game in analysis on the board page once none goes on (D-69), which the player may have brought there. */
|
|
21
22
|
analysis?: {
|
|
22
23
|
game: string;
|
|
23
24
|
brought?: true;
|
|
24
|
-
analyst?:
|
|
25
|
+
analyst?: "done" | "offer";
|
|
26
|
+
review?: string;
|
|
25
27
|
};
|
|
26
28
|
/** A game going on from before this chat, told once a chat: the player says whether to go on with it. */
|
|
27
29
|
fromBefore?: true;
|
|
@@ -29,6 +31,8 @@ interface Status {
|
|
|
29
31
|
coach?: true;
|
|
30
32
|
/** The files an older init wrote in the player's folder, which this version would write otherwise. */
|
|
31
33
|
outdated?: string[];
|
|
34
|
+
/** The chat has not read the opponent's instructions since it started, or was cleared or summarized (D-102). */
|
|
35
|
+
unread?: true;
|
|
32
36
|
}
|
|
33
37
|
/** What Chesspal sees now, the first call of a chat: a dashboard under 150 tokens while the player is to move, with the next step. */
|
|
34
38
|
export declare const status: import("./registry.ts").Command<z.ZodObject<{}, z.core.$strip>, Status>;
|
package/dist/commands/status.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { gameReviewOf, leadOf, reviewOf } from "../core/index.js";
|
|
2
3
|
import { archivedName, cleanSince, endOf, gameInAnalysis, lastChatStart } from "../games/index.js";
|
|
3
|
-
import {
|
|
4
|
+
import { outdatedHostFiles } from "../host-files/index.js";
|
|
4
5
|
import { gameFolder } from "../profiles/index.js";
|
|
5
6
|
import { version } from "../version.js";
|
|
6
|
-
import {
|
|
7
|
-
import { closingLines, newGameStep, positionLines, sidesLine, wordLines } from "./answer-lines.js";
|
|
7
|
+
import { analystLine, analystRuns, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
|
|
8
|
+
import { closingLines, newGameStep, positionLines, shownMoves, sidesLine, wordLines } from "./answer-lines.js";
|
|
8
9
|
import { gameAt } from "./clock.js";
|
|
9
10
|
import { summaryOf, withThought } from "./game-summary.js";
|
|
11
|
+
import { instructionsRead } from "./instructions.js";
|
|
10
12
|
import { levelText } from "./levels.js";
|
|
11
13
|
import { closedPage, pageGiven, pageLine } from "./page-link.js";
|
|
12
14
|
import { playerLine } from "./profiles.js";
|
|
13
15
|
import { defineCommand } from "./registry.js";
|
|
14
16
|
import { unansweredWords } from "./say.js";
|
|
15
17
|
import { speechLine, speechOf } from "./speech.js";
|
|
18
|
+
import { namedGame } from "./study.js";
|
|
16
19
|
/** What Chesspal sees now, the first call of a chat: a dashboard under 150 tokens while the player is to move, with the next step. */
|
|
17
20
|
export const status = defineCommand({
|
|
18
21
|
name: "status",
|
|
19
22
|
description: "Shows what Chesspal sees now: its version, the profile's folder and the game, with your next step. Call it at the start of every chat, "
|
|
20
|
-
+ "
|
|
23
|
+
+ "with `instructions`.",
|
|
21
24
|
input: z.object({}),
|
|
22
25
|
// Read only for the host: a flag that has fallen is written, as the time already decided.
|
|
23
26
|
readOnly: true,
|
|
@@ -28,7 +31,10 @@ export const status = defineCommand({
|
|
|
28
31
|
const folder = gameFolder(context.home());
|
|
29
32
|
const player = playerLine(context);
|
|
30
33
|
const speech = speechOf(folder, context.language);
|
|
31
|
-
const
|
|
34
|
+
const chatStart = lastChatStart(context.folder);
|
|
35
|
+
const first = firstInChat(context, chatStart?.at);
|
|
36
|
+
// An older init's files are told once a chat, at its first status: reading them all is the slowest part of a status.
|
|
37
|
+
const outdated = context.told === undefined || first ? outdatedHostFiles(context.folder, context.platform) : [];
|
|
32
38
|
const status = { version, folder, ...(player === undefined ? {} : { player }), ...(speech === undefined ? {} : { speech }), ...(outdated.length === 0 ? {} : { outdated }) };
|
|
33
39
|
if (context.page !== undefined) {
|
|
34
40
|
status.page = context.page;
|
|
@@ -36,7 +42,9 @@ export const status = defineCommand({
|
|
|
36
42
|
if (closedPage(context) !== undefined) {
|
|
37
43
|
status.pageClosed = true;
|
|
38
44
|
}
|
|
39
|
-
|
|
45
|
+
if (!instructionsRead(context, chatStart?.at)) {
|
|
46
|
+
status.unread = true;
|
|
47
|
+
}
|
|
40
48
|
try {
|
|
41
49
|
const current = gameAt(folder, context.now());
|
|
42
50
|
// With no game the player may have written before the first (D-77).
|
|
@@ -49,7 +57,8 @@ export const status = defineCommand({
|
|
|
49
57
|
if (status.pageClosed === true) {
|
|
50
58
|
pageGiven(context, current.started);
|
|
51
59
|
}
|
|
52
|
-
|
|
60
|
+
// A game a summarized or resumed chat still holds is its own; without the host's word of the chat's start, any game began before it.
|
|
61
|
+
if (first && endOf(current.game) === undefined && (chatStart === undefined || cleanSince(chatStart, current.started))) {
|
|
53
62
|
status.fromBefore = true;
|
|
54
63
|
}
|
|
55
64
|
// The player's move on the board page that ended the game: the coach judges it too (D-88).
|
|
@@ -59,8 +68,14 @@ export const status = defineCommand({
|
|
|
59
68
|
}
|
|
60
69
|
const analysis = current === undefined || status.game?.end !== undefined ? gameInAnalysis(folder) : undefined;
|
|
61
70
|
if (analysis !== undefined) {
|
|
62
|
-
// The analyst agent goes over a game the player brought, where it runs, and the chat over what it reviewed (D-89).
|
|
63
|
-
|
|
71
|
+
// The analyst agent goes over a game the player brought, or whose review was deleted, where it runs, and the chat over what it reviewed (D-89).
|
|
72
|
+
const review = analysis.brought === true ? undefined : reviewProgress(context, analysis.game);
|
|
73
|
+
status.analysis = {
|
|
74
|
+
game: analysis.game,
|
|
75
|
+
...(analysis.brought === true ? { brought: true } : {}),
|
|
76
|
+
...(analystRuns(context) ? { analyst: goneOver(context, analysis.game) ? "done" : "offer" } : {}),
|
|
77
|
+
...(review === undefined ? {} : { review }),
|
|
78
|
+
};
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
81
|
catch (error) {
|
|
@@ -68,8 +83,9 @@ export const status = defineCommand({
|
|
|
68
83
|
}
|
|
69
84
|
return status;
|
|
70
85
|
},
|
|
71
|
-
format: ({ version, folder, page, pageClosed, player, speech, game, problem, words = [], analysis, fromBefore, coach, outdated }) => [
|
|
86
|
+
format: ({ version, folder, page, pageClosed, player, speech, game, problem, words = [], analysis, fromBefore, coach, outdated, unread }) => [
|
|
72
87
|
`Chesspal ${version}. Folder: ${folder}`,
|
|
88
|
+
...(unread === true ? [unreadLine] : []),
|
|
73
89
|
...(outdated === undefined ? [] : [outdatedLine(outdated)]),
|
|
74
90
|
...(page === undefined ? [] : [pageLine(page, pageClosed === true)]),
|
|
75
91
|
...(player === undefined ? [] : [player]),
|
|
@@ -84,45 +100,73 @@ function outdatedLine(files) {
|
|
|
84
100
|
return `This folder was set up by an older Chesspal (${files.join(", ")}): tell the player, in their language, to close this chat, run "chesspal init" `
|
|
85
101
|
+ "in this folder and open a new chat. Until then the agents and your instructions are the old ones.";
|
|
86
102
|
}
|
|
103
|
+
// The opponent's instructions, which the chat reads before its first reply (D-102).
|
|
104
|
+
const unreadLine = "Call `instructions` before you answer the player, if you have not called it since this chat started or was summarized.";
|
|
87
105
|
// The game going on as a chat starts: the player may have come back for it, or want another.
|
|
88
106
|
const fromBeforeLine = "A game from before this chat is going on: ask the player whether to go on with it. For a new one, " + newGameStep;
|
|
89
107
|
/**
|
|
90
108
|
* Whether this is the chat's first status: once a chat where the MCP server keeps what it told, again in a chat cleared or summarized, since its start keys
|
|
91
109
|
* it (D-57); never on the command line.
|
|
92
110
|
*/
|
|
93
|
-
function firstInChat(context) {
|
|
94
|
-
const key = `game before ${
|
|
111
|
+
function firstInChat(context, chatStart) {
|
|
112
|
+
const key = `game before ${chatStart ?? ""}`;
|
|
95
113
|
if (context.told === undefined || context.told.has(key)) {
|
|
96
114
|
return false;
|
|
97
115
|
}
|
|
98
116
|
context.told.add(key);
|
|
99
117
|
return true;
|
|
100
118
|
}
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (brought === true) {
|
|
109
|
-
return analyst === true
|
|
119
|
+
/**
|
|
120
|
+
* The game in analysis, with how far its review has got; the next step when the player has just brought it on the board page, unless an analyst is at it
|
|
121
|
+
* already; the analyst agent for a game whose review was deleted, where it runs (Stefano).
|
|
122
|
+
*/
|
|
123
|
+
function analysisLine({ game, brought, analyst, review }) {
|
|
124
|
+
if (brought === true && analyst !== "done") {
|
|
125
|
+
return analyst === "offer"
|
|
110
126
|
? `The player brought ${game} on the board page to analyse it. Next: ${analystLine(game)}`
|
|
111
127
|
: `The player brought ${game} on the board page to analyse it. Next: call analyse_game {"game": "${game}"}, then keep your review on that page with annotate_game, and go over it with them.`;
|
|
112
128
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
const shown = `In analysis on the board page: ${game}${review === undefined ? "" : ` (${review})`}`;
|
|
130
|
+
if (analyst === "done") {
|
|
131
|
+
return `${shown}. ${reviewedLine(game)}`;
|
|
132
|
+
}
|
|
133
|
+
return analyst === "offer" ? `${shown}. When the player wants it gone over, ${analystLine(game)}` : `${shown}; analyse_game {"game": "${game}"} goes over it with the player.`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* How far the review of a game has got, as the board page shows it (Priya): done once it holds the word on the whole game and who is ahead after every move
|
|
137
|
+
* but the last; the analyst may still write notes after its word, so that alone is not the end.
|
|
138
|
+
*/
|
|
139
|
+
function reviewProgress(context, game) {
|
|
140
|
+
try {
|
|
141
|
+
const { record } = namedGame(context, game);
|
|
142
|
+
const worded = gameReviewOf(record) !== undefined;
|
|
143
|
+
const estimated = record.moves.filter(({ comment }) => leadOf(comment) !== undefined).length;
|
|
144
|
+
if (worded && estimated >= record.moves.length - 1) {
|
|
145
|
+
return "its review is done";
|
|
146
|
+
}
|
|
147
|
+
const noted = record.moves.filter(({ comment }) => {
|
|
148
|
+
const review = reviewOf(comment);
|
|
149
|
+
return review !== undefined && (review.badge !== "book" || review.note !== undefined);
|
|
150
|
+
}).length;
|
|
151
|
+
const word = worded ? ", its word on the game written" : "";
|
|
152
|
+
return noted === 0 && estimated === 0 && !worded
|
|
153
|
+
? "no review yet"
|
|
154
|
+
: `its review so far: notes on ${noted} moves, who is ahead after ${estimated} of ${record.moves.length}${word}`;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
116
159
|
}
|
|
117
160
|
/**
|
|
118
161
|
* The game's lines; a game the player brought to analyse takes the place of the next step. The coach to start goes just before the next step, where
|
|
119
162
|
* the AI acts on it (Priya).
|
|
120
163
|
*/
|
|
121
164
|
function gameLines(game, words, brought, pageClosed, coach) {
|
|
122
|
-
const closing = [...(coach ? [
|
|
165
|
+
const closing = [...(coach ? [coachOnEndLine] : []), ...(brought ? wordLines(words) : closingLines(game, words, pageClosed))];
|
|
123
166
|
if (game === undefined) {
|
|
124
167
|
return ["No game.", ...closing];
|
|
125
168
|
}
|
|
126
|
-
|
|
169
|
+
// With the figurines, as every answer writes moves: in letters the AI copied them into its message (Priya).
|
|
170
|
+
const moves = game.lastMoves === "" ? [] : [`Moves: ${shownMoves(game.lastMoves)}`];
|
|
127
171
|
return [`Game: ${sidesLine(game.ai)}, ${levelText(game.level)}.`, ...moves, ...positionLines(game), ...closing];
|
|
128
172
|
}
|
package/dist/commands/study.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { Game, type GameRecord } from "../core/index.ts";
|
|
3
|
-
import { type CurrentGame } from "../games/index.ts";
|
|
3
|
+
import { type CurrentGame, type Shown } from "../games/index.ts";
|
|
4
4
|
import type { Context } from "./context.ts";
|
|
5
5
|
/** What look and position take to show any position of a game that is over. */
|
|
6
6
|
export declare const studyInput: {
|
|
@@ -21,10 +21,20 @@ export interface StudiedPosition {
|
|
|
21
21
|
export declare function noGameGoingOn(context: Context): CurrentGame | undefined;
|
|
22
22
|
/** The game played or kept to study by that name; without one, the current game over, or else the latest of the archive; refused while a game goes on. */
|
|
23
23
|
export declare function gameToStudy(context: Context, name?: string): Studied;
|
|
24
|
+
/**
|
|
25
|
+
* The game look and position show: without a name, for the chat, the one in analysis on the board page, which the player's words there name by their move
|
|
26
|
+
* alone (D-101, Priya); else as gameToStudy. An agent names its game, since the page may turn to another meanwhile.
|
|
27
|
+
*/
|
|
28
|
+
export declare function gameShown(context: Context, name?: string): Studied;
|
|
24
29
|
/** A game played or kept to study by its name as the lists write it, or without its folder or its .pgn; refused when it is not there. */
|
|
25
30
|
export declare function namedGame(context: Context, name: string): Required<Studied>;
|
|
26
31
|
/** The index of a move in the game's record, given as analyse_game numbers it: 12. for White's, 12... for Black's, 12...Kf8 too. */
|
|
27
32
|
export declare function moveIndex(record: GameRecord, move: string): number;
|
|
33
|
+
/**
|
|
34
|
+
* A position of a game over as the board page shows it in analysis (D-101): the game's name, how many of its moves it shows, and the last of them numbered;
|
|
35
|
+
* none for a game or a position there is not, or while a game goes on.
|
|
36
|
+
*/
|
|
37
|
+
export declare function shownPosition(context: Context, game: string, at: number): Shown | undefined;
|
|
28
38
|
/** The number of each move of a game as the analysis writes it: 1. for White's first move, 1... for Black's. */
|
|
29
39
|
export declare function moveNumbers(record: GameRecord): string[];
|
|
30
40
|
/**
|
package/dist/commands/study.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// goes on, nothing is studied: the analysis would help the game being played (D-60, D-62).
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { Game } from "../core/index.js";
|
|
5
|
-
import { archivedGame, archivedGames, archivedName, endOf, keptGame, listedName } from "../games/index.js";
|
|
5
|
+
import { archivedGame, archivedGames, archivedName, endOf, gameInAnalysis, keptGame, listedName } from "../games/index.js";
|
|
6
6
|
import { gameFolder } from "../profiles/index.js";
|
|
7
7
|
import { gameAt } from "./clock.js";
|
|
8
8
|
import { colourName } from "./view.js";
|
|
9
9
|
/** What look and position take to show any position of a game that is over. */
|
|
10
10
|
export const studyInput = {
|
|
11
|
-
game: z.string().optional().describe("a game's name, as past_games, study_games or analyse_game writes it: one of its positions"),
|
|
11
|
+
game: z.string().optional().describe("a game's name, as past_games, study_games or analyse_game writes it: one of its positions; without it, the game on the board page"),
|
|
12
12
|
move: z.string().optional().describe("the position after this move of a game over, as analyse_game numbers it: 12. for White's, 12... for Black's"),
|
|
13
13
|
};
|
|
14
14
|
/** The current game when it is over, or none; refused while it goes on, for every game: games are analysed once the one being played is over. */
|
|
@@ -27,16 +27,31 @@ export function gameToStudy(context, name) {
|
|
|
27
27
|
if (name !== undefined) {
|
|
28
28
|
return namedGame(context, name);
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
// A game closed before any move has nothing to study: the one before it is the last played.
|
|
31
|
+
if (current !== undefined && current.game.record.moves.length > 0) {
|
|
31
32
|
// The archive's record, which keeps the AI's review (D-69).
|
|
32
33
|
const archived = archivedName(folder, current);
|
|
33
34
|
return archived === undefined ? { record: current.game.record } : { record: archivedGame(folder, archived), name: archived };
|
|
34
35
|
}
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
for (const latest of archivedGames(folder)) {
|
|
37
|
+
const record = archivedGame(folder, latest);
|
|
38
|
+
if (record.moves.length > 0) {
|
|
39
|
+
return { record, name: latest };
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
|
-
|
|
42
|
+
throw new Error("There is no game to study: play one with new_game, or give analyse_game the PGN of a game played elsewhere.");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The game look and position show: without a name, for the chat, the one in analysis on the board page, which the player's words there name by their move
|
|
46
|
+
* alone (D-101, Priya); else as gameToStudy. An agent names its game, since the page may turn to another meanwhile.
|
|
47
|
+
*/
|
|
48
|
+
export function gameShown(context, name) {
|
|
49
|
+
const analysis = name === undefined && context.agent === undefined ? gameInAnalysis(gameFolder(context.home())) : undefined;
|
|
50
|
+
if (analysis === undefined) {
|
|
51
|
+
return gameToStudy(context, name);
|
|
52
|
+
}
|
|
53
|
+
noGameGoingOn(context);
|
|
54
|
+
return namedGame(context, analysis.game);
|
|
40
55
|
}
|
|
41
56
|
/** A game played or kept to study by its name as the lists write it, or without its folder or its .pgn; refused when it is not there. */
|
|
42
57
|
export function namedGame(context, name) {
|
|
@@ -48,6 +63,23 @@ export function namedGame(context, name) {
|
|
|
48
63
|
export function moveIndex(record, move) {
|
|
49
64
|
return plyOf(record, moveNumbers(record), move);
|
|
50
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* A position of a game over as the board page shows it in analysis (D-101): the game's name, how many of its moves it shows, and the last of them numbered;
|
|
68
|
+
* none for a game or a position there is not, or while a game goes on.
|
|
69
|
+
*/
|
|
70
|
+
export function shownPosition(context, game, at) {
|
|
71
|
+
try {
|
|
72
|
+
noGameGoingOn(context);
|
|
73
|
+
const { name, record } = namedGame(context, game);
|
|
74
|
+
if (!Number.isInteger(at) || at < 0 || at > record.moves.length) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return at === 0 ? { game: name, at } : { game: name, at, move: `${moveNumbers(record)[at - 1]}${record.moves[at - 1].san}` };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
51
83
|
/** The number of each move of a game as the analysis writes it: 1. for White's first move, 1... for Black's. */
|
|
52
84
|
export function moveNumbers(record) {
|
|
53
85
|
const start = new Game({ tags: record.tags, moves: [] }).position;
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import type { Facts, Material, Side } from "../core/index.ts";
|
|
2
|
-
/**
|
|
3
|
-
export
|
|
1
|
+
import type { Chances, Facts, Material, Possibilities, Side } from "../core/index.ts";
|
|
2
|
+
/** What the moves would do (D-100), and the other side's threats, none in check. */
|
|
3
|
+
export interface Possible {
|
|
4
|
+
readonly possibilities: Possibilities;
|
|
5
|
+
readonly threats: Chances | undefined;
|
|
6
|
+
/** The level sees the threats' double attacks and discovered checks, and the stalemate traps: from the strong level up. */
|
|
7
|
+
readonly strong: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The view after the player's move, in the masters' order (D-100): the check, the checks, the captures, the threats, what the moves would do, the pins,
|
|
11
|
+
* skewers and loose pieces, the pieces attacked, the moves of each piece.
|
|
12
|
+
*/
|
|
13
|
+
export declare function compactView(facts: Facts, turn: Side, possible?: Possible): string[];
|
|
4
14
|
/** The view of a level without the tactical facts (D-60): the check and the legal moves by piece, which are the rules (D-17). */
|
|
5
15
|
export declare function boardView(facts: Facts, turn: Side): string[];
|
|
6
|
-
/** The whole view, for look:
|
|
7
|
-
export declare function fullView(facts: Facts, turn: Side): string[];
|
|
16
|
+
/** The whole view, for look: the same lines as the compact view, with every piece's attackers and defenders. */
|
|
17
|
+
export declare function fullView(facts: Facts, turn: Side, possible?: Possible): string[];
|
|
18
|
+
/** What the moves of the side to move would do, in one line for a move of the analysis: fork Nc7+ (Ke8 Ra8); stalemate Qf7; none when nothing. */
|
|
19
|
+
export declare function tacticsText(facts: Facts, possibilities: Possibilities, withTraps: boolean): string | undefined;
|
|
8
20
|
/** What is at risk, in one line for a move of the analysis (D-68): undefended Black Nf6; by a cheaper piece White Qd1; outnumbered Black Pe5; none when nothing is. */
|
|
9
21
|
export declare function riskLine(facts: Facts): string | undefined;
|
|
10
22
|
export declare function colourName(side: Side): string;
|