From 1fc4d7259fe9581c3aec45ec2230bd6abaa1f3e1 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Fri, 27 Mar 2026 19:03:35 -0400 Subject: [PATCH] Agents/TUI: align with current pi APIs --- src/agents/compaction.retry.test.ts | 2 +- src/agents/compaction.ts | 1 - src/agents/skills-install.download.test.ts | 7 +- src/agents/skills-status.test.ts | 6 +- .../skills.buildworkspaceskillstatus.test.ts | 7 +- .../skills.resolveskillspromptforrun.test.ts | 7 +- src/agents/skills/compact-format.test.ts | 12 +-- src/agents/skills/source.ts | 2 +- src/cli/skills-cli.formatting.test.ts | 7 +- .../components/filterable-select-list.test.ts | 81 +++++++++++++++++++ src/tui/components/filterable-select-list.ts | 6 +- src/tui/components/searchable-select-list.ts | 6 +- 12 files changed, 96 insertions(+), 48 deletions(-) create mode 100644 src/tui/components/filterable-select-list.test.ts diff --git a/src/agents/compaction.retry.test.ts b/src/agents/compaction.retry.test.ts index 30f81ca6664..31404e2e9b2 100644 --- a/src/agents/compaction.retry.test.ts +++ b/src/agents/compaction.retry.test.ts @@ -56,7 +56,7 @@ describe("compaction retry integration", () => { } as unknown as NonNullable; const invokeGenerateSummary = (signal = new AbortController().signal) => - mockGenerateSummary(testMessages, testModel, 1000, "test-api-key", undefined, signal); + mockGenerateSummary(testMessages, testModel, 1000, "test-api-key", signal); const runSummaryRetry = (options: Parameters[1]) => retryAsync(() => invokeGenerateSummary(), options); diff --git a/src/agents/compaction.ts b/src/agents/compaction.ts index 5a920e34160..20a6d80fa43 100644 --- a/src/agents/compaction.ts +++ b/src/agents/compaction.ts @@ -257,7 +257,6 @@ async function summarizeChunks(params: { model, params.reserveTokens, params.apiKey, - undefined, params.signal, effectiveInstructions, summary, diff --git a/src/agents/skills-install.download.test.ts b/src/agents/skills-install.download.test.ts index 9f30eabeaee..bd8559b60fe 100644 --- a/src/agents/skills-install.download.test.ts +++ b/src/agents/skills-install.download.test.ts @@ -1,7 +1,6 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { createSyntheticSourceInfo } from "@mariozechner/pi-coding-agent"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { installDownloadSpec } from "./skills-install-download.js"; import { setTempStateDir } from "./skills-install.download-test-utils.js"; @@ -61,11 +60,7 @@ function buildEntry(name: string): SkillEntry { description: `${name} test skill`, filePath: path.join(skillDir, "SKILL.md"), baseDir: skillDir, - sourceInfo: createSyntheticSourceInfo(path.join(skillDir, "SKILL.md"), { - source: "openclaw-workspace", - scope: "project", - baseDir: skillDir, - }), + source: "openclaw-workspace", disableModelInvocation: false, }, frontmatter: {}, diff --git a/src/agents/skills-status.test.ts b/src/agents/skills-status.test.ts index 2d3abda70b4..3e0b2ab6783 100644 --- a/src/agents/skills-status.test.ts +++ b/src/agents/skills-status.test.ts @@ -1,4 +1,3 @@ -import { createSyntheticSourceInfo } from "@mariozechner/pi-coding-agent"; import { describe, expect, it } from "vitest"; import { buildWorkspaceSkillStatus } from "./skills-status.js"; import type { SkillEntry } from "./skills/types.js"; @@ -18,10 +17,7 @@ describe("buildWorkspaceSkillStatus", () => { description: "test", filePath: "/tmp/os-scoped", baseDir: "/tmp", - sourceInfo: createSyntheticSourceInfo("/tmp/os-scoped", { - source: "test", - baseDir: "/tmp", - }), + source: "test", disableModelInvocation: false, }, frontmatter: {}, diff --git a/src/agents/skills.buildworkspaceskillstatus.test.ts b/src/agents/skills.buildworkspaceskillstatus.test.ts index ccbbf9bb597..4b3cca8808f 100644 --- a/src/agents/skills.buildworkspaceskillstatus.test.ts +++ b/src/agents/skills.buildworkspaceskillstatus.test.ts @@ -1,4 +1,3 @@ -import { createSyntheticSourceInfo } from "@mariozechner/pi-coding-agent"; import { describe, expect, it } from "vitest"; import { withEnv } from "../test-utils/env.js"; import { buildWorkspaceSkillStatus } from "./skills-status.js"; @@ -25,11 +24,7 @@ function makeEntry(params: { description: `desc:${params.name}`, filePath: `/tmp/${params.name}/SKILL.md`, baseDir: `/tmp/${params.name}`, - sourceInfo: createSyntheticSourceInfo(`/tmp/${params.name}/SKILL.md`, { - source: params.source ?? "openclaw-workspace", - scope: "project", - baseDir: `/tmp/${params.name}`, - }), + source: params.source ?? "openclaw-workspace", disableModelInvocation: false, }, frontmatter: {}, diff --git a/src/agents/skills.resolveskillspromptforrun.test.ts b/src/agents/skills.resolveskillspromptforrun.test.ts index 98510d68d10..305e11f2f4e 100644 --- a/src/agents/skills.resolveskillspromptforrun.test.ts +++ b/src/agents/skills.resolveskillspromptforrun.test.ts @@ -1,4 +1,3 @@ -import { createSyntheticSourceInfo } from "@mariozechner/pi-coding-agent"; import { describe, expect, it } from "vitest"; import { resolveSkillsPromptForRun } from "./skills.js"; import type { SkillEntry } from "./skills/types.js"; @@ -18,11 +17,7 @@ describe("resolveSkillsPromptForRun", () => { description: "Demo", filePath: "/app/skills/demo-skill/SKILL.md", baseDir: "/app/skills/demo-skill", - sourceInfo: createSyntheticSourceInfo("/app/skills/demo-skill/SKILL.md", { - source: "openclaw-bundled", - scope: "project", - baseDir: "/app/skills/demo-skill", - }), + source: "openclaw-bundled", disableModelInvocation: false, }, frontmatter: {}, diff --git a/src/agents/skills/compact-format.test.ts b/src/agents/skills/compact-format.test.ts index c631d5f5803..cd9d6f42f15 100644 --- a/src/agents/skills/compact-format.test.ts +++ b/src/agents/skills/compact-format.test.ts @@ -1,9 +1,5 @@ import os from "node:os"; -import { - createSyntheticSourceInfo, - formatSkillsForPrompt, - type Skill, -} from "@mariozechner/pi-coding-agent"; +import { formatSkillsForPrompt, type Skill } from "@mariozechner/pi-coding-agent"; import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import type { SkillEntry } from "./types.js"; @@ -19,11 +15,7 @@ function makeSkill(name: string, desc = "A skill", filePath = `/skills/${name}/S description: desc, filePath, baseDir: `/skills/${name}`, - sourceInfo: createSyntheticSourceInfo(filePath, { - source: "workspace", - scope: "project", - baseDir: `/skills/${name}`, - }), + source: "workspace", disableModelInvocation: false, }; } diff --git a/src/agents/skills/source.ts b/src/agents/skills/source.ts index f1ff2885ec3..d90f0514ccd 100644 --- a/src/agents/skills/source.ts +++ b/src/agents/skills/source.ts @@ -1,5 +1,5 @@ import type { Skill } from "@mariozechner/pi-coding-agent"; export function resolveSkillSource(skill: Skill): string { - return skill.sourceInfo.source ?? "unknown"; + return skill.source ?? "unknown"; } diff --git a/src/cli/skills-cli.formatting.test.ts b/src/cli/skills-cli.formatting.test.ts index d7ca6b702ed..7002bdc4aec 100644 --- a/src/cli/skills-cli.formatting.test.ts +++ b/src/cli/skills-cli.formatting.test.ts @@ -1,7 +1,6 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { createSyntheticSourceInfo } from "@mariozechner/pi-coding-agent"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { buildWorkspaceSkillStatus } from "../agents/skills-status.js"; import type { SkillEntry } from "../agents/skills.js"; @@ -39,11 +38,7 @@ describe("skills-cli (e2e)", () => { description: "Capture UI screenshots", filePath: path.join(baseDir, "SKILL.md"), baseDir, - sourceInfo: createSyntheticSourceInfo(path.join(baseDir, "SKILL.md"), { - source: "openclaw-bundled", - scope: "project", - baseDir, - }), + source: "openclaw-bundled", disableModelInvocation: false, }, frontmatter: {}, diff --git a/src/tui/components/filterable-select-list.test.ts b/src/tui/components/filterable-select-list.test.ts new file mode 100644 index 00000000000..af4f1881f12 --- /dev/null +++ b/src/tui/components/filterable-select-list.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from "vitest"; +import { + FilterableSelectList, + type FilterableSelectItem, + type FilterableSelectListTheme, +} from "./filterable-select-list.js"; + +const mockTheme: FilterableSelectListTheme = { + selectedPrefix: (t) => `[${t}]`, + selectedText: (t) => `**${t}**`, + description: (t) => `(${t})`, + scrollInfo: (t) => `~${t}~`, + noMatch: (t) => `!${t}!`, + filterLabel: (t) => `>${t}<`, +}; + +const testItems: FilterableSelectItem[] = [ + { + value: "session-1", + label: "first session", + description: "Oldest", + searchText: "alpha", + }, + { + value: "session-2", + label: "second session", + description: "Newest", + searchText: "beta", + }, +]; + +describe("FilterableSelectList", () => { + function typeInput(list: FilterableSelectList, text: string) { + for (const ch of text) { + list.handleInput(ch); + } + } + + it("clears the active filter before cancelling", () => { + const list = new FilterableSelectList(testItems, 5, mockTheme); + let cancelled = false; + list.onCancel = () => { + cancelled = true; + }; + + typeInput(list, "beta"); + expect(list.getFilterText()).toBe("beta"); + expect(list.getSelectedItem()?.value).toBe("session-2"); + + list.handleInput("\x1b"); + + expect(cancelled).toBe(false); + expect(list.getFilterText()).toBe(""); + expect(list.render(80).join("\n")).toContain("first session"); + expect(list.render(80).join("\n")).toContain("second session"); + }); + + it("calls onCancel when escape is pressed with an empty filter", () => { + const list = new FilterableSelectList(testItems, 5, mockTheme); + let cancelled = false; + list.onCancel = () => { + cancelled = true; + }; + + list.handleInput("\x1b"); + + expect(cancelled).toBe(true); + }); + + it("calls onCancel when ctrl+c is pressed with an empty filter", () => { + const list = new FilterableSelectList(testItems, 5, mockTheme); + let cancelled = false; + list.onCancel = () => { + cancelled = true; + }; + + list.handleInput("\u0003"); + + expect(cancelled).toBe(true); + }); +}); diff --git a/src/tui/components/filterable-select-list.ts b/src/tui/components/filterable-select-list.ts index 53aac87cdea..e6b3e7bdbfd 100644 --- a/src/tui/components/filterable-select-list.ts +++ b/src/tui/components/filterable-select-list.ts @@ -1,7 +1,7 @@ import type { Component } from "@mariozechner/pi-tui"; import { + getEditorKeybindings, Input, - getKeybindings, matchesKey, type SelectItem, SelectList, @@ -110,8 +110,8 @@ export class FilterableSelectList implements Component { } // Escape: clear filter or cancel - const kb = getKeybindings(); - if (kb.matches(keyData, "tui.select.cancel")) { + const keybindings = getEditorKeybindings(); + if (keybindings.matches(keyData, "selectCancel")) { if (this.filterText) { this.filterText = ""; this.input.setValue(""); diff --git a/src/tui/components/searchable-select-list.ts b/src/tui/components/searchable-select-list.ts index 56526fdbcd8..cff3a6b9a7f 100644 --- a/src/tui/components/searchable-select-list.ts +++ b/src/tui/components/searchable-select-list.ts @@ -1,6 +1,6 @@ import { type Component, - getKeybindings, + getEditorKeybindings, Input, isKeyRelease, matchesKey, @@ -362,8 +362,8 @@ export class SearchableSelectList implements Component { return; } - const kb = getKeybindings(); - if (kb.matches(keyData, "tui.select.cancel")) { + const keybindings = getEditorKeybindings(); + if (keybindings.matches(keyData, "selectCancel")) { if (this.onCancel) { this.onCancel(); }