a997f13601
* feat(kanban): add Kanban board with task-manager skill, modal UI, and desk clutter Implement a full Kanban board system for tracking agent tasks: - Add task-manager skill with shared JSON task store for persistence - Render board as a floating modal over the live 3D office (not immersive) - Auto-create tasks from actionable user messages with heuristic filtering - Sync task status through OpenClaw agent lifecycle events - Collapse task details panel by default, expand on card click - Add dynamic desk clutter (papers, folders, etc.) reflecting active task count - Exclude done tasks from desk clutter count - Extract KANBAN_CLUTTER_OFFSET for easy positioning adjustment - Add install flow with progress bar for the task-manager skill - Include unit and e2e test coverage Made-with: Cursor * feat(kanban): production-harden task board with AI-free classification, resilient persistence, and modal UX - Harden shared task store with atomic writes, payload size limits, and server-side enum validation - Add client resilience: request timeouts (AbortController), exponential backoff retries, poll deduplication - Implement optimistic UI with rollback on all card mutations (update, move, archive) - Add modal accessibility: focus trap, Escape to close, aria-modal, keyboard card navigation - Trust OpenClaw agent lifecycle phase=start as task classification signal instead of regex heuristics - Keep regex heuristic only as lightweight filter for direct chat events (conversational noise) - Expand verb recognition with typo tolerance and broader action vocabulary - Create tasks from agent runs even when no chat event is received (external channel support) - Merge dual header bars into single bar; reposition close button outside modal corner - Exclude done tasks from desk clutter count; make clutter position configurable via KANBAN_CLUTTER_OFFSET - Update default furniture layout to match user configuration - Ensure kanban_board furniture persists in local storage across sessions - Add comprehensive test coverage for store, API route, and controller logic Made-with: Cursor --------- Co-authored-by: iamlukethedev <lucas.guilherme@smartwayslfl.com>
90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
buildOfficeSkillTriggerHoldMaps,
|
|
DEFAULT_SKILL_TRIGGER_FALLBACKS_BY_SKILL_KEY,
|
|
OFFICE_SKILL_TRIGGER_PLACE_REGISTRY,
|
|
} from "@/lib/office/places";
|
|
import {
|
|
listPackagedSkillTriggerDefinitions,
|
|
resolveTriggeredSkillDefinition,
|
|
} from "@/lib/skills/triggers";
|
|
|
|
describe("skill triggers", () => {
|
|
it("parses packaged skill trigger definitions from SKILL.md", () => {
|
|
const todoTrigger = listPackagedSkillTriggerDefinitions().find(
|
|
(entry) => entry.skillKey === "todo-board",
|
|
);
|
|
const taskManagerTrigger = listPackagedSkillTriggerDefinitions().find(
|
|
(entry) => entry.skillKey === "task-manager",
|
|
);
|
|
|
|
expect(todoTrigger).not.toBeUndefined();
|
|
expect(todoTrigger?.movementTarget).toBe("desk");
|
|
expect(todoTrigger?.activationPhrases).toContain("todo");
|
|
expect(todoTrigger?.activationPhrases).toContain("blocked tasks");
|
|
expect(taskManagerTrigger).not.toBeUndefined();
|
|
expect(taskManagerTrigger?.movementTarget).toBe("desk");
|
|
expect(taskManagerTrigger?.activationPhrases).toContain("add a task");
|
|
});
|
|
|
|
it("matches the running agent's latest request against enabled skill triggers", () => {
|
|
const todoTrigger = listPackagedSkillTriggerDefinitions().find(
|
|
(entry) => entry.skillKey === "todo-board",
|
|
);
|
|
|
|
const matched = resolveTriggeredSkillDefinition({
|
|
isAgentRunning: true,
|
|
lastUserMessage: "On telegram, add this to my todo list.",
|
|
transcriptEntries: [],
|
|
triggers: todoTrigger ? [todoTrigger] : [],
|
|
});
|
|
|
|
expect(matched?.skillKey).toBe("todo-board");
|
|
expect(matched?.movementTarget).toBe("desk");
|
|
});
|
|
|
|
it("does not match triggers when the agent is not running", () => {
|
|
const todoTrigger = listPackagedSkillTriggerDefinitions().find(
|
|
(entry) => entry.skillKey === "todo-board",
|
|
);
|
|
|
|
const matched = resolveTriggeredSkillDefinition({
|
|
isAgentRunning: false,
|
|
lastUserMessage: "Add this to my todo list.",
|
|
transcriptEntries: [],
|
|
triggers: todoTrigger ? [todoTrigger] : [],
|
|
});
|
|
|
|
expect(matched).toBeNull();
|
|
});
|
|
|
|
it("keeps trigger places and fallback definitions in one central registry", () => {
|
|
expect(OFFICE_SKILL_TRIGGER_PLACE_REGISTRY.desk.interactionTarget).toBe(
|
|
"desk",
|
|
);
|
|
expect(OFFICE_SKILL_TRIGGER_PLACE_REGISTRY.github.interactionTarget).toBe(
|
|
"server_room",
|
|
);
|
|
expect(
|
|
DEFAULT_SKILL_TRIGGER_FALLBACKS_BY_SKILL_KEY["todo-board"]
|
|
?.movementTarget,
|
|
).toBe("desk");
|
|
});
|
|
|
|
it("builds animation hold maps from the central place registry", () => {
|
|
const holdMaps = buildOfficeSkillTriggerHoldMaps({
|
|
"agent-a": "desk",
|
|
"agent-b": "github",
|
|
"agent-c": "gym",
|
|
"agent-d": "qa_lab",
|
|
});
|
|
|
|
expect(holdMaps.deskHoldByAgentId).toEqual({ "agent-a": true });
|
|
expect(holdMaps.githubHoldByAgentId).toEqual({ "agent-b": true });
|
|
expect(holdMaps.gymHoldByAgentId).toEqual({ "agent-c": true });
|
|
expect(holdMaps.qaHoldByAgentId).toEqual({ "agent-d": true });
|
|
expect(holdMaps.skillGymHoldByAgentId).toEqual({ "agent-c": true });
|
|
});
|
|
});
|