workflow-ai 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/configs/pipeline.yaml +12 -5
- package/package.json +1 -1
- package/src/runner.mjs +36 -38
package/configs/pipeline.yaml
CHANGED
|
@@ -233,6 +233,12 @@ pipeline:
|
|
|
233
233
|
agent: qwen-code
|
|
234
234
|
fallback_agent: kilo-deepseek
|
|
235
235
|
skill: execute-task
|
|
236
|
+
counter: task_attempts
|
|
237
|
+
agent_by_attempt:
|
|
238
|
+
1: qwen-code
|
|
239
|
+
2: claude-sonnet
|
|
240
|
+
3: claude-opus
|
|
241
|
+
4: kilo-deepseek
|
|
236
242
|
goto:
|
|
237
243
|
default:
|
|
238
244
|
stage: move-to-review
|
|
@@ -275,6 +281,7 @@ pipeline:
|
|
|
275
281
|
agent: claude-sonnet
|
|
276
282
|
fallback_agent: qwen-code
|
|
277
283
|
skill: review-result
|
|
284
|
+
counter: task_attempts
|
|
278
285
|
goto:
|
|
279
286
|
passed:
|
|
280
287
|
stage: move-ticket
|
|
@@ -294,19 +301,19 @@ pipeline:
|
|
|
294
301
|
# -------------------------------------------------------------------------
|
|
295
302
|
# 4b. increment-task-attempts
|
|
296
303
|
# Отдельный стейдж обновления счётчика попыток выполнения тикета.
|
|
297
|
-
# При достижении max — блокирует тикет. Иначе —
|
|
304
|
+
# При достижении max — блокирует тикет. Иначе — отправляет на доработку.
|
|
305
|
+
# Проверка лимита происходит ДО повторного execute-task.
|
|
298
306
|
# -------------------------------------------------------------------------
|
|
299
307
|
increment-task-attempts:
|
|
300
308
|
description: "Обновить счётчик попыток выполнения тикета"
|
|
301
309
|
type: update-counter
|
|
302
310
|
counter: task_attempts
|
|
303
|
-
max:
|
|
311
|
+
max: 6
|
|
304
312
|
goto:
|
|
305
313
|
default:
|
|
306
|
-
stage:
|
|
314
|
+
stage: execute-task
|
|
307
315
|
params:
|
|
308
316
|
ticket_id: "$context.ticket_id"
|
|
309
|
-
target: ready
|
|
310
317
|
max_reached:
|
|
311
318
|
stage: move-ticket
|
|
312
319
|
params:
|
|
@@ -366,7 +373,7 @@ pipeline:
|
|
|
366
373
|
description: "Обновить счётчик итераций анализа плана"
|
|
367
374
|
type: update-counter
|
|
368
375
|
counter: plan_iterations
|
|
369
|
-
max:
|
|
376
|
+
max: 2
|
|
370
377
|
goto:
|
|
371
378
|
default:
|
|
372
379
|
stage: decompose-gaps
|
package/package.json
CHANGED
package/src/runner.mjs
CHANGED
|
@@ -681,7 +681,17 @@ class StageExecutor {
|
|
|
681
681
|
throw new Error(`Stage not found: ${stageId}`);
|
|
682
682
|
}
|
|
683
683
|
|
|
684
|
-
|
|
684
|
+
// Выбираем агента: если есть agent_by_attempt и счётчик — ротация по попыткам
|
|
685
|
+
let agentId = stage.agent;
|
|
686
|
+
if (stage.agent_by_attempt && stage.counter) {
|
|
687
|
+
const attempt = this.counters[stage.counter] || 0;
|
|
688
|
+
if (stage.agent_by_attempt[attempt]) {
|
|
689
|
+
agentId = stage.agent_by_attempt[attempt];
|
|
690
|
+
if (this.logger) {
|
|
691
|
+
this.logger.info(`Agent rotation: attempt ${attempt} → ${agentId}`, stageId);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
685
695
|
const agent = this.pipeline.agents[agentId];
|
|
686
696
|
if (!agent) {
|
|
687
697
|
throw new Error(`Agent not found: ${agentId}`);
|
|
@@ -783,9 +793,19 @@ class StageExecutor {
|
|
|
783
793
|
if (!line.trim()) continue;
|
|
784
794
|
try {
|
|
785
795
|
const obj = JSON.parse(line);
|
|
796
|
+
// Claude: content_block_delta с delta.text
|
|
786
797
|
if (obj.type === 'content_block_delta' && obj.delta?.text) {
|
|
787
798
|
process.stdout.write(obj.delta.text);
|
|
788
799
|
}
|
|
800
|
+
// Qwen/Claude: assistant message с content text
|
|
801
|
+
else if (obj.type === 'assistant' && obj.message?.content) {
|
|
802
|
+
for (const block of obj.message.content) {
|
|
803
|
+
if (block.type === 'text' && block.text) {
|
|
804
|
+
process.stdout.write(block.text);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
// result содержит финальный текст (дублирует assistant) — пропускаем
|
|
789
809
|
} catch {
|
|
790
810
|
// не JSON — выводим как есть
|
|
791
811
|
process.stdout.write(line + '\n');
|
|
@@ -800,6 +820,18 @@ class StageExecutor {
|
|
|
800
820
|
|
|
801
821
|
child.on('close', (code) => {
|
|
802
822
|
clearTimeout(timeoutId);
|
|
823
|
+
// Обрабатываем остаток буфера стриминга
|
|
824
|
+
if (stdoutBuffer.trim()) {
|
|
825
|
+
try {
|
|
826
|
+
const obj = JSON.parse(stdoutBuffer);
|
|
827
|
+
if (obj.type === 'content_block_delta' && obj.delta?.text) {
|
|
828
|
+
process.stdout.write(obj.delta.text);
|
|
829
|
+
}
|
|
830
|
+
} catch {
|
|
831
|
+
process.stdout.write(stdoutBuffer + '\n');
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
process.stdout.write('\n');
|
|
803
835
|
|
|
804
836
|
if (timedOut) return;
|
|
805
837
|
|
|
@@ -1101,7 +1133,8 @@ class PipelineRunner {
|
|
|
1101
1133
|
return {
|
|
1102
1134
|
steps: this.stepCount,
|
|
1103
1135
|
tasksExecuted: this.tasksExecuted,
|
|
1104
|
-
context: this.context
|
|
1136
|
+
context: this.context,
|
|
1137
|
+
failed: !this.running && this.stepCount < maxSteps
|
|
1105
1138
|
};
|
|
1106
1139
|
}
|
|
1107
1140
|
|
|
@@ -1134,41 +1167,6 @@ class PipelineRunner {
|
|
|
1134
1167
|
this.updateContext(transition.params, result.result);
|
|
1135
1168
|
}
|
|
1136
1169
|
|
|
1137
|
-
// Проверяем retry-логику с agent_by_attempt
|
|
1138
|
-
if (transition.agent_by_attempt && stage.counter) {
|
|
1139
|
-
const attempt = this.counters[stage.counter] || 1;
|
|
1140
|
-
const maxAttempts = transition.max || 3;
|
|
1141
|
-
|
|
1142
|
-
if (attempt < maxAttempts) {
|
|
1143
|
-
// Ещё есть попытки — переходим на указанный stage с переопределением агента
|
|
1144
|
-
const nextStage = transition.stage || stageId;
|
|
1145
|
-
const overrideAgent = transition.agent_by_attempt[attempt];
|
|
1146
|
-
|
|
1147
|
-
if (overrideAgent) {
|
|
1148
|
-
// Переопределяем агента для следующей попытки
|
|
1149
|
-
stage.agent = overrideAgent;
|
|
1150
|
-
this.logger.info(`Retry attempt ${attempt}: overriding agent to ${overrideAgent}`, stageId);
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
this.logger.retry(stageId, attempt, maxAttempts);
|
|
1154
|
-
this.logger.gotoTransition(stageId, nextStage, status, transition.params);
|
|
1155
|
-
return nextStage;
|
|
1156
|
-
} else {
|
|
1157
|
-
// Попытки исчерпаны — переходим на on_max
|
|
1158
|
-
this.logger.info(`Max attempts (${maxAttempts}) reached for ${stageId}`, stageId);
|
|
1159
|
-
this.logger.retry(stageId, attempt, maxAttempts);
|
|
1160
|
-
|
|
1161
|
-
if (transition.on_max) {
|
|
1162
|
-
if (transition.on_max.params) {
|
|
1163
|
-
this.updateContext(transition.on_max.params, result.result);
|
|
1164
|
-
}
|
|
1165
|
-
const nextStage = transition.on_max.stage || 'end';
|
|
1166
|
-
this.logger.gotoTransition(stageId, nextStage, status, transition.on_max.params);
|
|
1167
|
-
return nextStage;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1170
|
const nextStage = transition.stage || 'end';
|
|
1173
1171
|
this.logger.gotoTransition(stageId, nextStage, status, transition.params);
|
|
1174
1172
|
return nextStage;
|
|
@@ -1437,7 +1435,7 @@ async function runPipeline(argv = process.argv.slice(2)) {
|
|
|
1437
1435
|
console.log(`Steps executed: ${result.steps}`);
|
|
1438
1436
|
console.log(`Tasks completed: ${result.tasksExecuted}`);
|
|
1439
1437
|
|
|
1440
|
-
return { exitCode: 0, result };
|
|
1438
|
+
return { exitCode: result.failed ? 1 : 0, result };
|
|
1441
1439
|
|
|
1442
1440
|
} catch (err) {
|
|
1443
1441
|
console.error(`\nError: ${err.message}`);
|