gentle-pi 4.5.0 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/gentle-music.md +6 -3
- package/docs/gentle-shell.md +1 -1
- package/docs/readme-reference.md +3 -1
- package/extensions/gentle-ai.ts +7 -5
- package/extensions/gentle-music.ts +10 -0
- package/package.json +1 -1
- package/tests/design-canvas-server.test.ts +6 -2
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +1 -1
- package/tests/gentle-ai.test.ts +4 -4
- package/tests/gentle-music.test.ts +36 -1
- package/tests/installer-server.test.ts +2 -2
- package/tests/package-manifest.test.ts +2 -2
- package/tests/persona-reply-language.test.ts +42 -0
package/docs/gentle-music.md
CHANGED
|
@@ -42,7 +42,9 @@ When you quit Pi after using `/gentle:music`, Gentle Shell runs `gentle-stream c
|
|
|
42
42
|
|
|
43
43
|
## Installation
|
|
44
44
|
|
|
45
|
-
Gentle Shell
|
|
45
|
+
gentle-stream runs on macOS and Linux, including Gentle Shell inside WSL. It needs POSIX features native Windows lacks (file locks, a Unix control socket, process groups), so on native Windows `/gentle:music` only says it is not available yet: it never offers, installs, or runs the player.
|
|
46
|
+
|
|
47
|
+
On macOS and Linux, Gentle Shell looks for gentle-stream in this order:
|
|
46
48
|
|
|
47
49
|
1. `gentle-stream` on `PATH`.
|
|
48
50
|
2. In a bundled Gentle Shell, the gentle-stream it installed inside its own folder (see below).
|
|
@@ -57,13 +59,13 @@ uv tool install 'gentle-stream[samples] @ git+https://github.com/Gentleman-Progr
|
|
|
57
59
|
|
|
58
60
|
### Installed by a bundled Gentle Shell
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
On macOS and Linux, a bundled Gentle Shell (the one the web installer sets up) offers to install the player for you the first time `/gentle:music` finds none:
|
|
61
63
|
|
|
62
64
|
> Install the gentle-stream music player inside Gentle Shell? It downloads uv, Python 3.13 and the player (about 150 MB) into the Gentle Shell folder.
|
|
63
65
|
|
|
64
66
|
Decline and you get the manual hint above; nothing is downloaded. Accept and Gentle Shell shows its progress, installs the player, and starts the music. Later runs use it directly, with no question.
|
|
65
67
|
|
|
66
|
-
Everything stays inside the Gentle Shell folder (`~/.gentle-shell
|
|
68
|
+
Everything stays inside the Gentle Shell folder (`~/.gentle-shell`). Nothing is added to your `PATH`, and your own Python, uv, and uv settings are never used or changed:
|
|
67
69
|
|
|
68
70
|
| What | Where | Size on disk |
|
|
69
71
|
| :-- | :-- | :-- |
|
|
@@ -77,6 +79,7 @@ The player is installed with its `samples` extra (pyfluidsynth). If that extra c
|
|
|
77
79
|
|
|
78
80
|
## Limits
|
|
79
81
|
|
|
82
|
+
- Native Windows is not supported yet (see [Installation](#installation)); use WSL.
|
|
80
83
|
- The player needs the interactive terminal. In RPC, JSON, and print modes, `/gentle:music` shows a notice and does nothing.
|
|
81
84
|
- There is no default shortcut.
|
|
82
85
|
- gentle-stream starts its own `gentle-shell` process to interpret moods; Gentle Shell does not pass its own launcher path.
|
package/docs/gentle-shell.md
CHANGED
|
@@ -397,7 +397,7 @@ The canvas server binds `127.0.0.1` on a random port and needs a random per-serv
|
|
|
397
397
|
|
|
398
398
|
### Gentle Music
|
|
399
399
|
|
|
400
|
-
`/gentle:music [mood]` hands the terminal to the [gentle-stream](https://github.com/Gentleman-Programming/gentle-stream) player, like Pi's external editor, passing the mood and the session model. In the player, `b` returns to Pi with the music playing and `q` returns and stops it. `/gentle:music next`, `pause`, `play`, `stop`, and `status` control the music without leaving Pi. Quitting Pi after using the command stops the music. gentle-stream runs from `PATH`, or through `uvx`/`uv` when it is not installed. A bundled Gentle Shell without any of them offers, with your consent, to install the player inside its own folder: a pinned, checksum-verified uv 0.13.6 in `runtime/`, and the uv-managed Python 3.13 and gentle-stream in `music/` (about 150 MB to download, about 210 MB on disk). A pnpm or npm install keeps the manual install hint. See [Gentle Music](gentle-music.md).
|
|
400
|
+
`/gentle:music [mood]` hands the terminal to the [gentle-stream](https://github.com/Gentleman-Programming/gentle-stream) player, like Pi's external editor, passing the mood and the session model. In the player, `b` returns to Pi with the music playing and `q` returns and stops it. `/gentle:music next`, `pause`, `play`, `stop`, and `status` control the music without leaving Pi. Quitting Pi after using the command stops the music. gentle-stream runs from `PATH`, or through `uvx`/`uv` when it is not installed. On native Windows the player is not available yet (it needs POSIX; WSL works), so the command only says so. A bundled Gentle Shell on macOS or Linux without any of them offers, with your consent, to install the player inside its own folder: a pinned, checksum-verified uv 0.13.6 in `runtime/`, and the uv-managed Python 3.13 and gentle-stream in `music/` (about 150 MB to download, about 210 MB on disk). A pnpm or npm install keeps the manual install hint. See [Gentle Music](gentle-music.md).
|
|
401
401
|
|
|
402
402
|
### Bridge providers
|
|
403
403
|
|
package/docs/readme-reference.md
CHANGED
|
@@ -988,7 +988,9 @@ Delegation contract:
|
|
|
988
988
|
| Persona | Behavior |
|
|
989
989
|
| ----------- | ------------------------------------------------------------------------------------------------------------- |
|
|
990
990
|
| `gentleman` | Senior architect, teacher, direct technical feedback, Rioplatense Spanish/voseo when the user writes Spanish. |
|
|
991
|
-
| `neutral` | Same discipline, warmer professional language, no regional expression.
|
|
991
|
+
| `neutral` | Same discipline, warmer professional language, no regional expression, no voseo in any verb form. |
|
|
992
|
+
|
|
993
|
+
Both modes reply in the language of the user's latest message and never default to Spanish, so an English message gets an English reply even after context compaction.
|
|
992
994
|
|
|
993
995
|
Saved globally at:
|
|
994
996
|
|
package/extensions/gentle-ai.ts
CHANGED
|
@@ -1405,7 +1405,7 @@ const PERSONA_OPTIONS = ["gentleman", "neutral"] as const;
|
|
|
1405
1405
|
|
|
1406
1406
|
const GENTLEMAN_PERSONA_PROMPT = `Persona:
|
|
1407
1407
|
- Be direct, technical, and concise.
|
|
1408
|
-
- Always respond in the same language the user writes in.
|
|
1408
|
+
- Always respond in the same language the user writes in. Judge it by the user's latest message, never by earlier context or a compaction summary, and never default to Spanish: a message in English gets a reply in English.
|
|
1409
1409
|
- When the user writes Spanish, answer in natural Rioplatense Spanish with voseo.
|
|
1410
1410
|
- Act as a senior architect and teacher: concepts before code, no shortcuts.
|
|
1411
1411
|
- Treat AI as a tool directed by the human; never present yourself as a default chatbot.
|
|
@@ -1414,9 +1414,9 @@ const GENTLEMAN_PERSONA_PROMPT = `Persona:
|
|
|
1414
1414
|
|
|
1415
1415
|
const NEUTRAL_PERSONA_PROMPT = `Persona:
|
|
1416
1416
|
- Be direct, technical, concise, warm, and professional.
|
|
1417
|
-
- Always respond in the same language the user writes in.
|
|
1417
|
+
- Always respond in the same language the user writes in. Judge it by the user's latest message, never by earlier context or a compaction summary, and never default to Spanish: a message in English gets a reply in English.
|
|
1418
1418
|
- Do not use slang or regional expressions.
|
|
1419
|
-
- When the user writes Spanish, use neutral/professional Spanish. Do NOT use voseo
|
|
1419
|
+
- When the user writes Spanish, use neutral/professional Spanish. Do NOT use voseo or any regional conjugations, regular or irregular: use tú or usted forms, never vos forms (tienes, not tenés; ajustas, not ajustás; vives, not vivís; mira, not mirá; haz, not hacé).
|
|
1420
1420
|
- Act as a senior architect and teacher: concepts before code, no shortcuts.
|
|
1421
1421
|
- Treat AI as a tool directed by the human; never present yourself as a default chatbot.
|
|
1422
1422
|
- Push back when the user asks for code without enough context or understanding.
|
|
@@ -4519,10 +4519,12 @@ function routingScreenNotices(
|
|
|
4519
4519
|
const unmatched = unmatchedProfileKeys(Object.fromEntries(profileKeys.map((key) => [key, { model: undefined, thinking: undefined }])), known);
|
|
4520
4520
|
const ignored = unmatched.filter((entry) => entry.suggestion === undefined).map((entry) => entry.key);
|
|
4521
4521
|
if (ignored.length > 0) {
|
|
4522
|
-
notices.push(
|
|
4522
|
+
notices.push(
|
|
4523
|
+
`Profile key${ignored.length === 1 ? "" : "s"} ${ignored.map((key) => `"${key}"`).join(", ")} match${ignored.length === 1 ? "es" : ""} no agent configured in gentle-shell, so ${ignored.length === 1 ? "it is" : "they are"} ignored at launch.`,
|
|
4524
|
+
);
|
|
4523
4525
|
}
|
|
4524
4526
|
for (const entry of unmatched) {
|
|
4525
|
-
if (entry.suggestion !== undefined) notices.push(`
|
|
4527
|
+
if (entry.suggestion !== undefined) notices.push(`Profile key "${entry.key}" matches no agent configured in gentle-shell; did you mean ${entry.suggestion}?`);
|
|
4526
4528
|
}
|
|
4527
4529
|
// Issue #2400: model_profiles keys in subagents.json that match nothing and
|
|
4528
4530
|
// suggest nothing. A session binding or a winning pin replaces those launches'
|
|
@@ -27,6 +27,9 @@ export type MusicControlCommand = (typeof MUSIC_CONTROL_COMMANDS)[number];
|
|
|
27
27
|
|
|
28
28
|
export const MUSIC_INSTALL_TITLE = "Install the music player?";
|
|
29
29
|
export const MUSIC_INSTALL_OFFER = "Install the gentle-stream music player inside Gentle Shell? It downloads uv, Python 3.13 and the player (about 150 MB) into the Gentle Shell folder.";
|
|
30
|
+
// gentle-stream needs POSIX (fcntl locks, an AF_UNIX control socket, process
|
|
31
|
+
// groups), so a native Windows Pi can neither install nor run it; WSL is Linux.
|
|
32
|
+
export const MUSIC_WINDOWS_UNSUPPORTED = "/gentle:music is not available on Windows yet: the gentle-stream player runs on macOS and Linux. It works when Gentle Shell runs inside WSL.";
|
|
30
33
|
const UV_INSTALL_URL = "https://docs.astral.sh/uv/getting-started/installation/";
|
|
31
34
|
const CTL_TIMEOUT_MS = 30_000;
|
|
32
35
|
const SHUTDOWN_TIMEOUT_MS = 2_000;
|
|
@@ -85,6 +88,8 @@ export interface GentleMusicDeps {
|
|
|
85
88
|
which?: MusicWhich;
|
|
86
89
|
/** The bundled install's player, or undefined outside a bundled install. */
|
|
87
90
|
bundled?: () => Promise<BundledMusicPlayer | undefined>;
|
|
91
|
+
/** The platform Pi runs on; gentle-stream does not run on native Windows. */
|
|
92
|
+
platform?: NodeJS.Platform;
|
|
88
93
|
spawn?: MusicSpawn;
|
|
89
94
|
signals?: MusicSignals;
|
|
90
95
|
usage?: MusicUsage;
|
|
@@ -259,6 +264,7 @@ export default function gentleMusic(pi: ExtensionAPI, deps: GentleMusicDeps = {}
|
|
|
259
264
|
const usage = deps.usage ?? processUsage();
|
|
260
265
|
const shutdownTimeoutMs = deps.shutdownTimeoutMs ?? SHUTDOWN_TIMEOUT_MS;
|
|
261
266
|
const bundled = deps.bundled ?? defaultBundled;
|
|
267
|
+
const platform = deps.platform ?? process.platform;
|
|
262
268
|
|
|
263
269
|
const findPlayer = async (): Promise<{ stream?: GentleStreamCommand; offer?: BundledMusicPlayer }> => {
|
|
264
270
|
let player: BundledMusicPlayer | undefined;
|
|
@@ -326,6 +332,10 @@ export default function gentleMusic(pi: ExtensionAPI, deps: GentleMusicDeps = {}
|
|
|
326
332
|
description: "Open gentle-stream full screen (optional mood text); b returns with the music playing, q stops it. next, pause, play, stop, status control the music without leaving Pi.",
|
|
327
333
|
handler: async (args, ctx) => {
|
|
328
334
|
if (!ctx.hasUI) return;
|
|
335
|
+
if (platform === "win32") {
|
|
336
|
+
ctx.ui.notify(MUSIC_WINDOWS_UNSUPPORTED, "warning");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
329
339
|
if (ctx.mode !== "tui") {
|
|
330
340
|
ctx.ui.notify("/gentle:music needs the interactive terminal.", "warning");
|
|
331
341
|
return;
|
package/package.json
CHANGED
|
@@ -562,9 +562,13 @@ function openEvents(port: number) {
|
|
|
562
562
|
req.on("error", reject);
|
|
563
563
|
});
|
|
564
564
|
req.end();
|
|
565
|
-
|
|
565
|
+
// A waiter that never matches must fail loud, not hang the whole suite:
|
|
566
|
+
// an exhausted inotify limit silently stops the server's change events.
|
|
567
|
+
const waitFor = (pattern: RegExp) => new Promise<RegExpExecArray>((resolve, reject) => {
|
|
568
|
+
const timeout = setTimeout(() => reject(new Error(`events never matched ${pattern} within 5s; got: ${JSON.stringify(buffer.slice(-200))}`)), 5000);
|
|
569
|
+
const settle = (match: RegExpExecArray) => { clearTimeout(timeout); resolve(match); };
|
|
566
570
|
const match = pattern.exec(buffer);
|
|
567
|
-
if (match)
|
|
571
|
+
if (match) settle(match); else waiters.push({ pattern, resolve: settle });
|
|
568
572
|
});
|
|
569
573
|
return { ready, waitFor, req };
|
|
570
574
|
}
|
|
@@ -13,7 +13,7 @@ Identity contract:
|
|
|
13
13
|
|
|
14
14
|
Persona:
|
|
15
15
|
- Be direct, technical, and concise.
|
|
16
|
-
- Always respond in the same language the user writes in.
|
|
16
|
+
- Always respond in the same language the user writes in. Judge it by the user's latest message, never by earlier context or a compaction summary, and never default to Spanish: a message in English gets a reply in English.
|
|
17
17
|
- When the user writes Spanish, answer in natural Rioplatense Spanish with voseo.
|
|
18
18
|
- Act as a senior architect and teacher: concepts before code, no shortcuts.
|
|
19
19
|
- Treat AI as a tool directed by the human; never present yourself as a default chatbot.
|
package/tests/gentle-ai.test.ts
CHANGED
|
@@ -1539,10 +1539,10 @@ test("models screen offers only the effort levels each model supports and shows
|
|
|
1539
1539
|
test("models screen lists configuration problems under the profile name without cutting them", () => {
|
|
1540
1540
|
const notices = [
|
|
1541
1541
|
"The local pin file /home/user/project/.git/gentle-ai/profile-pin.json is not a valid pin and is ignored. Fix or remove it.",
|
|
1542
|
-
'
|
|
1542
|
+
'Profile key "sdd-design" matches no agent configured in gentle-shell, so it is ignored at launch.',
|
|
1543
1543
|
];
|
|
1544
1544
|
const plain = renderScreen([], [], [], { width: 80, notices }).map(stripAnsi);
|
|
1545
|
-
assert.ok(plain.some((line) => line.includes('⚠
|
|
1545
|
+
assert.ok(plain.some((line) => line.includes('⚠ Profile key "sdd-design"')));
|
|
1546
1546
|
assert.ok(!plain.some((line) => line.includes("…")));
|
|
1547
1547
|
});
|
|
1548
1548
|
|
|
@@ -3531,7 +3531,7 @@ test("the models screen flags profile keys no agent matches and never flags prov
|
|
|
3531
3531
|
await fixture.run("gentle:profiles");
|
|
3532
3532
|
const flat = (rendered: string) => rendered.replace(/\s*│\s*/g, " ").replace(/\s+/g, " ");
|
|
3533
3533
|
assert.equal(rendered.split("\n").length, 24, "the frame spans the terminal height");
|
|
3534
|
-
assert.match(flat(rendered), /⚠
|
|
3534
|
+
assert.match(flat(rendered), /⚠ Profile key "sdd-design" matches no agent configured in gentle-shell, so it is ignored at launch\./);
|
|
3535
3535
|
assert.doesNotMatch(flat(rendered), /review-refuter"/, "provider review roles are never flagged");
|
|
3536
3536
|
assert.match(rendered, /Orchestrator\s+nan\/glm5\.3\s+high/);
|
|
3537
3537
|
assert.match(rendered, /worker\s+openai\/alpha\s+high/);
|
|
@@ -3555,7 +3555,7 @@ test("at 60 columns the unmatched-key notice wraps instead of being cut", async
|
|
|
3555
3555
|
assert.ok(panel);
|
|
3556
3556
|
const narrow = stripAnsi(panel.render(60).join("\n"));
|
|
3557
3557
|
const flat = (rendered: string) => rendered.replace(/\s*│\s*/g, " ").replace(/\s+/g, " ");
|
|
3558
|
-
assert.match(flat(narrow), /⚠
|
|
3558
|
+
assert.match(flat(narrow), /⚠ Profile key "sdd-design" matches no agent configured in gentle-shell, so it is ignored at launch\./);
|
|
3559
3559
|
assert.ok(!narrow.split("\n").some((line) => line.includes("…")), "nothing is cut");
|
|
3560
3560
|
});
|
|
3561
3561
|
|
|
@@ -5,13 +5,14 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { delimiter, join } from "node:path";
|
|
6
6
|
import test, { type TestContext } from "node:test";
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import
|
|
8
|
+
import gentleMusicExtension, {
|
|
9
9
|
findOnPath,
|
|
10
10
|
formatMusicStatus,
|
|
11
11
|
GENTLE_STREAM_SPEC,
|
|
12
12
|
installHint,
|
|
13
13
|
MUSIC_COMMAND_NAME,
|
|
14
14
|
MUSIC_INSTALL_OFFER,
|
|
15
|
+
MUSIC_WINDOWS_UNSUPPORTED,
|
|
15
16
|
MUSIC_INSTALL_TITLE,
|
|
16
17
|
musicTuiArgs,
|
|
17
18
|
parseMusicArgs,
|
|
@@ -20,6 +21,11 @@ import gentleMusic, {
|
|
|
20
21
|
type MusicSpawn,
|
|
21
22
|
} from "../extensions/gentle-music.ts";
|
|
22
23
|
|
|
24
|
+
// The music tests describe a supported (POSIX) host unless they pass `platform`,
|
|
25
|
+
// so they hold on every OS the suite runs on, Windows included.
|
|
26
|
+
const gentleMusic = (pi: Parameters<typeof gentleMusicExtension>[0], deps: Parameters<typeof gentleMusicExtension>[1] = {}) =>
|
|
27
|
+
gentleMusicExtension(pi, { platform: "linux", ...deps });
|
|
28
|
+
|
|
23
29
|
// /gentle:music hands the terminal to gentle-stream's own TUI, the way Pi's
|
|
24
30
|
// external editor does, and controls the background player with
|
|
25
31
|
// `gentle-stream ctl …` without taking the terminal.
|
|
@@ -411,6 +417,35 @@ test("accepting the offer installs the player with progress, then starts the mus
|
|
|
411
417
|
assert.deepEqual(spawned[0]!.args, ["--model", "anthropic/claude-sonnet-5-5", "--mood", "calm"]);
|
|
412
418
|
});
|
|
413
419
|
|
|
420
|
+
test("on Windows /gentle:music explains it is not available yet and never offers, installs or runs anything", async () => {
|
|
421
|
+
assert.equal(MUSIC_WINDOWS_UNSUPPORTED, "/gentle:music is not available on Windows yet: the gentle-stream player runs on macOS and Linux. It works when Gentle Shell runs inside WSL.");
|
|
422
|
+
for (const args of ["", "calm", "status", "stop"]) {
|
|
423
|
+
const { pi, commands } = fakePi();
|
|
424
|
+
const context = fakeContext();
|
|
425
|
+
const asked = withConfirm(context, true);
|
|
426
|
+
const { spawn, spawned } = fakeSpawn([{ code: 0 }], context.calls);
|
|
427
|
+
const { state, bundled } = fakeBundled({ installed: true });
|
|
428
|
+
gentleMusic(pi, { which: onPath({ "gentle-stream": "C:\\bin\\gentle-stream.exe", uvx: "C:\\bin\\uvx.exe" }), spawn, bundled, signals: fakeSignals().signals, platform: "win32" });
|
|
429
|
+
await commands.get(MUSIC_COMMAND_NAME)!.handler(args, context.ctx);
|
|
430
|
+
assert.deepEqual(context.notices, [[MUSIC_WINDOWS_UNSUPPORTED, "warning"]], args);
|
|
431
|
+
assert.deepEqual(asked, [], args);
|
|
432
|
+
assert.equal(state.installs, 0, args);
|
|
433
|
+
assert.equal(spawned.length, 0, args);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("on Windows the not-available notice wins over the interactive-terminal notice", async () => {
|
|
438
|
+
for (const mode of ["rpc", "json", "print"]) {
|
|
439
|
+
const { pi, commands } = fakePi();
|
|
440
|
+
const context = fakeContext(mode);
|
|
441
|
+
const { spawn, spawned } = fakeSpawn([{ code: 0 }], context.calls);
|
|
442
|
+
gentleMusic(pi, { which: onPath({ uvx: "C:\\bin\\uvx.exe" }), spawn, platform: "win32" });
|
|
443
|
+
await commands.get(MUSIC_COMMAND_NAME)!.handler("", context.ctx);
|
|
444
|
+
assert.deepEqual(context.notices, [[MUSIC_WINDOWS_UNSUPPORTED, "warning"]], mode);
|
|
445
|
+
assert.equal(spawned.length, 0, mode);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
414
449
|
test("an installed prefix player is used without asking; PATH gentle-stream still comes first", async () => {
|
|
415
450
|
const { bundled } = fakeBundled({ installed: true });
|
|
416
451
|
for (const [names, expected] of [[{}, "/prefix/music/bin/gentle-stream"], [{ uvx: "/bin/uvx" }, "/prefix/music/bin/gentle-stream"], [{ "gentle-stream": "/bin/gentle-stream" }, "/bin/gentle-stream"]] as const) {
|
|
@@ -306,8 +306,8 @@ test("/api/plan names the found and required version of an older Gentle Shell an
|
|
|
306
306
|
const view = await plan(port, await login());
|
|
307
307
|
assert.deepEqual(view.blockers.map((blocker: { code: string; tool: string }) => [blocker.code, blocker.tool]), [["incompatible-tool", "shell"]]);
|
|
308
308
|
assert.equal(view.blockers[0].guidance,
|
|
309
|
-
"Gentle Shell 3.4.0 is installed globally with pnpm, but this installer needs 4.5.
|
|
310
|
-
"Update it with `pnpm add -g gentle-pi@4.5.
|
|
309
|
+
"Gentle Shell 3.4.0 is installed globally with pnpm, but this installer needs 4.5.1 or newer. Nothing was replaced. " +
|
|
310
|
+
"Update it with `pnpm add -g gentle-pi@4.5.1`, or remove it with `pnpm remove -g gentle-pi`, then select Check again.");
|
|
311
311
|
assert.deepEqual(view.actions, []);
|
|
312
312
|
} finally {
|
|
313
313
|
await host.close("test");
|
|
@@ -1793,9 +1793,9 @@ test("pi-pretty wrapper uses cached ESM loading for compiled and pnpm symlink in
|
|
|
1793
1793
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1794
1794
|
});
|
|
1795
1795
|
|
|
1796
|
-
test("Gentle Shell v4.5.
|
|
1796
|
+
test("Gentle Shell v4.5.1 package manifest declares the release version", () => {
|
|
1797
1797
|
const packageJson = readPackageJson();
|
|
1798
|
-
assert.equal(packageJson.version, "4.5.
|
|
1798
|
+
assert.equal(packageJson.version, "4.5.1", "the release manifest must be explicitly pinned to v4.5.1");
|
|
1799
1799
|
assert.equal(packageJson.scripts?.test, "node scripts/run-test-suite.mjs");
|
|
1800
1800
|
assert.ok(packageJson.files?.includes("assets/"));
|
|
1801
1801
|
assert.ok(packageJson.files?.includes("contracts/"));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { __testing } from "../extensions/gentle-ai.ts";
|
|
4
|
+
|
|
5
|
+
// gentle-shell #55 / #57 / #125: the reply language is inferred every turn,
|
|
6
|
+
// never stored, so the persona prompt itself must say how to infer it. The
|
|
7
|
+
// rule lives on the single existing language-match bullet (the concept-level
|
|
8
|
+
// duplication guard allows exactly one), so these tests read the rendered
|
|
9
|
+
// prompt rather than a second rule.
|
|
10
|
+
|
|
11
|
+
for (const persona of ["gentleman", "neutral"] as const) {
|
|
12
|
+
test(`[${persona}] the reply language follows the user's latest message, never a Spanish default (#125, #57)`, () => {
|
|
13
|
+
const prompt = __testing.buildGentlePrompt(persona);
|
|
14
|
+
assert.match(prompt, /Always respond in the same language the user writes in\./);
|
|
15
|
+
assert.match(
|
|
16
|
+
prompt,
|
|
17
|
+
/latest message, never by earlier context or a compaction summary/,
|
|
18
|
+
`${persona} prompt must judge the reply language by the user's latest message, so a compaction summary cannot flip it (#57)`,
|
|
19
|
+
);
|
|
20
|
+
assert.match(
|
|
21
|
+
prompt,
|
|
22
|
+
/never default to Spanish: a message in English gets a reply in English/,
|
|
23
|
+
`${persona} prompt must forbid a Spanish default, so an English message never gets a Spanish reply (#125)`,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test("[neutral] the voseo prohibition covers regular and irregular forms, not only four irregular verbs (#55)", () => {
|
|
29
|
+
const prompt = __testing.buildGentlePrompt("neutral");
|
|
30
|
+
assert.match(prompt, /Do NOT use voseo/);
|
|
31
|
+
assert.match(prompt, /regular or irregular: use tú or usted forms, never vos forms/);
|
|
32
|
+
for (const pair of ["ajustas, not ajustás", "vives, not vivís", "mira, not mirá", "haz, not hacé"]) {
|
|
33
|
+
assert.ok(prompt.includes(pair), `neutral prompt must name the voseo replacement "${pair}"`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("[gentleman] Spanish replies keep Rioplatense voseo and get no neutral-only rule", () => {
|
|
38
|
+
const prompt = __testing.buildGentlePrompt("gentleman");
|
|
39
|
+
assert.match(prompt, /When the user writes Spanish, answer in natural Rioplatense Spanish with voseo\./);
|
|
40
|
+
assert.match(prompt, /Language: natural Rioplatense Spanish with voseo when the user writes Spanish\./);
|
|
41
|
+
assert.doesNotMatch(prompt, /never vos forms/, "gentleman prompt must not inherit the neutral voseo prohibition");
|
|
42
|
+
});
|