mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 01:09:40 +02:00
Single source of truth for AI Workbench types shared between the webapp
(Vite/SvelteKit) and the server-side mana-ai Bun service. Prevents the
two runtimes from drifting on prompt shape or mission structure.
- `@mana/shared-ai` package:
- `actor.ts` — Actor union (user | ai | system) + helpers, mirrors the
webapp's runtime type so server-side consumers parse incoming actors
without re-declaring
- `missions/types.ts` — Mission, MissionCadence, MissionInputRef,
MissionIteration, PlanStep, MissionState. Adds optional
`iteration.source: 'browser' | 'server'` to distinguish foreground
vs server-produced iterations (groundwork for proposal write-back)
- `planner/prompt.ts` — `buildPlannerPrompt` pure function
- `planner/parser.ts` — `parsePlannerResponse` strict JSON validator
- Vitest smoke tests (2) cover prompt → parse round-trip + unknown-
tool rejection
- Webapp:
- `missions/types.ts` re-exports from shared-ai, keeps webapp-local
`MISSIONS_TABLE` constant + `planStepStatusFromProposal` bridge
- `missions/planner/{types,prompt,parser}.ts` become re-export stubs
so existing imports keep working unchanged
- Existing webapp tests (60) continue to pass — the wire code didn't
move, just its home
Next: mana-ai service imports buildPlannerPrompt/parsePlannerResponse
from shared-ai + wires mana-llm + writes iteration back as a
'source=server' row (tracked in services/mana-ai/CLAUDE.md).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
60 lines
1.5 KiB
TypeScript
60 lines
1.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { parsePlannerResponse } from './parser';
|
|
import { buildPlannerPrompt } from './prompt';
|
|
import type { AiPlanInput } from './types';
|
|
import type { Mission } from '../missions/types';
|
|
|
|
const TOOLS = new Set(['create_task']);
|
|
|
|
function mission(): Mission {
|
|
return {
|
|
id: 'm',
|
|
createdAt: '2026-04-14T00:00:00Z',
|
|
updatedAt: '2026-04-14T00:00:00Z',
|
|
title: 'Test',
|
|
conceptMarkdown: '',
|
|
objective: 'x',
|
|
inputs: [],
|
|
cadence: { kind: 'manual' },
|
|
state: 'active',
|
|
iterations: [],
|
|
};
|
|
}
|
|
|
|
describe('shared-ai planner', () => {
|
|
it('prompt + parser round-trip a valid plan', () => {
|
|
const input: AiPlanInput = {
|
|
mission: mission(),
|
|
resolvedInputs: [],
|
|
availableTools: [
|
|
{
|
|
name: 'create_task',
|
|
module: 'todo',
|
|
description: 'Creates a task',
|
|
parameters: [{ name: 'title', type: 'string', required: true, description: 'Title' }],
|
|
},
|
|
],
|
|
};
|
|
const { system } = buildPlannerPrompt(input);
|
|
expect(system).toContain('create_task');
|
|
|
|
const response = `\`\`\`json
|
|
{
|
|
"summary": "test",
|
|
"steps": [
|
|
{ "summary": "s", "toolName": "create_task", "params": { "title": "x" }, "rationale": "why" }
|
|
]
|
|
}
|
|
\`\`\``;
|
|
const r = parsePlannerResponse(response, TOOLS);
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
it('rejects unknown tool names', () => {
|
|
const r = parsePlannerResponse(
|
|
`{"summary":"","steps":[{"toolName":"delete_everything","params":{},"rationale":"lol"}]}`,
|
|
TOOLS
|
|
);
|
|
expect(r.ok).toBe(false);
|
|
});
|
|
});
|