@xynogen/pix-core 0.1.4 → 0.1.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.
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/lib/data.ts +6 -6
- package/src/nudge/capability.test.ts +5 -5
- package/src/nudge/capability.ts +5 -5
- package/src/tool/todo/todo.test.ts +45 -1
- package/src/tool/todo/todo.ts +37 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import registerSkillLoader from "@xynogen/pix-skills";
|
|
19
20
|
import registerAgentSop from "./commands/agent-sop/agent-sop.ts";
|
|
20
21
|
import registerClear from "./commands/clear/clear.ts";
|
|
21
22
|
import registerCopyAll from "./commands/copy-all/copy-all.ts";
|
|
@@ -34,6 +35,7 @@ import registerWelcome from "./ui/welcome.ts";
|
|
|
34
35
|
|
|
35
36
|
export default function (pi: ExtensionAPI): void {
|
|
36
37
|
registerAgentSop(pi);
|
|
38
|
+
registerSkillLoader(pi);
|
|
37
39
|
registerWelcome(pi);
|
|
38
40
|
registerFooter(pi);
|
|
39
41
|
registerDiagnostics(pi);
|
package/src/lib/data.ts
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* models.ts — lookupModelsDev, lookupBenchmark
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
export type {
|
|
15
|
+
BenchmarkEntry,
|
|
16
|
+
ModelsDevApi,
|
|
17
|
+
ModelsDevModel,
|
|
18
|
+
} from "../../../pix-data/src/index.ts";
|
|
14
19
|
export {
|
|
15
20
|
benchmark,
|
|
16
21
|
buildModelsDevIndex,
|
|
@@ -21,12 +26,7 @@ export {
|
|
|
21
26
|
lookupInIndex,
|
|
22
27
|
lookupModelsDev,
|
|
23
28
|
modelsDev,
|
|
24
|
-
} from "
|
|
25
|
-
export type {
|
|
26
|
-
BenchmarkEntry,
|
|
27
|
-
ModelsDevApi,
|
|
28
|
-
ModelsDevModel,
|
|
29
|
-
} from "@xynogen/pix-data";
|
|
29
|
+
} from "../../../pix-data/src/index.ts";
|
|
30
30
|
|
|
31
31
|
export default function (_pi: unknown): void {
|
|
32
32
|
// pix-data warms this cache on startup — nothing to do here.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
1
2
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
import { describe, expect, test } from "bun:test";
|
|
4
4
|
import {
|
|
5
5
|
buildOrientation,
|
|
6
6
|
CAPABILITY_REMINDER,
|
|
@@ -48,8 +48,8 @@ describe("CAPABILITY_REMINDER", () => {
|
|
|
48
48
|
expect(CAPABILITY_REMINDER.toLowerCase()).toContain("improvis");
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test("nudges model to call
|
|
52
|
-
expect(CAPABILITY_REMINDER).toContain("
|
|
51
|
+
test("nudges model to call read_skills() when a skill matches", () => {
|
|
52
|
+
expect(CAPABILITY_REMINDER).toContain("read_skills()");
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test("points at /toolbox slash command for discovery (not a function call)", () => {
|
|
@@ -132,9 +132,9 @@ describe("buildOrientation", () => {
|
|
|
132
132
|
expect(out).toContain("2 skills");
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
test("explains how to use
|
|
135
|
+
test("explains how to use read_skills() and /toolbox for discovery", () => {
|
|
136
136
|
const out = buildOrientation([tool("read", "builtin")], []);
|
|
137
|
-
expect(out).toContain("
|
|
137
|
+
expect(out).toContain("read_skills()");
|
|
138
138
|
expect(out).toContain("/toolbox");
|
|
139
139
|
// toolbox must NOT appear as a function call
|
|
140
140
|
expect(out).not.toContain("toolbox(");
|
package/src/nudge/capability.ts
CHANGED
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
* 1. FIRST prompt of the session — an orientation block: a high-level
|
|
11
11
|
* description of WHAT is available (counts of tools / MCP tools / skills)
|
|
12
12
|
* and HOW to explore it. We deliberately do NOT dump the whole inventory
|
|
13
|
-
* every turn — the model should call
|
|
13
|
+
* every turn — the model should call read_skills() for skills and use /toolbox
|
|
14
14
|
* (slash command, user-facing) to discover/enable gated tools.
|
|
15
15
|
* 2. EVERY subsequent prompt — the terse one-line CAPABILITY_REMINDER, a
|
|
16
|
-
* cheap (~40 tok) reinforcement that steers toward
|
|
16
|
+
* cheap (~40 tok) reinforcement that steers toward read_skills() and /toolbox.
|
|
17
17
|
*
|
|
18
18
|
* NOTE: `toolbox` is a slash command only (/toolbox) — NOT a model-callable
|
|
19
19
|
* function tool. The model cannot call toolbox() in function definitions.
|
|
20
|
-
* The `
|
|
20
|
+
* The `read_skills` tool IS model-callable: read_skills() lists/loads bundled skills.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { existsSync } from "node:fs";
|
|
@@ -34,7 +34,7 @@ type LoadedSkill = NonNullable<BuildSystemPromptOptions["skills"]>[number];
|
|
|
34
34
|
export const CAPABILITY_REMINDER =
|
|
35
35
|
"Reminder — check knowledge resources " +
|
|
36
36
|
"(skills/tools/MCP/web/user) before improvising. " +
|
|
37
|
-
"Matching skill? Call
|
|
37
|
+
"Matching skill? Call read_skills() first. " +
|
|
38
38
|
"Use /toolbox to discover/enable gated tools. " +
|
|
39
39
|
"All tools callable via function definitions.";
|
|
40
40
|
|
|
@@ -128,7 +128,7 @@ export function buildOrientation(
|
|
|
128
128
|
if (gateLine) lines.push(gateLine);
|
|
129
129
|
lines.push(
|
|
130
130
|
"Don't improvise what a capability covers — ask the user, search the web, or pull docs first.",
|
|
131
|
-
"`
|
|
131
|
+
"`read_skills()` lists/loads bundled skills — call it when a skill matches your task. " +
|
|
132
132
|
"/toolbox (slash command) discovers and enables gated tools.",
|
|
133
133
|
);
|
|
134
134
|
if (skillNames.length) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import registerTodo from "./todo.ts";
|
|
2
|
+
import registerTodo, { renderTodoLines, type TodoItem } from "./todo.ts";
|
|
3
|
+
|
|
4
|
+
// Stub theme tags each fragment with its color/bold so assertions can verify
|
|
5
|
+
// which status got which tint, without depending on real ANSI codes.
|
|
6
|
+
const tagTheme = {
|
|
7
|
+
fg: (color: string, text: string) => `[${color}]${text}[/]`,
|
|
8
|
+
bold: (text: string) => `<b>${text}</b>`,
|
|
9
|
+
};
|
|
3
10
|
|
|
4
11
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
5
12
|
|
|
@@ -600,3 +607,40 @@ describe("restore", () => {
|
|
|
600
607
|
expect(text(result)).toBe("(no todos)");
|
|
601
608
|
});
|
|
602
609
|
});
|
|
610
|
+
|
|
611
|
+
describe("renderTodoLines (colored TUI render)", () => {
|
|
612
|
+
const items: TodoItem[] = [
|
|
613
|
+
{ id: 1, text: "alpha", status: "done" },
|
|
614
|
+
{ id: 2, text: "bravo", status: "in_progress" },
|
|
615
|
+
{ id: 3, text: "charlie", status: "pending" },
|
|
616
|
+
{ id: 4, text: "delta", status: "blocked" },
|
|
617
|
+
];
|
|
618
|
+
|
|
619
|
+
test("empty list renders muted placeholder", () => {
|
|
620
|
+
expect(renderTodoLines([], tagTheme)).toBe("[muted](no todos)[/]");
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("tints each glyph by status", () => {
|
|
624
|
+
const out = renderTodoLines(items, tagTheme);
|
|
625
|
+
expect(out).toContain("[success]●[/]"); // done
|
|
626
|
+
expect(out).toContain("[accent]◐[/]"); // in_progress
|
|
627
|
+
expect(out).toContain("[muted]○[/]"); // pending
|
|
628
|
+
expect(out).toContain("[error]⊘[/]"); // blocked
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test("highlights the in-progress row bold + accent", () => {
|
|
632
|
+
const out = renderTodoLines(items, tagTheme);
|
|
633
|
+
expect(out).toContain("<b>[accent]2. bravo[/]</b>");
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("dims completed rows and uses text color for active-but-not-running", () => {
|
|
637
|
+
const out = renderTodoLines(items, tagTheme);
|
|
638
|
+
expect(out).toContain("[muted]1. alpha[/]"); // done body muted
|
|
639
|
+
expect(out).toContain("[text]3. charlie[/]"); // pending body text
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("shows the done/total count header", () => {
|
|
643
|
+
const out = renderTodoLines(items, tagTheme);
|
|
644
|
+
expect(out).toContain("[muted]Todos 1/4 done:[/]");
|
|
645
|
+
});
|
|
646
|
+
});
|
package/src/tool/todo/todo.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
13
14
|
import { Type } from "typebox";
|
|
14
15
|
|
|
15
16
|
export type TodoStatus = "pending" | "in_progress" | "done" | "blocked";
|
|
@@ -27,6 +28,38 @@ const TODO_GLYPH: Record<TodoStatus, string> = {
|
|
|
27
28
|
blocked: "⊘",
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
/** Theme color key per status — drives both glyph and (for active) row tint. */
|
|
32
|
+
const TODO_COLOR: Record<TodoStatus, string> = {
|
|
33
|
+
pending: "muted",
|
|
34
|
+
in_progress: "accent",
|
|
35
|
+
done: "success",
|
|
36
|
+
blocked: "error",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type TodoTheme = {
|
|
40
|
+
fg: (color: string, text: string) => string;
|
|
41
|
+
bold: (text: string) => string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Colored checklist for the TUI: glyphs tinted by status, active row bold. */
|
|
45
|
+
export function renderTodoLines(items: TodoItem[], theme: TodoTheme): string {
|
|
46
|
+
if (!items.length) return theme.fg("muted", "(no todos)");
|
|
47
|
+
const done = items.filter((t) => t.status === "done").length;
|
|
48
|
+
const head = theme.fg("muted", `Todos ${done}/${items.length} done:`);
|
|
49
|
+
const lines = items.map((t) => {
|
|
50
|
+
const color = TODO_COLOR[t.status];
|
|
51
|
+
const glyph = theme.fg(color, TODO_GLYPH[t.status]);
|
|
52
|
+
const body = `${t.id}. ${t.text}`;
|
|
53
|
+
// Highlight the in-flight task so the eye lands on it first.
|
|
54
|
+
const label =
|
|
55
|
+
t.status === "in_progress"
|
|
56
|
+
? theme.bold(theme.fg("accent", body))
|
|
57
|
+
: theme.fg(t.status === "done" ? "muted" : "text", body);
|
|
58
|
+
return `${glyph} ${label}`;
|
|
59
|
+
});
|
|
60
|
+
return `${head}\n${lines.join("\n")}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
30
63
|
const parseItems = (raw: string): string[] =>
|
|
31
64
|
raw
|
|
32
65
|
.split("\n")
|
|
@@ -102,6 +135,10 @@ export default function registerTodo(pi: ExtensionAPI): void {
|
|
|
102
135
|
}),
|
|
103
136
|
),
|
|
104
137
|
}),
|
|
138
|
+
renderResult(_result, _options, theme) {
|
|
139
|
+
return new Text(renderTodoLines(todos, theme as TodoTheme), 0, 0);
|
|
140
|
+
},
|
|
141
|
+
|
|
105
142
|
async execute(_id, params) {
|
|
106
143
|
// AgentToolResult now requires a `details` field. These todo results have
|
|
107
144
|
// no structured details, so emit `undefined` via small local helpers.
|