loki-mode 6.18.0 → 6.19.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/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v6.18.0
6
+ # Loki Mode v6.19.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -267,4 +267,4 @@ The following features are documented in skill modules but not yet fully automat
267
267
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
268
268
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
269
269
 
270
- **v6.18.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
270
+ **v6.19.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.18.0
1
+ 6.19.0
package/autonomy/loki CHANGED
@@ -435,6 +435,7 @@ show_help() {
435
435
  echo " agent [cmd] Agent type dispatch (list|info|run|start|review)"
436
436
  echo " remote [PRD] Start remote session (connect from phone/browser, Claude Pro/Max)"
437
437
  echo " trigger Event-driven autonomous execution (schedules, webhooks)"
438
+ echo " failover [cmd] Cross-provider auto-failover (status|--enable|--test|--chain)"
438
439
  echo " plan <PRD> Dry-run PRD analysis: complexity, cost, and execution plan"
439
440
  echo " version Show version"
440
441
  echo " help Show this help"
@@ -8299,6 +8300,225 @@ for line in sys.stdin:
8299
8300
  esac
8300
8301
  }
8301
8302
 
8303
+ # Cross-provider auto-failover management (v6.19.0)
8304
+ cmd_failover() {
8305
+ local loki_dir="${TARGET_DIR:-.}/.loki"
8306
+ local failover_file="$loki_dir/state/failover.json"
8307
+ local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8308
+
8309
+ if [ $# -eq 0 ]; then
8310
+ # No args: show current failover config and health
8311
+ if [ ! -f "$failover_file" ]; then
8312
+ echo -e "${BOLD}Cross-Provider Auto-Failover${NC}"
8313
+ echo ""
8314
+ echo "Status: DISABLED"
8315
+ echo ""
8316
+ echo "Enable with: loki failover --enable"
8317
+ echo "Or set: LOKI_FAILOVER=true loki start"
8318
+ return 0
8319
+ fi
8320
+
8321
+ echo -e "${BOLD}Cross-Provider Auto-Failover${NC}"
8322
+ echo ""
8323
+ python3 << 'PYEOF'
8324
+ import json, os
8325
+ fpath = os.path.join(os.environ.get('TARGET_DIR', '.'), '.loki/state/failover.json')
8326
+ try:
8327
+ with open(fpath) as f:
8328
+ d = json.load(f)
8329
+ status = "ENABLED" if d.get("enabled") else "DISABLED"
8330
+ print(f"Status: {status}")
8331
+ print(f"Chain: {' -> '.join(d.get('chain', []))}")
8332
+ print(f"Current provider: {d.get('currentProvider', 'unknown')}")
8333
+ print(f"Primary provider: {d.get('primaryProvider', 'unknown')}")
8334
+ print(f"Failover count: {d.get('failoverCount', 0)}")
8335
+ last = d.get('lastFailover')
8336
+ print(f"Last failover: {last if last else 'never'}")
8337
+ print()
8338
+ print("Health Status:")
8339
+ health = d.get('healthCheck', {})
8340
+ for provider in d.get('chain', []):
8341
+ h = health.get(provider, 'unknown')
8342
+ indicator = '[OK]' if h == 'healthy' else '[--]' if h == 'unknown' else '[!!]'
8343
+ print(f" {indicator} {provider}: {h}")
8344
+ except FileNotFoundError:
8345
+ print("Failover not initialized. Run: loki failover --enable")
8346
+ except Exception as e:
8347
+ print(f"Error reading failover state: {e}")
8348
+ PYEOF
8349
+ return 0
8350
+ fi
8351
+
8352
+ while [[ $# -gt 0 ]]; do
8353
+ case "$1" in
8354
+ --enable)
8355
+ mkdir -p "$loki_dir/state"
8356
+ local current_provider
8357
+ current_provider=$(cat "$loki_dir/state/provider" 2>/dev/null || echo "claude")
8358
+ local chain="${LOKI_FAILOVER_CHAIN:-claude,codex,gemini}"
8359
+
8360
+ cat > "$failover_file" << FEOF
8361
+ {
8362
+ "enabled": true,
8363
+ "chain": $(printf '%s' "$chain" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip().split(",")))' 2>/dev/null || echo '["claude","codex","gemini"]'),
8364
+ "currentProvider": "$current_provider",
8365
+ "primaryProvider": "$current_provider",
8366
+ "lastFailover": null,
8367
+ "failoverCount": 0,
8368
+ "healthCheck": {}
8369
+ }
8370
+ FEOF
8371
+ echo -e "${GREEN}Failover enabled${NC}"
8372
+ echo "Chain: $chain"
8373
+ echo "Primary: $current_provider"
8374
+ shift
8375
+ ;;
8376
+ --disable)
8377
+ if [ -f "$failover_file" ]; then
8378
+ python3 -c "
8379
+ import json
8380
+ with open('$failover_file') as f: d = json.load(f)
8381
+ d['enabled'] = False
8382
+ with open('$failover_file', 'w') as f: json.dump(d, f, indent=2)
8383
+ " 2>/dev/null
8384
+ echo -e "${YELLOW}Failover disabled${NC}"
8385
+ else
8386
+ echo "Failover not initialized."
8387
+ fi
8388
+ shift
8389
+ ;;
8390
+ --chain)
8391
+ shift
8392
+ local new_chain="$1"
8393
+ if [ -z "$new_chain" ]; then
8394
+ echo -e "${RED}Error: --chain requires a comma-separated list of providers${NC}"
8395
+ return 1
8396
+ fi
8397
+ # Validate each provider in chain
8398
+ local IFS=','
8399
+ for p in $new_chain; do
8400
+ case "$p" in
8401
+ claude|codex|gemini|cline|aider) ;;
8402
+ *) echo -e "${RED}Error: invalid provider '$p' in chain${NC}"; return 1 ;;
8403
+ esac
8404
+ done
8405
+ unset IFS
8406
+
8407
+ if [ ! -f "$failover_file" ]; then
8408
+ echo -e "${RED}Error: failover not initialized. Run: loki failover --enable${NC}"
8409
+ return 1
8410
+ fi
8411
+
8412
+ python3 -c "
8413
+ import json
8414
+ with open('$failover_file') as f: d = json.load(f)
8415
+ d['chain'] = '$new_chain'.split(',')
8416
+ with open('$failover_file', 'w') as f: json.dump(d, f, indent=2)
8417
+ " 2>/dev/null
8418
+ echo "Failover chain updated: $new_chain"
8419
+ shift
8420
+ ;;
8421
+ --test)
8422
+ echo -e "${BOLD}Testing all providers in failover chain...${NC}"
8423
+ echo ""
8424
+ # Source provider loader for health checks
8425
+ if [ -f "$script_dir/../providers/loader.sh" ]; then
8426
+ source "$script_dir/../providers/loader.sh"
8427
+ fi
8428
+
8429
+ local chain_providers="claude,codex,gemini"
8430
+ if [ -f "$failover_file" ]; then
8431
+ chain_providers=$(python3 -c "import json; print(','.join(json.load(open('$failover_file')).get('chain', ['claude','codex','gemini'])))" 2>/dev/null || echo "claude,codex,gemini")
8432
+ fi
8433
+
8434
+ local IFS=','
8435
+ local all_pass=true
8436
+ for p in $chain_providers; do
8437
+ printf " %-10s " "$p:"
8438
+
8439
+ # Check CLI installed
8440
+ local cli_name="$p"
8441
+ if command -v "$cli_name" &>/dev/null; then
8442
+ local cli_version
8443
+ cli_version=$("$cli_name" --version 2>/dev/null | head -1 || echo "unknown")
8444
+ printf "CLI %-20s " "[$cli_version]"
8445
+ else
8446
+ printf "CLI %-20s " "[NOT INSTALLED]"
8447
+ echo -e "${RED}FAIL${NC}"
8448
+ all_pass=false
8449
+ continue
8450
+ fi
8451
+
8452
+ # Check API key
8453
+ local has_key=false
8454
+ case "$p" in
8455
+ claude) [ -n "${ANTHROPIC_API_KEY:-}" ] && has_key=true ;;
8456
+ codex) [ -n "${OPENAI_API_KEY:-}" ] && has_key=true ;;
8457
+ gemini) [ -n "${GOOGLE_API_KEY:-${GEMINI_API_KEY:-}}" ] && has_key=true ;;
8458
+ cline|aider) has_key=true ;; # Key check varies
8459
+ esac
8460
+
8461
+ if [ "$has_key" = "true" ]; then
8462
+ printf "Key [OK] "
8463
+ echo -e "${GREEN}PASS${NC}"
8464
+ else
8465
+ printf "Key [MISSING] "
8466
+ echo -e "${RED}FAIL${NC}"
8467
+ all_pass=false
8468
+ fi
8469
+ done
8470
+ unset IFS
8471
+
8472
+ echo ""
8473
+ if [ "$all_pass" = "true" ]; then
8474
+ echo -e "${GREEN}All providers healthy${NC}"
8475
+ else
8476
+ echo -e "${YELLOW}Some providers unavailable - failover chain may be limited${NC}"
8477
+ fi
8478
+ shift
8479
+ ;;
8480
+ --reset)
8481
+ if [ -f "$failover_file" ]; then
8482
+ rm -f "$failover_file"
8483
+ echo "Failover state reset to defaults."
8484
+ else
8485
+ echo "No failover state to reset."
8486
+ fi
8487
+ shift
8488
+ ;;
8489
+ --help|-h)
8490
+ echo -e "${BOLD}loki failover${NC} - Cross-provider auto-failover management (v6.19.0)"
8491
+ echo ""
8492
+ echo "Usage: loki failover [options]"
8493
+ echo ""
8494
+ echo "Options:"
8495
+ echo " (no args) Show failover status and health"
8496
+ echo " --enable Enable auto-failover"
8497
+ echo " --disable Disable auto-failover"
8498
+ echo " --chain X,Y,Z Set failover chain (e.g., claude,codex,gemini)"
8499
+ echo " --test Test all providers in chain"
8500
+ echo " --reset Reset failover state to defaults"
8501
+ echo " --help, -h Show this help"
8502
+ echo ""
8503
+ echo "Environment:"
8504
+ echo " LOKI_FAILOVER=true Enable failover at startup"
8505
+ echo " LOKI_FAILOVER_CHAIN=X,Y,Z Set default chain"
8506
+ echo ""
8507
+ echo "When a rate limit (429/529) is detected, Loki automatically switches"
8508
+ echo "to the next healthy provider in the chain. After each successful"
8509
+ echo "iteration on a fallback provider, the primary is health-checked and"
8510
+ echo "execution switches back when it recovers."
8511
+ return 0
8512
+ ;;
8513
+ *)
8514
+ echo -e "${RED}Unknown option: $1${NC}"
8515
+ echo "Run 'loki failover --help' for usage."
8516
+ return 1
8517
+ ;;
8518
+ esac
8519
+ done
8520
+ }
8521
+
8302
8522
  # Dry-run PRD analysis and cost estimation (v6.18.0)
