feat: workflow scaffold templates for meta coder (#3)
This commit is contained in:
parent
c37cf73ab7
commit
04a8683d0a
@ -54,6 +54,7 @@ ${testerFeedback}
|
|||||||
## 参考
|
## 参考
|
||||||
- 先阅读项目结构了解上下文
|
- 先阅读项目结构了解上下文
|
||||||
- 参考已有代码风格
|
- 参考已有代码风格
|
||||||
|
- **必读模板**:\`templates/workflow.ts.tmpl\` 和 \`templates/workflow.test.ts.tmpl\`,严格按模板结构写 workflow + test
|
||||||
|
|
||||||
## 步骤
|
## 步骤
|
||||||
1. 理解任务需求
|
1. 理解任务需求
|
||||||
|
|||||||
83
templates/workflow.test.ts.tmpl
Normal file
83
templates/workflow.test.ts.tmpl
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* {{WORKFLOW_NAME}} workflow tests
|
||||||
|
*
|
||||||
|
* 小橘 🍊 (NEKO Team)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it } from 'bun:test';
|
||||||
|
import { mkdtempSync, rmSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { createStore, createWorkflowRule } from '@uncaged/pulse';
|
||||||
|
import { {{WORKFLOW_EXPORT}} } from './{{WORKFLOW_FILE}}.js';
|
||||||
|
|
||||||
|
describe('{{WORKFLOW_NAME}} workflow', () => {
|
||||||
|
let tmpDir: string;
|
||||||
|
let store: ReturnType<typeof createStore>;
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
tmpDir = mkdtempSync(join(tmpdir(), 'engine-test-'));
|
||||||
|
store = createStore({
|
||||||
|
eventsDbPath: join(tmpDir, 'test.db'),
|
||||||
|
objectsDir: join(tmpDir, 'objects'),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
store?.close();
|
||||||
|
if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs full lifecycle: START → ... → END', async () => {
|
||||||
|
setup();
|
||||||
|
const rule = createWorkflowRule({{WORKFLOW_EXPORT}}, store);
|
||||||
|
|
||||||
|
// Trigger workflow
|
||||||
|
const hash = await store.putObject('test input');
|
||||||
|
await store.appendEvent({
|
||||||
|
occurredAt: Date.now(),
|
||||||
|
kind: '{{WORKFLOW_NAME}}.__start__',
|
||||||
|
key: 'test-1',
|
||||||
|
hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tick until quiescent
|
||||||
|
const executed: string[] = [];
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
const r = await rule.tick();
|
||||||
|
if (r.executed.length === 0) break;
|
||||||
|
for (const ex of r.executed) executed.push(ex.role);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all roles executed in order
|
||||||
|
expect(executed).toEqual(['step1', 'step2']);
|
||||||
|
|
||||||
|
// Verify __end__ event was written
|
||||||
|
const events = await store.getAfter(0);
|
||||||
|
const endEvent = events.find((e) => e.kind === '{{WORKFLOW_NAME}}.__end__');
|
||||||
|
expect(endEvent).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('produces expected output', async () => {
|
||||||
|
setup();
|
||||||
|
const rule = createWorkflowRule({{WORKFLOW_EXPORT}}, store);
|
||||||
|
|
||||||
|
const hash = await store.putObject('hello');
|
||||||
|
await store.appendEvent({
|
||||||
|
occurredAt: Date.now(),
|
||||||
|
kind: '{{WORKFLOW_NAME}}.__start__',
|
||||||
|
key: 'test-2',
|
||||||
|
hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
const r1 = await rule.tick();
|
||||||
|
expect(r1.executed.length).toBe(1);
|
||||||
|
expect(r1.executed[0].role).toBe('step1');
|
||||||
|
// TODO: assert on content/meta
|
||||||
|
|
||||||
|
const r2 = await rule.tick();
|
||||||
|
expect(r2.executed.length).toBe(1);
|
||||||
|
expect(r2.executed[0].role).toBe('step2');
|
||||||
|
// TODO: assert on content/meta
|
||||||
|
});
|
||||||
|
});
|
||||||
83
templates/workflow.ts.tmpl
Normal file
83
templates/workflow.ts.tmpl
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* {{WORKFLOW_NAME}} workflow
|
||||||
|
*
|
||||||
|
* {{DESCRIPTION}}
|
||||||
|
*
|
||||||
|
* 小橘 🍊 (NEKO Team)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { END, START, type Role, type WorkflowType } from '@uncaged/pulse';
|
||||||
|
|
||||||
|
// ── Role meta types ───────────────────────────────────────────
|
||||||
|
// Define a type for each role's meta output.
|
||||||
|
// Keep meta small — only what moderator and downstream roles need.
|
||||||
|
|
||||||
|
type Step1Meta = {
|
||||||
|
// e.g. result: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Step2Meta = {
|
||||||
|
// e.g. summary: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Roles type map ────────────────────────────────────────────
|
||||||
|
|
||||||
|
type {{WORKFLOW_ROLES_TYPE}} = {
|
||||||
|
step1: Role<Step1Meta>;
|
||||||
|
step2: Role<Step2Meta>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Workflow definition ───────────────────────────────────────
|
||||||
|
|
||||||
|
export const {{WORKFLOW_EXPORT}}: WorkflowType<{{WORKFLOW_ROLES_TYPE}}> = {
|
||||||
|
name: '{{WORKFLOW_NAME}}',
|
||||||
|
|
||||||
|
roles: {
|
||||||
|
/**
|
||||||
|
* Step 1: first role in the pipeline.
|
||||||
|
* Receives the full message chain (including __start__).
|
||||||
|
*/
|
||||||
|
step1: async (chain) => {
|
||||||
|
const start = chain.find((m) => m.role === '__start__');
|
||||||
|
const input = start?.content ?? '';
|
||||||
|
|
||||||
|
// TODO: implement role logic
|
||||||
|
const result = `processed: ${input}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: result,
|
||||||
|
meta: { /* step1 meta */ } as Step1Meta,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 2: second role, receives chain including step1 output.
|
||||||
|
*/
|
||||||
|
step2: async (chain) => {
|
||||||
|
const prev = chain.find((m) => m.role === 'step1');
|
||||||
|
|
||||||
|
// TODO: implement role logic
|
||||||
|
const summary = `done: ${prev?.content ?? ''}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: summary,
|
||||||
|
meta: { /* step2 meta */ } as Step2Meta,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moderator: state machine that decides the next role.
|
||||||
|
*
|
||||||
|
* Input.role is the LAST completed role (or START for new topics).
|
||||||
|
* Return the next role name, or END to finish.
|
||||||
|
*/
|
||||||
|
moderator: (output) => {
|
||||||
|
switch (output.role) {
|
||||||
|
case START: return 'step1';
|
||||||
|
case 'step1': return 'step2';
|
||||||
|
case 'step2': return END;
|
||||||
|
default: return END;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user