chesspal 0.2.4 → 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.
Files changed (183) hide show
  1. package/README.md +11 -12
  2. package/dist/cli/cli.js +83 -3
  3. package/dist/cli/command-line.js +26 -9
  4. package/dist/cli/icon.d.ts +25 -0
  5. package/dist/cli/icon.js +80 -0
  6. package/dist/commands/agents.d.ts +19 -2
  7. package/dist/commands/agents.js +39 -13
  8. package/dist/commands/ai-say.js +3 -4
  9. package/dist/commands/analyse-game.d.ts +4 -0
  10. package/dist/commands/analyse-game.js +46 -13
  11. package/dist/commands/annotate-game.d.ts +2 -0
  12. package/dist/commands/annotate-game.js +40 -18
  13. package/dist/commands/answer-lines.d.ts +2 -0
  14. package/dist/commands/answer-lines.js +13 -3
  15. package/dist/commands/coach.js +4 -2
  16. package/dist/commands/context.d.ts +9 -1
  17. package/dist/commands/context.js +5 -0
  18. package/dist/commands/delete-review.js +9 -5
  19. package/dist/commands/game-summary.d.ts +2 -0
  20. package/dist/commands/game-summary.js +4 -0
  21. package/dist/commands/index.d.ts +3 -2
  22. package/dist/commands/index.js +7 -4
  23. package/dist/commands/init.js +2 -4
  24. package/dist/commands/instructions.d.ts +9 -0
  25. package/dist/commands/instructions.js +30 -0
  26. package/dist/commands/lead-input.d.ts +1 -1
  27. package/dist/commands/lead-input.js +7 -4
  28. package/dist/commands/page-link.js +6 -5
  29. package/dist/commands/past-games.d.ts +1 -1
  30. package/dist/commands/past-games.js +12 -5
  31. package/dist/commands/player-move.js +2 -3
  32. package/dist/commands/profiles.d.ts +23 -9
  33. package/dist/commands/profiles.js +76 -35
  34. package/dist/commands/ratings.d.ts +8 -0
  35. package/dist/commands/ratings.js +45 -0
  36. package/dist/commands/registry.d.ts +1 -1
  37. package/dist/commands/registry.js +3 -2
  38. package/dist/commands/say.d.ts +1 -1
  39. package/dist/commands/say.js +1 -1
  40. package/dist/commands/status.d.ts +3 -1
  41. package/dist/commands/status.js +46 -27
  42. package/dist/commands/study.js +8 -5
  43. package/dist/core/index.d.ts +1 -1
  44. package/dist/core/index.js +1 -1
  45. package/dist/core/review.d.ts +2 -0
  46. package/dist/core/review.js +5 -1
  47. package/dist/games/ai-model.d.ts +4 -2
  48. package/dist/games/ai-model.js +10 -1
  49. package/dist/games/analysis.d.ts +26 -3
  50. package/dist/games/analysis.js +41 -6
  51. package/dist/games/archive.d.ts +6 -3
  52. package/dist/games/archive.js +13 -5
  53. package/dist/games/current-game.d.ts +10 -1
  54. package/dist/games/current-game.js +49 -15
  55. package/dist/games/files.d.ts +5 -0
  56. package/dist/games/files.js +42 -1
  57. package/dist/games/index.d.ts +3 -3
  58. package/dist/games/index.js +3 -3
  59. package/dist/home/data-folder.d.ts +2 -0
  60. package/dist/home/data-folder.js +5 -1
  61. package/dist/home/index.d.ts +1 -1
  62. package/dist/home/index.js +1 -1
  63. package/dist/host-files/agents.d.ts +2 -2
  64. package/dist/host-files/agents.js +12 -6
  65. package/dist/host-files/host-files.d.ts +5 -3
  66. package/dist/host-files/host-files.js +15 -8
  67. package/dist/host-files/index.d.ts +2 -2
  68. package/dist/host-files/index.js +2 -2
  69. package/dist/page/app/app.js +29 -16
  70. package/dist/page/app/title.js +17 -0
  71. package/dist/page/avatars/torretta.js +4 -4
  72. package/dist/page/dialogs/analyses.js +4 -4
  73. package/dist/page/dialogs/dialog.js +4 -1
  74. package/dist/page/dialogs/dialogs.css +84 -10
  75. package/dist/page/dialogs/profiles.js +95 -42
  76. package/dist/page/game/column.js +5 -1
  77. package/dist/page/game/plies.js +7 -4
  78. package/dist/page/guide/blocks.js +67 -0
  79. package/dist/page/guide/en.md +66 -0
  80. package/dist/page/guide/foot.js +18 -0
  81. package/dist/page/guide/guide.css +200 -0
  82. package/dist/page/guide/guide.js +89 -0
  83. package/dist/page/guide/it.md +66 -0
  84. package/dist/page/guide/markdown.js +67 -0
  85. package/dist/page/helps/helps.css +7 -10
  86. package/dist/page/layout/favicon.svg +13 -0
  87. package/dist/page/layout/header.js +3 -3
  88. package/dist/page/layout/icon.icns +0 -0
  89. package/dist/page/layout/icon.ico +0 -0
  90. package/dist/page/layout/icon.png +0 -0
  91. package/dist/page/layout/layout.css +111 -4
  92. package/dist/page/layout/window.js +45 -0
  93. package/dist/page/main.js +4 -1
  94. package/dist/page/players/card.js +1 -1
  95. package/dist/page/profile/profile.js +14 -9
  96. package/dist/page/profile/settings.js +9 -0
  97. package/dist/page/style.css +2 -0
  98. package/dist/page/table/opener.css +65 -0
  99. package/dist/page/table/opener.js +59 -0
  100. package/dist/page/talk/chat.css +10 -3
  101. package/dist/page/talk/talk.css +38 -26
  102. package/dist/page/talk/talk.js +4 -2
  103. package/dist/page/text.js +83 -17
  104. package/dist/page/theme.css +7 -0
  105. package/dist/profiles/index.d.ts +2 -1
  106. package/dist/profiles/index.js +2 -1
  107. package/dist/profiles/profiles.d.ts +10 -15
  108. package/dist/profiles/profiles.js +30 -15
  109. package/dist/profiles/ratings.d.ts +35 -0
  110. package/dist/profiles/ratings.js +61 -0
  111. package/dist/server/bell.d.ts +24 -3
  112. package/dist/server/bell.js +84 -10
  113. package/dist/server/caller.d.ts +2 -1
  114. package/dist/server/caller.js +3 -2
  115. package/dist/server/mcp.d.ts +7 -3
  116. package/dist/server/mcp.js +100 -52
  117. package/dist/server/page/analyses.d.ts +2 -0
  118. package/dist/server/page/analyses.js +20 -5
  119. package/dist/server/page/attach.d.ts +4 -0
  120. package/dist/server/page/attach.js +112 -0
  121. package/dist/server/page/chat.d.ts +60 -0
  122. package/dist/server/page/chat.js +79 -0
  123. package/dist/server/page/events.d.ts +7 -0
  124. package/dist/server/page/events.js +20 -0
  125. package/dist/server/page/files.js +10 -1
  126. package/dist/server/page/game.js +43 -23
  127. package/dist/server/page/page.d.ts +47 -7
  128. package/dist/server/page/page.js +69 -48
  129. package/dist/server/page/profiles.d.ts +5 -0
  130. package/dist/server/page/profiles.js +26 -10
  131. package/dist/server/page/table.d.ts +39 -0
  132. package/dist/server/page/table.js +102 -0
  133. package/dist/server/table/browser.d.ts +6 -0
  134. package/dist/server/table/browser.js +48 -0
  135. package/dist/server/table/hosts/chats.d.ts +7 -0
  136. package/dist/server/table/hosts/chats.js +31 -0
  137. package/dist/server/table/hosts/find.d.ts +2 -0
  138. package/dist/server/table/hosts/find.js +20 -0
  139. package/dist/server/table/hosts/hosts.d.ts +34 -0
  140. package/dist/server/table/hosts/hosts.js +49 -0
  141. package/dist/server/table/hosts/index.d.ts +3 -0
  142. package/dist/server/table/hosts/index.js +4 -0
  143. package/dist/server/table/hosts/play.d.ts +25 -0
  144. package/dist/server/table/hosts/play.js +152 -0
  145. package/dist/server/table/hosts/seat.d.ts +25 -0
  146. package/dist/server/table/hosts/seat.js +74 -0
  147. package/dist/server/table/hosts/terminal.d.ts +6 -0
  148. package/dist/server/table/hosts/terminal.js +63 -0
  149. package/dist/server/table/icon/index.d.ts +32 -0
  150. package/dist/server/table/icon/index.js +83 -0
  151. package/dist/server/table/icon/linux.d.ts +5 -0
  152. package/dist/server/table/icon/linux.js +47 -0
  153. package/dist/server/table/icon/macos.d.ts +3 -0
  154. package/dist/server/table/icon/macos.js +38 -0
  155. package/dist/server/table/icon/windows.d.ts +3 -0
  156. package/dist/server/table/icon/windows.js +48 -0
  157. package/dist/server/table/index.d.ts +6 -0
  158. package/dist/server/table/index.js +7 -0
  159. package/dist/server/table/launch.d.ts +19 -0
  160. package/dist/server/table/launch.js +78 -0
  161. package/dist/server/table/link.d.ts +10 -0
  162. package/dist/server/table/link.js +230 -0
  163. package/dist/server/table/table.d.ts +35 -0
  164. package/dist/server/table/table.js +94 -0
  165. package/dist/sites/chesscom.d.ts +4 -0
  166. package/dist/sites/chesscom.js +25 -0
  167. package/dist/sites/choice.d.ts +17 -0
  168. package/dist/sites/choice.js +13 -0
  169. package/dist/sites/index.d.ts +2 -0
  170. package/dist/sites/index.js +4 -0
  171. package/dist/sites/lichess.d.ts +4 -0
  172. package/dist/sites/lichess.js +32 -0
  173. package/dist/sites/request.d.ts +11 -0
  174. package/dist/sites/request.js +15 -0
  175. package/dist/sites/sites.d.ts +6 -0
  176. package/dist/sites/sites.js +33 -0
  177. package/instructions/analysis.md +1 -1
  178. package/instructions/analyst-agent.md +1 -1
  179. package/instructions/coach.md +2 -1
  180. package/instructions/opponent.md +6 -6
  181. package/instructions/ponder.md +1 -0
  182. package/instructions/start.md +1 -0
  183. package/package.json +1 -1