8303
8523
  cmd_plan() {
8304
8524
  local prd_file=""
@@ -8966,6 +9186,9 @@ main() {
8966
9186
  plan)
8967
9187
  cmd_plan "$@"
8968
9188
  ;;
9189
+ failover)
9190
+ cmd_failover "$@"
9191
+ ;;
8969
9192
  version|--version|-v)
8970
9193
  cmd_version
8971
9194
  ;;
package/autonomy/run.sh CHANGED
@@ -6525,6 +6525,278 @@ calculate_wait() {
6525
6525
  echo $wait_time
6526
6526
  }
6527
6527
 
6528
+ #===============================================================================
6529
+ # Cross-Provider Auto-Failover (v6.19.0)
6530
+ #===============================================================================
6531
+
6532
+ # Initialize failover state file on startup
6533
+ init_failover_state() {
6534
+ local failover_dir="${TARGET_DIR:-.}/.loki/state"
6535
+ local failover_file="$failover_dir/failover.json"
6536
+
6537
+ # Only create if failover is enabled via env or config
6538
+ if [ "${LOKI_FAILOVER:-false}" != "true" ]; then
6539
+ return
6540
+ fi
6541
+
6542
+ mkdir -p "$failover_dir"
6543
+
6544
+ if [ ! -f "$failover_file" ]; then
6545
+ local chain="${LOKI_FAILOVER_CHAIN:-claude,codex,gemini}"
6546
+ local primary="${PROVIDER_NAME:-claude}"
6547
+ cat > "$failover_file" << FEOF
6548
+ {
6549
+ "enabled": true,
6550
+ "chain": $(printf '%s' "$chain" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip().split(",")))' 2>/dev/null || echo '["claude","codex","gemini"]'),
6551
+ "currentProvider": "$primary",
6552
+ "primaryProvider": "$primary",
6553
+ "lastFailover": null,
6554
+ "failoverCount": 0,
6555
+ "healthCheck": {
6556
+ "$primary": "healthy"
6557
+ }
6558
+ }
6559
+ FEOF
6560
+ log_info "Failover initialized: chain=$chain, primary=$primary"
6561
+ fi
6562
+ }
6563
+
6564
+ # Read failover config from state file
6565
+ # Sets: FAILOVER_ENABLED, FAILOVER_CHAIN, FAILOVER_CURRENT, FAILOVER_PRIMARY
6566
+ read_failover_config() {
6567
+ local failover_file="${TARGET_DIR:-.}/.loki/state/failover.json"
6568
+
6569
+ if [ ! -f "$failover_file" ]; then
6570
+ FAILOVER_ENABLED="false"
6571
+ return 1
6572
+ fi
6573
+
6574
+ eval "$(python3 << 'PYEOF' 2>/dev/null || echo 'FAILOVER_ENABLED=false'
6575
+ import json, os
6576
+ try:
6577
+ with open(os.path.join(os.environ.get('TARGET_DIR', '.'), '.loki/state/failover.json')) as f:
6578
+ d = json.load(f)
6579
+ chain = ','.join(d.get('chain', ['claude','codex','gemini']))
6580
+ print(f'FAILOVER_ENABLED={str(d.get("enabled", False)).lower()}')
6581
+ print(f'FAILOVER_CHAIN="{chain}"')
6582
+ print(f'FAILOVER_CURRENT="{d.get("currentProvider", "claude")}"')
6583
+ print(f'FAILOVER_PRIMARY="{d.get("primaryProvider", "claude")}"')
6584
+ print(f'FAILOVER_COUNT={d.get("failoverCount", 0)}')
6585
+ except Exception:
6586
+ print('FAILOVER_ENABLED=false')
6587
+ PYEOF
6588
+ )"
6589
+ }
6590
+
6591
+ # Update failover state file
6592
+ update_failover_state() {
6593
+ local key="$1"
6594
+ local value="$2"
6595
+ local failover_file="${TARGET_DIR:-.}/.loki/state/failover.json"
6596
+
6597
+ [ ! -f "$failover_file" ] && return 1
6598
+
6599
+ python3 << PYEOF 2>/dev/null || true
6600
+ import json, os
6601
+ fpath = os.path.join(os.environ.get('TARGET_DIR', '.'), '.loki/state/failover.json')
6602
+ try:
6603
+ with open(fpath) as f:
6604
+ d = json.load(f)
6605
+ key = "$key"
6606
+ value = "$value"
6607
+ # Handle type conversion
6608
+ if value == "null":
6609
+ d[key] = None
6610
+ elif value == "true":
6611
+ d[key] = True
6612
+ elif value == "false":
6613
+ d[key] = False
6614
+ elif value.isdigit():
6615
+ d[key] = int(value)
6616
+ else:
6617
+ d[key] = value
6618
+ with open(fpath, 'w') as f:
6619
+ json.dump(d, f, indent=2)
6620
+ except Exception:
6621
+ pass
6622
+ PYEOF
6623
+ }
6624
+
6625
+ # Update health status for a specific provider in failover.json
6626
+ update_failover_health() {
6627
+ local provider="$1"
6628
+ local status="$2" # healthy, unhealthy, unknown
6629
+ local failover_file="${TARGET_DIR:-.}/.loki/state/failover.json"
6630
+
6631
+ [ ! -f "$failover_file" ] && return 1
6632
+
6633
+ python3 << PYEOF 2>/dev/null || true
6634
+ import json, os
6635
+ fpath = os.path.join(os.environ.get('TARGET_DIR', '.'), '.loki/state/failover.json')
6636
+ try:
6637
+ with open(fpath) as f:
6638
+ d = json.load(f)
6639
+ if 'healthCheck' not in d:
6640
+ d['healthCheck'] = {}
6641
+ d['healthCheck']["$provider"] = "$status"
6642
+ with open(fpath, 'w') as f:
6643
+ json.dump(d, f, indent=2)
6644
+ except Exception:
6645
+ pass
6646
+ PYEOF
6647
+ }
6648
+
6649
+ # Check provider health: API key exists + CLI installed
6650
+ # Returns: 0 if healthy, 1 if unhealthy
6651
+ check_provider_health() {
6652
+ local provider="$1"
6653
+
6654
+ # Check CLI is installed
6655
+ case "$provider" in
6656
+ claude)
6657
+ command -v claude &>/dev/null || return 1
6658
+ [ -n "${ANTHROPIC_API_KEY:-}" ] || return 1
6659
+ ;;
6660
+ codex)
6661
+ command -v codex &>/dev/null || return 1
6662
+ [ -n "${OPENAI_API_KEY:-}" ] || return 1
6663
+ ;;
6664
+ gemini)
6665
+ command -v gemini &>/dev/null || return 1
6666
+ [ -n "${GOOGLE_API_KEY:-${GEMINI_API_KEY:-}}" ] || return 1
6667
+ ;;
6668
+ cline)
6669
+ command -v cline &>/dev/null || return 1
6670
+ ;;
6671
+ aider)
6672
+ command -v aider &>/dev/null || return 1
6673
+ ;;
6674
+ *)
6675
+ return 1
6676
+ ;;
6677
+ esac
6678
+
6679
+ return 0
6680
+ }
6681
+
6682
+ # Attempt failover to next healthy provider in chain
6683
+ # Called when rate limit is detected on current provider
6684
+ # Returns: 0 if failover succeeded, 1 if all providers exhausted
6685
+ attempt_provider_failover() {
6686
+ read_failover_config || return 1
6687
+
6688
+ if [ "$FAILOVER_ENABLED" != "true" ]; then
6689
+ return 1
6690
+ fi
6691
+
6692
+ local current="${FAILOVER_CURRENT:-${PROVIDER_NAME:-claude}}"
6693
+ log_warn "Failover: rate limit on $current, checking chain: $FAILOVER_CHAIN"
6694
+
6695
+ # Mark current as unhealthy
6696
+ update_failover_health "$current" "unhealthy"
6697
+
6698
+ # Walk the chain looking for the next healthy provider
6699
+ local IFS=','
6700
+ local found_current=false
6701
+ local tried_wrap=false
6702
+
6703
+ # Two passes: first from current position to end, then from start to current
6704
+ for provider in $FAILOVER_CHAIN $FAILOVER_CHAIN; do
6705
+ if [ "$provider" = "$current" ]; then
6706
+ if [ "$found_current" = "true" ]; then
6707
+ # We've wrapped around, all exhausted
6708
+ break
6709
+ fi
6710
+ found_current=true
6711
+ continue
6712
+ fi
6713
+
6714
+ [ "$found_current" != "true" ] && continue
6715
+
6716
+ # Check if this provider is healthy
6717
+ if check_provider_health "$provider"; then
6718
+ log_info "Failover: switching from $current to $provider"
6719
+
6720
+ # Load the new provider config
6721
+ local provider_dir
6722
+ provider_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/providers"
6723
+ if [ -f "$provider_dir/$provider.sh" ]; then
6724
+ source "$provider_dir/$provider.sh"
6725
+ fi
6726
+
6727
+ # Update state
6728
+ update_failover_state "currentProvider" "$provider"
6729
+ update_failover_state "lastFailover" "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
6730
+ update_failover_state "failoverCount" "$((FAILOVER_COUNT + 1))"
6731
+ update_failover_health "$provider" "healthy"
6732
+
6733
+ # Update runtime provider vars
6734
+ PROVIDER_NAME="$provider"
6735
+
6736
+ emit_event_json "provider_failover" \
6737
+ "from=$current" \
6738
+ "to=$provider" \
6739
+ "reason=rate_limit" \
6740
+ "iteration=$ITERATION_COUNT" 2>/dev/null || true
6741
+
6742
+ log_info "Failover: now using $provider (failover #$((FAILOVER_COUNT + 1)))"
6743
+ return 0
6744
+ else
6745
+ log_debug "Failover: $provider is unhealthy, skipping"
6746
+ update_failover_health "$provider" "unhealthy"
6747
+ fi
6748
+ done
6749
+
6750
+ log_warn "Failover: all providers in chain exhausted, falling back to retry"
6751
+ return 1
6752
+ }
6753
+
6754
+ # Check if primary provider has recovered after running on a fallback
6755
+ # Called after each successful iteration when on a non-primary provider
6756
+ # Returns: 0 if switched back to primary, 1 if still on fallback
6757
+ check_primary_recovery() {
6758
+ read_failover_config || return 1
6759
+
6760
+ if [ "$FAILOVER_ENABLED" != "true" ]; then
6761
+ return 1
6762
+ fi
6763
+
6764
+ local current="${FAILOVER_CURRENT:-${PROVIDER_NAME:-claude}}"
6765
+ local primary="${FAILOVER_PRIMARY:-claude}"
6766
+
6767
+ # Already on primary
6768
+ if [ "$current" = "$primary" ]; then
6769
+ return 1
6770
+ fi
6771
+
6772
+ # Check if primary is healthy again
6773
+ if check_provider_health "$primary"; then
6774
+ log_info "Failover: primary provider $primary appears healthy, switching back"
6775
+
6776
+ # Load primary provider config
6777
+ local provider_dir
6778
+ provider_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/providers"
6779
+ if [ -f "$provider_dir/$primary.sh" ]; then
6780
+ source "$provider_dir/$primary.sh"
6781
+ fi
6782
+
6783
+ update_failover_state "currentProvider" "$primary"
6784
+ update_failover_health "$primary" "healthy"
6785
+
6786
+ PROVIDER_NAME="$primary"
6787
+
6788
+ emit_event_json "provider_recovery" \
6789
+ "from=$current" \
6790
+ "to=$primary" \
6791
+ "iteration=$ITERATION_COUNT" 2>/dev/null || true
6792
+
6793
+ log_info "Failover: recovered to primary provider $primary"
6794
+ return 0
6795
+ fi
6796
+
6797
+ return 1
6798
+ }
6799
+
6528
6800
  #===============================================================================
