App-karr
view release on metacpan or search on metacpan
lib/App/karr/Foundation.pm view on Meta::CPAN
my $hash_before = $self->_ref_hash( $repo ) // '';
my ( $exit, $output ) = $self->_run_command( $repo, $karr, $cmd );
$last_exit = $exit;
$first = 0;
$iter++;
# Common error we can observe (bad exit, timeout, or a known log pattern):
# don't penalize any task â leave the board untouched and back off.
my $err = ( $exit != 0 ) ? "exit=$exit" : undef;
$err //= $self->_match_error( $output, $patterns );
if ( defined $err ) {
$self->_append_log( $repo, "COMMON-ERROR $err" );
$self->_state_set( $repo, last_error => $err );
$outcome = 'common-error';
last;
}
my $hash_after = $self->_ref_hash( $repo ) // '';
my $progressed = ( $hash_before ne $hash_after ) ? 1 : 0;
$outcome = 'progress' if $progressed;
my %after = $self->_task_states( $repo );
my @stuck = $self->_stuck_tasks( \%before, \%after );
# Reset the attempt counter for any task that is no longer stuck
# (advanced, blocked, or gone), then bump/auto-block the stuck ones.
my %is_stuck = map { $_ => 1 } @stuck;
my $attempts = $self->_state_get( $repo, 'attempts' ) // {};
$self->_reset_attempts( $repo, $_ ) for grep { !$is_stuck{$_} } keys %$attempts;
for my $id ( @stuck ) {
my $n = $self->_bump_attempts( $repo, $id );
next if $n < $max_attempts;
$self->_autoblock_task( $repo, $id,
"auto-block: no progress after $n attempts (foundation)" );
$self->_reset_attempts( $repo, $id );
}
# Agent did nothing useful and grabbed nothing â stop, nothing to attribute.
if ( !$progressed && !@stuck ) {
$outcome = 'idle';
last;
}
last unless $drain; # drain disabled â single run
}
return { outcome => $outcome, exit => $last_exit };
}
# ---------------------------------------------------------------------------
# Common-error detection
# ---------------------------------------------------------------------------
sub _error_patterns {
my ( $self, $karr ) = @_;
my @default = (
'rate limit', 'rate-limit', 'usage limit', 'quota exceeded', 'quota',
'overloaded', 'too many requests', '429', '529',
'unauthorized', 'forbidden', 'authentication', 'invalid api key',
'credentials', '401', '403',
'connection refused', 'connection reset', 'network', 'timed out',
'service unavailable', '503', '500 internal',
);
return [ @default, @{ $karr->{error_patterns} // [] } ];
}
sub _match_error {
my ( $self, $text, $patterns ) = @_;
return undef unless defined $text && length $text;
for my $p ( @$patterns ) {
return $p if $text =~ /\Q$p\E/i;
}
return undef;
}
# ---------------------------------------------------------------------------
# Attempt counter (per task, persisted in .karr.state)
# ---------------------------------------------------------------------------
sub _bump_attempts {
my ( $self, $repo, $id ) = @_;
my $a = $self->_state_get( $repo, 'attempts' ) // {};
$a->{$id} = ( $a->{$id} // 0 ) + 1;
$self->_state_set( $repo, attempts => $a );
return $a->{$id};
}
sub _reset_attempts {
my ( $self, $repo, $id ) = @_;
my $a = $self->_state_get( $repo, 'attempts' ) // {};
return unless exists $a->{$id};
delete $a->{$id};
$self->_state_set( $repo, attempts => $a );
}
# ---------------------------------------------------------------------------
# Auto-block (in-process via BoardStore, no karr CLI)
# ---------------------------------------------------------------------------
sub _autoblock_task {
my ( $self, $repo, $id, $reason ) = @_;
return if $self->dry_run;
my $git = App::karr::Git->new( dir => "$repo" );
return unless $git->is_repo;
my $store = App::karr::BoardStore->new( git => $git );
my $task = $store->find_task( $id ) or return;
$task->blocked( $reason );
$store->save_task( $task );
$git->push; # best-effort propagate to remote
$self->_append_log( $repo, "AUTOBLOCK task#$id: $reason" );
return 1;
}
# ---------------------------------------------------------------------------
# Exponential cooldown (1, 2, 4, 8, ... minutes, capped) on common-error
# ---------------------------------------------------------------------------
sub _cooldown_active {
my ( $self, $repo ) = @_;
my $until = $self->_state_get( $repo, 'cooldown_until' ) or return 0;
( run in 1.145 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )