alissa-tools-github-devloop 0.8.60__py3-none-any.whl → 0.8.61__py3-none-any.whl
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.
- alissa/tools/github/devloop/loop.py +54 -10
- alissa/tools/github/devloop/state.py +48 -0
- alissa/tools/github/devloop/version +1 -1
- {alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/METADATA +1 -1
- {alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/RECORD +10 -10
- {alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/WHEEL +0 -0
- {alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/entry_points.txt +0 -0
- {alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/licenses/LICENSE +0 -0
- {alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/licenses/NOTICE +0 -0
- {alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/top_level.txt +0 -0
|
@@ -2274,7 +2274,9 @@ ESCALATION_LIVE_STALLED = "live-stalled"
|
|
|
2274
2274
|
# assignment. Once-ever like ESCALATION_ASSIGNMENT -- the remedy (unassign,
|
|
2275
2275
|
# or leave it to whoever is working it by hand) does not change per poll.
|
|
2276
2276
|
# ASSIGNED_OUTSIDE_SEEN is the episode's durable first-seen clock, kept in the
|
|
2277
|
-
# activity_log ledger so a daemon restart does not restart the wait
|
|
2277
|
+
# activity_log ledger so a daemon restart does not restart the wait, and
|
|
2278
|
+
# dropped by the first issue pass that no longer reaches the skip (issue
|
|
2279
|
+
# #204), so the next episode on the same issue starts a clock of its own.
|
|
2278
2280
|
ESCALATION_ASSIGNED_OUTSIDE = "assigned-outside"
|
|
2279
2281
|
ASSIGNED_OUTSIDE_SEEN = "assigned-outside-seen"
|
|
2280
2282
|
|
|
@@ -4737,6 +4739,10 @@ class DevWatcher:
|
|
|
4737
4739
|
self._shell_streaks: "dict[str, tuple[int, int]]" = {}
|
|
4738
4740
|
# The process reaper's own clock (janitor cadence, issue #186).
|
|
4739
4741
|
self._orphans_at: "float | None" = None
|
|
4742
|
+
# The issues whose assigned-outside episode this issue pass saw
|
|
4743
|
+
# still running (issue #204): `_assigned_outside` adds to it, and the
|
|
4744
|
+
# end of `_poll_issues` ends every other episode's first-seen clock.
|
|
4745
|
+
self._outside_seen: "set[tuple[str, int]]" = set()
|
|
4740
4746
|
|
|
4741
4747
|
# -- per-issue decision ------------------------------------------------
|
|
4742
4748
|
|
|
@@ -4856,6 +4862,13 @@ class DevWatcher:
|
|
|
4856
4862
|
if claim is not None:
|
|
4857
4863
|
return self._claim_deferral(claim, attempts)
|
|
4858
4864
|
|
|
4865
|
+
# What adoption put on the ledger, carried by hand for the reads below
|
|
4866
|
+
# that would otherwise go back to the ledger for it: a dry run writes
|
|
4867
|
+
# no row, so without these the dry pass judged the attempt cap
|
|
4868
|
+
# against 0 billed attempts and liveness against no session name at
|
|
4869
|
+
# all, and could SPAWN where the real pass ESCALATES (issue #204).
|
|
4870
|
+
adopted = 0
|
|
4871
|
+
adopted_session: "str | None" = None
|
|
4859
4872
|
if login in issue.assignees and age is None:
|
|
4860
4873
|
# Self-assigned but the ledger never spawned. Issue #200: when the
|
|
4861
4874
|
# issue's own activity comment carries an EXPIRED spawn line by
|
|
@@ -4867,6 +4880,7 @@ class DevWatcher:
|
|
|
4867
4880
|
orphans = self._orphaned_spawns(issue, session_name(issue, 1))
|
|
4868
4881
|
if orphans:
|
|
4869
4882
|
attempts, age = self._adopt_orphaned_spawns(issue, orphans)
|
|
4883
|
+
adopted, adopted_session = attempts, orphans[-1].session
|
|
4870
4884
|
else:
|
|
4871
4885
|
# No spawn line at all: assigned outside this daemon (by
|
|
4872
4886
|
# hand, or under another workspace's ledger). Not ours to
|
|
@@ -4931,8 +4945,9 @@ class DevWatcher:
|
|
|
4931
4945
|
)
|
|
4932
4946
|
linked = [url for url, state in links if state == PR_OPEN]
|
|
4933
4947
|
if linked:
|
|
4934
|
-
row_session =
|
|
4935
|
-
issue.full_name, number
|
|
4948
|
+
row_session = (
|
|
4949
|
+
self.state.newest_spawn_session(issue.full_name, number)
|
|
4950
|
+
or adopted_session
|
|
4936
4951
|
)
|
|
4937
4952
|
liveness = self.session_liveness(row_session, roster)
|
|
4938
4953
|
if liveness is Liveness.ALIVE and row_session:
|
|
@@ -5067,9 +5082,11 @@ class DevWatcher:
|
|
|
5067
5082
|
# rows resolve INDETERMINATE otherwise, and since they respawn
|
|
5068
5083
|
# unconditionally TODAY, deferring them forever would be a
|
|
5069
5084
|
# regression dressed as caution.
|
|
5070
|
-
live_session =
|
|
5071
|
-
issue.full_name, number
|
|
5072
|
-
|
|
5085
|
+
live_session = (
|
|
5086
|
+
self.state.newest_spawn_session(issue.full_name, number)
|
|
5087
|
+
or adopted_session
|
|
5088
|
+
or session_name(issue, attempts)
|
|
5089
|
+
)
|
|
5073
5090
|
liveness = self.session_liveness(live_session, roster)
|
|
5074
5091
|
# Falling through means DEAD, and DEAD here is CONFIRMED (a
|
|
5075
5092
|
# successful listing without this name) -- so the retry below
|
|
@@ -5181,7 +5198,13 @@ class DevWatcher:
|
|
|
5181
5198
|
# Billable attempts only (issue #142): an attempt refunded by a
|
|
5182
5199
|
# usage limit never counts toward the cap; the numbering below still
|
|
5183
5200
|
# counts it, so session names stay monotonic.
|
|
5184
|
-
|
|
5201
|
+
# An adopted orphan is billable by construction (its row never carries
|
|
5202
|
+
# a limit stamp), and on a dry run it has no row to count: `max` lets
|
|
5203
|
+
# the dry pass reach the cap decision the real one does (issue #204).
|
|
5204
|
+
billed = max(
|
|
5205
|
+
self.state.attempt_count(issue.full_name, number, billable=True),
|
|
5206
|
+
adopted,
|
|
5207
|
+
)
|
|
5185
5208
|
if billed >= self.config.attempt_cap:
|
|
5186
5209
|
if self.state.escalated(issue.full_name, number, ESCALATION_CAP):
|
|
5187
5210
|
return Decision(Action.CAPPED, "already escalated", attempts)
|
|
@@ -10263,8 +10286,10 @@ class DevWatcher:
|
|
|
10263
10286
|
Rows are numbered 1..k in line order (the ledger has none for this
|
|
10264
10287
|
issue), keeping the session name the line carries; numbering by the
|
|
10265
10288
|
name's own suffix could REPLACE a row when a line is missing. Not
|
|
10266
|
-
written on a dry run (no ledger write is)
|
|
10267
|
-
|
|
10289
|
+
written on a dry run (no ledger write is); `evaluate` carries the
|
|
10290
|
+
returned count and the newest line's session name into the reads
|
|
10291
|
+
that would otherwise look for these rows (the attempt cap, the
|
|
10292
|
+
lane's liveness), so the dry pass reaches the same decision."""
|
|
10268
10293
|
for attempt, orphan in enumerate(orphans, start=1):
|
|
10269
10294
|
log.warning(
|
|
10270
10295
|
"%s: adopted orphaned attempt %d from activity log (`%s`, "
|
|
@@ -10294,8 +10319,15 @@ class DevWatcher:
|
|
|
10294
10319
|
Not silent forever (issue #200): once the skip has persisted for
|
|
10295
10320
|
STALLED_DEFER_MULTIPLE stale windows (clock: the episode's durable
|
|
10296
10321
|
first-seen row) it posts ONE operator comment, recorded only after
|
|
10297
|
-
it lands so a failed post retries next poll.
|
|
10322
|
+
it lands so a failed post retries next poll.
|
|
10323
|
+
|
|
10324
|
+
The clock is per EPISODE (issue #204): the issue pass ends it the
|
|
10325
|
+
first time it does not reach this skip for the issue -- unassigned,
|
|
10326
|
+
adopted, spawned, closed or unlabeled -- so a hand assignment weeks
|
|
10327
|
+
later waits its own full window instead of paging on its first
|
|
10328
|
+
poll. The page itself stays once-ever."""
|
|
10298
10329
|
full_name, number = issue.full_name, issue.number
|
|
10330
|
+
self._outside_seen.add((full_name, number))
|
|
10299
10331
|
reason = (
|
|
10300
10332
|
f"assigned to {login} outside this daemon (no spawn on the "
|
|
10301
10333
|
f"ledger) — unassign the issue to let the loop take it"
|
|
@@ -13803,6 +13835,7 @@ class DevWatcher:
|
|
|
13803
13835
|
)
|
|
13804
13836
|
|
|
13805
13837
|
results = []
|
|
13838
|
+
self._outside_seen = set()
|
|
13806
13839
|
for owner, repo, number, assignees in _oldest_first(found):
|
|
13807
13840
|
slug = f"{owner}/{repo}#{number}"
|
|
13808
13841
|
# Defense in depth: the search is already repo-scoped, but nothing
|
|
@@ -13837,10 +13870,21 @@ class DevWatcher:
|
|
|
13837
13870
|
log.error("%s: %s", slug, exc)
|
|
13838
13871
|
# Owed: an evaluation that failed is unknown, never settled.
|
|
13839
13872
|
decision = Decision(Action.SKIPPED, str(exc), owed=True)
|
|
13873
|
+
# Unknown is not ended either: a transient failure must
|
|
13874
|
+
# not restart an assigned-outside wait.
|
|
13875
|
+
self._outside_seen.add((f"{owner}/{repo}", number))
|
|
13840
13876
|
|
|
13841
13877
|
level = logging.INFO if decision.action != Action.SKIPPED else logging.DEBUG
|
|
13842
13878
|
log.log(level, "%s → %s (%s)", slug, decision.action.value, decision.reason)
|
|
13843
13879
|
results.append((slug, decision))
|
|
13880
|
+
# Issue #204: an assigned-outside episode lasts exactly as long as
|
|
13881
|
+
# the skip keeps answering. Every first-seen clock this pass did not
|
|
13882
|
+
# reach the skip for has ended; drop it, or a later hand assignment
|
|
13883
|
+
# of the same issue inherits a wait that is weeks old and pages on
|
|
13884
|
+
# its first poll. Not reached when the pass raised (RateLimited), and
|
|
13885
|
+
# never on a dry run, which writes nothing.
|
|
13886
|
+
if not self.config.dry_run:
|
|
13887
|
+
self.state.prune_activity(ASSIGNED_OUTSIDE_SEEN, self._outside_seen)
|
|
13844
13888
|
return results
|
|
13845
13889
|
|
|
13846
13890
|
def _poll_prs(
|
|
@@ -1289,6 +1289,54 @@ class State:
|
|
|
1289
1289
|
f"record_activity({repo_slug}#{number}, {kind})", write),
|
|
1290
1290
|
)
|
|
1291
1291
|
|
|
1292
|
+
def prune_activity(
|
|
1293
|
+
self, kind: str, keep: "set[tuple[str, int]]"
|
|
1294
|
+
) -> int:
|
|
1295
|
+
"""Drop every activity_log row of exactly this KIND whose
|
|
1296
|
+
(repo_slug, number) is not in `keep`. Returns how many rows were
|
|
1297
|
+
dropped (0 when the store refused the write).
|
|
1298
|
+
|
|
1299
|
+
For a kind whose row is an EPISODE's first-seen clock rather than a
|
|
1300
|
+
posted line's dedupe (issue #204: `assigned-outside-seen`): the
|
|
1301
|
+
caller passes the subjects whose episode it saw still running this
|
|
1302
|
+
pass, and every other clock of the kind ends, so a later episode on
|
|
1303
|
+
the same item starts its own wait instead of inheriting this one's.
|
|
1304
|
+
|
|
1305
|
+
BEST-EFFORT with the SELECT inside the closure, and a pass with
|
|
1306
|
+
nothing to drop reports `False`, for the reasons `prune_escalations`
|
|
1307
|
+
gives. A held (unwritten) row of the kind is released alongside."""
|
|
1308
|
+
dropped: "list[tuple[str, int]]" = []
|
|
1309
|
+
|
|
1310
|
+
def write() -> "bool | None":
|
|
1311
|
+
del dropped[:]
|
|
1312
|
+
rows = self._db.execute(
|
|
1313
|
+
"SELECT repo_slug, number FROM activity_log WHERE kind=?",
|
|
1314
|
+
(kind,),
|
|
1315
|
+
).fetchall()
|
|
1316
|
+
dropped.extend(
|
|
1317
|
+
(row["repo_slug"], int(row["number"])) for row in rows
|
|
1318
|
+
if (row["repo_slug"], int(row["number"])) not in keep
|
|
1319
|
+
)
|
|
1320
|
+
if not dropped:
|
|
1321
|
+
return False
|
|
1322
|
+
for repo_slug, number in dropped:
|
|
1323
|
+
self._db.execute(
|
|
1324
|
+
"DELETE FROM activity_log WHERE repo_slug=? AND number=? "
|
|
1325
|
+
"AND kind=?",
|
|
1326
|
+
(repo_slug, number, kind),
|
|
1327
|
+
)
|
|
1328
|
+
self._db.commit()
|
|
1329
|
+
return True
|
|
1330
|
+
|
|
1331
|
+
wrote = self._best_effort(f"prune_activity({kind})", write)
|
|
1332
|
+
for key in [
|
|
1333
|
+
key for key in self._pending_gates
|
|
1334
|
+
if key[0] == "activity_log" and key[3] == kind
|
|
1335
|
+
and (key[1], key[2]) not in keep
|
|
1336
|
+
]:
|
|
1337
|
+
self._pending_gates.pop(key, None)
|
|
1338
|
+
return len(dropped) if wrote else 0
|
|
1339
|
+
|
|
1292
1340
|
# -- janitor holdouts (issue #99) ---------------------------------------
|
|
1293
1341
|
#
|
|
1294
1342
|
# The workspace janitor's once-per-episode memory. Every method here is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.8.
|
|
1
|
+
0.8.61
|
{alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/RECORD
RENAMED
|
@@ -10,13 +10,13 @@ alissa/tools/github/devloop/hubs.py,sha256=yjnsnSHLG7GNNP8X2hILeAMV6P5n4JGc_IaAx
|
|
|
10
10
|
alissa/tools/github/devloop/hungtests.py,sha256=TozIDydUe_Kn1m5EtGeGtYt_BrLqGznrNjikLYhxH-Q,24621
|
|
11
11
|
alissa/tools/github/devloop/integration_check.py,sha256=JkFwz3JqOQBIvzszmiql-IMVGSH3lDers6mJUHh9zMM,17928
|
|
12
12
|
alissa/tools/github/devloop/janitor.py,sha256=R8REoHBeY2cfW7fYJVhKr-F5z9Mjlc-vmv-z4KtvBZc,27360
|
|
13
|
-
alissa/tools/github/devloop/loop.py,sha256=
|
|
13
|
+
alissa/tools/github/devloop/loop.py,sha256=0GhXYt-Ou6b4hCaVlW3MwBZojRpvtxgA7uTkXKMnknU,707186
|
|
14
14
|
alissa/tools/github/devloop/loop_events.py,sha256=B8wq4_MMXbf1-od3pk9OHVo6F6W2lzzUZmU6GsKcCkM,38113
|
|
15
15
|
alissa/tools/github/devloop/parking.py,sha256=7h3Gg2XQ0eqlvn0M6K6NeEg9xbqnlUPrZNs9gRofzxc,12410
|
|
16
16
|
alissa/tools/github/devloop/proc.py,sha256=nzfsxJSHFWRQcrXmfv588KazFysv8dVQrhMPH1q5eRk,3134
|
|
17
17
|
alissa/tools/github/devloop/prompts.py,sha256=VzN6ec5eF2wwg7QSCRg52WLcxo-tGbGRgQbc4HSahUc,55169
|
|
18
|
-
alissa/tools/github/devloop/state.py,sha256=
|
|
19
|
-
alissa/tools/github/devloop/version,sha256=
|
|
18
|
+
alissa/tools/github/devloop/state.py,sha256=mZcKMtWKdwjRk4up74WVI_QZaOJF4JLjGWFMhx1k2FA,91591
|
|
19
|
+
alissa/tools/github/devloop/version,sha256=G3rr6PpxmjA80IOCaXyiYP6ce9bJP9MLMtfPh8hyo00,7
|
|
20
20
|
alissa/tools/github/devloop/version.py,sha256=znuuOaitXGrBpbskNy78_rlVpFdtmKmk_3k1ACxZrl0,1418
|
|
21
21
|
alissa/tools/github/devloop/webui/__init__.py,sha256=vdXYoyGYzD7BrJ5lV6XdH8Bpw_pej8-RK0hjfxop7-8,1513
|
|
22
22
|
alissa/tools/github/devloop/webui/__main__.py,sha256=wORyW5AkgsniK0TZcrOrvm_P7Q4Mr-fH-4S6qj9XcgA,6137
|
|
@@ -25,10 +25,10 @@ alissa/tools/github/devloop/webui/page.py,sha256=4OD3e01ByvVM4Wo04AZyFuyOYTFEMT2
|
|
|
25
25
|
alissa/tools/github/devloop/webui/server.py,sha256=3aGYGesjUSrPrgcpfy-oYDefBNKnAj3lGfRzwsRkv_I,27031
|
|
26
26
|
alissa/tools/github/devloop/webui/sources.py,sha256=Uw_2B5QGSmW6f66WfVbQb6yLLYmSv6uuF7HNJ5OSr_I,39314
|
|
27
27
|
alissa/tools/github/devloop/webui/sysinfo.py,sha256=ptlogH9alFv0vZ-pQfyUaA3w-K7LQS3tQHGv0d8Tdlk,9649
|
|
28
|
-
alissa_tools_github_devloop-0.8.
|
|
29
|
-
alissa_tools_github_devloop-0.8.
|
|
30
|
-
alissa_tools_github_devloop-0.8.
|
|
31
|
-
alissa_tools_github_devloop-0.8.
|
|
32
|
-
alissa_tools_github_devloop-0.8.
|
|
33
|
-
alissa_tools_github_devloop-0.8.
|
|
34
|
-
alissa_tools_github_devloop-0.8.
|
|
28
|
+
alissa_tools_github_devloop-0.8.61.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
29
|
+
alissa_tools_github_devloop-0.8.61.dist-info/licenses/NOTICE,sha256=FKstNs3uw_tQod4_1XcFLD1FUTJQXf4FC0oXJAOA_HE,260
|
|
30
|
+
alissa_tools_github_devloop-0.8.61.dist-info/METADATA,sha256=0xoJnkXt8Xv4qVsf_C7lFVHdGVm05j410cO-eRY29-Q,15973
|
|
31
|
+
alissa_tools_github_devloop-0.8.61.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
32
|
+
alissa_tools_github_devloop-0.8.61.dist-info/entry_points.txt,sha256=8aC7Ajzx4bu9ZRR37a40IDfuStacvZP5KsAxY1dbFKU,145
|
|
33
|
+
alissa_tools_github_devloop-0.8.61.dist-info/top_level.txt,sha256=DodjDg-l-TWQnlxG-Vuc3G5sB4O7cWGQwe6XrVx3u-A,7
|
|
34
|
+
alissa_tools_github_devloop-0.8.61.dist-info/RECORD,,
|
{alissa_tools_github_devloop-0.8.60.dist-info → alissa_tools_github_devloop-0.8.61.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|