add pipeline task system, agentic music tasks, copy buttons, and pi-bridge cleanup
- Pipeline mode: new task mode that chains agentic tasks sequentially with foreach/subdirectory iteration and skip_if conditions - Pipeline executor backend (WebSocket at /api/tasks/pipeline/ws) with support for both Pi and Claude Code models - Frontend PipelineRunner component with step progress, streaming output, and aggregate cost tracking - New agentic tasks: prepare-discography, fetch-album-info, build-discography (pipeline combining both) - Seed parser extended to handle pipeline steps in frontmatter config - CopyButton component added to assistant bubbles, error bubbles, and tool input/output sections - Removed obsolete SearXNG/Apify/browser relay code from pi-bridge Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,59 @@ function parseFrontmatter(content: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse pipeline steps
|
||||
const stepsMatch = yaml.match(/^steps:\s*\n((?:[ \t]+.+\n?)*)/m);
|
||||
if (stepsMatch) {
|
||||
const stepsBlock = stepsMatch[1]!;
|
||||
const steps: Record<string, unknown>[] = [];
|
||||
let currentStep: Record<string, unknown> | null = null;
|
||||
let inInputs = false;
|
||||
let stepInputs: Record<string, string> = {};
|
||||
|
||||
for (const line of stepsBlock.split('\n')) {
|
||||
// New step entry (2 spaces + dash)
|
||||
const stepStart = line.match(/^\s{2}-\s+task:\s*(.+)$/);
|
||||
if (stepStart) {
|
||||
if (currentStep) {
|
||||
if (Object.keys(stepInputs).length > 0) currentStep.inputs = stepInputs;
|
||||
steps.push(currentStep);
|
||||
}
|
||||
currentStep = { task: stepStart[1]!.trim() };
|
||||
stepInputs = {};
|
||||
inInputs = false;
|
||||
continue;
|
||||
}
|
||||
if (!currentStep) continue;
|
||||
|
||||
// Step-level properties (4 spaces)
|
||||
const propMatch = line.match(/^\s{4}(\w[\w_-]*):\s*(.*)$/);
|
||||
if (propMatch) {
|
||||
const key = propMatch[1]!;
|
||||
const value = propMatch[2]!.trim();
|
||||
if (key === 'inputs' && value === '') {
|
||||
inInputs = true;
|
||||
} else {
|
||||
inInputs = false;
|
||||
currentStep[key] = value;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step input entries (6 spaces)
|
||||
const inputMatch = line.match(/^\s{6}(\w[\w_-]*):\s*(.+)$/);
|
||||
if (inputMatch && inInputs) {
|
||||
stepInputs[inputMatch[1]!] = inputMatch[2]!.trim();
|
||||
}
|
||||
}
|
||||
if (currentStep) {
|
||||
if (Object.keys(stepInputs).length > 0) currentStep.inputs = stepInputs;
|
||||
steps.push(currentStep);
|
||||
}
|
||||
if (steps.length > 0) {
|
||||
meta.config = { steps };
|
||||
}
|
||||
}
|
||||
|
||||
return { meta, body };
|
||||
}
|
||||
|
||||
@@ -151,6 +204,7 @@ async function seedTasks() {
|
||||
args: (meta.args as string[]) || null,
|
||||
trigger: (meta.trigger as TriggerConfig[]) || null,
|
||||
inputs: (meta.inputs as Record<string, unknown>) || null,
|
||||
config: (meta.config as Record<string, unknown>) || null,
|
||||
};
|
||||
|
||||
// Upsert: check by dirName + scope since unique constraint doesn't work with NULL userId
|
||||
@@ -174,6 +228,7 @@ async function seedTasks() {
|
||||
args: values.args,
|
||||
trigger: values.trigger,
|
||||
inputs: values.inputs,
|
||||
config: values.config,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(tasks.id, existing[0]!.id));
|
||||
|
||||
Reference in New Issue
Block a user