loki-mode 4.2.0 → 5.1.1
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/README.md +49 -7
- package/SKILL.md +29 -14
- package/VERSION +1 -1
- package/autonomy/loki +27 -2
- package/autonomy/run.sh +468 -45
- package/bin/postinstall.js +11 -4
- package/docs/INSTALLATION.md +78 -6
- package/package.json +3 -2
- package/providers/claude.sh +108 -0
- package/providers/codex.sh +130 -0
- package/providers/gemini.sh +131 -0
- package/providers/loader.sh +184 -0
- package/references/multi-provider.md +423 -0
- package/skills/00-index.md +9 -0
- package/skills/model-selection.md +64 -11
- package/skills/providers.md +184 -0
package/autonomy/run.sh
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# ./autonomy/run.sh --parallel ./prd.md # Parallel mode with PRD
|
|
12
12
|
#
|
|
13
13
|
# Environment Variables:
|
|
14
|
+
# LOKI_PROVIDER - AI provider: claude (default), codex, gemini
|
|
14
15
|
# LOKI_MAX_RETRIES - Max retry attempts (default: 50)
|
|
15
16
|
# LOKI_BASE_WAIT - Base wait time in seconds (default: 60)
|
|
16
17
|
# LOKI_MAX_WAIT - Max wait time in seconds (default: 3600)
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
# Autonomous Loop Controls (Ralph Wiggum Mode):
|
|
48
49
|
# LOKI_COMPLETION_PROMISE - EXPLICIT stop condition text (default: none - runs forever)
|
|
49
50
|
# Example: "ALL TESTS PASSING 100%"
|
|
50
|
-
# Only stops when
|
|
51
|
+
# Only stops when the AI provider outputs this EXACT text
|
|
51
52
|
# LOKI_MAX_ITERATIONS - Max loop iterations before exit (default: 1000)
|
|
52
53
|
# LOKI_PERPETUAL_MODE - Ignore ALL completion signals (default: false)
|
|
53
54
|
# Set to 'true' for truly infinite operation
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
# LOKI_PARALLEL_MODE - Enable git worktree-based parallelism (default: false)
|
|
66
67
|
# Use --parallel flag or set to 'true'
|
|
67
68
|
# LOKI_MAX_WORKTREES - Maximum parallel worktrees (default: 5)
|
|
68
|
-
# LOKI_MAX_PARALLEL_SESSIONS - Maximum concurrent
|
|
69
|
+
# LOKI_MAX_PARALLEL_SESSIONS - Maximum concurrent AI sessions (default: 3)
|
|
69
70
|
# LOKI_PARALLEL_TESTING - Run testing stream in parallel (default: true)
|
|
70
71
|
# LOKI_PARALLEL_DOCS - Run documentation stream in parallel (default: true)
|
|
71
72
|
# LOKI_PARALLEL_BLOG - Run blog stream if site has blog (default: false)
|
|
@@ -458,6 +459,43 @@ AUTO_MERGE=${LOKI_AUTO_MERGE:-true}
|
|
|
458
459
|
COMPLEXITY_TIER=${LOKI_COMPLEXITY:-auto}
|
|
459
460
|
DETECTED_COMPLEXITY=""
|
|
460
461
|
|
|
462
|
+
# Multi-Provider Support (v5.0.0)
|
|
463
|
+
# Provider: claude (default), codex, gemini
|
|
464
|
+
LOKI_PROVIDER=${LOKI_PROVIDER:-claude}
|
|
465
|
+
|
|
466
|
+
# Source provider configuration
|
|
467
|
+
PROVIDERS_DIR="$PROJECT_DIR/providers"
|
|
468
|
+
if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
|
|
469
|
+
# shellcheck source=/dev/null
|
|
470
|
+
source "$PROVIDERS_DIR/loader.sh"
|
|
471
|
+
|
|
472
|
+
# Validate provider
|
|
473
|
+
if ! validate_provider "$LOKI_PROVIDER"; then
|
|
474
|
+
echo "ERROR: Unknown provider: $LOKI_PROVIDER" >&2
|
|
475
|
+
echo "Supported providers: ${SUPPORTED_PROVIDERS[*]}" >&2
|
|
476
|
+
exit 1
|
|
477
|
+
fi
|
|
478
|
+
|
|
479
|
+
# Load provider config
|
|
480
|
+
if ! load_provider "$LOKI_PROVIDER"; then
|
|
481
|
+
echo "ERROR: Failed to load provider config: $LOKI_PROVIDER" >&2
|
|
482
|
+
exit 1
|
|
483
|
+
fi
|
|
484
|
+
else
|
|
485
|
+
# Fallback: Claude-only mode (backwards compatibility)
|
|
486
|
+
PROVIDER_NAME="claude"
|
|
487
|
+
PROVIDER_CLI="claude"
|
|
488
|
+
PROVIDER_AUTONOMOUS_FLAG="--dangerously-skip-permissions"
|
|
489
|
+
PROVIDER_PROMPT_FLAG="-p"
|
|
490
|
+
PROVIDER_DEGRADED=false
|
|
491
|
+
PROVIDER_DISPLAY_NAME="Claude Code"
|
|
492
|
+
PROVIDER_HAS_PARALLEL=true
|
|
493
|
+
PROVIDER_HAS_SUBAGENTS=true
|
|
494
|
+
PROVIDER_HAS_TASK_TOOL=true
|
|
495
|
+
PROVIDER_HAS_MCP=true
|
|
496
|
+
PROVIDER_PROMPT_POSITIONAL=false
|
|
497
|
+
fi
|
|
498
|
+
|
|
461
499
|
# Track worktree PIDs for cleanup (requires bash 4+ for associative arrays)
|
|
462
500
|
# Check bash version for parallel mode compatibility
|
|
463
501
|
BASH_VERSION_MAJOR="${BASH_VERSION%%.*}"
|
|
@@ -495,6 +533,7 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
|
495
533
|
log_warning() { log_warn "$@"; } # Alias for backwards compatibility
|
|
496
534
|
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
497
535
|
log_step() { echo -e "${CYAN}[STEP]${NC} $*"; }
|
|
536
|
+
log_debug() { [[ "${LOKI_DEBUG:-}" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" || true; }
|
|
498
537
|
|
|
499
538
|
#===============================================================================
|
|
500
539
|
# Complexity Tier Detection (Auto-Claude pattern)
|
|
@@ -597,6 +636,94 @@ get_phase_names() {
|
|
|
597
636
|
esac
|
|
598
637
|
}
|
|
599
638
|
|
|
639
|
+
#===============================================================================
|
|
640
|
+
# Dynamic Tier Selection (RARV-aware model routing)
|
|
641
|
+
#===============================================================================
|
|
642
|
+
# Maps RARV cycle phases to optimal model tiers:
|
|
643
|
+
# - Reason phase -> planning tier (opus/xhigh/high)
|
|
644
|
+
# - Act phase -> development tier (sonnet/high/medium)
|
|
645
|
+
# - Reflect phase -> development tier (sonnet/high/medium)
|
|
646
|
+
# - Verify phase -> fast tier (haiku/low/low)
|
|
647
|
+
|
|
648
|
+
# Global tier for current iteration (set by get_rarv_tier)
|
|
649
|
+
CURRENT_TIER="development"
|
|
650
|
+
|
|
651
|
+
# Get the appropriate tier based on RARV cycle step
|
|
652
|
+
# Args: iteration_count (defaults to ITERATION_COUNT)
|
|
653
|
+
# Returns: tier name (planning, development, fast)
|
|
654
|
+
get_rarv_tier() {
|
|
655
|
+
local iteration="${1:-$ITERATION_COUNT}"
|
|
656
|
+
local rarv_step=$((iteration % 4))
|
|
657
|
+
|
|
658
|
+
case $rarv_step in
|
|
659
|
+
0) # Reason phase - planning/architecture
|
|
660
|
+
echo "planning"
|
|
661
|
+
;;
|
|
662
|
+
1) # Act phase - implementation
|
|
663
|
+
echo "development"
|
|
664
|
+
;;
|
|
665
|
+
2) # Reflect phase - review/analysis
|
|
666
|
+
echo "development"
|
|
667
|
+
;;
|
|
668
|
+
3) # Verify phase - testing/validation
|
|
669
|
+
echo "fast"
|
|
670
|
+
;;
|
|
671
|
+
*) # Fallback to development
|
|
672
|
+
echo "development"
|
|
673
|
+
;;
|
|
674
|
+
esac
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
# Get RARV phase name for logging
|
|
678
|
+
get_rarv_phase_name() {
|
|
679
|
+
local iteration="${1:-$ITERATION_COUNT}"
|
|
680
|
+
local rarv_step=$((iteration % 4))
|
|
681
|
+
|
|
682
|
+
case $rarv_step in
|
|
683
|
+
0) echo "REASON" ;;
|
|
684
|
+
1) echo "ACT" ;;
|
|
685
|
+
2) echo "REFLECT" ;;
|
|
686
|
+
3) echo "VERIFY" ;;
|
|
687
|
+
*) echo "UNKNOWN" ;;
|
|
688
|
+
esac
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
# Get provider-specific tier parameter based on current tier
|
|
692
|
+
# Uses provider config variables for the tier mapping
|
|
693
|
+
get_provider_tier_param() {
|
|
694
|
+
local tier="${1:-$CURRENT_TIER}"
|
|
695
|
+
|
|
696
|
+
case "${PROVIDER_NAME:-claude}" in
|
|
697
|
+
claude)
|
|
698
|
+
case "$tier" in
|
|
699
|
+
planning) echo "${PROVIDER_MODEL_PLANNING:-opus}" | sed 's/claude-\([a-z]*\).*/\1/' ;;
|
|
700
|
+
development) echo "${PROVIDER_MODEL_DEVELOPMENT:-sonnet}" | sed 's/claude-\([a-z]*\).*/\1/' ;;
|
|
701
|
+
fast) echo "${PROVIDER_MODEL_FAST:-haiku}" | sed 's/claude-\([a-z]*\).*/\1/' ;;
|
|
702
|
+
*) echo "sonnet" ;;
|
|
703
|
+
esac
|
|
704
|
+
;;
|
|
705
|
+
codex)
|
|
706
|
+
case "$tier" in
|
|
707
|
+
planning) echo "${PROVIDER_EFFORT_PLANNING:-xhigh}" ;;
|
|
708
|
+
development) echo "${PROVIDER_EFFORT_DEVELOPMENT:-high}" ;;
|
|
709
|
+
fast) echo "${PROVIDER_EFFORT_FAST:-low}" ;;
|
|
710
|
+
*) echo "high" ;;
|
|
711
|
+
esac
|
|
712
|
+
;;
|
|
713
|
+
gemini)
|
|
714
|
+
case "$tier" in
|
|
715
|
+
planning) echo "${PROVIDER_THINKING_PLANNING:-high}" ;;
|
|
716
|
+
development) echo "${PROVIDER_THINKING_DEVELOPMENT:-medium}" ;;
|
|
717
|
+
fast) echo "${PROVIDER_THINKING_FAST:-low}" ;;
|
|
718
|
+
*) echo "medium" ;;
|
|
719
|
+
esac
|
|
720
|
+
;;
|
|
721
|
+
*)
|
|
722
|
+
echo "development"
|
|
723
|
+
;;
|
|
724
|
+
esac
|
|
725
|
+
}
|
|
726
|
+
|
|
600
727
|
#===============================================================================
|
|
601
728
|
# GitHub Integration Functions (v4.1.0)
|
|
602
729
|
#===============================================================================
|
|
@@ -1155,9 +1282,15 @@ remove_worktree() {
|
|
|
1155
1282
|
wait "$pid" 2>/dev/null || true
|
|
1156
1283
|
fi
|
|
1157
1284
|
|
|
1158
|
-
# Remove worktree
|
|
1159
|
-
git -C "$TARGET_DIR" worktree remove "$worktree_path" --force 2>/dev/null ||
|
|
1160
|
-
|
|
1285
|
+
# Remove worktree (with safety check for rm -rf)
|
|
1286
|
+
git -C "$TARGET_DIR" worktree remove "$worktree_path" --force 2>/dev/null || {
|
|
1287
|
+
# Safety check: only rm -rf if path looks like a worktree (contains .git or is under TARGET_DIR)
|
|
1288
|
+
if [[ -n "$worktree_path" && "$worktree_path" != "/" && "$worktree_path" == "$TARGET_DIR"* ]]; then
|
|
1289
|
+
rm -rf "$worktree_path" 2>/dev/null
|
|
1290
|
+
else
|
|
1291
|
+
log_warn "Skipping unsafe rm -rf for path: $worktree_path"
|
|
1292
|
+
fi
|
|
1293
|
+
}
|
|
1161
1294
|
|
|
1162
1295
|
unset WORKTREE_PATHS[$stream_name]
|
|
1163
1296
|
unset WORKTREE_PIDS[$stream_name]
|
|
@@ -1192,13 +1325,40 @@ spawn_worktree_session() {
|
|
|
1192
1325
|
local log_file="$worktree_path/.loki/logs/session-${stream_name}.log"
|
|
1193
1326
|
mkdir -p "$(dirname "$log_file")"
|
|
1194
1327
|
|
|
1195
|
-
|
|
1328
|
+
# Check provider parallel support
|
|
1329
|
+
if [ "${PROVIDER_HAS_PARALLEL:-false}" != "true" ]; then
|
|
1330
|
+
log_warn "Provider ${PROVIDER_NAME:-unknown} does not support parallel sessions"
|
|
1331
|
+
log_warn "Running sequentially instead (degraded mode)"
|
|
1332
|
+
return 1
|
|
1333
|
+
fi
|
|
1334
|
+
|
|
1335
|
+
log_step "Spawning ${PROVIDER_DISPLAY_NAME:-Claude} session: $stream_name"
|
|
1196
1336
|
|
|
1197
1337
|
(
|
|
1198
1338
|
cd "$worktree_path"
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1339
|
+
# Provider-specific invocation for parallel sessions
|
|
1340
|
+
case "${PROVIDER_NAME:-claude}" in
|
|
1341
|
+
claude)
|
|
1342
|
+
claude --dangerously-skip-permissions \
|
|
1343
|
+
-p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
1344
|
+
>> "$log_file" 2>&1
|
|
1345
|
+
;;
|
|
1346
|
+
codex)
|
|
1347
|
+
codex exec --dangerously-bypass-approvals-and-sandbox \
|
|
1348
|
+
"Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
1349
|
+
>> "$log_file" 2>&1
|
|
1350
|
+
;;
|
|
1351
|
+
gemini)
|
|
1352
|
+
# Note: -p flag is DEPRECATED per gemini --help. Using positional prompt.
|
|
1353
|
+
gemini --yolo \
|
|
1354
|
+
"Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
1355
|
+
>> "$log_file" 2>&1
|
|
1356
|
+
;;
|
|
1357
|
+
*)
|
|
1358
|
+
log_error "Unknown provider: ${PROVIDER_NAME}"
|
|
1359
|
+
return 1
|
|
1360
|
+
;;
|
|
1361
|
+
esac
|
|
1202
1362
|
) &
|
|
1203
1363
|
|
|
1204
1364
|
local pid=$!
|
|
@@ -1263,17 +1423,33 @@ resolve_conflicts_with_ai() {
|
|
|
1263
1423
|
# Get conflict markers
|
|
1264
1424
|
local conflict_content=$(cat "$file")
|
|
1265
1425
|
|
|
1266
|
-
# Use
|
|
1267
|
-
local resolution
|
|
1268
|
-
You are resolving a git merge conflict. The file below contains conflict markers.
|
|
1426
|
+
# Use AI to resolve conflict (provider-aware)
|
|
1427
|
+
local resolution=""
|
|
1428
|
+
local conflict_prompt="You are resolving a git merge conflict. The file below contains conflict markers.
|
|
1269
1429
|
Your task is to merge both changes intelligently, preserving functionality from both sides.
|
|
1270
1430
|
|
|
1271
1431
|
FILE: $file
|
|
1272
1432
|
CONTENT:
|
|
1273
1433
|
$conflict_content
|
|
1274
1434
|
|
|
1275
|
-
Output ONLY the resolved file content with no conflict markers. No explanations.
|
|
1276
|
-
|
|
1435
|
+
Output ONLY the resolved file content with no conflict markers. No explanations."
|
|
1436
|
+
|
|
1437
|
+
case "${PROVIDER_NAME:-claude}" in
|
|
1438
|
+
claude)
|
|
1439
|
+
resolution=$(claude --dangerously-skip-permissions -p "$conflict_prompt" --output-format text 2>/dev/null)
|
|
1440
|
+
;;
|
|
1441
|
+
codex)
|
|
1442
|
+
resolution=$(codex exec --dangerously-bypass-approvals-and-sandbox "$conflict_prompt" 2>/dev/null)
|
|
1443
|
+
;;
|
|
1444
|
+
gemini)
|
|
1445
|
+
# Note: -p flag is DEPRECATED per gemini --help. Using positional prompt.
|
|
1446
|
+
resolution=$(gemini --yolo "$conflict_prompt" 2>/dev/null)
|
|
1447
|
+
;;
|
|
1448
|
+
*)
|
|
1449
|
+
log_error "Unknown provider: ${PROVIDER_NAME}"
|
|
1450
|
+
return 1
|
|
1451
|
+
;;
|
|
1452
|
+
esac
|
|
1277
1453
|
|
|
1278
1454
|
if [ -n "$resolution" ]; then
|
|
1279
1455
|
echo "$resolution" > "$file"
|
|
@@ -1478,15 +1654,31 @@ check_prerequisites() {
|
|
|
1478
1654
|
|
|
1479
1655
|
local missing=()
|
|
1480
1656
|
|
|
1481
|
-
# Check
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1657
|
+
# Check Provider CLI (uses PROVIDER_CLI from loaded provider config)
|
|
1658
|
+
local cli_name="${PROVIDER_CLI:-claude}"
|
|
1659
|
+
local display_name="${PROVIDER_DISPLAY_NAME:-Claude Code}"
|
|
1660
|
+
log_step "Checking $display_name CLI..."
|
|
1661
|
+
if command -v "$cli_name" &> /dev/null; then
|
|
1662
|
+
local version=$("$cli_name" --version 2>/dev/null | head -1 || echo "unknown")
|
|
1663
|
+
log_info "$display_name CLI: $version"
|
|
1486
1664
|
else
|
|
1487
|
-
missing+=("
|
|
1488
|
-
log_error "
|
|
1489
|
-
|
|
1665
|
+
missing+=("$cli_name")
|
|
1666
|
+
log_error "$display_name CLI not found"
|
|
1667
|
+
case "$cli_name" in
|
|
1668
|
+
claude)
|
|
1669
|
+
log_info "Install: https://claude.ai/code or npm install -g @anthropic-ai/claude-code"
|
|
1670
|
+
;;
|
|
1671
|
+
codex)
|
|
1672
|
+
log_info "Install: npm install -g @openai/codex"
|
|
1673
|
+
;;
|
|
1674
|
+
gemini)
|
|
1675
|
+
# TODO: Verify official Gemini CLI package name when available
|
|
1676
|
+
log_info "Install: npm install -g @google/gemini-cli (or visit https://ai.google.dev/)"
|
|
1677
|
+
;;
|
|
1678
|
+
*)
|
|
1679
|
+
log_info "Install the $cli_name CLI for your provider"
|
|
1680
|
+
;;
|
|
1681
|
+
esac
|
|
1490
1682
|
fi
|
|
1491
1683
|
|
|
1492
1684
|
# Check Python 3
|
|
@@ -1560,8 +1752,16 @@ check_prerequisites() {
|
|
|
1560
1752
|
check_skill_installed() {
|
|
1561
1753
|
log_header "Checking Loki Mode Skill"
|
|
1562
1754
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1755
|
+
# Build skill locations array dynamically based on provider
|
|
1756
|
+
local skill_locations=()
|
|
1757
|
+
|
|
1758
|
+
# Add provider-specific skill directory if set (e.g., ~/.claude/skills for Claude)
|
|
1759
|
+
if [ -n "${PROVIDER_SKILL_DIR:-}" ]; then
|
|
1760
|
+
skill_locations+=("${PROVIDER_SKILL_DIR}/loki-mode/SKILL.md")
|
|
1761
|
+
fi
|
|
1762
|
+
|
|
1763
|
+
# Add local project skill locations
|
|
1764
|
+
skill_locations+=(
|
|
1565
1765
|
".claude/skills/loki-mode/SKILL.md"
|
|
1566
1766
|
"$PROJECT_DIR/SKILL.md"
|
|
1567
1767
|
)
|
|
@@ -1573,7 +1773,14 @@ check_skill_installed() {
|
|
|
1573
1773
|
fi
|
|
1574
1774
|
done
|
|
1575
1775
|
|
|
1576
|
-
|
|
1776
|
+
# For providers without skill system (Codex, Gemini), this is expected
|
|
1777
|
+
if [ -z "${PROVIDER_SKILL_DIR:-}" ]; then
|
|
1778
|
+
log_info "Provider ${PROVIDER_NAME:-unknown} has no native skill directory"
|
|
1779
|
+
log_info "Skill will be passed via prompt injection"
|
|
1780
|
+
else
|
|
1781
|
+
log_warn "Loki Mode skill not found in standard locations"
|
|
1782
|
+
fi
|
|
1783
|
+
|
|
1577
1784
|
log_info "The skill will be used from: $PROJECT_DIR/SKILL.md"
|
|
1578
1785
|
|
|
1579
1786
|
if [ -f "$PROJECT_DIR/SKILL.md" ]; then
|
|
@@ -2133,7 +2340,7 @@ generate_dashboard() {
|
|
|
2133
2340
|
<div class="column failed"><h2>Failed <span class="count" id="failed-badge">0</span></h2><div id="failed-tasks"></div></div>
|
|
2134
2341
|
</div>
|
|
2135
2342
|
<div class="updated" id="updated">Last updated: -</div>
|
|
2136
|
-
<div class="powered-by">Powered by <span
|
|
2343
|
+
<div class="powered-by">Powered by <span>${PROVIDER_DISPLAY_NAME:-Claude}</span></div>
|
|
2137
2344
|
<button class="refresh" onclick="loadData()">Refresh</button>
|
|
2138
2345
|
<script>
|
|
2139
2346
|
async function loadJSON(path) {
|
|
@@ -2688,9 +2895,33 @@ calculate_wait() {
|
|
|
2688
2895
|
# Rate Limit Detection
|
|
2689
2896
|
#===============================================================================
|
|
2690
2897
|
|
|
2691
|
-
# Detect
|
|
2692
|
-
# Returns:
|
|
2693
|
-
|
|
2898
|
+
# Detect if output contains rate limit indicators (provider-agnostic)
|
|
2899
|
+
# Returns: 0 if rate limit detected, 1 otherwise
|
|
2900
|
+
is_rate_limited() {
|
|
2901
|
+
local log_file="$1"
|
|
2902
|
+
|
|
2903
|
+
# Generic patterns that work across all providers
|
|
2904
|
+
# - HTTP 429 status code
|
|
2905
|
+
# - "rate limit" / "rate-limit" / "ratelimit" text
|
|
2906
|
+
# - "too many requests" text
|
|
2907
|
+
# - "quota exceeded" text
|
|
2908
|
+
# - "request limit" text
|
|
2909
|
+
# - "retry after" / "retry-after" headers
|
|
2910
|
+
if grep -qiE '(429|rate.?limit|too many requests|quota exceeded|request limit|retry.?after)' "$log_file" 2>/dev/null; then
|
|
2911
|
+
return 0
|
|
2912
|
+
fi
|
|
2913
|
+
|
|
2914
|
+
# Claude-specific: "resets Xam/pm" format
|
|
2915
|
+
if grep -qE 'resets [0-9]+[ap]m' "$log_file" 2>/dev/null; then
|
|
2916
|
+
return 0
|
|
2917
|
+
fi
|
|
2918
|
+
|
|
2919
|
+
return 1
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
# Parse Claude-specific reset time from log
|
|
2923
|
+
# Returns: seconds to wait, or 0 if no reset time found
|
|
2924
|
+
parse_claude_reset_time() {
|
|
2694
2925
|
local log_file="$1"
|
|
2695
2926
|
|
|
2696
2927
|
# Look for rate limit message like "resets 4am" or "resets 10pm"
|
|
@@ -2734,6 +2965,84 @@ detect_rate_limit() {
|
|
|
2734
2965
|
echo $wait_secs
|
|
2735
2966
|
}
|
|
2736
2967
|
|
|
2968
|
+
# Parse Retry-After header value (common across providers)
|
|
2969
|
+
# Returns: seconds to wait, or 0 if not found
|
|
2970
|
+
parse_retry_after() {
|
|
2971
|
+
local log_file="$1"
|
|
2972
|
+
|
|
2973
|
+
# Look for Retry-After header (case insensitive)
|
|
2974
|
+
# Format: "Retry-After: 60" or "retry-after: 60"
|
|
2975
|
+
local retry_secs=$(grep -ioE 'retry.?after:?\s*[0-9]+' "$log_file" 2>/dev/null | tail -1 | grep -oE '[0-9]+$')
|
|
2976
|
+
|
|
2977
|
+
if [ -n "$retry_secs" ]; then
|
|
2978
|
+
echo "$retry_secs"
|
|
2979
|
+
else
|
|
2980
|
+
echo 0
|
|
2981
|
+
fi
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
# Calculate default backoff based on provider rate limit
|
|
2985
|
+
# Uses PROVIDER_RATE_LIMIT_RPM from loaded provider config
|
|
2986
|
+
# Returns: seconds to wait
|
|
2987
|
+
calculate_rate_limit_backoff() {
|
|
2988
|
+
local rpm="${PROVIDER_RATE_LIMIT_RPM:-50}"
|
|
2989
|
+
|
|
2990
|
+
# Calculate wait time based on RPM
|
|
2991
|
+
# If RPM is 50, that's ~1.2 requests per second
|
|
2992
|
+
# Default backoff: 60 seconds / RPM * 60 = wait for 1 minute window
|
|
2993
|
+
# But add some buffer, so wait for 2 minute windows
|
|
2994
|
+
local wait_secs=$((120 * 60 / rpm))
|
|
2995
|
+
|
|
2996
|
+
# Minimum 60 seconds, maximum 300 seconds for default backoff
|
|
2997
|
+
if [ "$wait_secs" -lt 60 ]; then
|
|
2998
|
+
wait_secs=60
|
|
2999
|
+
elif [ "$wait_secs" -gt 300 ]; then
|
|
3000
|
+
wait_secs=300
|
|
3001
|
+
fi
|
|
3002
|
+
|
|
3003
|
+
echo $wait_secs
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
# Detect rate limit from log and calculate wait time until reset
|
|
3007
|
+
# Provider-agnostic: checks generic patterns first, then provider-specific
|
|
3008
|
+
# Returns: seconds to wait, or 0 if no rate limit detected
|
|
3009
|
+
detect_rate_limit() {
|
|
3010
|
+
local log_file="$1"
|
|
3011
|
+
|
|
3012
|
+
# First check if rate limited at all
|
|
3013
|
+
if ! is_rate_limited "$log_file"; then
|
|
3014
|
+
echo 0
|
|
3015
|
+
return
|
|
3016
|
+
fi
|
|
3017
|
+
|
|
3018
|
+
# Rate limit detected - now determine wait time
|
|
3019
|
+
local wait_secs=0
|
|
3020
|
+
|
|
3021
|
+
# Try provider-specific reset time parsing
|
|
3022
|
+
case "${PROVIDER_NAME:-claude}" in
|
|
3023
|
+
claude)
|
|
3024
|
+
wait_secs=$(parse_claude_reset_time "$log_file")
|
|
3025
|
+
;;
|
|
3026
|
+
codex|gemini|*)
|
|
3027
|
+
# No provider-specific reset time format known
|
|
3028
|
+
# Fall through to generic parsing
|
|
3029
|
+
;;
|
|
3030
|
+
esac
|
|
3031
|
+
|
|
3032
|
+
# If no provider-specific time, try generic Retry-After header
|
|
3033
|
+
if [ "$wait_secs" -eq 0 ]; then
|
|
3034
|
+
wait_secs=$(parse_retry_after "$log_file")
|
|
3035
|
+
fi
|
|
3036
|
+
|
|
3037
|
+
# If still no specific time, use calculated backoff based on provider RPM
|
|
3038
|
+
if [ "$wait_secs" -eq 0 ]; then
|
|
3039
|
+
wait_secs=$(calculate_rate_limit_backoff)
|
|
3040
|
+
log_debug "Using calculated backoff (${PROVIDER_RATE_LIMIT_RPM:-50} RPM): ${wait_secs}s"
|
|
3041
|
+
fi
|
|
3042
|
+
|
|
3043
|
+
echo $wait_secs
|
|
3044
|
+
}
|
|
3045
|
+
|
|
2737
3046
|
# Format seconds into human-readable time
|
|
2738
3047
|
format_duration() {
|
|
2739
3048
|
local secs="$1"
|
|
@@ -3035,24 +3344,40 @@ run_autonomous() {
|
|
|
3035
3344
|
|
|
3036
3345
|
save_state $retry "running" 0
|
|
3037
3346
|
|
|
3038
|
-
# Run
|
|
3347
|
+
# Run AI provider with live output
|
|
3039
3348
|
local start_time=$(date +%s)
|
|
3040
3349
|
local log_file=".loki/logs/autonomy-$(date +%Y%m%d).log"
|
|
3041
3350
|
|
|
3042
3351
|
echo ""
|
|
3043
3352
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
3044
|
-
echo -e "${CYAN} CLAUDE CODE OUTPUT (live)${NC}"
|
|
3353
|
+
echo -e "${CYAN} ${PROVIDER_DISPLAY_NAME:-CLAUDE CODE} OUTPUT (live)${NC}"
|
|
3354
|
+
if [ "${PROVIDER_DEGRADED:-false}" = "true" ]; then
|
|
3355
|
+
echo -e "${YELLOW} [DEGRADED MODE: Sequential execution only]${NC}"
|
|
3356
|
+
fi
|
|
3045
3357
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
3046
3358
|
echo ""
|
|
3047
3359
|
|
|
3048
3360
|
# Log start time
|
|
3049
3361
|
echo "=== Session started at $(date) ===" >> "$log_file"
|
|
3362
|
+
echo "=== Provider: ${PROVIDER_NAME:-claude} ===" >> "$log_file"
|
|
3050
3363
|
echo "=== Prompt: $prompt ===" >> "$log_file"
|
|
3051
3364
|
|
|
3365
|
+
# Dynamic tier selection based on RARV cycle phase
|
|
3366
|
+
CURRENT_TIER=$(get_rarv_tier "$ITERATION_COUNT")
|
|
3367
|
+
local rarv_phase=$(get_rarv_phase_name "$ITERATION_COUNT")
|
|
3368
|
+
local tier_param=$(get_provider_tier_param "$CURRENT_TIER")
|
|
3369
|
+
echo "=== RARV Phase: $rarv_phase, Tier: $CURRENT_TIER ($tier_param) ===" >> "$log_file"
|
|
3370
|
+
log_info "RARV Phase: $rarv_phase -> Tier: $CURRENT_TIER ($tier_param)"
|
|
3371
|
+
|
|
3052
3372
|
set +e
|
|
3053
|
-
#
|
|
3054
|
-
|
|
3055
|
-
|
|
3373
|
+
# Provider-specific invocation with dynamic tier selection
|
|
3374
|
+
case "${PROVIDER_NAME:-claude}" in
|
|
3375
|
+
claude)
|
|
3376
|
+
# Claude: Full features with stream-json output and agent tracking
|
|
3377
|
+
# Uses dynamic tier for model selection based on RARV phase
|
|
3378
|
+
# Pass tier to Python via environment for dashboard display
|
|
3379
|
+
LOKI_CURRENT_MODEL="$tier_param" \
|
|
3380
|
+
claude --dangerously-skip-permissions --model "$tier_param" -p "$prompt" \
|
|
3056
3381
|
--output-format stream-json --verbose 2>&1 | \
|
|
3057
3382
|
tee -a "$log_file" | \
|
|
3058
3383
|
python3 -u -c '
|
|
@@ -3069,6 +3394,9 @@ MAGENTA = "\033[0;35m"
|
|
|
3069
3394
|
DIM = "\033[2m"
|
|
3070
3395
|
NC = "\033[0m"
|
|
3071
3396
|
|
|
3397
|
+
# Get current model tier from environment (set by run.sh dynamic tier selection)
|
|
3398
|
+
CURRENT_MODEL = os.environ.get("LOKI_CURRENT_MODEL", "sonnet")
|
|
3399
|
+
|
|
3072
3400
|
# Agent tracking
|
|
3073
3401
|
AGENTS_FILE = ".loki/state/agents.json"
|
|
3074
3402
|
QUEUE_IN_PROGRESS = ".loki/queue/in-progress.json"
|
|
@@ -3082,7 +3410,7 @@ def init_orchestrator():
|
|
|
3082
3410
|
"agent_id": orchestrator_id,
|
|
3083
3411
|
"tool_id": orchestrator_id,
|
|
3084
3412
|
"agent_type": "orchestrator",
|
|
3085
|
-
"model":
|
|
3413
|
+
"model": CURRENT_MODEL,
|
|
3086
3414
|
"current_task": "Initializing...",
|
|
3087
3415
|
"status": "active",
|
|
3088
3416
|
"spawned_at": session_start,
|
|
@@ -3256,7 +3584,35 @@ if __name__ == "__main__":
|
|
|
3256
3584
|
except BrokenPipeError:
|
|
3257
3585
|
sys.exit(0)
|
|
3258
3586
|
'
|
|
3259
|
-
|
|
3587
|
+
local exit_code=${PIPESTATUS[0]}
|
|
3588
|
+
;;
|
|
3589
|
+
|
|
3590
|
+
codex)
|
|
3591
|
+
# Codex: Degraded mode - no stream-json, no agent tracking
|
|
3592
|
+
# Uses positional prompt after exec subcommand
|
|
3593
|
+
# Note: Effort is set via env var, not CLI flag
|
|
3594
|
+
# Uses dynamic tier from RARV phase (tier_param already set above)
|
|
3595
|
+
CODEX_MODEL_REASONING_EFFORT="$tier_param" \
|
|
3596
|
+
codex exec --dangerously-bypass-approvals-and-sandbox \
|
|
3597
|
+
"$prompt" 2>&1 | tee -a "$log_file"
|
|
3598
|
+
local exit_code=${PIPESTATUS[0]}
|
|
3599
|
+
;;
|
|
3600
|
+
|
|
3601
|
+
gemini)
|
|
3602
|
+
# Gemini: Degraded mode - no stream-json, no agent tracking
|
|
3603
|
+
# Note: Thinking level is set via settings.json, not CLI flag
|
|
3604
|
+
# Log the dynamic tier for debugging (tier_param already set above)
|
|
3605
|
+
echo "[loki] Gemini thinking tier: $tier_param (configure in ~/.gemini/settings.json)" >> "$log_file"
|
|
3606
|
+
gemini --yolo \
|
|
3607
|
+
"$prompt" 2>&1 | tee -a "$log_file"
|
|
3608
|
+
local exit_code=${PIPESTATUS[0]}
|
|
3609
|
+
;;
|
|
3610
|
+
|
|
3611
|
+
*)
|
|
3612
|
+
log_error "Unknown provider: ${PROVIDER_NAME:-unknown}"
|
|
3613
|
+
local exit_code=1
|
|
3614
|
+
;;
|
|
3615
|
+
esac
|
|
3260
3616
|
set -e
|
|
3261
3617
|
|
|
3262
3618
|
echo ""
|
|
@@ -3269,7 +3625,7 @@ if __name__ == "__main__":
|
|
|
3269
3625
|
local end_time=$(date +%s)
|
|
3270
3626
|
local duration=$((end_time - start_time))
|
|
3271
3627
|
|
|
3272
|
-
log_info "Claude exited with code $exit_code after ${duration}s"
|
|
3628
|
+
log_info "${PROVIDER_DISPLAY_NAME:-Claude} exited with code $exit_code after ${duration}s"
|
|
3273
3629
|
save_state $retry "exited" $exit_code
|
|
3274
3630
|
|
|
3275
3631
|
# Check for success - ONLY stop on explicit completion promise
|
|
@@ -3294,7 +3650,7 @@ if __name__ == "__main__":
|
|
|
3294
3650
|
|
|
3295
3651
|
# Warn if Claude says it's "done" but no explicit promise
|
|
3296
3652
|
if is_completed; then
|
|
3297
|
-
log_warn "Claude claims completion, but no explicit promise fulfilled."
|
|
3653
|
+
log_warn "${PROVIDER_DISPLAY_NAME:-Claude} claims completion, but no explicit promise fulfilled."
|
|
3298
3654
|
log_warn "Projects are never truly complete - there are always improvements!"
|
|
3299
3655
|
fi
|
|
3300
3656
|
|
|
@@ -3504,28 +3860,76 @@ main() {
|
|
|
3504
3860
|
|
|
3505
3861
|
# Parse arguments
|
|
3506
3862
|
PRD_PATH=""
|
|
3507
|
-
|
|
3508
|
-
|
|
3863
|
+
REMAINING_ARGS=()
|
|
3864
|
+
while [[ $# -gt 0 ]]; do
|
|
3865
|
+
case "$1" in
|
|
3509
3866
|
--parallel)
|
|
3510
3867
|
PARALLEL_MODE=true
|
|
3868
|
+
shift
|
|
3869
|
+
;;
|
|
3870
|
+
--provider)
|
|
3871
|
+
if [[ -n "${2:-}" ]]; then
|
|
3872
|
+
LOKI_PROVIDER="$2"
|
|
3873
|
+
# Reload provider config
|
|
3874
|
+
if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
|
|
3875
|
+
if ! validate_provider "$LOKI_PROVIDER"; then
|
|
3876
|
+
log_error "Unknown provider: $LOKI_PROVIDER"
|
|
3877
|
+
log_info "Supported providers: ${SUPPORTED_PROVIDERS[*]}"
|
|
3878
|
+
exit 1
|
|
3879
|
+
fi
|
|
3880
|
+
if ! load_provider "$LOKI_PROVIDER"; then
|
|
3881
|
+
log_error "Failed to load provider config: $LOKI_PROVIDER"
|
|
3882
|
+
exit 1
|
|
3883
|
+
fi
|
|
3884
|
+
fi
|
|
3885
|
+
shift 2
|
|
3886
|
+
else
|
|
3887
|
+
log_error "--provider requires a value (claude, codex, gemini)"
|
|
3888
|
+
exit 1
|
|
3889
|
+
fi
|
|
3890
|
+
;;
|
|
3891
|
+
--provider=*)
|
|
3892
|
+
LOKI_PROVIDER="${1#*=}"
|
|
3893
|
+
# Reload provider config
|
|
3894
|
+
if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
|
|
3895
|
+
if ! validate_provider "$LOKI_PROVIDER"; then
|
|
3896
|
+
log_error "Unknown provider: $LOKI_PROVIDER"
|
|
3897
|
+
log_info "Supported providers: ${SUPPORTED_PROVIDERS[*]}"
|
|
3898
|
+
exit 1
|
|
3899
|
+
fi
|
|
3900
|
+
if ! load_provider "$LOKI_PROVIDER"; then
|
|
3901
|
+
log_error "Failed to load provider config: $LOKI_PROVIDER"
|
|
3902
|
+
exit 1
|
|
3903
|
+
fi
|
|
3904
|
+
fi
|
|
3905
|
+
shift
|
|
3511
3906
|
;;
|
|
3512
3907
|
--help|-h)
|
|
3513
3908
|
echo "Usage: ./autonomy/run.sh [OPTIONS] [PRD_PATH]"
|
|
3514
3909
|
echo ""
|
|
3515
3910
|
echo "Options:"
|
|
3516
|
-
echo " --parallel
|
|
3517
|
-
echo " --
|
|
3911
|
+
echo " --parallel Enable git worktree-based parallel workflows"
|
|
3912
|
+
echo " --provider <name> Provider: claude (default), codex, gemini"
|
|
3913
|
+
echo " --help, -h Show this help message"
|
|
3518
3914
|
echo ""
|
|
3519
3915
|
echo "Environment variables: See header comments in this script"
|
|
3916
|
+
echo ""
|
|
3917
|
+
echo "Provider capabilities:"
|
|
3918
|
+
if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
|
|
3919
|
+
print_capability_matrix
|
|
3920
|
+
fi
|
|
3520
3921
|
exit 0
|
|
3521
3922
|
;;
|
|
3522
3923
|
*)
|
|
3523
|
-
if [ -z "$PRD_PATH" ]; then
|
|
3524
|
-
PRD_PATH="$
|
|
3924
|
+
if [ -z "$PRD_PATH" ] && [[ ! "$1" == -* ]]; then
|
|
3925
|
+
PRD_PATH="$1"
|
|
3525
3926
|
fi
|
|
3927
|
+
REMAINING_ARGS+=("$1")
|
|
3928
|
+
shift
|
|
3526
3929
|
;;
|
|
3527
3930
|
esac
|
|
3528
3931
|
done
|
|
3932
|
+
set -- "${REMAINING_ARGS[@]}"
|
|
3529
3933
|
|
|
3530
3934
|
# Validate PRD if provided
|
|
3531
3935
|
if [ -n "$PRD_PATH" ] && [ ! -f "$PRD_PATH" ]; then
|
|
@@ -3533,9 +3937,28 @@ main() {
|
|
|
3533
3937
|
exit 1
|
|
3534
3938
|
fi
|
|
3535
3939
|
|
|
3940
|
+
# Show provider info
|
|
3941
|
+
log_info "Provider: ${PROVIDER_DISPLAY_NAME:-Claude Code} (${PROVIDER_NAME:-claude})"
|
|
3942
|
+
if [ "${PROVIDER_DEGRADED:-false}" = "true" ]; then
|
|
3943
|
+
log_warn "Degraded mode: Parallel agents and Task tool not available"
|
|
3944
|
+
# Check if array exists and has elements before iterating
|
|
3945
|
+
if [ -n "${PROVIDER_DEGRADED_REASONS+x}" ] && [ ${#PROVIDER_DEGRADED_REASONS[@]} -gt 0 ]; then
|
|
3946
|
+
log_info "Limitations:"
|
|
3947
|
+
for reason in "${PROVIDER_DEGRADED_REASONS[@]}"; do
|
|
3948
|
+
log_info " - $reason"
|
|
3949
|
+
done
|
|
3950
|
+
fi
|
|
3951
|
+
fi
|
|
3952
|
+
|
|
3536
3953
|
# Show parallel mode status
|
|
3537
3954
|
if [ "$PARALLEL_MODE" = "true" ]; then
|
|
3538
|
-
|
|
3955
|
+
if [ "${PROVIDER_HAS_PARALLEL:-false}" = "true" ]; then
|
|
3956
|
+
log_info "Parallel mode enabled (git worktrees)"
|
|
3957
|
+
else
|
|
3958
|
+
log_warn "Parallel mode requested but not supported by ${PROVIDER_NAME:-unknown}"
|
|
3959
|
+
log_warn "Running in sequential mode instead"
|
|
3960
|
+
PARALLEL_MODE=false
|
|
3961
|
+
fi
|
|
3539
3962
|
fi
|
|
3540
3963
|
|
|
3541
3964
|
# Check prerequisites (unless skipped)
|