osdy-pi 1.11.8 → 1.11.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/extensions/osdy-pi/animation.test.ts +29 -2
- package/extensions/osdy-pi/constants.ts +48 -0
- package/extensions/osdy-pi/control-center-preferences.test.ts +4 -2
- package/extensions/osdy-pi/control-center-preferences.ts +5 -1
- package/extensions/osdy-pi/control-center.test.ts +16 -2
- package/extensions/osdy-pi/editor-settings.test.ts +13 -1
- package/extensions/osdy-pi/runtime.test.ts +10 -2
- package/extensions/osdy-pi/runtime.ts +1 -0
- package/extensions/osdy-pi/types.ts +1 -1
- package/extensions/osdy-pi/ui.test.ts +47 -1
- package/extensions/osdy-pi/working-child-activity.test.ts +16 -16
- package/extensions/osdy-pi/working-child-activity.ts +4 -4
- package/extensions/osdy-pi/working-controller.test.ts +27 -27
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Run **`/osdyConfig`** in interactive Pi to open the Osdy Control Center. The ten
|
|
|
63
63
|
- TODO shows configured selection, current eligibility, and actually loaded SDK registration separately. Opt-in/out requires an idle session and a Cancel-first inline confirmation naming the target, owned Gentle filter effects, and reload. Successful selection closes/disposes the panel and releases its editor hold before reloading once; restart Pi if reload fails. Rejections stay in the panel. No installs or task-history changes occur; legacy `/osdy-pi todo on|off|status` remains available.
|
|
64
64
|
- Agents shows concise **Joker / Gentle** choices without activating anything. With detail focus, visible lowercase-only **j Joker / g Gentle** letters open the existing confirmation directly; only available, unambiguous rows get shortcuts. **↑/↓ + Enter** remains the fallback. Profile and Agent selections survive category navigation; unavailable choices fall back to the current row. Letters never bypass category focus, confirmation, path input, loading, querying, or saving. Selection is idle-only and Cancel-first confirmed, naming the real target, possible Joker installation, owned agent filters, and preserved unrelated resources. Normal personal Pi only: `pi:dev` and isolated/override sessions are blocked, never redirected to personal settings. Success closes/disposes the panel and releases its editor hold before reloading once; restart Pi if reload fails. Legacy `/osdy-pi agents setup|on|off|status` is unchanged.
|
|
65
65
|
- Theme lists Pi's available themes and marks the current theme. Explicit **Page x/N** pages contain at most **8 themes**, fewer in short viewports or with expanded help. **Page Up/Down** changes pages, **Home/End** reaches the first/last theme, and arrows cross page boundaries. Selection survives category navigation and resizing; resizing adjusts the page around the selected theme. A successful selection applies live and saves globally in the active Pi agent directory; project settings can override it at startup. Failures appear in the modal.
|
|
66
|
-
- Header offers **osdy-theme / neon**; Mascot offers **current / Bts**; Editor offers **auto / extended / simple**, with the current preference marked. Editor also shows the effective mode: small terminals stay simple/native even with extended selected.
|
|
66
|
+
- Header offers **osdy-theme / neon**; Mascot offers **current / Bts / Osdy-Halloween**; Editor offers **auto / extended / simple**, with the current preference marked. Editor also shows the effective mode: small terminals stay simple/native even with extended selected.
|
|
67
67
|
- Preferences save immediately through the same runtime and global Osdy settings store as `/osdy-pi` commands (`<agent-dir>/extensions/osdy-pi/settings.json`). Header/Mascot apply live without closing the panel. Editor replacement waits until all overlays close; if another overlay remains, the next existing responsive watcher tick applies the latest choice. The effective mode reports the actually mounted editor while queued. Opening a category only reads values; while Osdy is disabled, choices save for later without enabling it.
|
|
68
68
|
- A selection shows **Saving** until persistence finishes and **Saved globally** only on success. Failed saves retain the live preference but report **could not be saved**; select again to retry. Application errors report **Not saved**, without claiming the runtime change succeeded. Duplicate selections are ignored while saving.
|
|
69
69
|
- **Git** shows the current branch (or detached HEAD), changed-entry count/clean status, and the current widget position as a read-only session detail. Working-tree enablement applies and saves through the same owner as `/osdy-pi working-tree`; Git inspection never writes Git. `/osdy-pi diff` and position commands are unchanged.
|
|
@@ -300,7 +300,7 @@ Before rename or removal, close this Pi process when it uses the target and **ma
|
|
|
300
300
|
| Area | Included behavior |
|
|
301
301
|
| --- | --- |
|
|
302
302
|
| Themes | 23 built-in themes, including Osdy, Kanagawa, Dracula, Gruvbox, Nord, Rosé Pine, Daniela Cute, Halloween, Halloween Killer, Catppuccin, Matrix, Lucent Orange, and three Spider-Man-inspired palettes |
|
|
303
|
-
| Header and mascot | Independently selectable `osdy-theme`/`neon` headers and `current`/`bts` mascots |
|
|
303
|
+
| Header and mascot | Independently selectable `osdy-theme`/`neon` headers and `current`/`bts`/`osdy-halloween` mascots |
|
|
304
304
|
| Messages | Native colored user cards without emojis and plain assistant Markdown with a separate assistant `🦝` row |
|
|
305
305
|
| Input | Responsive auto editor by default, with selectable simple Pi-native or extended framed modes |
|
|
306
306
|
| Status | Six activity labels with compact per-state glyphs and a shared theme-color pulse, responsive footer metrics, dynamic extension statuses, and Codex subscription quota with low-capacity emphasis |
|
|
@@ -366,10 +366,12 @@ Header and mascot choices are independent:
|
|
|
366
366
|
| Element | Choices | Command |
|
|
367
367
|
| --- | --- | --- |
|
|
368
368
|
| Header | `osdy-theme` (default), `neon` | `/osdy-pi header osdy-theme|neon` |
|
|
369
|
-
| Mascot | `current` (default), `bts` | `/osdy-pi mascot current
|
|
369
|
+
| Mascot | `current` (default), `bts`, `osdy-halloween` | `/osdy-pi mascot current\|bts\|osdy-halloween` |
|
|
370
370
|
|
|
371
371
|
Both commands update the UI immediately. Add `status` instead of a choice to inspect the current selection. Choices persist in the global Osdy Pi settings across reloads and sessions. The old top-level style commands and `classic` header aliases are no longer registered.
|
|
372
372
|
|
|
373
|
+
Choose **Osdy-Halloween** in `/osdyConfig` → **Mascot**, or run `/osdy-pi mascot osdy-halloween`. It dresses the same Current raccoon in ivory fangs and a high-collared dark/crimson vampire cape. Its glow follows the active theme's `accent`, `mdHeading`, and `mdLink` colors; no Halloween theme is required. Current remains the default, and Current/Bts artwork and version-1 settings compatibility are unchanged.
|
|
374
|
+
|
|
373
375
|
The header also shows **`osdy-pi v<version>`** at the top right in the theme's accent color, read from the installed Osdy package—not Pi's host version or the workspace manifest. It uses an empty first row or adds a row without overwriting artwork; invalid/unreadable versions or labels wider than the terminal are omitted.
|
|
374
376
|
|
|
375
377
|
In normal mode, the selected header and mascot render side by side when both fit. The Neon header derives its highlights from the active theme rather than using one fixed palette. Each mascot keeps its own tone map while its animated edge glow can resolve through theme colors. Header and mascot scaling remain independent, so any combination follows the same responsive layout rules.
|
|
@@ -407,7 +409,7 @@ The enabled state, editor mode, working-tree visibility preference, header, and
|
|
|
407
409
|
| TODO selection | `/osdy-pi todo on\|off\|status` |
|
|
408
410
|
| Session TODO (opt-in, starting in 1.5.0) | `/todos` (read-only) |
|
|
409
411
|
| Header | `/osdy-pi header osdy-theme\|neon\|status` |
|
|
410
|
-
| Mascot | `/osdy-pi mascot current\|bts\|status` |
|
|
412
|
+
| Mascot | `/osdy-pi mascot current\|bts\|osdy-halloween\|status` |
|
|
411
413
|
| Editor | `/osdy-pi editor auto\|extended\|simple\|on\|off\|toggle\|status` |
|
|
412
414
|
| Working tree | `/osdy-pi working-tree on\|off\|toggle\|status` |
|
|
413
415
|
| Working tree | `/osdy-pi working-tree position top\|bottom\|status` |
|
|
@@ -492,14 +494,14 @@ While work is active, Osdy Pi centers an activity label and compact animated gly
|
|
|
492
494
|
|
|
493
495
|
| Source | Example | Meaning |
|
|
494
496
|
| --- | --- | --- |
|
|
495
|
-
| Validated public `agent` argument | `
|
|
496
|
-
| Public foreground progress from Joker (`pi-subagents-j0k3r`) | `
|
|
497
|
+
| Validated public `agent` argument | `Working...` | Generic fallback, not measured progress or a claimed editing operation. This provider-neutral fallback works with Gentle Agents, which currently emits no live child progress. |
|
|
498
|
+
| Public foreground progress from Joker (`pi-subagents-j0k3r`) | `Exploring...` | Named `read` activity selects both the Exploring label and its existing glyph frames, overriding fallback. |
|
|
497
499
|
|
|
498
|
-
Live child activity uses the same six states and corresponding animated glyphs listed above,
|
|
500
|
+
Live child activity uses the same six states and corresponding animated glyphs listed above, without agent names: `Thinking...`, `Exploring...`, `Verifying...`, `Working...`, `Delegating...`, or `Executing...`. Both public thinking and response streaming select Thinking; model labels, reasoning and response text are never read. Named reads/searches/direct documentation tools select Exploring, `lens_diagnostics` selects Verifying, edits/writes select Working, and `subagent_run` selects Delegating. Only named checks qualify: public tool metadata lacks arguments, so `bash`, wrappers needing selectors, and unknown tool names select Executing. Tool completion/failure events classify the reported operation while the child is running; they do not claim whole-task completion.
|
|
499
501
|
|
|
500
|
-
The fallback is
|
|
502
|
+
The fallback is `Working...` **with the Working glyph**, regardless of optional `label` metadata. Missing or empty tool names supply no usable operation; named unknown tools instead select Executing. A continuation with an absent agent validates the generic `Subagent` identity without displaying it, never inferring an identity; missing/invalid run identity or explicitly invalid continuation identity fails closed to ordinary parent activity unless a live snapshot supplies its own validated public agent. Full tasks, prompts, context, content, trails and transcripts are never read or displayed.
|
|
501
503
|
|
|
502
|
-
For multiple running children, the indicator uses the first child in snapshot order with a compact additional-child count. Missing, malformed, terminal or no-running-child progress restores both the
|
|
504
|
+
For multiple running children, the indicator uses the first child in snapshot order with a compact additional-child count (`(+N)`). Missing, malformed, terminal or no-running-child progress restores both the generic `Working...` fallback text and Working glyph, or ordinary parent activity when validated agent identity is unavailable. Labels are bounded, control-safe and clipped to terminal width. Matching tool end, agent/session end and reload cleanup discard per-call state.
|
|
503
505
|
|
|
504
506
|
**Background limitation:** agent fallbacks appear only while their parent call is active. There is no persistent background live feed, polling or task cache.
|
|
505
507
|
|
|
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { registerHooks } from "node:module";
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
// @ts-expect-error Node's native TypeScript runner resolves test-only TypeScript source imports.
|
|
5
|
-
import { HEADER_VARIANTS } from "./constants.ts";
|
|
5
|
+
import { HEADER_VARIANTS, mascotForChoice } from "./constants.ts";
|
|
6
6
|
import type { SimpleTheme } from "./types.js";
|
|
7
7
|
|
|
8
8
|
registerHooks({
|
|
@@ -19,7 +19,7 @@ registerHooks({
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
// @ts-expect-error Node's native TypeScript runner resolves test-only TypeScript source imports.
|
|
22
|
-
const { colorAsciiCharacter } = await import("./animation.ts");
|
|
22
|
+
const { colorAsciiCharacter, animateAsciiLineWithToneMap } = await import("./animation.ts");
|
|
23
23
|
|
|
24
24
|
class RecordingTheme implements SimpleTheme {
|
|
25
25
|
readonly calls: Array<{ name: string; text: string }> = [];
|
|
@@ -74,6 +74,33 @@ void test("Neon renders main and dark pink through bounded accent styling", () =
|
|
|
74
74
|
assert.equal(`${main}${dark.join("")}`.includes("\u001B[48;"), false);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
void test("Osdy-Halloween resolves edge glow against the live theme in static and animated frames", () => {
|
|
78
|
+
const { art, tonePalette } = mascotForChoice("osdy-halloween");
|
|
79
|
+
assert.deepEqual([tonePalette.p, tonePalette.c, tonePalette.v], ["accent", "mdHeading", "mdLink"]);
|
|
80
|
+
let themeId = "first";
|
|
81
|
+
const calls: string[] = [];
|
|
82
|
+
const theme: SimpleTheme = { fg(name, text) { calls.push(name); return `<${themeId}:${name}:${text}>`; } };
|
|
83
|
+
for (const style of ["static", "animated"] as const) {
|
|
84
|
+
for (const frame of [0, 7, 28]) {
|
|
85
|
+
const render = () => art.mascot.map((line, row) =>
|
|
86
|
+
animateAsciiLineWithToneMap(line, art.toneMap[row] ?? "", row, frame, theme, tonePalette, style)).join("\n");
|
|
87
|
+
themeId = "first";
|
|
88
|
+
const first = render();
|
|
89
|
+
themeId = "second";
|
|
90
|
+
const second = render();
|
|
91
|
+
assert.notEqual(first, second);
|
|
92
|
+
assert.equal(first.replaceAll("first:", "second:"), second, "only theme-resolved colors change");
|
|
93
|
+
assert.ok(second.includes("\u001B[38;2;"), "fixed body/costume foreground colors remain");
|
|
94
|
+
assert.ok(!second.includes("\u001B[48;"), "transparent background");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
assert.deepEqual([...new Set(calls)].sort(), ["accent", "mdHeading", "mdLink"].sort());
|
|
98
|
+
for (let row = 0; row < art.mascot.length; row++) {
|
|
99
|
+
const occupied = Array.from(art.mascot[row] ?? "").flatMap((glyph, column) => glyph === " " ? [] : [column]);
|
|
100
|
+
assert.deepEqual(occupied.slice(-3).reverse().map((column) => art.toneMap[row]?.[column]), ["p", "c", "v"].slice(0, occupied.length));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
77
104
|
void test("Neon never maps a tone to mdHeading or mdLink", () => {
|
|
78
105
|
for (const color of Object.values(neonPalette())) {
|
|
79
106
|
if (typeof color === "string") {
|
|
@@ -457,6 +457,52 @@ function addRightEdgeGlow(art: MascotArt): MascotArt {
|
|
|
457
457
|
};
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
+
// Transparent overlay at Current's mouth/shoulders (row 17 onward).
|
|
461
|
+
// ▓ = dark cape, ▒ = crimson lining, █/▼ = ivory fangs. Keep the face and tail.
|
|
462
|
+
const VAMPIRE_COSTUME = [
|
|
463
|
+
" ▓ ▓",
|
|
464
|
+
" ▓▒▓ █ █ ▓▒▒▓",
|
|
465
|
+
" ▓▒▒▒▓ ▼ ▼ ▓▒▒▒▒▓",
|
|
466
|
+
" ▓▒▒▒▒▒▒▓ ▓▒▒▒▒▒▒▓",
|
|
467
|
+
" ▓▓▒▒▒▒▒▓▓ ▓▓▒▒▒▒▒▓▓",
|
|
468
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▒▓▓▓",
|
|
469
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▓▓▓",
|
|
470
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▒▓▓",
|
|
471
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▓▓",
|
|
472
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▓▓",
|
|
473
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▓▓",
|
|
474
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▓▓",
|
|
475
|
+
" ▓▓▓▒▒▒▓▓ ▓▓▒▒▓▓",
|
|
476
|
+
" ▓▓▓▓▒▒▓▓ ▓▓▒▒▓▓",
|
|
477
|
+
" ▓▓▓▓▒▒▓▓ ▓▓▒▓▓",
|
|
478
|
+
" ▓▓▓▓▓▓ ▓▓▓",
|
|
479
|
+
] as const;
|
|
480
|
+
|
|
481
|
+
function addVampireCostume(art: MascotArt): MascotArt {
|
|
482
|
+
const rows = art.mascot.map((line, row) => {
|
|
483
|
+
const glyphs = Array.from(line);
|
|
484
|
+
// Reserve b for lining; retain the few original eye glints in ivory h.
|
|
485
|
+
const tones = Array.from((art.toneMap[row] ?? "").replaceAll("b", "h"));
|
|
486
|
+
for (const [column, glyph] of Array.from(VAMPIRE_COSTUME[row - 17] ?? "").entries()) {
|
|
487
|
+
if (glyph === " ") continue;
|
|
488
|
+
glyphs[column] = glyph;
|
|
489
|
+
if (glyph === "▓") tones[column] = "d";
|
|
490
|
+
else if (glyph === "▒") tones[column] = "b";
|
|
491
|
+
else tones[column] = "h";
|
|
492
|
+
}
|
|
493
|
+
return { mascot: glyphs.join(""), toneMap: tones.join("") };
|
|
494
|
+
});
|
|
495
|
+
return { mascot: rows.map((row) => row.mascot), toneMap: rows.map((row) => row.toneMap) };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const HALLOWEEN_MASCOT_TONES: MascotTonePalette = {
|
|
499
|
+
...HTML_MASCOT_TONES,
|
|
500
|
+
b: "#9F2342",
|
|
501
|
+
};
|
|
502
|
+
const HALLOWEEN_MASCOT = addRightEdgeGlow(addVampireCostume({
|
|
503
|
+
mascot: HTML_MASCOT,
|
|
504
|
+
toneMap: HTML_MASCOT_MAP,
|
|
505
|
+
}));
|
|
460
506
|
const ROSE_MASCOT = addRightEdgeGlow({
|
|
461
507
|
mascot: HTML_MASCOT,
|
|
462
508
|
toneMap: HTML_MASCOT_MAP,
|
|
@@ -468,6 +514,7 @@ const BTS_MASCOT_ART = addRightEdgeGlow({
|
|
|
468
514
|
for (const [art, palette] of [
|
|
469
515
|
[ROSE_MASCOT, HTML_MASCOT_TONES],
|
|
470
516
|
[BTS_MASCOT_ART, BTS_MASCOT_TONES],
|
|
517
|
+
[HALLOWEEN_MASCOT, HALLOWEEN_MASCOT_TONES],
|
|
471
518
|
] as const) {
|
|
472
519
|
assertRawHexPalette(palette);
|
|
473
520
|
assertMascotStructure(art, palette);
|
|
@@ -481,6 +528,7 @@ export type MascotConfig = {
|
|
|
481
528
|
export const MASCOTS: Record<MascotChoice, MascotConfig> = {
|
|
482
529
|
current: { art: ROSE_MASCOT, tonePalette: HTML_MASCOT_TONES },
|
|
483
530
|
bts: { art: BTS_MASCOT_ART, tonePalette: BTS_MASCOT_TONES },
|
|
531
|
+
"osdy-halloween": { art: HALLOWEEN_MASCOT, tonePalette: HALLOWEEN_MASCOT_TONES },
|
|
484
532
|
};
|
|
485
533
|
|
|
486
534
|
export function mascotForChoice(choice: MascotChoice): MascotConfig {
|
|
@@ -21,7 +21,7 @@ const snapshot: VisualPreferenceSnapshot = { enabled: true, headerVariant: "osdy
|
|
|
21
21
|
void test("preference rows expose every catalog value and exactly one current indicator", () => {
|
|
22
22
|
for (const [category, field, values] of [
|
|
23
23
|
["Header", "headerVariant", ["osdy-theme", "neon"]],
|
|
24
|
-
["Mascot", "mascot", ["current", "bts"]],
|
|
24
|
+
["Mascot", "mascot", ["current", "bts", "osdy-halloween"]],
|
|
25
25
|
["Editor", "editorMode", ["auto", "extended", "simple"]],
|
|
26
26
|
] as const) {
|
|
27
27
|
for (const value of values) {
|
|
@@ -30,7 +30,9 @@ void test("preference rows expose every catalog value and exactly one current in
|
|
|
30
30
|
assert.deepEqual(detail.rows.map((row) => "value" in row.action ? row.action.value : undefined), values);
|
|
31
31
|
assert.equal(detail.rows.filter((row) => row.current).length, 1);
|
|
32
32
|
assert.equal(detail.rows.find((row) => row.current)?.action.kind, category.toLowerCase());
|
|
33
|
-
|
|
33
|
+
const label = value === "bts" ? "Bts" : value === "osdy-halloween" ? "Osdy-Halloween" : value;
|
|
34
|
+
assert.match(detail.summary, new RegExp(`Current: ${label}`));
|
|
35
|
+
assert.equal(detail.rows.find((row) => row.current)?.label, label);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
});
|
|
@@ -17,7 +17,11 @@ export interface ControlCenterPreferences {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function visualPreferenceLabel(action: VisualPreferenceAction): string {
|
|
20
|
-
|
|
20
|
+
if (action.kind === "mascot") {
|
|
21
|
+
if (action.value === "bts") return "Bts";
|
|
22
|
+
if (action.value === "osdy-halloween") return "Osdy-Halloween";
|
|
23
|
+
}
|
|
24
|
+
return action.value;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/** Explicit view adaptation only; application and persistence belong to runtime. */
|
|
@@ -336,7 +336,7 @@ void test("inline preferences show all supported values and live current/effecti
|
|
|
336
336
|
const f = preferenceFixture();
|
|
337
337
|
for (const [category, choices, current] of [
|
|
338
338
|
["Header", ["osdy-theme", "neon"], "osdy-theme"],
|
|
339
|
-
["Mascot", ["current", "Bts"], "current"],
|
|
339
|
+
["Mascot", ["current", "Bts", "Osdy-Halloween"], "current"],
|
|
340
340
|
["Editor", ["auto", "extended", "simple"], "auto"],
|
|
341
341
|
] as const) {
|
|
342
342
|
f.panel.handleInput(down);
|
|
@@ -350,6 +350,20 @@ void test("inline preferences show all supported values and live current/effecti
|
|
|
350
350
|
assert.deepEqual(f.applied, []);
|
|
351
351
|
});
|
|
352
352
|
|
|
353
|
+
void test("Mascot selects Osdy-Halloween and reports its exact saved label", async () => {
|
|
354
|
+
const f = preferenceFixture();
|
|
355
|
+
f.panel.handleInput(down);
|
|
356
|
+
f.panel.handleInput(down);
|
|
357
|
+
f.panel.handleInput(right);
|
|
358
|
+
f.panel.handleInput("\x1b[F");
|
|
359
|
+
f.panel.handleInput("\r");
|
|
360
|
+
assert.deepEqual(f.applied, [{ kind: "mascot", value: "osdy-halloween" }]);
|
|
361
|
+
assert.match(f.panel.render(100).join("\n"), /Saving.*Osdy-Halloween/);
|
|
362
|
+
f.complete(true);
|
|
363
|
+
await Promise.resolve();
|
|
364
|
+
assert.match(f.panel.render(100).join("\n"), /Saved globally: Osdy-Halloween/);
|
|
365
|
+
});
|
|
366
|
+
|
|
353
367
|
void test("changing categories resets selection to their current row, even after a longer theme list", () => {
|
|
354
368
|
const f = preferenceFixture();
|
|
355
369
|
f.panel.handleInput(right);
|
|
@@ -427,7 +441,7 @@ void test("inline preference rendering stays bounded at narrow widths and differ
|
|
|
427
441
|
f.panel.handleInput(down);
|
|
428
442
|
f.panel.handleInput(right);
|
|
429
443
|
f.panel.handleInput("\x1b[F");
|
|
430
|
-
assert.match(f.panel.render(48).join("\n"), new RegExp(`> ${["neon", "
|
|
444
|
+
assert.match(f.panel.render(48).join("\n"), new RegExp(`> ${["neon", "Osdy-Halloween", "simple"][category]}`));
|
|
431
445
|
for (const height of [1, 4, 7, 9, 18]) {
|
|
432
446
|
f.resize(height);
|
|
433
447
|
for (const width of [1, 8, 24, 48, 80]) {
|
|
@@ -97,7 +97,7 @@ void test("editor settings normalize only supported versioned modes", () => {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
void test("mascot choices preserve the version-one current default, migrate raccoon to Bts, and persist Bts", async () => {
|
|
100
|
-
assert.deepEqual(MASCOT_CHOICES, ["current", "bts"]);
|
|
100
|
+
assert.deepEqual(MASCOT_CHOICES, ["current", "bts", "osdy-halloween"]);
|
|
101
101
|
assert.equal(
|
|
102
102
|
normalizeEditorSettings({ version: 1, mascot: "invalid" }).mascot,
|
|
103
103
|
"current",
|
|
@@ -129,6 +129,18 @@ void test("mascot choices preserve the version-one current default, migrate racc
|
|
|
129
129
|
assert.equal((await store.load()).mascot, "bts");
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
void test("Osdy-Halloween round-trips through a fresh version-one settings store", async () => {
|
|
133
|
+
const fileSystem = new MemoryEditorSettingsFileSystem();
|
|
134
|
+
const path = "/agent/extensions/osdy-pi/settings.json";
|
|
135
|
+
const settings = normalizeEditorSettings({ version: 1, mascot: "osdy-halloween" });
|
|
136
|
+
assert.equal(settings.mascot, "osdy-halloween");
|
|
137
|
+
await createEditorSettingsStore(path, fileSystem).save(settings);
|
|
138
|
+
const persisted: unknown = JSON.parse(fileSystem.content ?? "{}");
|
|
139
|
+
assert.deepEqual(persisted, { ...settings, version: 1 });
|
|
140
|
+
assert.deepEqual(await createEditorSettingsStore(path, fileSystem).load(), settings);
|
|
141
|
+
assert.equal(normalizeEditorSettings({ version: 2, mascot: "osdy-halloween" }).mascot, "current");
|
|
142
|
+
});
|
|
143
|
+
|
|
132
144
|
void test("enabled defaults safely and persists globally", async () => {
|
|
133
145
|
assert.equal(
|
|
134
146
|
normalizeEditorSettings({ version: 1, editorMode: "simple" }).enabled,
|
|
@@ -152,6 +152,7 @@ void test("shared visual actions apply immediately, save the complete settings,
|
|
|
152
152
|
for (const enabled of [true, false]) {
|
|
153
153
|
for (const action of [
|
|
154
154
|
{ kind: "header", value: "neon" }, { kind: "mascot", value: "bts" },
|
|
155
|
+
{ kind: "mascot", value: "osdy-halloween" },
|
|
155
156
|
{ kind: "editor", value: "simple" }, { kind: "editor", value: "extended" },
|
|
156
157
|
{ kind: "editor", value: "auto" },
|
|
157
158
|
] as const satisfies readonly VisualPreferenceAction[]) {
|
|
@@ -903,6 +904,7 @@ void test("registered legacy commands and osdyConfig share live values and exist
|
|
|
903
904
|
for (const [args, expected, field, value] of [
|
|
904
905
|
["header neon", "osdy-pi header: neon", "headerVariant", "neon"],
|
|
905
906
|
["mascot bts", "osdy-pi mascot: Bts", "mascot", "bts"],
|
|
907
|
+
["mascot osdy-halloween", "osdy-pi mascot: Osdy-Halloween", "mascot", "osdy-halloween"],
|
|
906
908
|
["editor off", "osdy-pi editor mode: simple", "editorMode", "simple"],
|
|
907
909
|
["editor on", "osdy-pi editor mode: extended", "editorMode", "extended"],
|
|
908
910
|
["editor toggle", "osdy-pi editor mode: simple", "editorMode", "simple"],
|
|
@@ -916,7 +918,9 @@ void test("registered legacy commands and osdyConfig share live values and exist
|
|
|
916
918
|
assert.equal(writes.at(-1)?.workingTreeEnabled, false);
|
|
917
919
|
assert.equal(notices.at(-1)?.message, "osdy-pi working tree disabled");
|
|
918
920
|
const before = writes.length;
|
|
919
|
-
|
|
921
|
+
await legacy.handler("mascot status", ctx);
|
|
922
|
+
assert.deepEqual(notices.at(-1), { message: "osdy-pi mascot: Osdy-Halloween", level: "info" });
|
|
923
|
+
for (const args of ["working-tree status", "header status", "mascot status", "editor status", "header invalid", "mascot bts extra", "mascot osdy-halloween extra", "mascot invalid", "editor simple extra"]) await legacy.handler(args, ctx);
|
|
920
924
|
assert.equal(writes.length, before, "status and invalid inputs never save");
|
|
921
925
|
const beforeModalGitCalls = gitCalls.length;
|
|
922
926
|
const beforeModalNotices = notices.length;
|
|
@@ -1348,15 +1352,19 @@ void test("uninstall command is offered and wired through guarded Pi CLI removal
|
|
|
1348
1352
|
assert.match(source, /pi\.exec\("pi", \["remove", source/);
|
|
1349
1353
|
});
|
|
1350
1354
|
|
|
1351
|
-
void test("mascot command completes
|
|
1355
|
+
void test("mascot command completes all three choices and status while persisting with immediate refresh", () => {
|
|
1352
1356
|
assert.deepEqual(getOsdyCommandCompletions("mascot"), [
|
|
1353
1357
|
{ value: "mascot current", label: "mascot current" },
|
|
1354
1358
|
{ value: "mascot bts", label: "mascot bts" },
|
|
1359
|
+
{ value: "mascot osdy-halloween", label: "mascot osdy-halloween" },
|
|
1355
1360
|
{ value: "mascot status", label: "mascot status" },
|
|
1356
1361
|
]);
|
|
1357
1362
|
assert.deepEqual(getOsdyCommandCompletions("mascot b"), [
|
|
1358
1363
|
{ value: "mascot bts", label: "mascot bts" },
|
|
1359
1364
|
]);
|
|
1365
|
+
assert.deepEqual(getOsdyCommandCompletions("mascot o"), [
|
|
1366
|
+
{ value: "mascot osdy-halloween", label: "mascot osdy-halloween" },
|
|
1367
|
+
]);
|
|
1360
1368
|
assert.deepEqual(getOsdyCommandCompletions("mascot d"), []);
|
|
1361
1369
|
|
|
1362
1370
|
const source = readFileSync(new URL("./runtime.ts", import.meta.url), "utf8");
|
|
@@ -44,7 +44,7 @@ export const HEADER_VARIANT_CHOICES = ["osdy-theme", "neon"] as const;
|
|
|
44
44
|
|
|
45
45
|
export type HeaderVariant = (typeof HEADER_VARIANT_CHOICES)[number];
|
|
46
46
|
|
|
47
|
-
export const MASCOT_CHOICES = ["current", "bts"] as const;
|
|
47
|
+
export const MASCOT_CHOICES = ["current", "bts", "osdy-halloween"] as const;
|
|
48
48
|
|
|
49
49
|
export type MascotChoice = (typeof MASCOT_CHOICES)[number];
|
|
50
50
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { registerHooks } from "node:module";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -244,12 +245,57 @@ void test("selected headers render through foreground-only tone-map animation",
|
|
|
244
245
|
void test("mascot selection wires Bts art through the responsive renderer", () => {
|
|
245
246
|
const ui = readFileSync(new URL("./ui.ts", import.meta.url), "utf8");
|
|
246
247
|
|
|
247
|
-
assert.deepEqual(Object.keys(MASCOTS), ["current", "bts"]);
|
|
248
|
+
assert.deepEqual(Object.keys(MASCOTS), ["current", "bts", "osdy-halloween"]);
|
|
248
249
|
assert.ok(mascotForChoice("bts"));
|
|
249
250
|
assert.match(ui, /const mascot = mascotForChoice\(state\.mascot\);/);
|
|
250
251
|
assert.match(ui, /scaleMascot\(\s*mascot\.art,/);
|
|
251
252
|
});
|
|
252
253
|
|
|
254
|
+
void test("Current and Bts retain their original glyphs, tone maps, and palettes", () => {
|
|
255
|
+
for (const [choice, fingerprint] of [
|
|
256
|
+
["current", "5d399dc6d6cf7f88dce300ca224d157e23e21186b1991a4f57b65fe60c34d8ac"],
|
|
257
|
+
["bts", "e2040efe00cf709b799834d5183ddae3273cfb515b8dfa0f63ae1b9c5def71ec"],
|
|
258
|
+
] as const) {
|
|
259
|
+
assert.equal(createHash("sha256").update(JSON.stringify(MASCOTS[choice])).digest("hex"), fingerprint);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
void test("Osdy-Halloween preserves Current's face and tail beneath a bounded vampire costume", () => {
|
|
264
|
+
const { art, tonePalette } = mascotForChoice("osdy-halloween");
|
|
265
|
+
const current = MASCOTS.current.art;
|
|
266
|
+
assert.deepEqual(art.mascot.slice(0, 17), current.mascot.slice(0, 17), "same ears, mask, eyes and snout");
|
|
267
|
+
for (let row = 21; row < current.mascot.length; row++) {
|
|
268
|
+
assert.equal(art.mascot[row]?.slice(44), current.mascot[row]?.slice(44), "original striped tail");
|
|
269
|
+
}
|
|
270
|
+
assert.notDeepEqual(art.mascot.slice(17), current.mascot.slice(17));
|
|
271
|
+
const fangs = art.mascot.slice(17, 20).join("\n");
|
|
272
|
+
assert.match(fangs, /▼[^▼\n]+▼/, "two ivory fang tips separated by the original muzzle");
|
|
273
|
+
assert.equal([...fangs].filter((glyph) => glyph === "▼").length, 2, "one-column fang tips");
|
|
274
|
+
assert.equal([...fangs].filter((glyph) => glyph === "█").length, 2, "one-column stems keep fangs two rows tall");
|
|
275
|
+
assert.equal(art.mascot.length, current.mascot.length);
|
|
276
|
+
assert.deepEqual(art.mascot.map((row) => row.length), current.mascot.map((row) => row.length));
|
|
277
|
+
assert.equal(art.toneMap[17]?.[8], "d", "raised collar has a dark outer edge");
|
|
278
|
+
assert.equal(art.toneMap[20]?.[12], "b", "collar has crimson lining");
|
|
279
|
+
assert.equal(tonePalette.b, "#9F2342");
|
|
280
|
+
assert.equal(tonePalette.d, MASCOTS.current.tonePalette.d);
|
|
281
|
+
for (const [width, rows] of [[100, 100], [48, 28], [32, 20], [1, 1]] as const) {
|
|
282
|
+
const scaled = scaleMascot(art, width, rows);
|
|
283
|
+
assert.ok(scaled.mascot.length <= rows);
|
|
284
|
+
assert.equal(scaled.mascot.length, scaled.toneMap.length);
|
|
285
|
+
if (width >= 32) assert.match(scaled.mascot.join("\n"), /▼+[^▼\n]+▼+/, "both fangs survive responsive sampling");
|
|
286
|
+
for (let row = 0; row < scaled.mascot.length; row++) {
|
|
287
|
+
const glyphs = Array.from(scaled.mascot[row] ?? "");
|
|
288
|
+
const tones = Array.from(scaled.toneMap[row] ?? "");
|
|
289
|
+
assert.ok(visibleWidth(scaled.mascot[row] ?? "") <= width);
|
|
290
|
+
assert.equal(glyphs.length, tones.length);
|
|
291
|
+
for (let column = 0; column < glyphs.length; column++) {
|
|
292
|
+
if (glyphs[column] !== " ") assert.ok(Object.keys(tonePalette).includes(tones[column] ?? ""));
|
|
293
|
+
if (glyphs[column] === "▼") assert.equal(tones[column], "h", "fangs retain ivory rather than glow/lining tones");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
253
299
|
void test("Bts retains the 60 by 26 raccoon glyph structure and transparent tone cells", () => {
|
|
254
300
|
const art = MASCOTS.bts.art;
|
|
255
301
|
assert.equal(art.mascot.length, 26);
|
|
@@ -9,24 +9,24 @@ registerHooks({ resolve(specifier, context, nextResolve) {
|
|
|
9
9
|
return nextResolve(specifier, context);
|
|
10
10
|
} });
|
|
11
11
|
const { childActivityState: activity, childAssignmentState: assignment } = await import("./working-child-activity.js");
|
|
12
|
-
const working = (
|
|
12
|
+
const working = () => ({ activity: "working", label: "Working..." });
|
|
13
13
|
|
|
14
14
|
void test("fallback uses only validated public agent identity regardless of optional label", () => {
|
|
15
|
-
assert.deepEqual(assignment("subagent_run", { agent: "explore", label: "map footer data" }), working(
|
|
15
|
+
assert.deepEqual(assignment("subagent_run", { agent: "explore", label: "map footer data" }), working());
|
|
16
16
|
for (const args of [undefined, null, [], "scout", {}, { agent: 1 }, { agent: "\u0000\n" }, { agent: "a".repeat(4097) }, { label: "secret" }])
|
|
17
17
|
assert.equal(assignment("subagent_run", args), undefined);
|
|
18
18
|
for (const label of [undefined, null, 1, [], "", "\u0000", "x".repeat(4097)])
|
|
19
|
-
assert.deepEqual(assignment("subagent_run", { agent: "scout", label, task: "SECRET", prompt: "SECRET", context: "SECRET" }), working(
|
|
19
|
+
assert.deepEqual(assignment("subagent_run", { agent: "scout", label, task: "SECRET", prompt: "SECRET", context: "SECRET" }), working());
|
|
20
20
|
const metadata = { agent: "scout", get label() { throw new Error("label must not be read"); },
|
|
21
21
|
get task() { throw new Error("task must not be read"); }, get prompt() { throw new Error("prompt must not be read"); },
|
|
22
22
|
get context() { throw new Error("context must not be read"); } };
|
|
23
|
-
assert.deepEqual(assignment("subagent_run", metadata), working(
|
|
23
|
+
assert.deepEqual(assignment("subagent_run", metadata), working());
|
|
24
24
|
assert.equal(assignment("read", { agent: "scout" }), undefined);
|
|
25
|
-
assert.deepEqual(assignment("subagent_continue", { id: "scout" }), working(
|
|
25
|
+
assert.deepEqual(assignment("subagent_continue", { id: "scout" }), working());
|
|
26
26
|
for (const agent of [42, null, "", "\u0000"])
|
|
27
27
|
assert.equal(assignment("subagent_continue", { agent }), undefined);
|
|
28
|
-
assert.deepEqual(assignment("subagent_run", { agent: "\u001b[31m探索\u001b[0m\n\u202eagent" }), working(
|
|
29
|
-
assert.deepEqual(assignment("subagent_run", { agent: "🙂".repeat(1000) }), working(
|
|
28
|
+
assert.deepEqual(assignment("subagent_run", { agent: "\u001b[31m探索\u001b[0m\n\u202eagent" }), working());
|
|
29
|
+
assert.deepEqual(assignment("subagent_run", { agent: "🙂".repeat(1000) }), working());
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
function task(agent = "scout", kind = "tool_running", tool_names: unknown = ["read"]) {
|
|
@@ -54,18 +54,18 @@ void test("public operations return structured six-state activity and label", ()
|
|
|
54
54
|
];
|
|
55
55
|
for (const kind of ["tool_running", "tool_completed", "tool_failed"])
|
|
56
56
|
for (const [tool, state, label] of cases)
|
|
57
|
-
assert.deepEqual(activity(progress(task("scout", kind, [tool]))), { activity: state, label
|
|
57
|
+
assert.deepEqual(activity(progress(task("scout", kind, [tool]))), { activity: state, label });
|
|
58
58
|
for (const kind of ["thinking", "streaming_response"])
|
|
59
|
-
assert.deepEqual(activity(progress(task("scout", kind))), { activity: "thinking", label: "
|
|
59
|
+
assert.deepEqual(activity(progress(task("scout", kind))), { activity: "thinking", label: "Thinking..." });
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
void test("multiple children select first running snapshot child and validate all children", () => {
|
|
63
63
|
assert.deepEqual(activity(progress({ agent: "old", status: "completed" }, task("first"), task("second"), task("third"))),
|
|
64
|
-
{ activity: "exploring", label: "
|
|
65
|
-
assert.deepEqual(activity(progress(task("first", "tool_running", ["read", "write"]))), { activity: "exploring", label: "
|
|
64
|
+
{ activity: "exploring", label: "Exploring... (+2)" });
|
|
65
|
+
assert.deepEqual(activity(progress(task("first", "tool_running", ["read", "write"]))), { activity: "exploring", label: "Exploring..." });
|
|
66
66
|
assert.equal(activity(progress(task(), task("bad", "tool_running", ["read", 1]))), undefined);
|
|
67
67
|
assert.equal(activity(progress(...Array.from({ length: 65 }, () => task()))), undefined);
|
|
68
|
-
assert.equal(activity(progress(...Array.from({ length: 64 }, () => task())))?.label, "
|
|
68
|
+
assert.equal(activity(progress(...Array.from({ length: 64 }, () => task())))?.label, "Exploring... (+63)");
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
void test("terminal, missing, malformed and unsupported progress has no usable activity", () => {
|
|
@@ -90,16 +90,16 @@ void test("oversized snapshot agent is rejected despite otherwise valid live act
|
|
|
90
90
|
assert.equal(activity(progress(task("a".repeat(4097)))), undefined);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
void test("snapshot agent at the input bound remains valid
|
|
93
|
+
void test("snapshot agent at the input bound remains valid without appearing in the label", () => {
|
|
94
94
|
assert.deepEqual(activity(progress(task("a".repeat(4096)))),
|
|
95
|
-
{ activity: "exploring", label:
|
|
95
|
+
{ activity: "exploring", label: "Exploring..." });
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
void test("external names are single-line, control-free and bounded before classification", () => {
|
|
99
99
|
const dirty = "\u001b[31mAgent\u001b[0m\n\t\u202eName\u0007";
|
|
100
100
|
assert.deepEqual(activity(progress(task(dirty, "tool_running", ["\u001b]0;title\u0007read\nfile"]))),
|
|
101
|
-
{ activity: "executing", label: "
|
|
101
|
+
{ activity: "executing", label: "Executing..." });
|
|
102
102
|
assert.deepEqual(activity(progress(task("a".repeat(1000), "tool_running", ["b".repeat(1000)]))),
|
|
103
|
-
{ activity: "executing", label:
|
|
103
|
+
{ activity: "executing", label: "Executing..." });
|
|
104
104
|
assert.equal(activity(progress(task("\u0000\n"))), undefined);
|
|
105
105
|
});
|
|
@@ -4,8 +4,8 @@ import { classifyWorkingActivity, WORKING_ACTIVITY_LABELS } from "./working-acti
|
|
|
4
4
|
|
|
5
5
|
export type ChildWorkingState = { activity: WorkingActivity; label: string };
|
|
6
6
|
|
|
7
|
-
function childState(
|
|
8
|
-
return { activity, label:
|
|
7
|
+
function childState(activity: WorkingActivity): ChildWorkingState {
|
|
8
|
+
return { activity, label: WORKING_ACTIVITY_LABELS[activity] };
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function record(value: unknown): value is Record<string, unknown> {
|
|
@@ -27,7 +27,7 @@ export function childAssignmentState(toolName: string, args: unknown): ChildWork
|
|
|
27
27
|
if (!record(args)) return undefined;
|
|
28
28
|
const agent = args.agent === undefined && toolName === "subagent_continue" ? "Subagent" : safeLabel(args.agent, 32);
|
|
29
29
|
if (!agent) return undefined;
|
|
30
|
-
return childState(
|
|
30
|
+
return childState("working");
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function observableActivity(value: unknown): WorkingActivity | undefined {
|
|
@@ -63,7 +63,7 @@ export function childActivityState(partialResult: unknown): ChildWorkingState |
|
|
|
63
63
|
const agent = safeLabel(task.agent, 32);
|
|
64
64
|
const activity = record(task.live_activity) ? observableActivity(task.live_activity.current) : undefined;
|
|
65
65
|
if (!agent || !activity) return undefined;
|
|
66
|
-
first ??= childState(
|
|
66
|
+
first ??= childState(activity);
|
|
67
67
|
running++;
|
|
68
68
|
}
|
|
69
69
|
if (!first) return undefined;
|
|
@@ -122,7 +122,7 @@ void test("child states select matching text and every existing glyph frame", (t
|
|
|
122
122
|
t.mock.timers.enable({ apis: ["setInterval"] });
|
|
123
123
|
const { working, controller: c } = fixture();
|
|
124
124
|
c.onToolStart("subagent_run", "child", { agent: "scout" });
|
|
125
|
-
assertDisplay(working, "working", "
|
|
125
|
+
assertDisplay(working, "working", "Working...");
|
|
126
126
|
const cases: Array<[string, string | undefined, WorkingActivity, string]> = [
|
|
127
127
|
["thinking", undefined, "thinking", "Thinking..."],
|
|
128
128
|
["streaming_response", undefined, "thinking", "Thinking..."],
|
|
@@ -135,7 +135,7 @@ void test("child states select matching text and every existing glyph frame", (t
|
|
|
135
135
|
for (const [kind, tool, activity, label] of cases) {
|
|
136
136
|
c.onToolUpdate("subagent_run", "child", { details: { tasks: [{ agent: "scout", status: "running",
|
|
137
137
|
live_activity: { current: { kind, label: "IGNORED", tool_names: tool ? [tool] : undefined } } }] } });
|
|
138
|
-
assertDisplay(working, activity,
|
|
138
|
+
assertDisplay(working, activity, label);
|
|
139
139
|
}
|
|
140
140
|
c.onShutdown();
|
|
141
141
|
});
|
|
@@ -145,21 +145,21 @@ void test("child updates respect tool identity and latest remaining tool ownersh
|
|
|
145
145
|
const { working, controller: c } = fixture();
|
|
146
146
|
c.onToolStart("subagent_run", "a");
|
|
147
147
|
c.onToolUpdate("subagent_run", "a", childProgress("scout"));
|
|
148
|
-
assertDisplay(working, "exploring", "
|
|
148
|
+
assertDisplay(working, "exploring", "Exploring...");
|
|
149
149
|
c.onToolStart("subagent_run", "b");
|
|
150
150
|
c.onToolUpdate("subagent_run", "b", childProgress("writer", "edit"));
|
|
151
151
|
c.onToolUpdate("subagent_run", "a", childProgress("scout", "grep"));
|
|
152
152
|
c.onToolUpdate("read", "b", childProgress("wrong"));
|
|
153
153
|
c.onToolUpdate("subagent_run", "missing", childProgress("late"));
|
|
154
154
|
c.onToolEnd("missing");
|
|
155
|
-
assertDisplay(working, "working", "
|
|
155
|
+
assertDisplay(working, "working", "Working...");
|
|
156
156
|
c.onToolStart("read", "ordinary");
|
|
157
157
|
c.onToolUpdate("subagent_run", "ordinary", childProgress("wrong"));
|
|
158
158
|
assert.equal(working.label, "Exploring...");
|
|
159
|
-
c.onToolEnd("ordinary"); assertDisplay(working, "working", "
|
|
160
|
-
c.onToolEnd("b"); assertDisplay(working, "exploring", "
|
|
159
|
+
c.onToolEnd("ordinary"); assertDisplay(working, "working", "Working...");
|
|
160
|
+
c.onToolEnd("b"); assertDisplay(working, "exploring", "Exploring...");
|
|
161
161
|
c.onToolUpdate("subagent_run", "b", childProgress("late"));
|
|
162
|
-
assertDisplay(working, "exploring", "
|
|
162
|
+
assertDisplay(working, "exploring", "Exploring...");
|
|
163
163
|
c.onToolUpdate("subagent_run", "a", childProgress("scout", "read", "completed"));
|
|
164
164
|
assert.equal(working.label, "Delegating...");
|
|
165
165
|
c.onToolUpdate("subagent_run", "a", childProgress("scout"));
|
|
@@ -187,16 +187,16 @@ void test("agent fallback appears at start and resets after unavailable, malform
|
|
|
187
187
|
t.mock.timers.enable({ apis: ["setInterval"] });
|
|
188
188
|
const { working, controller: c } = fixture();
|
|
189
189
|
c.onToolStart("subagent_run", "a", { agent: "explore", label: "map footer data", task: "SECRET TASK", prompt: "SECRET PROMPT" });
|
|
190
|
-
assertDisplay(working, "working", "
|
|
190
|
+
assertDisplay(working, "working", "Working...");
|
|
191
191
|
c.onToolUpdate("subagent_run", "a", childProgress("explore"));
|
|
192
|
-
assertDisplay(working, "exploring", "
|
|
192
|
+
assertDisplay(working, "exploring", "Exploring...");
|
|
193
193
|
for (const update of [undefined, {}, { details: { tasks: null } }, { details: { tasks: [{ agent: "explore", status: "running" }] } }, childProgress("explore", "read", "completed"), childProgress("explore", ""),
|
|
194
194
|
{ details: { tasks: [{ agent: "explore", status: "running", live_activity: { current: { kind: "tool_running", tool_names: [] } } }] } },
|
|
195
195
|
{ details: { tasks: [{ agent: "explore", status: "running", live_activity: { current: { kind: "tool_running" } } }] } }]) {
|
|
196
196
|
c.onToolUpdate("subagent_run", "a", childProgress("explore"));
|
|
197
|
-
assertDisplay(working, "exploring", "
|
|
197
|
+
assertDisplay(working, "exploring", "Exploring...");
|
|
198
198
|
c.onToolUpdate("subagent_run", "a", update);
|
|
199
|
-
assertDisplay(working, "working", "
|
|
199
|
+
assertDisplay(working, "working", "Working...");
|
|
200
200
|
}
|
|
201
201
|
c.onToolEnd("a"); assert.equal(working.label, "Thinking...");
|
|
202
202
|
});
|
|
@@ -205,28 +205,28 @@ void test("continuations use generic identity without guessing and preserve call
|
|
|
205
205
|
t.mock.timers.enable({ apis: ["setInterval"] });
|
|
206
206
|
const { working, controller: c } = fixture();
|
|
207
207
|
c.onToolStart("subagent_run", "a", { agent: "scout" });
|
|
208
|
-
assert.equal(working.label, "
|
|
208
|
+
assert.equal(working.label, "Working...");
|
|
209
209
|
c.onToolStart("subagent_continue", "b", { id: "a", label: "check footer" });
|
|
210
|
-
assert.equal(working.label, "
|
|
210
|
+
assert.equal(working.label, "Working...");
|
|
211
211
|
c.onToolStart("subagent_continue", "a", { agent: "wrong", label: "wrong" });
|
|
212
212
|
c.onToolUpdate("subagent_run", "b", childProgress("wrong"));
|
|
213
213
|
c.onToolUpdate("subagent_continue", "missing", childProgress("wrong"));
|
|
214
|
-
assert.equal(working.label, "
|
|
214
|
+
assert.equal(working.label, "Working...");
|
|
215
215
|
c.onToolUpdate("subagent_continue", "b", childProgress("writer", "edit"));
|
|
216
|
-
assertDisplay(working, "working", "
|
|
216
|
+
assertDisplay(working, "working", "Working...");
|
|
217
217
|
c.onToolUpdate("subagent_run", "a", childProgress("scout"));
|
|
218
|
-
assertDisplay(working, "working", "
|
|
218
|
+
assertDisplay(working, "working", "Working...");
|
|
219
219
|
c.onToolUpdate("subagent_continue", "b", {});
|
|
220
|
-
assert.equal(working.label, "
|
|
221
|
-
c.onToolEnd("b"); assertDisplay(working, "exploring", "
|
|
220
|
+
assert.equal(working.label, "Working...");
|
|
221
|
+
c.onToolEnd("b"); assertDisplay(working, "exploring", "Exploring...");
|
|
222
222
|
c.onToolUpdate("subagent_continue", "b", childProgress("late"));
|
|
223
|
-
c.onToolUpdate("subagent_run", "a", {}); assert.equal(working.label, "
|
|
223
|
+
c.onToolUpdate("subagent_run", "a", {}); assert.equal(working.label, "Working...");
|
|
224
224
|
c.onToolEnd("a");
|
|
225
225
|
c.onToolStart("subagent_continue", "c", { agent: "writer", label: "finish" });
|
|
226
|
-
assert.equal(working.label, "
|
|
226
|
+
assert.equal(working.label, "Working...");
|
|
227
227
|
c.onToolEnd("c");
|
|
228
228
|
c.onToolStart("subagent_continue", "d", { id: "c", task: "SECRET" });
|
|
229
|
-
assert.equal(working.label, "
|
|
229
|
+
assert.equal(working.label, "Working...");
|
|
230
230
|
c.onToolEnd("d");
|
|
231
231
|
});
|
|
232
232
|
|
|
@@ -237,7 +237,7 @@ void test("identity failures stay parent-owned and live snapshots retain validat
|
|
|
237
237
|
c.onToolStart("subagent_run", "run", args);
|
|
238
238
|
assertDisplay(working, "delegating", "Delegating...");
|
|
239
239
|
c.onToolUpdate("subagent_run", "run", childProgress("public"));
|
|
240
|
-
assertDisplay(working, "exploring", "
|
|
240
|
+
assertDisplay(working, "exploring", "Exploring...");
|
|
241
241
|
c.onToolUpdate("subagent_run", "run", {});
|
|
242
242
|
assertDisplay(working, "delegating", "Delegating...");
|
|
243
243
|
c.onToolEnd("run");
|
|
@@ -246,17 +246,17 @@ void test("identity failures stay parent-owned and live snapshots retain validat
|
|
|
246
246
|
assertDisplay(working, "executing", "Executing...");
|
|
247
247
|
c.onToolEnd("continue");
|
|
248
248
|
c.onToolStart("subagent_continue", "continue", {});
|
|
249
|
-
assertDisplay(working, "working", "
|
|
249
|
+
assertDisplay(working, "working", "Working...");
|
|
250
250
|
c.onToolUpdate("subagent_continue", "continue", { details: { tasks: [
|
|
251
251
|
{ agent: "first", status: "running", live_activity: { current: { kind: "tool_running", tool_names: ["read"] } } },
|
|
252
252
|
{ agent: "second", status: "running", live_activity: { current: { kind: "thinking" } } },
|
|
253
253
|
] } });
|
|
254
|
-
assertDisplay(working, "exploring", "
|
|
254
|
+
assertDisplay(working, "exploring", "Exploring... (+1)");
|
|
255
255
|
c.onToolStart("subagent_run", "continue", { agent: "wrong" });
|
|
256
256
|
c.onToolUpdate("subagent_run", "continue", childProgress("wrong"));
|
|
257
|
-
assertDisplay(working, "exploring", "
|
|
257
|
+
assertDisplay(working, "exploring", "Exploring... (+1)");
|
|
258
258
|
c.onToolUpdate("subagent_continue", "continue", {});
|
|
259
|
-
assertDisplay(working, "working", "
|
|
259
|
+
assertDisplay(working, "working", "Working...");
|
|
260
260
|
c.onShutdown();
|
|
261
261
|
});
|
|
262
262
|
|
|
@@ -265,7 +265,7 @@ void test("assignment metadata is cleared on agent and session cleanup", (t) =>
|
|
|
265
265
|
const { working, controller: c } = fixture();
|
|
266
266
|
for (const cleanup of [() => c.onAgentEnd(), () => c.onShutdown()]) {
|
|
267
267
|
c.onToolStart("subagent_run", "a", { agent: "scout", label: "map" });
|
|
268
|
-
assert.equal(working.label, "
|
|
268
|
+
assert.equal(working.label, "Working...");
|
|
269
269
|
cleanup(); c.onToolUpdate("subagent_run", "a", childProgress("late"));
|
|
270
270
|
c.refreshWorking(); assert.equal(working.label, "Thinking...");
|
|
271
271
|
assert.equal(working.active, false); assert.equal(working.timer, undefined);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osdy-pi",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.10",
|
|
4
4
|
"description": "Osdy Pi package: themes, header, and custom editor for Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"typescript": "^5.9.3",
|
|
84
84
|
"typescript-eslint": "^8.46.3"
|
|
85
85
|
},
|
|
86
|
-
"releaseSource": "
|
|
86
|
+
"releaseSource": "96f670c11788dfb1a336c664a266b8581546ff59",
|
|
87
87
|
"repository": {
|
|
88
88
|
"type": "git",
|
|
89
89
|
"url": "git+https://github.com/OsdyOrtiz/Osdy-Pi.git"
|