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.
@@ -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: 3
311
+ max: 6
304
312
  goto:
305
313
  default:
306
- stage: move-ticket
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: 3
376
+ max: 2
370
377
  goto:
371
378
  default:
372
379
  stage: decompose-gaps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow-ai",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "AI Agent Workflow Coordinator — kanban-based pipeline for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
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
- const agentId = stage.agent;
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}`);