chesspal 0.3.4 → 0.3.6

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 (104) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/cli.js +10 -2
  3. package/dist/cli/command-line.js +17 -9
  4. package/dist/cli/icon.d.ts +2 -0
  5. package/dist/cli/icon.js +11 -1
  6. package/dist/commands/analysis/analyse.d.ts +2 -2
  7. package/dist/commands/analysis/analyse.js +3 -3
  8. package/dist/commands/answers/agents.d.ts +10 -2
  9. package/dist/commands/answers/agents.js +30 -7
  10. package/dist/commands/answers/index.d.ts +1 -1
  11. package/dist/commands/answers/index.js +1 -1
  12. package/dist/commands/answers/lines.js +3 -2
  13. package/dist/commands/answers/summary.d.ts +2 -1
  14. package/dist/commands/answers/view.d.ts +1 -1
  15. package/dist/commands/answers/view.js +2 -2
  16. package/dist/commands/game/look.d.ts +7 -3
  17. package/dist/commands/game/look.js +9 -4
  18. package/dist/commands/game/new.js +1 -1
  19. package/dist/commands/game/ponder.js +3 -3
  20. package/dist/commands/game/position.d.ts +2 -1
  21. package/dist/commands/game/position.js +9 -4
  22. package/dist/commands/game/status.d.ts +3 -1
  23. package/dist/commands/game/status.js +19 -9
  24. package/dist/commands/profiles/past-games.d.ts +7 -2
  25. package/dist/commands/profiles/past-games.js +26 -10
  26. package/dist/commands/talk/ai-say.js +1 -1
  27. package/dist/games/analysis.d.ts +7 -4
  28. package/dist/games/analysis.js +5 -3
  29. package/dist/games/index.d.ts +1 -1
  30. package/dist/games/index.js +1 -1
  31. package/dist/games/thoughts.d.ts +12 -4
  32. package/dist/games/thoughts.js +27 -10
  33. package/dist/home/index.d.ts +1 -0
  34. package/dist/home/index.js +1 -0
  35. package/dist/home/same-path.d.ts +2 -0
  36. package/dist/home/same-path.js +5 -0
  37. package/dist/host-files/host-files.js +8 -3
  38. package/dist/page/app/anchor.js +15 -11
  39. package/dist/page/app/app.js +30 -17
  40. package/dist/page/app/served.js +23 -36
  41. package/dist/page/app/watch.js +97 -0
  42. package/dist/page/avatars/torretta.css +108 -0
  43. package/dist/page/board/board.css +7 -6
  44. package/dist/page/board/marks.js +2 -2
  45. package/dist/page/board/pointer.js +2 -2
  46. package/dist/page/dialogs/analyses.js +9 -7
  47. package/dist/page/dialogs/rows.js +72 -0
  48. package/dist/page/dialogs/site-games.js +3 -1
  49. package/dist/page/dialogs/studied.js +11 -6
  50. package/dist/page/dialogs/study.js +2 -2
  51. package/dist/page/game/game.css +51 -10
  52. package/dist/page/game/move-list.js +7 -5
  53. package/dist/page/guide/en.md +1 -1
  54. package/dist/page/guide/it.md +1 -1
  55. package/dist/page/helps/helps.css +5 -5
  56. package/dist/page/helps/helps.js +3 -181
  57. package/dist/page/helps/rows.js +16 -0
  58. package/dist/page/helps/strategy.js +105 -0
  59. package/dist/page/helps/tactics.js +69 -0
  60. package/dist/page/layout/header.js +49 -6
  61. package/dist/page/layout/layout.css +17 -2
  62. package/dist/page/layout/window.js +4 -4
  63. package/dist/page/lead/lead.js +41 -4
  64. package/dist/page/sounds/sounds.js +1 -1
  65. package/dist/page/style.css +1 -0
  66. package/dist/page/talk/talk.css +0 -109
  67. package/dist/page/text.js +10 -0
  68. package/dist/page/theme.css +13 -0
  69. package/dist/profiles/profiles.js +5 -1
  70. package/dist/server/bell/bell.d.ts +0 -2
  71. package/dist/server/bell/bell.js +17 -7
  72. package/dist/server/bell/index.d.ts +1 -1
  73. package/dist/server/bell/index.js +1 -1
  74. package/dist/server/page/chat.d.ts +5 -1
  75. package/dist/server/page/chat.js +5 -1
  76. package/dist/server/page/game.js +18 -3
  77. package/dist/server/page/table.d.ts +2 -1
  78. package/dist/server/page/table.js +4 -2
  79. package/dist/server/page/update.d.ts +1 -1
  80. package/dist/server/page/update.js +4 -4
  81. package/dist/server/table/hosts/chats.d.ts +5 -0
  82. package/dist/server/table/hosts/chats.js +11 -4
  83. package/dist/server/table/hosts/play.d.ts +3 -1
  84. package/dist/server/table/hosts/play.js +17 -5
  85. package/dist/server/table/icon/index.d.ts +2 -0
  86. package/dist/server/table/icon/index.js +16 -2
  87. package/dist/server/table/index.d.ts +1 -1
  88. package/dist/server/table/index.js +1 -1
  89. package/dist/server/table/link.js +3 -1
  90. package/dist/server/table/table.js +2 -2
  91. package/dist/sites/index.d.ts +1 -1
  92. package/dist/sites/index.js +1 -1
  93. package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
  94. package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
  95. package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
  96. package/dist/sites/{choice.js → ratings/choice.js} +1 -1
  97. package/dist/sites/ratings/index.d.ts +1 -0
  98. package/dist/sites/ratings/index.js +2 -0
  99. package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
  100. package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
  101. package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
  102. package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
  103. package/instructions/start.md +1 -1
  104. package/package.json +1 -1