6529
6801
  # Rate Limit Detection
6530
6802
  #===============================================================================
@@ -8145,6 +8417,9 @@ run_autonomous() {
8145
8417
  load_state
8146
8418
  local retry=$RETRY_COUNT
8147
8419
 
8420
+ # Initialize Cross-Provider Failover (v6.19.0)
8421
+ init_failover_state
8422
+
8148
8423
  # Initialize Completion Council (v5.25.0)
8149
8424
  if type council_init &>/dev/null; then
8150
8425
  council_init "$prd_path"
@@ -8784,6 +9059,9 @@ if __name__ == "__main__":
8784
9059
  log_warn "Council will evaluate at next check interval (every ${COUNCIL_CHECK_INTERVAL:-5} iterations)"
8785
9060
  fi
8786
9061
 
9062
+ # Cross-provider failover: check if primary has recovered (v6.19.0)
9063
+ check_primary_recovery 2>/dev/null || true
9064
+
8787
9065
  # SUCCESS exit - continue IMMEDIATELY to next iteration (no wait!)
8788
9066
  log_step "Starting next iteration..."
8789
9067
  ((retry++))
@@ -8801,6 +9079,13 @@ if __name__ == "__main__":
8801
9079
  local wait_time
8802
9080
 
8803
9081
  if [ $rate_limit_wait -gt 0 ]; then
9082
+ # Cross-provider failover (v6.19.0): try switching provider before waiting
9083
+ if attempt_provider_failover 2>/dev/null; then
9084
+ log_info "Failover succeeded - retrying immediately with ${PROVIDER_NAME}"
9085
+ ((retry++))
9086
+ continue
9087
+ fi
9088
+
8804
9089
  wait_time=$rate_limit_wait
8805
9090
  local human_time=$(format_duration $wait_time)
8806
9091
  log_warn "Rate limit detected! Waiting until reset (~$human_time)..."
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.18.0"
10
+ __version__ = "6.19.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v6.18.0
5
+ **Version:** v6.19.0
6
6
 
7
7
  ---
8
8
 
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.18.0'
60
+ __version__ = '6.19.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.18.0",
3
+ "version": "6.19.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",