@@ -1,21 +1,21 @@
1
1
  // The analyst's tool (D-21, D-68): a game over, move by move in one call, facts only and never a judgment, with the analyst's instructions once in a chat.
2
2
  import { z } from "zod";
3
3
  import { clockOfComment, clockText, closingOf, Game, gameReviewOf, isReviewed, leadOf, leadText, levelOf, materialOf, readPgn, reviewed, reviewOf, timeControlOfTag, timeControlText, withGameReview, withLead, } from "../core/index.js";
4
- import { isGameName, keepToStudy, lastChatStart, planOf, rewriteGame, startAnalysis } from "../games/index.js";
5
- import { analystAgent, instructions } from "../host-files/index.js";
4
+ import { gameInAnalysis, isGameName, keepToStudy, lastChatStart, planOf, rewriteGame, startAnalysis } from "../games/index.js";
5
+ import { analystAgent, instructionsFile } from "../host-files/index.js";
6
6
  import { activeProfile, gameFolder } from "../profiles/index.js";
7
+ import { analystOf, anotherAnalyst } from "./agents.js";
7
8
  import { bookOpening, bookReaches } from "./opening-book.js";
9
+ import { playerLine } from "./profiles.js";
8
10
  import { defineCommand } from "./registry.js";
9
11
  import { speechLine, speechOf } from "./speech.js";
10
12
  import { gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn } from "./study.js";
11
13
  import { balanceText, colourName, riskLine, tacticsText } from "./view.js";
12
- // The analyst's instructions, read at the first analysis.
13
- let analystText;
14
14
  /** A game over move by move, for the analyst (D-68): the current one, one of the archive or kept to study, or one the player brings as a PGN, kept to study. */