package/README.md CHANGED
@@ -87,6 +87,7 @@ Chesspal works with any agent that can use Chesspal's tools (MCP) and the files
87
87
  - **To play in a folder of your own**, as with the Claude desktop app, type `chesspal init chess`, then open your agent in the folder `chess` and say hello: the AI answers with the link to the board page. Any name will do; your games are kept [elsewhere](#where-your-games-are).
88
88
  - **A move written in the chat** counts too: `e4`, `Nf3`, in the international notation (K Q R B N in every language), or in words.
89
89
  - **To update**, when a new version is out the table's page shows an arrow at the top: press it between games, and the table installs it and starts again, with its chat. By hand: close the AI's terminal, run `npm install -g chesspal` again, then `chesspal`. In a folder of your own, run `chesspal init` there too: it updates its own lines and keeps what you wrote there. In Codex, allow the hooks again when it says so.
90
+ - **To uninstall**, run `chesspal icon --remove` first, which takes the icon off your desktop and menu, then `npm uninstall -g chesspal`. Your games stay [where they are](#where-your-games-are).
90
91
 
91
92
  ## Where your games are
92
93
 
package/dist/cli/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { runCommandLine } from "./command-line.js";
3
3
  const args = process.argv.slice(2);
4
- // The table's own words (D-104): alone or open, its page; table, icon, play <host>. The host's: mcp, its server; listen, its doorbell; chat, how the chat
4
+ // The table's own words (D-104): alone or open, its page; table, icon (--remove), play <host>. The host's: mcp, its server; listen, its doorbell; chat, how the chat
5
5
  // started. Everything else is a command.
6
6
  if (args.length === 0) {
7
7
  await openTableHere(true);
@@ -16,7 +16,15 @@ else if (args.length === 1 && args[0] === "table") {
16
16
  else if (args.length === 1 && args[0] === "icon") {
17
17
  await putIconHere();
18
18
  }
19
- else if (args.length === 2 && args[0] === "play" && !args[1].startsWith("-")) {
19
+ else if (args.length === 2 && args[0] === "icon" && args[1] === "--remove") {
20
+ const [{ defaultContext }, { iconsRemoved }] = await Promise.all([import("../commands/index.js"), import("./icon.js")]);
21
+ console.log(iconsRemoved(defaultContext()));
22
+ }
23
+ else if (args[0] === "play" && (args.length === 2 || (args.length === 4 && args[2] === "--home")) && !args[1].startsWith("-")) {
24
+ // The data folder of a table started with CHESSPAL_HOME, which a terminal the table opens may not pass on (Terminal.app): the chat and its server read it.
25
+ if (args[3] !== undefined) {
26
+ process.env.CHESSPAL_HOME = args[3];
27
+ }
20
28
  await playHere(args[1]);
21
29
  }
22
30
  else if (args.length === 1 && args[0] === "mcp") {
@@ -10,8 +10,11 @@ const listenDescription = "Wait for the player's move on the board page and wake
10
10
  const chatDescription = "Note how the chat started: new, cleared, resumed or compacted. Claude Code and Codex run it when a chat starts, as init sets them up.";
11
11
  const tableDescription = "Serve the board page with no chat, until no browser has had it open for five minutes. `chesspal` alone starts it apart and opens the page.";
12
12
  const openDescription = "Open the board page as `chesspal` alone does, without asking about the icon: what the icon runs.";
13
- const iconDescription = "Put Chesspal's icon on the desktop and in the menu: it opens the board page as `chesspal` alone does, with no terminal.";
14
- const playDescription = "Play at the table in Claude Code or Codex, in this terminal: what the page's buttons open, with the model and effort chosen there; New game on the page starts the chat again, clean.";
13
+ const iconDescription = "Put Chesspal's icon on the desktop and in the menu: it opens the board page as `chesspal` alone does, with no terminal. `chesspal icon --remove` takes it away.";
14
+ const playDescription = "Start the AI in this terminal, in Claude Code or Codex, as the page's buttons do. It plays with the model and effort chosen on the page; New game there starts the chat again, clean.";
15
+ // What init and the icon's removal are to a player, in the general help: the command's own help says the files.
16
+ const initForPlayer = "Set up a folder of your own to play in, from Claude Code, Codex or the Claude desktop app.";
17
+ const iconRemoveForPlayer = "Take Chesspal's icon away, before you uninstall it.";
15
18
  // Those of them that take no options, by name.
16
19
  const plainCommands = new Map([["mcp", mcpDescription], ["chat", chatDescription], ["table", tableDescription], ["open", openDescription], ["icon", iconDescription]]);
17
20
  /** The `chesspal` command line: finds the command, validates its input, runs it and renders the result. */
@@ -149,21 +152,26 @@ function checkRepeatedOptions(command, tokens, options) {
149
152
  function inputFields(command) {
150
153
  return Object.entries(command.input.shape);
151
154
  }
152
- /** The commands, each with the first sentence of its description. */
155
+ /** The commands, each with the first sentence of its description: the player's first, then those of the AI and of whoever develops Chesspal. */
153
156
  function generalHelp(list) {
154
- const width = Math.max("listen".length, ...list.map((command) => command.name.length));
157
+ const width = Math.max("icon --remove".length, ...list.map((command) => command.name.length));
155
158
  const line = (name, description) => ` ${name.padEnd(width)} ${firstSentence(description)}`;
159
+ const init = list.find((command) => command.name === "init");
156
160
  return [
157
161
  `chesspal ${version}`,
158
162
  "",
159
- "Run `chesspal` alone to open the board page in your browser.",
163
+ "To play, type `chesspal` alone: the board opens in your browser, and from there you start the AI.",
160
164
  "",
161
- "Commands:",
162
- ...list.map((command) => line(command.name, command.description)),
165
+ "For the player:",
166
+ line("play", playDescription),
167
+ ...(init === undefined ? [] : [line(init.name, initForPlayer)]),
168
+ line("icon", iconDescription),
169
+ line("icon --remove", iconRemoveForPlayer),
170
+ "",
171
+ "For the AI, and for whoever develops Chesspal:",
172
+ ...list.filter((command) => command !== init).map((command) => line(command.name, command.description)),
163
173
  line("table", tableDescription),
164
174
  line("open", openDescription),
165
- line("icon", iconDescription),
166
- line("play", playDescription),
167
175
  line("mcp", mcpDescription),
168
176
  line("listen", listenDescription),
169
177
  line("chat", chatDescription),
@@ -23,3 +23,5 @@ export declare function iconsMade(context: Context, places?: IconPlaces): Promis
23
23
  said: string;
24
24
  failed: boolean;
25
25
  }>;
26
+ /** Takes the icon off the desktop and the menu, before Chesspal is uninstalled: what to tell the player. */
27
+ export declare function iconsRemoved(context: Context): string;
package/dist/cli/icon.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { styleText } from "node:util";
5
5
  import { speaksItalian } from "../commands/index.js";
6
- import { iconAsked, iconCommand, iconToRemake, makeIcons, noteIconAsked, noteIconMade } from "../server/table/index.js";
6
+ import { iconAsked, iconCommand, iconToRemake, makeIcons, noteIconAsked, noteIconMade, removeIcons } from "../server/table/index.js";
7
7
  /**
8
8
  * Asks once a machine, in a terminal on a screen, whether to put the icon on the desktop and in the menu, and puts it there on a yes: what to tell the
9
9
  * player, if anything. Once asked, an icon made by another Node or script is made again, without a word; a playtest's data folder is never asked about.
@@ -46,6 +46,12 @@ export async function iconsMade(context, places) {
46
46
  return { said: words.failed(error.message), failed: true };
47
47
  }
48
48
  }
49
+ /** Takes the icon off the desktop and the menu, before Chesspal is uninstalled: what to tell the player. */
50
+ export function iconsRemoved(context) {
51
+ const words = wordsFor(context);
52
+ const removed = removeIcons(context.home());
53
+ return removed.length === 0 ? words.noneRemoved : `${words.removed}\n${styleText("dim", removed.map((file) => ` ${file}`).join("\n"))}`;
54
+ }
49
55
  function processTerminal() {
50
56
  return { input: process.stdin, output: process.stdout, env: process.env };
51
57
  }
@@ -68,6 +74,8 @@ function wordsFor(context) {
68
74
  made: `Fatto: Chesspal è sul desktop e ${where}.`,
69
75
  later: "Va bene, niente icona. Se la vuoi più avanti, scrivi chesspal icon.",
70
76
  failed: (reason) => `Chesspal non è riuscito a mettere l'icona (${reason}). Riprova con chesspal icon.`,
77
+ removed: `Fatto: l'icona di Chesspal non è più sul desktop né ${where}. Per togliere Chesspal: npm uninstall -g chesspal. Le tue partite restano dove sono.`,
78
+ noneRemoved: "Chesspal non ha trovato sue icone su questo computer.",
71
79
  };
72
80
  }
73
81
  const where = { windows: "your Start menu", mac: "Applications", linux: "your applications menu" }[system];
@@ -76,5 +84,7 @@ function wordsFor(context) {
76
84
  made: `Done: Chesspal is on your desktop and in ${where}.`,
77
85
  later: "All right, no icon. To add it later, type chesspal icon.",
78
86
  failed: (reason) => `Chesspal could not add its icon (${reason}). Try again with chesspal icon.`,
87
+ removed: `Done: Chesspal's icon is gone from your desktop and ${where}. To remove Chesspal: npm uninstall -g chesspal. Your games stay where they are.`,
88
+ noneRemoved: "Chesspal found no icon of its own on this computer.",
79
89
  };
80
90
  }
@@ -2,14 +2,14 @@ import { z } from "zod";
2
2
  import { type AiLevel, type MoveReview, type PieceLetter, type Side } from "../../core/index.ts";
3
3
  /** A move as the analysis gives it: what happened, never how good it was (D-6). */
4
4
  interface MoveFacts {
5
- /** The move, numbered: 12...Nxe5. */
5
+ /** The move, numbered, with the pieces' figurines as the other tools write moves (D-86), since the analyst copies them into its notes: 12...♞xe5. */
6
6
  move: string;
7
7
  /** The piece it took. */
8
8
  took?: PieceLetter;
9
9
  /** The time it took and the time left after it, in milliseconds, where the record has the clock. */
10
10
  spent?: number;
11
11
  left?: number;
12
- /** Who is ahead in material after it, when it changed: White +1. */
12
+ /** Who is ahead in material after it, when it changed: White +1 in pawns. */
13
13
  material?: string;
14
14
  /** After it, the pieces at risk of either side when they changed from the line before ("none" once no piece is), and the mates the side to move has; none once the board ends the game. */
15
15
  atRisk?: string;
@@ -5,7 +5,7 @@ import { gameInAnalysis, isGameName, keepToStudy, lastChatStart, planOf, rewrite
5
5
  import { analystAgent, instructionsFile } from "../../host-files/index.js";
6
6
  import { activeProfile, gameFolder } from "../../profiles/index.js";
7
7
  import { pointsAtSite, siteGameName, siteOfGameName } from "../../sites/index.js";
8
- import { analystOf, anotherAnalyst, balanceText, bookOpening, bookReaches, colourName, gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn, riskLine, speechLine, speechOf, tacticsText } from "../answers/index.js";
8
+ import { analystOf, anotherAnalyst, balanceText, bookOpening, bookReaches, colourName, gameToStudy, knownTag, moveNumbers, namedGame, noGameGoingOn, riskLine, shownMove, speechLine, speechOf, tacticsText, } from "../answers/index.js";
9
9
  import { playerLine, siteText } from "../profiles/index.js";
10
10
  import { defineCommand } from "../registry/index.js";
11
11
  import { profileSiteGames } from "./site-games.js";
@@ -221,7 +221,7 @@ function movesOf(record, ai) {
221
221
  const previous = left[side];
222
222
  const risks = riskLine(facts);
223
223
  const move = {
224
- move: `${numbers[ply]}${san}`,
224
+ move: shownMove(`${numbers[ply]}${san}`),
225
225
  took: before.pieces.find((piece) => piece.square === played.move.to)?.piece ?? (san.includes("x") ? "P" : undefined),
226
226
  spent: clock === undefined || previous === undefined || increment === undefined ? undefined : Math.max(0, previous - clock + increment),
227
227
  left: clock,
@@ -266,7 +266,7 @@ function analysisHead(analysis) {
266
266
  const review = analysis.review === undefined ? [] : [`Your review: ${analysis.review}`];
267
267
  return [head, ...opening, ...review, `${name}.`, ...start];
268
268
  }
269
- /** 12...Nxe5: took P; spent 0:12, 4:32 left; material White +1; at risk: undefended Black Nf6; mate in one: Qh7#; tactics: fork Nc7+ (Ke8 Ra8); out of book; you said "…"; your plan: …; ahead -1.5 */
269
+ /** 12...♞xe5: took P; spent 0:12, 4:32 left; material White +1 in pawns; at risk: undefended Black Nf6; mate in one: Qh7#; tactics: fork Nc7+ (Ke8 Ra8); out of book; you said "…"; your plan: …; ahead -1.5 */
270
270
  function moveLine(move) {
271
271
  const left = move.left === undefined ? undefined : `${clockText(move.left)} left`;
272
272
  const parts = [
@@ -8,8 +8,16 @@ export declare function analystRuns(context: Context): boolean;
8
8
  * its end: once reviewed, or taken by the agent, the chat goes over that review and doesn't start the agent for it (Stefano).
9
9
  */
10
10
  export declare function withAnalyst(summary: GameSummary, context: Context, over?: CurrentGame): GameSummary;
11
- /** How the chat has a game over gone over by the analyst agent, on the player's yes or for the game they brought (D-89). */
12
- export declare function analystLine(game?: string): string;
11
+ /**
12
+ * How the chat has a game over gone over by the analyst agent, on the player's yes or for the game they brought (D-89); in Codex with spawn_agent by name,
13
+ * since a chat there called analyse_game itself instead (Priya).
14
+ */
15
+ export declare function analystLine(game?: string, codex?: boolean): string;
16
+ /**
17
+ * The analyst's line for the chat that looks at the game in analysis with no review and no analyst at it, as after «Delete the analysis», which rings no
18
+ * doorbell (Priya): once a change of the analysis, where the server keeps what it told.
19
+ */
20
+ export declare function analystOnView(context: Context, game: string | undefined): string | undefined;
13
21
  /** How the chat goes over a game the analyst agent has reviewed, or is reviewing: from that review, never with another agent (Stefano). */
14
22
  export declare function reviewedLine(game?: string): string;
15
23
  /** Whether a coach judges the player's moves when the helps are on: in Claude Code or Codex, with its agent in the player's folder (D-80, D-88, D-98). */
@@ -1,7 +1,7 @@
1
1
  // The agents that work beside the chat in Claude Code and Codex (D-98): the analyst that goes over a game over (D-89) and the coach that judges the player's moves (D-88).
2
2
  import { isReviewed } from "../../core/index.js";
3
3
  import { archivedName, coachStarted, gameInAnalysis, lastChatStart } from "../../games/index.js";
4
- import { agentHosts, analystAgent, coachAgent, hasAgent } from "../../host-files/index.js";
4
+ import { agentHosts, analystAgent, coachAgent, hasAgent, isCodex } from "../../host-files/index.js";
5
5
  import { gameFolder, helpsIn } from "../../profiles/index.js";
6
6
  import { namedGame } from "./study.js";
7
7
  import { unplayed } from "./summary.js";
@@ -20,13 +20,36 @@ export function withAnalyst(summary, context, over) {
20
20
  return summary;
21
21
  }
22
22
  const archived = over === undefined ? undefined : archivedName(gameFolder(context.home()), over);
23
- return { ...summary, end: { ...end, analyst: archived !== undefined && goneOver(context, archived) ? "done" : "offer" } };
23
+ const analyst = archived !== undefined && goneOver(context, archived) ? "done" : "offer";
24
+ return { ...summary, end: { ...end, analyst, ...(isCodex(context.host?.()) ? { codex: true } : {}) } };
24
25
  }
25
- /** How the chat has a game over gone over by the analyst agent, on the player's yes or for the game they brought (D-89). */
26
- export function analystLine(game) {
27
- return `start the chesspal-analyst agent in the background with the prompt "Analyse${game === undefined ? "" : ` ${game}`}. The player speaks <their language>.": it writes its review on the board `
28
- + "page, which shows it at work; tell the player in a word, talk the game over with them meanwhile whenever they ask, and when it ends go over it with them "
29
- + "from what it tells you.";
26
+ /**
27
+ * How the chat has a game over gone over by the analyst agent, on the player's yes or for the game they brought (D-89); in Codex with spawn_agent by name,
28
+ * since a chat there called analyse_game itself instead (Priya).
29
+ */
30
+ export function analystLine(game, codex = false) {
31
+ const prompt = `Analyse${game === undefined ? "" : ` ${game}`}. The player speaks <their language>.`;
32
+ const start = codex
33
+ ? `start the ${analystAgent} agent with spawn_agent {"agent_type": "${analystAgent}", "message": "${prompt}", "fork_turns": "none"}`
34
+ : `start the ${analystAgent} agent in the background with the prompt "${prompt}"`;
35
+ return `${start}: it writes its review on the board page, which shows it at work; tell the player in a word, talk the game over with them meanwhile whenever `
36
+ + "they ask, and when it ends go over it with them from what it tells you.";
37
+ }
38
+ /**
39
+ * The analyst's line for the chat that looks at the game in analysis with no review and no analyst at it, as after «Delete the analysis», which rings no
40
+ * doorbell (Priya): once a change of the analysis, where the server keeps what it told.
41
+ */
42
+ export function analystOnView(context, game) {
43
+ if (game === undefined || context.agent !== undefined || !analystRuns(context)) {
44
+ return undefined;
45
+ }
46
+ const analysis = gameInAnalysis(gameFolder(context.home()));
47
+ const key = `analyst on view ${game} ${analysis?.at ?? ""}`;
48
+ if (analysis?.game !== game || context.told?.has(key) === true || goneOver(context, game)) {
49
+ return undefined;
50
+ }
51
+ context.told?.add(key);
52
+ return `${game} has no review. When the player wants it gone over, ${analystLine(game, isCodex(context.host?.()))}`;
30
53
  }
31
54
  /** How the chat goes over a game the analyst agent has reviewed, or is reviewing: from that review, never with another agent (Stefano). */
32
55
  export function reviewedLine(game) {
@@ -1,4 +1,4 @@
1
- export { agentRuns, agentsLine, analystLine, analystOf, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.ts";
1
+ export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.ts";
2
2
  export { clockLine, clockSummaryOf, gameAt } from "./clock.ts";
3
3
  export { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.ts";
4
4
  export { aheadWords, leadFrom, leadInput } from "./lead-input.ts";
@@ -1,6 +1,6 @@
1
1
  // What the commands share to answer the AI: the lines it reads, the game's summary and view, the clock, the talk, the moves played, the games to study.
2
2
  // Above the registry only, below every other command folder.
3
- export { agentRuns, agentsLine, analystLine, analystOf, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
3
+ export { agentRuns, agentsLine, analystLine, analystOf, analystOnView, analystRuns, anotherAnalyst, coachOnEnd, coachOnEndLine, coachRuns, goneOver, reviewedLine, withAnalyst } from "./agents.js";
4
4
  export { clockLine, clockSummaryOf, gameAt } from "./clock.js";
5
5
  export { arrowsInput, drawingsOf, noteInput, squaresInput } from "./drawings.js";
6
6
  export { aheadWords, leadFrom, leadInput } from "./lead-input.js";
@@ -104,7 +104,7 @@ export function nextStep(game, words = [], pageClosed = false) {
104
104
  }
105
105
  if (game.end !== undefined) {
106
106
  const analysis = game.end.analyst === "offer"
107
- ? ` To go over it, on their yes, ${analystLine(game.end.archive)}`
107
+ ? ` To go over it, on their yes, ${analystLine(game.end.archive, game.end.codex === true)}`
108
108
  : game.end.analyst === "done"
109
109
  ? ` ${reviewedLine(game.end.archive)}`
110
110
  : "";
@@ -163,7 +163,8 @@ function clockLines(game) {
163
163
  }
164
164
  function whereLine(game) {
165
165
  if (game.end !== undefined) {
166
- const archived = game.end.archive === undefined ? "" : ` Archived in ${game.end.archive}.`;
166
+ // Saved where the player's games are: the AI says so, never the file, which past_games names when it is needed.
167
+ const archived = game.end.archive === undefined ? "" : " Saved with the player's games.";
167
168
  return `Game over: ${endText(game.end)}.${archived}`;
168
169
  }
169
170
  return `${game.turn === game.ai ? "You" : "Player"} (${colourName(game.turn)}) to move, move ${game.moveNumber}.`;
@@ -38,13 +38,14 @@ export interface GameSummary {
38
38
  mood?: MoodNow;
39
39
  /**
40
40
  * When the game is over: how it ended, its name in the archive once it is there, and, where an analyst agent runs (D-89), whether the chat offers it or
41
- * goes over the review it did.
41
+ * goes over the review it did, and whether the host is Codex, which starts it with spawn_agent.
42
42
  */
43
43
  end?: {
44
44
  result: string;
45
45
  termination?: string;
46
46
  archive?: string;
47
47
  analyst?: "offer" | "done";
48
+ codex?: true;
48
49
  };
49
50
  }
50
51
  /** A move numbered as in the game (4.Ng5, 4...d5), with the squares it went from and to. */
@@ -22,5 +22,5 @@ export declare function riskLine(facts: Facts): string | undefined;
22
22
  export declare function colourName(side: Side): string;
23
23
  /** The material as the player sees it beside the board (D-59): who is ahead and by how much, and the pieces each side took. */
24
24
  export declare function materialText(material: Material): string;
25
- /** Who is ahead in material, and by how many pawns: White +2, or even. */
25
+ /** Who is ahead in material, and by how many pawns, with the unit, so that no AI reads it as pieces: White +2 in pawns, or even. */
26
26
  export declare function balanceText(balance: number): string;
@@ -51,9 +51,9 @@ export function materialText(material) {
51
51
  }
52
52
  return `${ahead} (White took ${takenText(material.taken.white)}, Black took ${takenText(material.taken.black)})`;
53
53
  }
54
- /** Who is ahead in material, and by how many pawns: White +2, or even. */
54
+ /** Who is ahead in material, and by how many pawns, with the unit, so that no AI reads it as pieces: White +2 in pawns, or even. */
55
55
  export function balanceText(balance) {
56
- return balance === 0 ? "even" : `${balance > 0 ? "White" : "Black"} +${Math.abs(balance)}`;
56
+ return balance === 0 ? "even" : `${balance > 0 ? "White" : "Black"} +${Math.abs(balance)} in pawns`;
57
57
  }
58
58
  /** The pieces taken, counted by kind: Q 2R 7P. */
59
59
  function takenText(taken) {
@@ -24,11 +24,15 @@ export declare const look: import("../registry/registry.ts").Command<z.ZodObject
24
24
  }>>;
25
25
  }, z.core.$strip>, {
26
26
  board: string;
27
- } | {
27
+ } | ({
28
28
  game: Viewed;
29
29
  words: Written[];
30
- } | {
30
+ } & {
31
+ analyst?: string;
32
+ }) | ({
31
33
  study: StudyView;
32
34
  words: Written[];
33
- }>;
35
+ } & {
36
+ analyst?: string;
37
+ })>;
34
38
  export {};
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { boardText, materialOf, sees } from "../../core/index.js";
3
+ import { archivedName, endOf } from "../../games/index.js";
3
4
  import { analystAgent } from "../../host-files/index.js";
4
5
  import { gameFolder } from "../../profiles/index.js";
5
- import { fullView, gameAt, gameShown, headLines, materialText, newWords, positionAfter, positionLine, studyInput, summaryOf, wordLines } from "../answers/index.js";
6
+ import { analystOnView, fullView, gameAt, gameShown, headLines, materialText, newWords, positionAfter, positionLine, studyInput, summaryOf, wordLines } from "../answers/index.js";
6
7
  import { defineCommand } from "../registry/index.js";
7
8
  import { refuseUnseen } from "./levels.js";
8
9
  /** The whole view (D-26), when the compact one after the player's move is not enough; or the board for the player, where the host does not show it (D-36). */
@@ -34,13 +35,14 @@ export const look = defineCommand({
34
35
  }
35
36
  const { threats } = current.game;
36
37
  const viewed = { ...summaryOf(current, context.now()), facts: current.game.facts, possibilities: current.game.possibilities };
37
- return { game: threats === undefined ? viewed : { ...viewed, threats }, words: newWords(context) };
38
+ const over = endOf(current.game) === undefined ? undefined : archivedName(gameFolder(context.home()), current);
39
+ return withAnalystLine({ game: threats === undefined ? viewed : { ...viewed, threats }, words: newWords(context) }, analystOnView(context, over));
38
40
  },
39
41
  format: (look) => {
40
42
  if ("board" in look) {
41
43
  return look.board;
42
44
  }
43
- return [...("game" in look ? gameLines(look.game) : studyLines(look.study)), ...wordLines(look.words)].join("\n");
45
+ return [...("game" in look ? gameLines(look.game) : studyLines(look.study)), ...wordLines(look.words), ...(look.analyst === undefined ? [] : [look.analyst])].join("\n");
44
46
  },
45
47
  });
46
48
  /** A position of a game over, as the view of the game going on gives it. */
@@ -52,7 +54,10 @@ function studyLook(context, only, name, move) {
52
54
  return { board: boardText(position.fen) };
53
55
  }
54
56
  const view = { where: positionLine(at, studied.name), turn: position.turn, fen: position.fen, facts: at.game.facts, possibilities: at.game.possibilities, threats: at.game.threats };
55
- return { study: view, words: newWords(context) };
57
+ return withAnalystLine({ study: view, words: newWords(context) }, analystOnView(context, studied.name));
58
+ }
59
+ function withAnalystLine(look, analyst) {
60
+ return analyst === undefined ? look : { ...look, analyst };
56
61
  }
57
62
  /** The game going on: its head and the whole view, with the other side's chances from the strong level (D-100). */
58
63
  function gameLines(game) {
@@ -97,7 +97,7 @@ function startedLines({ game, archived, past, player, speech, closedPage: closed
97
97
  `New game: ${sidesLine(game.ai)}, ${levelText(game.level)}.`,
98
98
  ...(player === undefined ? [] : [player]),
99
99
  ...(speech === undefined ? [] : [speechLine(speech)]),
100
- ...(archived === undefined ? [] : [`The game before is in ${archived.name}${archived.finished ? "" : ", unfinished"}.`]),
100
+ ...(archived === undefined ? [] : [`The game before is saved with the player's games${archived.finished ? "" : ", unfinished"}.`]),
101
101
  ...(past.length === 0 ? [] : ["Last games with this player (past_games gives more, and their moves):", ...past]),
102
102
  ...positionLines(game),
103
103
  ...(closed === undefined ? [] : [pageLine(closed, true)]),
@@ -36,7 +36,7 @@ export const ponder = defineCommand({
36
36
  if (reply !== undefined || move !== undefined || why !== undefined || say !== undefined || done) {
37
37
  throw new Error("Give the position's number with it: call ponder {} first, which gives it. Nothing changed.");
38
38
  }
39
- notePondering(folder, thoughtKey(current), true);
39
+ notePondering(folder, thoughtKey(current), true, context.now());
40
40
  return { start: startOf(current) };
41
41
  }
42
42
  if ((say !== undefined || done) && (reply !== undefined || move !== undefined || why !== undefined || (say !== undefined && done))) {
@@ -47,7 +47,7 @@ export const ponder = defineCommand({
47
47
  return { said: position };
48
48
  }
49
49
  if (done) {
50
- notePondering(folder, thoughtKey(current), false);
50
+ notePondering(folder, thoughtKey(current), false, context.now());
51
51
  return await waitForMove(folder, context, current);
52
52
  }
53
53
  if (reply === undefined) {
@@ -71,7 +71,7 @@ export const ponder = defineCommand({
71
71
  if (!answer.played) {
72
72
  throw new Error(`${answer.message} Your legal moves after ${played.move.san}: ${answer.legal.join(" ")}. Nothing changed.`);
73
73
  }
74
- return { kept: keepThought(folder, thoughtKey(current), { reply: played.move.san, move: answer.move.san, why }), position };
74
+ return { kept: keepThought(folder, thoughtKey(current), { reply: played.move.san, move: answer.move.san, why }, context.now()), position };
75
75
  },
76
76
  format: (pondered) => {
77
77
  if ("stop" in pondered) {
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { type Profile } from "../../core/index.ts";
3
- /** The positional profile of a position: of a game studied, with where it stands. */
3
+ /** The positional profile of a position: of a game studied, with where it stands; with the analyst's line for a game in analysis without its review. */
4
4
  type Described = Profile & {
5
5
  where?: string;
6
+ analyst?: string;
6
7
  };
7
8
  /** The positional profile of the game in progress (D-19), or of any position of a game over (D-68): separate facts, never a score. */
8
9
  export declare const position: import("../registry/registry.ts").Command<z.ZodObject<{
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import {} from "../../core/index.js";
3
+ import { archivedName, endOf } from "../../games/index.js";
3
4
  import { analystAgent } from "../../host-files/index.js";
4
5
  import { gameFolder } from "../../profiles/index.js";
5
- import { colourName, gameAt, gameShown, materialText, positionAfter, positionLine, studyInput } from "../answers/index.js";
6
+ import { analystOnView, colourName, gameAt, gameShown, materialText, positionAfter, positionLine, studyInput } from "../answers/index.js";
6
7
  import { defineCommand } from "../registry/index.js";
7
8
  import { refuseUnseen } from "./levels.js";
8
9
  /** The positional profile of the game in progress (D-19), or of any position of a game over (D-68): separate facts, never a score. */
@@ -21,17 +22,21 @@ export const position = defineCommand({
21
22
  if (game !== undefined || move !== undefined) {
22
23
  const studied = gameShown(context, game);
23
24
  const at = positionAfter(studied.record, move);
24
- return { ...at.game.profile, where: positionLine(at, studied.name) };
25
+ return withAnalystLine({ ...at.game.profile, where: positionLine(at, studied.name) }, analystOnView(context, studied.name));
25
26
  }
26
27
  const current = gameAt(gameFolder(context.home()), context.now());
27
28
  if (current === undefined) {
28
29
  throw new Error("There is no game to describe: start one with new_game.");
29
30
  }
30
31
  refuseUnseen(current, "strategy", "position");
31
- return current.game.profile;
32
+ const over = endOf(current.game) === undefined ? undefined : archivedName(gameFolder(context.home()), current);
33
+ return withAnalystLine(current.game.profile, analystOnView(context, over));
32
34
  },
33
- format: (described) => [...(described.where === undefined ? [] : [described.where]), ...profileLines(described)].join("\n"),
35
+ format: (described) => [...(described.where === undefined ? [] : [described.where]), ...profileLines(described), ...(described.analyst === undefined ? [] : [described.analyst])].join("\n"),
34
36
  });
37
+ function withAnalystLine(described, analyst) {
38
+ return analyst === undefined ? described : { ...described, analyst };
39
+ }
35
40
  /** The profile as lines for the AI, White's facts before Black's; a line with nothing to say is left out. */
36
41
  export function profileLines(profile) {
37
42
  const sides = [["white", profile.white], ["black", profile.black]];
@@ -16,11 +16,13 @@ interface Status {
16
16
  problem?: string;
17
17
  /** What the player wrote on the board page that the AI has not answered yet, all of it. */
18
18
  words?: Written[];
19
- /** The game in analysis on the board page once none goes on (D-69), which the player may have brought there. */
19
+ /** The game in analysis on the board page once none goes on (D-69), which the player may have brought there, for a new review too. */
20
20
  analysis?: {
21
21
  game: string;
22
22
  brought?: true;
23
+ redo?: true;
23
24
  analyst?: "done" | "offer";
25
+ codex?: true;
24
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. */
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- import { gameReviewOf, leadOf, reviewOf } from "../../core/index.js";
2
+ import { Game, gameReviewOf, leadOf, reviewOf } from "../../core/index.js";
3
3
  import { archivedName, cleanSince, endOf, gameInAnalysis, lastChatStart } from "../../games/index.js";
4
- import { outdatedHostFiles } from "../../host-files/index.js";
4
+ import { isCodex, outdatedHostFiles } from "../../host-files/index.js";
5
5
  import { gameFolder } from "../../profiles/index.js";
6
6
  import { version } from "../../version.js";
7
7
  import { analystLine, analystRuns, closingLines, coachOnEnd, coachOnEndLine, coachRuns, gameAt, goneOver, namedGame, newGameStep, positionLines, reviewedLine, shownMoves, sidesLine, speechLine, speechOf, summaryOf, unansweredWords, withAnalyst, withThought, wordLines, } from "../answers/index.js";
@@ -68,7 +68,9 @@ export const status = defineCommand({
68
68
  status.analysis = {
69
69
  game: analysis.game,
70
70
  ...(analysis.brought === true ? { brought: true } : {}),
71
+ ...(analysis.redo === true ? { redo: true } : {}),
71
72
  ...(analystRuns(context) ? { analyst: goneOver(context, analysis.game) ? "done" : "offer" } : {}),
73
+ ...(analystRuns(context) && isCodex(context.host?.()) ? { codex: true } : {}),
72
74
  ...(review === undefined ? {} : { review }),
73
75
  };
74
76
  }
@@ -113,30 +115,38 @@ function firstInChat(context, chatStart) {
113
115
  }
114
116
  /**
115
117
  * 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
116
- * already; the analyst agent for a game whose review was deleted, where it runs (Stefano).
118
+ * already, at once when they asked for a new review (Priya); the analyst agent for a game whose review was deleted, where it runs (Stefano).
117
119
  */
118
- function analysisLine({ game, brought, analyst, review }) {
120
+ function analysisLine({ game, brought, redo, analyst, codex, review }) {
121
+ // A chat that remembered the old review put its notes back with annotate_game, and its corrections stand over every review after (Priya).
122
+ if (redo === true && analyst === "offer") {
123
+ return `The player asked on the board page for a new review of ${game}. Next, at once, even if an analyst was stopped before: ${analystLine(game, codex === true)} `
124
+ + "The new review replaces the old one: correct a note with annotate_game only when the player shows it wrong.";
125
+ }
119
126
  if (brought === true && analyst !== "done") {
120
127
  return analyst === "offer"
121
- ? `The player brought ${game} on the board page to analyse it. Next: ${analystLine(game)}`
128
+ ? `The player brought ${game} on the board page to analyse it. Next: ${analystLine(game, codex === true)}`
122
129
  : `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.`;
123
130
  }
124
131
  const shown = `In analysis on the board page: ${game}${review === undefined ? "" : ` (${review})`}`;
125
132
  if (analyst === "done") {
126
133
  return `${shown}. ${reviewedLine(game)}`;
127
134
  }
128
- return analyst === "offer" ? `${shown}. When the player wants it gone over, ${analystLine(game)}` : `${shown}; analyse_game {"game": "${game}"} goes over it with the player.`;
135
+ return analyst === "offer" ? `${shown}. When the player wants it gone over, ${analystLine(game, codex === true)}` : `${shown}; analyse_game {"game": "${game}"} goes over it with the player.`;
129
136
  }
130
137
  /**
131
- * 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
132
- * but the last; the analyst may still write notes after its word, so that alone is not the end.
138
+ * 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,
139
+ * but the last when the board ended the game there, which says it; the analyst may still write notes after its word, so that alone is not the end.
133
140
  */
134
141
  function reviewProgress(context, game) {
135
142
  try {
136
143
  const { record } = namedGame(context, game);
137
144
  const worded = gameReviewOf(record) !== undefined;
138
145
  const estimated = record.moves.filter(({ comment }) => leadOf(comment) !== undefined).length;
139
- if (worded && estimated >= record.moves.length - 1) {
146
+ // A game resigned or lost on time needs who is ahead after its last move too; the game is played over only when that is the one estimate missing.
147
+ const all = record.moves.length;
148
+ const done = estimated >= all || (estimated === all - 1 && new Game(record).outcome.over);
149
+ if (worded && done) {
140
150
  return "its review is done";
141
151
  }
142
152
  const noted = record.moves.filter(({ comment }) => {
@@ -1,11 +1,16 @@
1
1
  import { z } from "zod";
2
2
  /** The AI's memory of the games played (D-57): how the latest went, one line each, or one game whole from the archive. */
3
3
  export declare const pastGames: import("../registry/registry.ts").Command<z.ZodObject<{
4
+ search: z.ZodOptional<z.ZodString>;
4
5
  game: z.ZodOptional<z.ZodString>;
5
6
  }, z.core.$strip>, {
6
7
  games: string[];
8
+ searched: true | undefined;
7
9
  } | {
8
10
  pgn: string;
9
11
  }>;
10
- /** The latest games of the archive, one line each as the AI reads them: none when there is no archive. A game closed before any move is no game played. */
11
- export declare function pastGameLines(folder: string, count: number): string[];
12
+ /**
13
+ * The latest games of the archive, one line each as the AI reads them, those holding every word searched: none when there is no archive. A game closed
14
+ * before any move is no game played.
15
+ */
16
+ export declare function pastGameLines(folder: string, count: number, search?: string): string[];