feat: workflow daemon + submit-task CLI
CI / test (push) Has been cancelled

This commit is contained in:
2026-04-17 13:01:19 +00:00
parent 04ced5b5f0
commit 02847d9001
2 changed files with 164 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bun
/**
* 向 Pulse Workflow Daemon 投递一个 meta workflow 任务。
* 用法:bun run packages/pulse/src/bin/submit-task.ts <workflow> <topic-key> <task-file>
*/
import { createStore } from '../store.js';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';
const [,, workflow, topicKey, taskFile] = process.argv;
if (!workflow || !topicKey || !taskFile) {
console.error('Usage: submit-task.ts <workflow> <topic-key> <task-file>');
process.exit(1);
}
const store = createStore({
eventsDbPath: join(homedir(), '.upulse/scopes/workflows.db'),
objectsDir: join(homedir(), '.upulse/scopes/objects'),
});
const content = readFileSync(taskFile, 'utf-8');
const hash = store.putObject(content);
store.appendEvent({
occurredAt: Date.now(),
kind: `${workflow}.__start__`,
key: topicKey,
hash,
});
console.log(`✅ Submitted: ${workflow}.__start__ [${topicKey}]`);
console.log(` Content: ${content.slice(0, 100)}...`);
console.log(` Hash: ${hash}`);
store.close();
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/env bun
/**
* Pulse Workflow Daemon — 注册 workflow,定时 tick。
*
* 用法:bun run packages/pulse/src/bin/workflow-daemon.ts
* 环境变量:DASHSCOPE_API_KEY, CURSOR_API_KEY
*
* 小橘 🍊 (NEKO Team)
*/
import { createOpenAiLlmClient } from '../llm-client.js';
import { createStore } from '../store.js';
import { createWorkflowRule } from '../workflows/workflow-rule-adapter.js';
import { createWorkflowTicker } from '../workflows/index.js';
import { createCodingWorkflow } from '../workflows/coding.js';
import { createMetaWorkflow } from '../workflows/meta.js';
import { createMetaArchitectRole } from '../workflows/roles/meta-architect-llm.js';
import { createMetaCoderRole } from '../workflows/roles/meta-coder-cursor.js';
import { createMetaReviewerRole } from '../workflows/roles/meta-reviewer-cursor.js';
import { createMetaTesterRole } from '../workflows/roles/meta-tester.js';
import { createMetaPromoterRole } from '../workflows/roles/meta-promoter.js';
import { createCursorRunner } from '../workflows/roles/agent-executor.js';
import { join } from 'node:path';
import { mkdirSync, existsSync } from 'node:fs';
import { homedir } from 'node:os';
// ── Config ─────────────────────────────────────────────────────
const REPO_DIR = join(import.meta.dir, '../../../../..');
const DATA_DIR = join(homedir(), '.upulse/scopes');
const TICK_INTERVAL_MS = 30_000; // 30 seconds
// Ensure data dir
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
// ── LLM + Cursor ───────────────────────────────────────────────
const apiKey = process.env.DASHSCOPE_API_KEY;
if (!apiKey) {
console.error('❌ DASHSCOPE_API_KEY required');
process.exit(1);
}
const llm = createOpenAiLlmClient({
apiKey,
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
model: 'qwen-plus',
timeoutMs: 120_000,
});
const cursorRunner = createCursorRunner({
agentBin: `${homedir()}/.local/bin/agent`,
});
// ── Store ──────────────────────────────────────────────────────
const store = createStore({
eventsDbPath: join(DATA_DIR, 'workflows.db'),
objectsDir: join(DATA_DIR, 'objects'),
});
const logStore = createStore({
eventsDbPath: join(DATA_DIR, 'workflow-logs.db'),
objectsDir: join(DATA_DIR, 'log-objects'),
});
// ── Register Workflows ─────────────────────────────────────────
// 1. Coding workflow (with mock roles for now)
const codingWf = createCodingWorkflow();
const codingRule = createWorkflowRule(codingWf, store, logStore);
// 2. Meta workflow (real LLM + Cursor roles)
const metaWf = createMetaWorkflow({
architect: createMetaArchitectRole(llm),
coder: createMetaCoderRole(cursorRunner, llm, REPO_DIR),
reviewer: createMetaReviewerRole(cursorRunner, llm, REPO_DIR),
tester: createMetaTesterRole(llm, { repoDir: REPO_DIR }),
promoter: createMetaPromoterRole({
repoDir: REPO_DIR,
remote: 'gitflare',
branch: 'main',
}),
});
const metaRule = createWorkflowRule(metaWf, store, logStore);
// ── Ticker ─────────────────────────────────────────────────────
const tick = createWorkflowTicker([codingRule, metaRule]);
console.log('🍊 Pulse Workflow Daemon started');
console.log(` Repo: ${REPO_DIR}`);
console.log(` Store: ${DATA_DIR}/workflows.db`);
console.log(` Tick interval: ${TICK_INTERVAL_MS / 1000}s`);
console.log(` Workflows: coding, meta`);
console.log('');
// Initial tick
await tick();
console.log('✅ Initial tick done\n');
// Tick loop
const interval = setInterval(async () => {
try {
await tick();
} catch (err) {
console.error('❌ Tick error:', err);
}
}, TICK_INTERVAL_MS);
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\n⏹️ Shutting down...');
clearInterval(interval);
store.close();
logStore.close();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('\n⏹️ SIGTERM received, shutting down...');
clearInterval(interval);
store.close();
logStore.close();
process.exit(0);
});
// Keep alive
console.log('⏳ Waiting for events... (Ctrl+C to stop)\n');