@hubfluencer/mcp 0.9.1 → 0.11.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubfluencer/mcp",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "Model Context Protocol server for Hubfluencer — let AI agents generate post-ready shorts and editor ads.",
5
5
  "license": "MIT",
6
6
  "author": "Monocursive <contact@monocursive.com>",
package/src/core.ts CHANGED
@@ -265,6 +265,16 @@ export interface NormalizedStatus {
265
265
  ready: boolean;
266
266
  video_url: string | null;
267
267
  error: string | null;
268
+ /** Short only: exact API failure phase instead of a collapsed terminal state. */
269
+ failed_stage?: string;
270
+ /** Short only: stable machine-readable terminal failure classification. */
271
+ failure_code?: string;
272
+ /** Short only: structured provider/QC details for recovery decisions. */
273
+ failure_details?: Record<string, unknown>;
274
+ /** Short only: worker/recovery layer that finalized the failed attempt. */
275
+ failure_source?: string;
276
+ /** Short only: paid generation attempt number that failed. */
277
+ failure_attempt?: number;
268
278
  /** Editor only: the newest delivered render no longer matches the live project. */
269
279
  stale?: boolean;
270
280
  /** Editor only: current scene ids that are still generating. */
@@ -341,6 +351,16 @@ export function normalizeStatus(
341
351
  video_url: videoUrl,
342
352
  error: (d.error_message as string) ?? null,
343
353
  };
354
+ if (typeof d.failed_stage === "string")
355
+ status.failed_stage = d.failed_stage;
356
+ if (typeof d.failure_code === "string")
357
+ status.failure_code = d.failure_code;
358
+ if (d.failure_details && typeof d.failure_details === "object")
359
+ status.failure_details = d.failure_details as Record<string, unknown>;
360
+ if (typeof d.failure_source === "string")
361
+ status.failure_source = d.failure_source;
362
+ if (typeof d.failure_attempt === "number")
363
+ status.failure_attempt = d.failure_attempt;
344
364
  // Additive free-re-render bookkeeping (2026-07 API): surfaced only when
345
365
  // the server actually sends the booleans, so older payloads normalize
346
366
  // byte-identically. A failed free re-render does NOT flip stage to
@@ -437,15 +457,14 @@ export function normalizeStatus(
437
457
  );
438
458
  const batchStatus = (d.batch_generation_status as string) ?? null;
439
459
  // The batch enum's ACTIVE states (video_factory.ex): preparing (spinning up),
440
- // awaiting_previews (previews generating), running, and paused_for_retry
441
- // (auto-resuming). "queued" is not a real value and was dropped; "preparing"
442
- // and "awaiting_previews" were missing, so an in-flight batch briefly looked
443
- // idle and could be reported terminal mid-run.
460
+ // awaiting_previews (previews generating), and running. "queued" is not a real
461
+ // value and was dropped. paused_for_retry is deliberately NOT active: no worker
462
+ // owns it until the user explicitly retries or cancels the paused batch.
444
463
  const batchActive =
445
464
  batchStatus === "running" ||
446
465
  batchStatus === "preparing" ||
447
- batchStatus === "awaiting_previews" ||
448
- batchStatus === "paused_for_retry";
466
+ batchStatus === "awaiting_previews";
467
+ const batchRetryRequired = batchStatus === "paused_for_retry";
449
468
  // A parked batch that ran out of credits: it cannot progress without the user
450
469
  // topping up or reducing scope, so it is a needs-user-action TERMINAL state
451
470
  // (not active work to wait on).
@@ -546,14 +565,39 @@ export function normalizeStatus(
546
565
  currentMusic.status === "processing" ||
547
566
  newerAudioVersions.some(versionActive) ||
548
567
  newerMusicVersions.some(versionActive);
549
- const workActive =
550
- autopilotActive ||
568
+ const childWorkActive =
551
569
  batchActive ||
552
570
  renderActive ||
553
571
  scenarioActive ||
554
572
  narrationActive ||
555
573
  audioActive ||
556
574
  activeSegments.length > 0;
575
+ // A resumed Autopilot briefly exposes the previous attempt's failed child
576
+ // states before its new worker reaches and clears them. Suppress that history
577
+ // only while real child work is active or for a short, timestamped hand-off
578
+ // window. A parent stuck "running" with no worker must eventually surface the
579
+ // child failure instead of making wait_for_completion poll forever.
580
+ const autopilotStartedAt =
581
+ typeof d.autopilot_started_at === "string"
582
+ ? Date.parse(d.autopilot_started_at)
583
+ : Number.NaN;
584
+ const now = Date.now();
585
+ const autopilotResumeGraceActive =
586
+ autopilotActive &&
587
+ Number.isFinite(autopilotStartedAt) &&
588
+ autopilotStartedAt <= now &&
589
+ now - autopilotStartedAt <= 120_000;
590
+ const autopilotFailed = autopilot === "failed" || autopilot === "cancelled";
591
+ // Retrying a paused batch deliberately leaves the failed parent Autopilot
592
+ // marker in place while the already-paid child batch finishes. In that one
593
+ // state combination the parent/old scene failures are history, not a reason
594
+ // to return terminal immediately. Once the batch settles, the parent failure
595
+ // becomes actionable again and the agent can quote/relaunch Autopilot.
596
+ const batchRecoveryActive = autopilotFailed && batchActive;
597
+ const childFailureActionable =
598
+ !batchRecoveryActive &&
599
+ (!autopilotActive || (!childWorkActive && !autopilotResumeGraceActive));
600
+ const workActive = autopilotActive || childWorkActive;
557
601
  // Orchestration failures are historical once a repaired timeline has a fresh,
558
602
  // completed MP4. Keeping them terminal would permanently block download_result
559
603
  // after a successful granular recovery.
@@ -566,17 +610,26 @@ export function normalizeStatus(
566
610
  failedSegments.length === 0 &&
567
611
  !audioFailed;
568
612
  const ready = deliveredArtifactSupersedesOrchestrationFailure;
569
- const autopilotFailed = autopilot === "failed" || autopilot === "cancelled";
570
- const renderFailed = renderStatus === "failed";
613
+ const parentFailureActionable = autopilotFailed && !batchRecoveryActive;
614
+ const renderFailed = renderStatus === "failed" && childFailureActionable;
571
615
  const failed =
572
- (autopilotFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
616
+ (parentFailureActionable &&
617
+ !deliveredArtifactSupersedesOrchestrationFailure) ||
573
618
  renderFailed ||
574
- (scenarioApplyFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
575
- (scenarioFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
576
- (narrationFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
577
- (batchFailed && !deliveredArtifactSupersedesOrchestrationFailure) ||
578
- failedSegments.length > 0 ||
579
- audioFailed;
619
+ (childFailureActionable &&
620
+ scenarioApplyFailed &&
621
+ !deliveredArtifactSupersedesOrchestrationFailure) ||
622
+ (childFailureActionable &&
623
+ scenarioFailed &&
624
+ !deliveredArtifactSupersedesOrchestrationFailure) ||
625
+ (childFailureActionable &&
626
+ narrationFailed &&
627
+ !deliveredArtifactSupersedesOrchestrationFailure) ||
628
+ (childFailureActionable &&
629
+ batchFailed &&
630
+ !deliveredArtifactSupersedesOrchestrationFailure) ||
631
+ (childFailureActionable && failedSegments.length > 0) ||
632
+ (childFailureActionable && audioFailed);
580
633
  // Terminal = control should return to the agent because nothing will progress
581
634
  // without another tool call. With every async job type folded into workActive
582
635
  // above (autopilot, batch, segments, scenario, narration, audio, render), NO
@@ -585,21 +638,35 @@ export function normalizeStatus(
585
638
  // a new tool call can move it, so wait_for_completion must return instead of
586
639
  // polling its full budget. (ready is subsumed by !workActive but kept for
587
640
  // clarity; explicit failure states are terminal even while cleanup work spins.)
588
- const terminal = ready || failed || batchInsufficientCredits || !workActive;
641
+ const terminal =
642
+ ready ||
643
+ failed ||
644
+ batchRetryRequired ||
645
+ batchInsufficientCredits ||
646
+ !workActive;
589
647
  let stage = autopilot;
590
648
  // Failure always wins over an unrelated active flag. Workers can leave an
591
649
  // outer autopilot/batch state active after a child stage has failed; reporting
592
650
  // the active stage would make agents poll forever instead of taking action.
593
651
  if (ready) stage = "completed";
594
- else if (autopilot === "failed") stage = "failed";
595
- else if (autopilot === "cancelled") stage = "cancelled";
652
+ // A paused child batch is the immediate recovery gate even when its parent
653
+ // Autopilot run has already failed/cancelled. Reporting only the generic
654
+ // parent terminal state hides the one action that must happen before a new
655
+ // Autopilot run is allowed to start.
656
+ else if (batchRetryRequired) stage = "batch_retry_required";
657
+ else if (parentFailureActionable && autopilot === "failed") stage = "failed";
658
+ else if (parentFailureActionable && autopilot === "cancelled")
659
+ stage = "cancelled";
596
660
  else if (renderFailed) stage = "render_failed";
597
- else if (scenarioApplyFailed) stage = "scenario_apply_failed";
598
- else if (scenarioFailed) stage = "scenario_failed";
599
- else if (narrationFailed) stage = "narration_failed";
600
- else if (batchFailed) stage = "batch_failed";
601
- else if (failedSegments.length > 0) stage = "segment_failed";
602
- else if (audioFailed) stage = "audio_failed";
661
+ else if (childFailureActionable && scenarioApplyFailed)
662
+ stage = "scenario_apply_failed";
663
+ else if (childFailureActionable && scenarioFailed) stage = "scenario_failed";
664
+ else if (childFailureActionable && narrationFailed)
665
+ stage = "narration_failed";
666
+ else if (childFailureActionable && batchFailed) stage = "batch_failed";
667
+ else if (childFailureActionable && failedSegments.length > 0)
668
+ stage = "segment_failed";
669
+ else if (childFailureActionable && audioFailed) stage = "audio_failed";
603
670
  else if (batchInsufficientCredits) stage = "insufficient_credits";
604
671
  else if (activeSegments.length > 0) stage = "segment_processing";
605
672
  else if (batchActive) stage = "segment_generation";
@@ -613,6 +680,15 @@ export function normalizeStatus(
613
680
  .find(
614
681
  (value): value is string => typeof value === "string" && value.length > 0,
615
682
  );
683
+ const actionableChildError = childFailureActionable
684
+ ? ((latest.error_message as string) ??
685
+ (d.scenario_apply_error_message as string) ??
686
+ (d.scenario_error_message as string) ??
687
+ (d.narration_error_message as string) ??
688
+ (d.batch_generation_error_message as string) ??
689
+ segmentError ??
690
+ audioError)
691
+ : null;
616
692
  return {
617
693
  kind,
618
694
  slug,
@@ -622,17 +698,15 @@ export function normalizeStatus(
622
698
  video_url: videoUrl,
623
699
  error: ready
624
700
  ? null
625
- : ((d.autopilot_error_message as string) ??
626
- (latest.error_message as string) ??
627
- (d.scenario_apply_error_message as string) ??
628
- (d.scenario_error_message as string) ??
629
- (d.narration_error_message as string) ??
630
- (d.batch_generation_error_message as string) ??
631
- segmentError ??
632
- audioError ??
633
- (batchInsufficientCredits
634
- ? "Batch generation paused: not enough credits to finish. Top up or reduce scope, then resume."
635
- : null)),
701
+ : batchRetryRequired
702
+ ? "Batch generation is paused after a failed scene. Call retry_editor_batch or cancel_editor_batch before continuing."
703
+ : ((parentFailureActionable
704
+ ? (d.autopilot_error_message as string)
705
+ : null) ??
706
+ actionableChildError ??
707
+ (batchInsufficientCredits
708
+ ? "Batch generation paused: not enough credits to finish. Top up or reduce scope, then resume."
709
+ : null)),
636
710
  stale,
637
711
  active_segment_ids: activeSegments
638
712
  .map((segment) => segment.id)