15
15
  export const analyseGame = defineCommand({
16
16
  name: "analyse_game",
17
17
  description: "Gives a game that is over move by move, to comment it with the player: each move with what it took and the time it took, the material "
18
- + "when it changes, the pieces at risk, the mates in one and what the next moves would do after it (forks, double attacks, discovered checks, "
18
+ + "when it changes, the pieces at risk, the only legal move when there is one alone, the mates in one and what the next moves would do after it (forks, double attacks, discovered checks, "
19
19
  + "promotions, stalemates and stalemate traps), where the game left the opening book, and your comments and plans. Facts "
20
20
  + "only: the judgment is yours. Without game or pgn, the game just over, or else the last one played; game: a name as past_games or study_games "
21
21
  + "writes it; pgn: a game the player brings from elsewhere, kept to study in the profile. Never while a game goes on. The first time in a chat "
@@ -32,19 +32,20 @@ export const analyseGame = defineCommand({
32
32
  // A game's name given as pgn, as an AI often gives it, is that game (Priya).
33
33
  const named = pgn !== undefined && isGameName(pgn) ? pgn.trim() : game;
34
34
  const studied = pgn === undefined || named !== game ? gameToStudy(context, named) : importedGame(context, pgn);
35
- // One analyst a game: the chat goes over the review there is (Stefano).
36
- if (context.agent === analystAgent && isReviewed(studied.record)) {
37
- throw new Error("Stop now: this game has its review already. End with one line for the AI that started you: go over that review with the player.");
35
+ if (context.agent === analystAgent) {
36
+ oneAnalyst(context, studied);
38
37
  }
39
38
  const analysis = analysisOf(studied, activeProfile(context.home()).name);
40
39
  if (studied.name !== undefined) {
41
40
  markBook(context, { record: studied.record, name: studied.name }, analysis.moves);
42
41
  startAnalysis(gameFolder(context.home()), studied.name, context.now());
43
42
  }
44
- // The analyst's agent has its instructions in its own file, and the player's language in its prompt or, when Chesspal knows it, here (D-89).
43
+ // The analyst's agent has its instructions in its own file, and the player's language in its prompt or, when Chesspal knows it, here (D-89);
44
+ // who the player is, which the chat reads in status, here too, so that it writes for their level.
45
45
  if (context.agent === analystAgent) {
46
46
  const speech = speechOf(gameFolder(context.home()), context.language);
47
- return speech === undefined ? analysis : { ...analysis, speech: speechLine(speech) };
47
+ const profile = playerLine(context);
48
+ return { ...analysis, ...(speech === undefined ? {} : { speech: speechLine(speech) }), ...(profile === undefined ? {} : { profile }) };
48
49
  }
49
50
  const told = analystInstructions(context);
50
51
  return told === undefined ? analysis : { instructions: told, ...analysis };
@@ -52,10 +53,36 @@ export const analyseGame = defineCommand({
52
53
  format: (analysis) => [
53
54
  ...(analysis.instructions === undefined ? [] : [analysis.instructions, ""]),
54
55
  ...(analysis.speech === undefined ? [] : [analysis.speech]),
56
+ ...(analysis.profile === undefined ? [] : [analysis.profile]),
55
57
  ...analysisHead(analysis),
56
58
  ...analysis.moves.map(moveLine),
57
59
  ].join("\n"),
58
60
  });
61
+ /**
62
+ * One analyst a game: one started on a game reviewed already, or that another analyst has taken, stops, and the chat goes over that review (Stefano); the
63
+ * analyst that took it may call again, its review half written. The game is taken at once, before the long reading of it, and read again: of two analysts
64
+ * started together in two processes, as Codex runs them, one goes on (Federico).
65
+ */
66
+ function oneAnalyst(context, studied) {
67
+ const { name } = studied;
68
+ const holder = name === undefined ? undefined : analystOf(context, name);
69
+ if (holder !== undefined && holder === context.agentRun) {
70
+ return;
71
+ }
72
+ if (isReviewed(studied.record)) {
73
+ throw new Error("Stop now: this game has its review already. End with one line for the AI that started you: go over that review with the player.");
74
+ }
75
+ if (holder !== undefined) {
76
+ throw new Error(anotherAnalyst);
77
+ }
78
+ if (name !== undefined && context.agentRun !== undefined) {
79
+ const folder = gameFolder(context.home());
80
+ startAnalysis(folder, name, context.now(), { analyst: { game: name, run: context.agentRun, since: context.now().toISOString() } });
81
+ if (gameInAnalysis(folder)?.analyst?.run !== context.agentRun) {
82
+ throw new Error(anotherAnalyst);
83
+ }
84
+ }
85
+ }
59
86
  /** A game the player brings as a PGN, checked move by move and kept to study with its clock and Chesspal's own review: no other comment, no engine's evaluation (D-6). */
60
87
  function importedGame(context, pgn) {
61
88
  noGameGoingOn(context);
@@ -146,7 +173,7 @@ function movesOf(record, ai) {
146
173
  let inBook = record.tags.FEN === undefined && bookReaches(game.position.fen);
147
174
  let before = game.facts;
148
175
  let balance = materialOf(game.position.fen).balance;
149
- const start = { atRisk: riskLine(before), mates: matesOf(before), tactics: tacticsText(before, game.possibilities, true) };
176
+ const start = { atRisk: riskLine(before), oneLegal: oneLegalMove(before), mates: matesOf(before), tactics: tacticsText(before, game.possibilities, true) };
150
177
  // What was at risk on the line before: a move tells it only when it changed.
151
178
  let risk = start.atRisk;
152
179
  const moves = record.moves.map(({ san, comment }, ply) => {
@@ -170,6 +197,7 @@ function movesOf(record, ai) {
170
197
  left: clock,
171
198
  material: now === balance ? undefined : balanceText(now),
172
199
  atRisk: over || risks === risk ? undefined : (risks ?? "none"),
200
+ oneLegal: over ? undefined : oneLegalMove(facts),
173
201
  mates: over ? undefined : matesOf(facts),
174
202
  tactics: over ? undefined : tacticsText(facts, game.possibilities, true),
175
203
  outOfBook: outOfBook ? true : undefined,
@@ -216,6 +244,7 @@ function moveLine(move) {
216
244
  move.spent === undefined ? left : `spent ${clockText(move.spent)}, ${left}`,
217
245
  move.material === undefined ? undefined : `material ${move.material}`,
218
246
  move.atRisk === undefined ? undefined : `at risk: ${move.atRisk}`,
247
+ move.oneLegal === undefined ? undefined : `one legal move: ${move.oneLegal}`,
219
248
  move.mates === undefined ? undefined : `mate in one: ${move.mates.join(" ")}`,
220
249
  move.tactics === undefined ? undefined : `tactics: ${move.tactics}`,
221
250
  move.outOfBook ? "out of book" : undefined,
@@ -238,14 +267,18 @@ function analystInstructions(context) {
238
267
  return undefined;
239
268
  }
240
269
  context.told.add(key);
241
- analystText ??= `${instructions("analyst")}\n${instructions("analysis")}`;
242
- return analystText;
270
+ return `${instructionsFile("analyst")}\n${instructionsFile("analysis")}`;
243
271
  }
244
272
  /** Who is ahead after a move, as the AI estimated it with annotate_game or the coach during the game (D-90). */
245
273
  function estimateOf(comment) {
246
274
  const lead = leadOf(comment);
247
275
  return lead === undefined ? undefined : leadText(lead);
248
276
  }
277
+ /** The only legal move of the side to move, when it has one alone (D-103). */
278
+ function oneLegalMove(facts) {
279
+ const moves = facts.pieces.flatMap((piece) => piece.moves ?? []);
280
+ return moves.length === 1 ? moves[0] : undefined;
281
+ }
249
282
  /** The mates in one the side to move has; none when it has none. */
250
283
  function matesOf(facts) {
251
284
  const mates = facts.checks.filter((check) => check.endsWith("#"));
@@ -12,6 +12,8 @@ interface Annotated {
12
12
  unestimated: string[];
13
13
  /** The moves whose note the chat corrected with the player, told the analyst at each call: its notes leave them as they are (D-101). */
14
14
  left: string[];
15
+ /** The chat corrected a note of a review that already has its word on the game. */
16
+ summaryStands: boolean;
15
17
  }
16
18
  /** The AI's badges and notes on a game over, and its word on the whole game, written into the game's PGN (D-69). */
17
19
  export declare const annotateGame: import("./registry.ts").Command<z.ZodObject<{
@@ -1,13 +1,13 @@
1
1
  // The AI's review of a game over (D-69): a badge on each move that matters, as chess.com marks them, with a note when it needs one, and its word on the
2
2
  // whole game, kept in the game's PGN for the board page. The judgment is the AI's alone: Chesspal gives no criteria (D-6, D-43).
3
3
  import { z } from "zod";
4
- import { annotatorOf, badges, Game, leadOf, reviewed, reviewOf, withAnnotator, withGameReview, withLead } from "../core/index.js";
4
+ import { annotatorOf, badges, Game, gameReviewOf, leadOf, reviewed, reviewOf, withAnnotator, withGameReview, withLead } from "../core/index.js";
5
5
  import { gameInAnalysis, rewriteGame, startAnalysis } from "../games/index.js";
6
6
  import { analystAgent } from "../host-files/index.js";
7
7
  import { gameFolder } from "../profiles/index.js";
8
- import { analystRuns } from "./agents.js";
8
+ import { analystOf, analystRuns, anotherAnalyst } from "./agents.js";
9
9
  import { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.js";
10
- import { leadFrom, leadInput } from "./lead-input.js";
10
+ import { leadFrom } from "./lead-input.js";
11
11
  import { defineCommand } from "./registry.js";
12
12
  import { moveIndex, moveNumbers, namedGame, noGameGoingOn } from "./study.js";
13
13
  const summaryLimit = 1500;
@@ -31,9 +31,10 @@ export const annotateGame = defineCommand({
31
31
  arrows: arrowsInput,
32
32
  squares: squaresInput,
33
33
  })).optional().describe("the moves that matter, each with its badge, its note, or both"),
34
- ahead: z.record(z.string(), leadInput).optional().describe("who is ahead after each move, by the move as analyse_game numbers it: pawns from White's side as chess.com shows it, 1.5 for White, -3 for Black, 0 for "
35
- + 'even, or a mate you see in moves of the side that mates, "#3" for White\'s, "#-3" for Black\'s; every move, in one call or a few; a move given again '
36
- + "takes its new figure"),
34
+ // Checked in run, which leaves out a figure after the move that ended the game on the board.
35
+ ahead: z.record(z.string(), z.union([z.number(), z.string()])).optional().describe("who is ahead after each move, by the move as analyse_game numbers it: pawns from White's side as chess.com shows it, 1.5 for White, -3 for Black, 0 for "
36
+ + 'even, or a mate you see in moves of the side that mates, "#3" for White\'s, "#-3" for Black\'s; every move but the last when it mates or draws '
37
+ + "on the board, which says it, in one call or a few; a move given again takes its new figure"),
37
38
  summary: z.string().max(summaryLimit).optional().describe("your word on the whole game: a short paragraph, with a few points as lines starting with - if it helps; the moves have their own notes"),
38
39
  closing: z.string().max(closingLimit).optional().describe("your last word on how the game ended, its win or loss, in a sentence or two: the page shows it at the end"),
39
40
  }),
@@ -49,14 +50,21 @@ export const annotateGame = defineCommand({
49
50
  // would be lost (Federico).
50
51
  const caller = await context.caller?.();
51
52
  const studied = game === undefined ? gameAnalysed(context) : namedGame(context, game);
53
+ // An analyst whose game another has taken since, the review started again, writes no more (Priya).
54
+ const holder = context.agent === analystAgent ? analystOf(context, studied.name) : undefined;
55
+ if (holder !== undefined && holder !== context.agentRun) {
56
+ throw new Error(`${anotherAnalyst} Nothing changed.`);
57
+ }
52
58
  const folder = gameFolder(context.home());
53
59
  const numbers = moveNumbers(studied.record);
54
60
  // Where the analyst reviews the games, the chat's notes are corrections it made with the player: the analyst, at work then or later, leaves those notes
55
61
  // and is told of them at each call, and the chat is not asked for the analyst's work (D-101, Priya). Only a call the host ties to the chat is its own.
56
- const correction = context.chat === true && analystRuns(context) && estimates.length === 0 && summary === undefined && closing === undefined;
62
+ const correction = context.chat === true && analystRuns(context) && notes.length > 0 && summary === undefined && closing === undefined;
57
63
  const analysis = gameInAnalysis(folder);
58
64
  const corrected = context.chat !== true && analysis?.game === studied.name ? analysis.corrected ?? [] : [];
59
65
  let skipped = 0;
66
+ // The moves noted, as analyse_game numbers them: the chat's corrections.
67
+ const noted = [];
60
68
  // Every move is checked before the game is written: all or nothing.
61
69
  const moves = [...studied.record.moves];
62
70
  for (const { move, badge, note, arrows = [], squares = [] } of notes) {
@@ -64,6 +72,7 @@ export const annotateGame = defineCommand({
64
72
  throw new Error(`Give ${move} a badge, a note, or both: arrows and squares go with them. Nothing changed.`);
65
73
  }
66
74
  const index = indexIn(studied.record, move, "notes");
75
+ noted.push(numbers[index]);
67
76
  if (corrected.includes(numbers[index])) {
68
77
  skipped += 1;
69
78
  continue;
@@ -72,15 +81,17 @@ export const annotateGame = defineCommand({
72
81
  const kept = badge ?? (reviewOf(moves[index].comment)?.badge === "book" ? "book" : undefined);
73
82
  moves[index] = reviewed(moves[index], { ...(kept === undefined ? {} : { badge: kept }), ...(note === undefined ? {} : { note }), ...drawingsOf(arrows, squares) });
74
83
  }
75
- for (const [move, lead] of estimates) {
76
- const index = indexIn(studied.record, move, "ahead");
77
- moves[index] = withLead(moves[index], leadFrom(lead));
84
+ // After the move that ended the game on the board, which says it, a figure is left out rather than refused, "#0" too (Priya).
85
+ const over = new Game(studied.record).outcome.over;
86
+ const leads = estimates.map(([move, lead]) => ({ index: indexIn(studied.record, move, "ahead"), move, lead })).filter(({ index }) => !over || index < moves.length - 1);
87
+ for (const { index, move, lead } of leads) {
88
+ moves[index] = withLead(moves[index], leadAfter(move, lead));
78
89
  }
79
90
  // Who reviewed it, as far as the host says it for certain (D-81): on the command line, or by a host that names no model over one that did, as it was.
80
91
  const kept = caller === undefined || (caller.hostOnly === true && annotatorOf(studied.record) !== undefined);
81
92
  const marked = { ...(kept ? studied.record : withAnnotator(studied.record, caller)), moves };
82
93
  rewriteGame(folder, studied.name, summary === undefined && closing === undefined ? marked : withGameReview(marked, { ...(summary === undefined ? {} : { summary }), ...(closing === undefined ? {} : { closing }) }));
83
- startAnalysis(folder, studied.name, context.now(), false, correction ? notes.map(({ move }) => numbers[indexIn(studied.record, move, "notes")]) : []);
94
+ startAnalysis(folder, studied.name, context.now(), { corrected: correction ? noted : [] });
84
95
  const bookUnnoted = moves.flatMap((move, index) => {
85
96
  const review = reviewOf(move.comment);
86
97
  return review?.badge === "book" && review.note === undefined ? [numbers[index]] : [];
@@ -91,13 +102,15 @@ export const annotateGame = defineCommand({
91
102
  summary: summary !== undefined,
92
103
  closing: closing !== undefined,
93
104
  bookUnnoted: correction ? [] : bookUnnoted,
94
- estimated: estimates.length,
105
+ estimated: leads.length,
95
106
  // Asked every time, of an older review too: an analyst whose instructions predate the estimates learns of them here.
96
- unestimated: correction ? [] : unestimatedMoves(marked, numbers),
107
+ unestimated: correction ? [] : unestimatedMoves(marked, numbers, over),
97
108
  left: [...corrected],
109
+ // A correction after the review's word on the game: that word may now say otherwise (Priya).
110
+ summaryStands: correction && gameReviewOf(studied.record) !== undefined,
98
111
  };
99
112
  },
100
- format: ({ game, marked, summary, closing, bookUnnoted, estimated, unestimated, left }) => {
113
+ format: ({ game, marked, summary, closing, bookUnnoted, estimated, unestimated, left, summaryStands }) => {
101
114
  const kept = [
102
115
  marked === 0 ? undefined : `${marked} ${marked === 1 ? "move" : "moves"} marked`,
103
116
  estimated === 0 ? undefined : `who is ahead after ${estimated} ${estimated === 1 ? "move" : "moves"}`,
@@ -118,13 +131,22 @@ export const annotateGame = defineCommand({
118
131
  ? []
119
132
  : [`${left.join(" ")} ${one ? "keeps the note" : "keep the notes"} the AI wrote with the player: leave ${one ? "it" : "them"}, and don't contradict ${one ? "it" : "them"} in your summary${summary ? "; if this one does, give it again" : ""}.`];
120
133
  const head = kept.length === 0 ? `Nothing new kept in ${game}.` : `Kept in ${game}: ${[kept.slice(0, -1).join(", "), kept.at(-1)].filter((part) => part !== "").join(" and ")}. The board page shows them.`;
121
- return [head, ...corrected, ...book, ...ahead].join("\n");
134
+ const stands = summaryStands ? ["The review's word on the game stays as written: if it says otherwise, give summary again."] : [];
135
+ return [head, ...stands, ...corrected, ...book, ...ahead].join("\n");
122
136
  },
123
137
  });
124
138
  /** The moves without who is ahead after them, as analyse_game numbers them: all but the last when the board ended the game there, which says it. */
125
- function unestimatedMoves(record, numbers) {
126
- const ended = new Game(record).outcome.over;
127
- return record.moves.flatMap((move, index) => leadOf(move.comment) !== undefined || (ended && index === record.moves.length - 1) ? [] : [numbers[index]]);
139
+ function unestimatedMoves(record, numbers, over) {
140
+ return record.moves.flatMap((move, index) => leadOf(move.comment) !== undefined || (over && index === record.moves.length - 1) ? [] : [numbers[index]]);
141
+ }
142
+ /** Who is ahead after a move given in ahead, refused with the move it was given for. */
143
+ function leadAfter(move, value) {
144
+ try {
145
+ return leadFrom(value);
146
+ }
147
+ catch (error) {
148
+ throw new Error(`In ahead, after ${move}, ${error.message}`);
149
+ }
128
150
  }
129
151
  /** A move given in notes or in ahead, as analyse_game numbers it, refused with where it was given (Priya). */
130
152
  function indexIn(record, move, field) {
@@ -12,6 +12,8 @@ export declare function lastMoveText({ move, from, to }: LastMove): string;
12
12
  * (Priya, test of D-86): 4.Ng5 is 4.♞g5.
13
13
  */
14
14
  export declare function shownMove(move: string): string;
15
+ /** Numbered moves as the player is shown them, with the pieces' figurines (D-86): 4...g6 5.Nxe5 Bxd1 is 4...g6 5.♞xe5 ♝xd1. */
16
+ export declare function shownMoves(moves: string): string;
15
17
  /** The lines after a move, which the line before named: its note, where the game stands, a warning. closingLines end them. */
16
18
  export declare function moveLines({ note, game, warning }: MovePlayed): string[];
17
19
  /** The last lines of a tool's answer: the player's words from the board page, then the next step, which stays the last line. */
@@ -4,7 +4,7 @@ import { talkLimits } from "../games/index.js";
4
4
  import { onQuickModel } from "../host-files/index.js";
5
5
  import { analystLine, reviewedLine } from "./agents.js";
6
6
  import { clockLine } from "./clock.js";
7
- import { endText } from "./game-summary.js";
7
+ import { endText, unplayed } from "./game-summary.js";
8
8
  import { boardView, colourName, compactView, materialText } from "./view.js";
9
9
  /** Where the game stands: the AI's position to decide on, with its last move unless the lines before named it; the player's turn; or how it ended. */
10
10
  export function positionLines(game, withLastMove = true) {
@@ -63,6 +63,10 @@ export function shownMove(move) {
63
63
  const [, number = "", san = move] = /^(\d+\.+)(.+)$/.exec(move) ?? [];
64
64
  return `${number}${figurinesOf(san)}`;
65
65
  }
66
+ /** Numbered moves as the player is shown them, with the pieces' figurines (D-86): 4...g6 5.Nxe5 Bxd1 is 4...g6 5.♞xe5 ♝xd1. */
67
+ export function shownMoves(moves) {
68
+ return moves.split(" ").map(shownMove).join(" ");
69
+ }
66
70
  /** The lines after a move, which the line before named: its note, where the game stands, a warning. closingLines end them. */
67
71
  export function moveLines({ note, game, warning }) {
68
72
  return [...(note === undefined ? [] : [`Note: ${note}`]), ...positionLines(game, false), ...(warning === undefined ? [] : [warning])];
@@ -76,7 +80,7 @@ export function closingLines(game, words = [], pageClosed = false) {
76
80
  * theirs reads as one of Chesspal's.
77
81
  */
78
82
  export function wordLines(words) {
79
- return words.map(({ text, at }) => `Player wrote on the board page${at === undefined ? "" : `, at ${at}`}: «${text.replace(/\s+/g, " ")}»`);
83
+ return words.map(({ text, place }) => `Player wrote on the board page${place === undefined ? "" : `, at ${place}`}: «${text.replace(/\s+/g, " ")}»`);
80
84
  }
81
85
  /**
82
86
  * What the AI does next: the one line every host reads, even where the server's instructions are not shown. Words of the player's ask for an answer. With the
@@ -85,13 +89,19 @@ export function wordLines(words) {
85
89
  export function nextStep(game, words = [], pageClosed = false) {
86
90
  const step = pageClosed ? closedPageGameStep : newGameStep;
87
91
  // Words written on a game in analysis: the chat answers them there, and corrects the review where they show it wrong (D-101).
88
- if (words.some((word) => word.at !== undefined) && (game === undefined || game.end !== undefined)) {
92
+ if (words.some((word) => word.place !== undefined) && (game === undefined || game.end !== undefined)) {
89
93
  return "Next: answer the player with ai_say, about the move they were looking at. When they show you something the review missed, first correct that move's "
90
94
  + "note with annotate_game, that move only.";
91
95
  }
92
96
  if (game === undefined) {
93
97
  return words.length === 0 ? `Next: when the player wants a game, ${step}` : `Next: answer the player with ai_say. When they want a game, ${step}`;
94
98
  }
99
+ if (game.end !== undefined && unplayed(game)) {
100
+ return words.length === 0
101
+ ? "Next: tell the player how the game ended, with ai_say and how you feel as its mood if they play on the board page; then offer them, in words, another "
102
+ + `game. For another, ${step}`
103
+ : `Next: answer the player with ai_say. When they want another game, ${step}`;
104
+ }
95
105
  if (game.end !== undefined) {
96
106
  const analysis = game.end.analyst === "offer"
97
107
  ? ` To go over it, on their yes, ${analystLine(game.end.archive)}`
@@ -10,6 +10,7 @@ import { aheadWords, leadFrom, leadInput } from "./lead-input.js";
10
10
  import { knownMoves, openingLine } from "./opening-book.js";
11
11
  import { waitForPlayer, wordsOf } from "./ponder.js";
12
12
  import { profileLines } from "./position.js";
13
+ import { ratingsText } from "./profiles.js";
13
14
  import { defineCommand } from "./registry.js";
14
15
  import { speechLine, speechOf } from "./speech.js";
15
16
  import { colourName, compactView, materialText, riskLine } from "./view.js";
@@ -237,8 +238,9 @@ function nowLines(current, folder, player) {
237
238
  }
238
239
  /** How the player says they play, and their rating, when their profile says so. */
239
240
  function playerLines(context) {
240
- const { level, rating } = activeProfile(context.home());
241
- const parts = [level === undefined ? undefined : `plays at the ${level} level`, rating === undefined ? undefined : `is rated ${rating.value} on ${rating.site}`];
241
+ const profile = activeProfile(context.home());
242
+ const ratings = ratingsText(profile);
243
+ const parts = [profile.level === undefined ? undefined : `plays at the ${profile.level} level`, ratings === undefined ? undefined : `is rated ${ratings}`];
242
244
  const said = parts.filter((part) => part !== undefined);
243
245
  return said.length === 0 ? [] : [`The player ${said.join(" and ")}.`];
244
246
  }
@@ -20,6 +20,8 @@ export interface Context {
20
20
  pageOpen?: () => boolean;
21
21
  /** Opens a folder in the system's file manager: the data folder, from the board page; whether it could; none in the tests. */
22
22
  openFolder?: (folder: string) => Promise<boolean>;
23
+ /** Asks the sites the player plays on for their ratings (D-105): only the page and the command line, never a tool of the AI's (D-106); none in the tests. */
24
+ fetch?: typeof globalThis.fetch;
23
25
  /** Opens the New game window on the board pages open now, where the player chooses the game: whether one was open. */
24
26
  askNewGame?: () => boolean;
25
27
  /** The AI host, as it names itself to the MCP server: claude-code, which notes how each chat starts (D-57); none on the command line. */
@@ -29,7 +31,9 @@ export interface Context {
29
31
  /** Told when the board page has news that no file holds: words the AI was given, which it shows read (D-53), and the AI's model (D-82). */
30
32
  heard?: () => void;
31
33
  /** Told when the chat calls a tool, before it runs: the board page hears the chat listens, and the AI's clock, held while nobody did, starts. */
32
- calls?: () => void;
34
+ calls?: () => void | Promise<void>;
35
+ /** Told when a tool of the chat's has ended: the table the chat is attached to gets a copy of what it saw, else the chat looks for one (D-104). */
36
+ toolEnded?: () => void;
33
37
  /** The game the chat saw when its last tool ended, its folder and its stamp: the doorbell wakes it only for a game changed since. */
34
38
  seen?: string | undefined;
35
39
  /**
@@ -43,6 +47,8 @@ export interface Context {
43
47
  caller?: () => Promise<Caller | undefined>;
44
48
  /** The agent that makes this call of a tool it shares with the chat, as the analyst (D-89); none for the chat, or when it cannot be told. */
45
49
  agent?: string;
50
+ /** Which run of that agent makes the call, as the host tells it: two analysts started on one game are two runs. */
51
+ agentRun?: string;
46
52
  /** The chat makes this call of a tool it shares with an agent, as the host tells it: a caller that cannot be told is not the chat here (Federico). */
47
53
  chat?: true;
48
54
  }
@@ -63,5 +69,7 @@ export interface SeenPosition {
63
69
  }
64
70
  /** The context of a process started in the player's folder: the data folder is found at the first command that needs it, once. */
65
71
  export declare function defaultContext(): Context;
72
+ /** Whether the player's language is Italian: the command line speaks Italian to them then, English otherwise (D-32). */
73
+ export declare function speaksItalian(language: string | undefined): boolean;
66
74
  /** A data folder with a profile in it, the first made when there is none, named after the system's account (D-71). */
67
75
  export declare function withProfile(folder: string, account: string): string;
@@ -15,8 +15,13 @@ export function defaultContext() {
15
15
  language: systemLanguage(),
16
16
  given: new Set(),
17
17
  openFolder: (folder) => openFolder(folder, process.platform),
18
+ fetch: globalThis.fetch,
18
19
  };
19
20
  }
21
+ /** Whether the player's language is Italian: the command line speaks Italian to them then, English otherwise (D-32). */
22
+ export function speaksItalian(language) {
23
+ return language?.slice(0, 2).toLowerCase() === "it";
24
+ }
20
25
  /** A data folder with a profile in it, the first made when there is none, named after the system's account (D-71). */
21
26
  export function withProfile(folder, account) {
22
27
  ensureProfile(folder, account);
@@ -2,7 +2,7 @@
2
2
  // delete, never the AI.
3
3
  import { z } from "zod";
4
4
  import { isReviewed, unreviewed } from "../core/index.js";
5
- import { endAnalysis, forgetCoach, gameInAnalysis, isOverOnTable, rewriteGame, startAnalysis } from "../games/index.js";
5
+ import { forgetAnalyst, forgetCoach, gameInAnalysis, isOverOnTable, rewriteGame, startAnalysis } from "../games/index.js";
6
6
  import { gameFolder } from "../profiles/index.js";
7
7
  import { defineCommand } from "./registry.js";
8
8
  import { namedGame } from "./study.js";
@@ -24,10 +24,14 @@ export const deleteReview = defineCommand({
24
24
  if (isOverOnTable(folder, name)) {
25
25
  forgetCoach(folder);
26
26
  }
27
- // The page showing the game hears of it, and shows it without its review; the chat's corrections go with it.
28
- if (gameInAnalysis(folder)?.game === name) {
29
- endAnalysis(folder);
30
- startAnalysis(folder, name, context.now());
27
+ // The page showing the game hears of it, and shows it without its review; the chat's corrections and the analyst that took it go with it, the
28
+ // analyst also while the page shows another game.
29
+ const analysis = gameInAnalysis(folder);
30
+ if (analysis?.game === name) {
31
+ startAnalysis(folder, name, context.now(), { afresh: true });
32
+ }
33
+ else if (analysis?.analyst?.game === name) {
34
+ forgetAnalyst(folder);
31
35
  }
32
36
  return { game: name, reviewed: isReviewed(record) };
33
37
  },
@@ -74,6 +74,8 @@ export declare function summaryOf(current: CurrentGame, now: Date, archive?: str
74
74
  export declare function withThought(summary: GameSummary, folder: string, current: CurrentGame, coached: boolean): GameSummary;
75
75
  /** The current game `now`, refused when there is none or when it is over, on time too. */
76
76
  export declare function gameGoingOn(folder: string, now: Date): CurrentGame;
77
+ /** Whether a game has no move yet, or was closed before any: nothing to go over. */
78
+ export declare function unplayed(summary: GameSummary): boolean;
77
79
  /** How a game ended, in words: its result, and how it came when known. */
78
80
  export declare function endText({ result, termination }: {
79
81
  result: string;
@@ -62,6 +62,10 @@ export function gameGoingOn(folder, now) {
62
62
  }
63
63
  return current;
64
64
  }
65
+ /** Whether a game has no move yet, or was closed before any: nothing to go over. */
66
+ export function unplayed(summary) {
67
+ return summary.lastMoves === "";
68
+ }
65
69
  /** How a game ended, in words: its result, and how it came when known. */
66
70
  export function endText({ result, termination }) {
67
71
  return termination === undefined ? result : `${result}, ${termination}`;
@@ -2,7 +2,7 @@ import type { AnyCommand } from "./registry.ts";
2
2
  export { aiMove } from "./ai-move.ts";
3
3
  export { analyseGame } from "./analyse-game.ts";
4
4
  export { gameAt } from "./clock.ts";
5
- export { type Caller, type Context, defaultContext, type SeenPosition } from "./context.ts";
5
+ export { type Caller, type Context, defaultContext, type SeenPosition, speaksItalian } from "./context.ts";
6
6
  export { deleteGame } from "./delete-game.ts";
7
7
  export { deleteReview } from "./delete-review.ts";
8
8
  export { answerDraw, DrawRefused, offerDraw } from "./draw-offer.ts";
@@ -10,7 +10,8 @@ export { endGame } from "./end-game.ts";
10
10
  export { ChatNotClear, newGame, startNewGame } from "./new-game.ts";
11
11
  export { bookOpening, bookPlies } from "./opening-book.ts";
12
12
  export { playMove, PositionChanged, positionIn, samePosition } from "./play-move.ts";
13
- export { chooseDrawnAvatar, GameGoingOn, newProfile, setAccounts, useProfile } from "./profiles.ts";
13
+ export { chooseDrawnAvatar, editProfile, GameGoingOn, newProfile, useProfile } from "./profiles.ts";
14
+ export { ratingsAge, refreshRatings } from "./ratings.ts";
14
15
  export { type AnyCommand, baseType, type Command, commandLineFieldProblem, inputProblems, isBoolean, isList, takesNumbers } from "./registry.ts";
15
16
  export { say } from "./say.ts";
16
17
  export { gameToStudy, namedGame, shownPosition } from "./study.ts";
@@ -9,6 +9,7 @@ import { deleteReview } from "./delete-review.js";
9
9
  import { endGame } from "./end-game.js";
10
10
  import { exportGame } from "./export-game.js";
11
11
  import { init } from "./init.js";
12
+ import { instructions } from "./instructions.js";
12
13
  import { look } from "./look.js";
13
14
  import { newGame } from "./new-game.js";
14
15
  import { pastGames } from "./past-games.js";
@@ -16,14 +17,14 @@ import { playerMove } from "./player-move.js";
16
17
  import { playerSay } from "./player-say.js";
17
18
  import { ponder } from "./ponder.js";
18
19
  import { position } from "./position.js";
19
- import { newProfile, profiles, setAccounts, setAvatar, useProfile } from "./profiles.js";
20
+ import { editProfile, newProfile, profiles, setAvatar, useProfile } from "./profiles.js";
20
21
  import { status } from "./status.js";
21
22
  import { studyGames } from "./study-games.js";
22
23
  // What the server and the command line use besides the list: the context, the reading of an input, and the operations the page shares with the tools.
23
24
  export { aiMove } from "./ai-move.js";
24
25
  export { analyseGame } from "./analyse-game.js";
25
26
  export { gameAt } from "./clock.js";
26
- export { defaultContext } from "./context.js";
27
+ export { defaultContext, speaksItalian } from "./context.js";
27
28
  export { deleteGame } from "./delete-game.js";
28
29
  export { deleteReview } from "./delete-review.js";
29
30
  export { answerDraw, DrawRefused, offerDraw } from "./draw-offer.js";
@@ -31,13 +32,15 @@ export { endGame } from "./end-game.js";
31
32
  export { ChatNotClear, newGame, startNewGame } from "./new-game.js";
32
33
  export { bookOpening, bookPlies } from "./opening-book.js";
33
34
  export { playMove, PositionChanged, positionIn, samePosition } from "./play-move.js";
34
- export { chooseDrawnAvatar, GameGoingOn, newProfile, setAccounts, useProfile } from "./profiles.js";
35
+ export { chooseDrawnAvatar, editProfile, GameGoingOn, newProfile, useProfile } from "./profiles.js";
36
+ export { ratingsAge, refreshRatings } from "./ratings.js";
35
37
  export { baseType, commandLineFieldProblem, inputProblems, isBoolean, isList, takesNumbers } from "./registry.js";
36
38
  export { say } from "./say.js";
37
39
  export { gameToStudy, namedGame, shownPosition } from "./study.js";
38
40
  /** Every command of Chesspal, in the order the command line lists them and the MCP server offers them. */
39
41
  export const commands = [
40
42
  init,
43
+ instructions,
41
44
  status,
42
45
  newGame,
43
46
  playerMove,
@@ -60,5 +63,5 @@ export const commands = [
60
63
  newProfile,
61
64
  useProfile,
62
65
  setAvatar,
63
- setAccounts,
66
+ editProfile,
64
67
  ];
@@ -4,6 +4,7 @@ import { relative, resolve } from "node:path";
4
4
  import { styleText } from "node:util";
5
5
  import { z } from "zod";
6
6
  import { codexHooksPath, setUpHosts } from "../host-files/index.js";
7
+ import { speaksItalian } from "./context.js";
7
8
  import { defineCommand } from "./registry.js";
8
9
  /** Makes a folder the player's chess table: the files through which Claude Code and Codex find Chesspal and the opponent's instructions (D-31). */
9
10
  export const init = defineCommand({
@@ -28,7 +29,7 @@ export const init = defineCommand({
28
29
  };
29
30
  },
30
31
  format: ({ folder, written, enter, hooksChanged }, context) => {
31
- const words = wordsFor(context?.language);
32
+ const words = speaksItalian(context?.language) ? italian : english;
32
33
  const steps = [words.open(enter === undefined ? undefined : quoted(enter)), ...words.steps];
33
34
  return [
34
35
  styleText(["bold", "green"], `♜ ${written.length === 0 ? words.ready(folder) : words.set(folder)}`),
@@ -64,9 +65,6 @@ const italian = {
64
65
  hooksChanged: "Gli «hook» di Chesspal per Codex sono cambiati: quando Codex te lo chiede, scegli di nuovo Trust all.",
65
66
  updated: (count) => count === 1 ? "Aggiornato 1 file." : `Aggiornati ${count} file.`,
66
67
  };
67
- function wordsFor(language) {
68
- return language?.slice(0, 2).toLowerCase() === "it" ? italian : english;
69
- }
70
68
  /** A path as a shell reads it as one word: in quotes when it has spaces. */
71
69
  function quoted(path) {
72
70
  return /\s/.test(path) ? `"${path}"` : path;
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ import type { Context } from "./context.ts";
3
+ /** How to play as the player's opponent, which the chat reads before its first reply and again after a summary (D-102). */
4
+ export declare const instructions: import("./registry.ts").Command<z.ZodObject<{}, z.core.$strip>, string>;
5
+ /**
6
+ * Whether the chat has read the opponent's instructions since it started, or was cleared or summarized, as its start keys them (D-57); without the host's
7
+ * note of it, since the MCP server started. Always on the command line, which keeps nothing.
8
+ */
9
+ export declare function instructionsRead(context: Context, chatStart: string | undefined): boolean;
@@ -0,0 +1,30 @@
1
+ // The opponent's instructions, which the MCP server gives so that they follow the version installed (D-102).
2
+ import { z } from "zod";
3
+ import { lastChatStart } from "../games/index.js";
4
+ import { opponentInstructions } from "../host-files/index.js";
5
+ import { defineCommand } from "./registry.js";
6
+ /** How to play as the player's opponent, which the chat reads before its first reply and again after a summary (D-102). */
7
+ export const instructions = defineCommand({
8
+ name: "instructions",
9
+ description: "How to play as the player's opponent at this table: the tools, the board page, how to talk. Call it before your first reply in every chat, "
10
+ + "and again after the conversation is summarized; then call `status`.",
11
+ input: z.object({}),
12
+ readOnly: true,
13
+ idempotent: true,
14
+ alwaysLoad: true,
15
+ run: (input, context) => {
16
+ context.told?.add(readKey(lastChatStart(context.folder)?.at));
17
+ return opponentInstructions();
18
+ },
19
+ format: (text) => text,
20
+ });
21
+ /**
22
+ * Whether the chat has read the opponent's instructions since it started, or was cleared or summarized, as its start keys them (D-57); without the host's
23
+ * note of it, since the MCP server started. Always on the command line, which keeps nothing.
24
+ */
25
+ export function instructionsRead(context, chatStart) {
26
+ return context.told === undefined || context.told.has(readKey(chatStart));
27
+ }
28
+ function readKey(chatStart) {
29
+ return `instructions ${chatStart ?? ""}`;
30
+ }
@@ -4,5 +4,5 @@ import { type Lead } from "../core/index.ts";
4
4
  export declare const leadInput: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
5
5
  /** Who is ahead in words, the side and by how much, so that the AI reads its own sign back (Anatolij). */
6
6
  export declare function aheadWords(lead: Lead): string;
7
- /** The lead an input gave, checked by leadInput. */
7
+ /** The lead an input gave, refused with how to write it when it is none. */
8
8
  export declare function leadFrom(value: number | string): Lead;