This commit is contained in:
@@ -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();
|
||||
@@ -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');
|
||||
Reference in New Issue
Block a user