open-design/apps/daemon/tests/memory-tree.test.ts
Tom Huang 86ec951fb9
[codex] Add automation templates and proposal workflows (#2193)
* feat(web): introduce Automations tab with dual-track capability for routines

This commit adds a new Automations tab that consolidates routines, schedules, and live artifacts, allowing users to manage automations seamlessly. The tab features a modal for creating and editing automations, which supports various scheduling options (hourly, daily, weekdays, weekly) and project modes (create_each_run, reuse). The CLI is also updated to expose automation commands, ensuring consistency between the web UI and CLI interfaces.

Key changes include:
- New `NewAutomationModal` component for automation creation and editing.
- Updated `TasksView` to integrate the new Automations functionality.
- Enhanced styling for the Automations tab to improve user experience.

This implementation aligns with the dual-track capability exposure policy, ensuring all features are accessible via both the web UI and CLI.

* feat(daemon): enhance automation context handling and CLI commands

This commit introduces several improvements to the automation context management and updates the CLI commands accordingly. Key changes include:

- Added support for new context fields (`plugin`, `mcp`, `connector`) in automation commands.
- Updated the CLI to reflect new target options (`new-project`).
- Enhanced error messages for invalid target inputs.
- Introduced functions to handle context selection and normalization for routines, including the ability to parse and store context data in the database.
- Updated the database schema to include a new `context_json` field for routines.
- Improved the handling of context in routine routes and the web interface, ensuring that selected contexts are properly managed and displayed.

These changes aim to provide a more robust and flexible automation experience, aligning with the recent enhancements in the web UI.

* feat(web): enhance TasksView with automation run history and status indicators

This commit introduces several new features to the TasksView component, including:

- Added functionality to display automation run history for each routine, showing metadata such as status, timestamps, and project details.
- Implemented status indicators for routine runs, providing visual feedback on their current state (succeeded, failed, running, queued).
- Enhanced the UI to allow users to expand and view detailed run history, including the ability to open the corresponding project conversation.
- Updated styles to improve the presentation of automation statuses and history.

These changes aim to provide users with better insights into their automation routines and improve overall usability.

* feat(daemon): implement automation ingestion and proposal management

This commit introduces several new features related to automation ingestion and proposal management within the daemon. Key changes include:

- Added new modules for handling automation source packets and proposals, allowing for the storage, retrieval, and management of automation-related data.
- Implemented functions to list, create, and apply automation proposals, enhancing the automation workflow.
- Introduced new CLI commands for interacting with memory entries and automation sources, providing users with more control over their automation processes.
- Enhanced the server routes to support automation source and proposal APIs, enabling seamless integration with the existing system.

These changes aim to improve the overall automation experience, making it easier for users to manage and utilize automation proposals and ingestions effectively.
2026-05-19 16:35:28 +08:00

109 lines
3.1 KiB
TypeScript

import { promises as fsp } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
buildMemoryTree,
memoryDir,
readMemoryEntry,
updateMemoryTreeNode,
upsertMemoryEntry,
} from '../src/memory.js';
let dataDir = '';
beforeEach(async () => {
dataDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'od-memory-tree-'));
});
afterEach(async () => {
await fsp.rm(dataDir, { recursive: true, force: true });
});
describe('memory tree helpers', () => {
it('derives folder and entry nodes from the markdown memory store', async () => {
await upsertMemoryEntry(
dataDir,
{
name: 'Design agent goal',
description: 'Open Design should evolve from accepted work',
type: 'project',
body: '- Keep design-system extraction in the loop',
},
{ silent: true },
);
const tree = await buildMemoryTree(dataDir);
expect(tree).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'folder:project',
kind: 'folder',
path: '/project',
childrenCount: 1,
}),
expect.objectContaining({
id: 'project_design_agent_goal',
parentId: 'folder:project',
kind: 'entry',
path: '/project/project_design_agent_goal',
scope: 'project',
}),
]),
);
});
it('edits and moves an entry through the tree-aware patch helper', async () => {
const entry = await upsertMemoryEntry(
dataDir,
{
name: 'Reusable pattern',
description: 'Skill candidate',
type: 'reference',
body: '- Draft artifacts with provenance',
},
{ silent: true },
);
const updated = await updateMemoryTreeNode(dataDir, entry.id, {
name: 'Reusable pattern',
description: 'Skill candidate promoted from automation',
type: 'project',
body: '- Draft artifacts with provenance\n- Promote repeatable work into skills',
});
expect(updated.type).toBe('project');
expect(updated.description).toContain('promoted from automation');
expect(updated.body).toContain('Promote repeatable work into skills');
const stored = await readMemoryEntry(dataDir, entry.id);
expect(stored?.type).toBe('project');
const tree = await buildMemoryTree(dataDir);
expect(tree).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: entry.id,
parentId: 'folder:project',
path: `/project/${entry.id}`,
}),
]),
);
});
it('rejects derived folder edits', async () => {
await expect(
updateMemoryTreeNode(dataDir, 'folder:project', { name: 'Project' }),
).rejects.toThrow('memory tree folders are derived');
});
it('returns an empty folder tree when the store is empty', async () => {
await fsp.rm(memoryDir(dataDir), { recursive: true, force: true });
const tree = await buildMemoryTree(dataDir);
expect(tree.filter((node) => node.kind === 'folder')).toHaveLength(4);
expect(tree.filter((node) => node.kind === 'entry')).toHaveLength(0);
});
});