alissa-tools-github-devloop 0.8.42__py3-none-any.whl → 0.8.44__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.
@@ -257,7 +257,8 @@ def build_snapshot(
257
257
  `queued` (the undispatched alissa queue items, None when unread) and
258
258
  `drain` (the holding drain row, or None) feed the `idle` block, whose
259
259
  owed / timers come from the latest poll snapshot and whose live /
260
- managed come from `roster` -- the same reading as `sessions`.
260
+ managed come from `roster` -- the same reading as `sessions`, with a dead
261
+ row backing no held lane left out of `managed` (issue #166).
261
262
 
262
263
  `config` is the seat's `config_block`, or None to leave the key out
263
264
  (the console's push, which has no derived answer to give)."""
@@ -275,13 +276,20 @@ def build_snapshot(
275
276
  if session and session not in repo_by_session:
276
277
  repo_by_session[str(session)] = str(row["repo_slug"])
277
278
 
279
+ # A dead row counts as managed only while it backs a lane the seat still
280
+ # holds (issue #166): Studio's park guard reads `sessions.managed` as well
281
+ # as the idle block's, so both follow the console's rule.
282
+ roster_live, roster_managed, _gone = parking.roster_counts(
283
+ roster, parking.lane_sessions(latest, ledgers=ledgers),
284
+ )
285
+
278
286
  sessions_block: "dict | None" = None
279
287
  session_list: "list[dict] | None" = None
280
288
  if roster is not None:
281
289
  records = sources.sessions(listing=[dict(r) for r in roster])
282
290
  sessions_block = {
283
291
  "live": sum(1 for r in records if r.get("live")),
284
- "managed": len(records),
292
+ "managed": roster_managed,
285
293
  }
286
294
  session_list = [
287
295
  _session_row(r, repo_by_session=repo_by_session, parse_session=parse)
@@ -307,11 +315,13 @@ def build_snapshot(
307
315
  + int(latest.get("revert_candidates") or 0)
308
316
  )
309
317
 
310
- roster_live, roster_managed = parking.roster_counts(roster)
311
318
  idle = parking.idle_block(
312
319
  latest, live=roster_live, managed=roster_managed, queued=queued,
313
320
  drain=drain, now=now_ts,
314
321
  )
322
+ # `gone` is the console's observation; Studio's idle schema is strict and
323
+ # does not carry it.
324
+ idle.pop("gone", None)
315
325
 
316
326
  drift = sources.drift()
317
327
  snapshot = {
@@ -8,8 +8,8 @@ the devloop half of that contract, shared by the daemon (which computes the
8
8
  verdict per pass and pushes it with the fleet vitals) and the console (which
9
9
  serves it on ``GET /api/state`` and runs the drain handshake):
10
10
 
11
- * **The idle block.** ``{asOf, passAt, owed, live, managed, queued, timers,
12
- drainedAt}``. `owed` counts the candidates whose LAST evaluation was an
11
+ * **The idle block.** ``{asOf, passAt, owed, live, managed, queued, gone,
12
+ timers, drainedAt}``. `owed` counts the candidates whose LAST evaluation was an
13
13
  action or a timer the seat owns (a spawn, a fresh spawn row in its stale
14
14
  window, an owed respawn, a deferral, a pending stall ping, a grace the seat
15
15
  is waiting out, an evaluation that failed); a candidate the seat has
@@ -17,7 +17,9 @@ serves it on ``GET /api/state`` and runs the drain handshake):
17
17
  owed. Each pass records the verdict per candidate on its poll snapshot's
18
18
  stage (`owed`, `timer`), so the block is read back from the snapshot and
19
19
  costs no GitHub call. `live` / `managed` / `queued` are read from tmux and
20
- the alissa queue by whoever builds the block.
20
+ the alissa queue by whoever builds the block. `managed` is the live rows
21
+ plus the dead rows whose session backs a lane the seat still holds; a dead
22
+ row backing nothing is reported under `gone` instead (`roster_counts`).
21
23
  * **Unknown is never idle.** No snapshot yet → `owed` is null; a listing that
22
24
  failed → `live` / `managed` / `queued` are null. `idle_refusals` reads every
23
25
  null as a refusal.
@@ -29,7 +31,7 @@ serves it on ``GET /api/state`` and runs the drain handshake):
29
31
  from __future__ import annotations
30
32
 
31
33
  from datetime import datetime, timezone
32
- from typing import Iterable, Mapping, Sequence
34
+ from typing import Collection, Iterable, Mapping, Sequence
33
35
 
34
36
  # The timer kinds of the design's idle block (§2.1). devloop owns the first
35
37
  # three; the last two are revloop's and are listed so the union is stated once.
@@ -111,19 +113,107 @@ def queued_items(queues: "object") -> "int | None":
111
113
  return count
112
114
 
113
115
 
116
+ # The tmux prefix `alissa tmux` puts on a managed session's real name
117
+ # (`alissa.MANAGED_PREFIX`, restated so this module imports nothing of the
118
+ # daemon's). A listing row carries both spellings (`name` bare, `session`
119
+ # prefixed), a snapshot stage the bare one, a queue the prefixed one.
120
+ MANAGED_PREFIX = "ali-"
121
+
122
+ # The spawn ledgers (`Sources.ledgers` keys) with the snapshot edge each one
123
+ # feeds, the column holding its issue / PR number, and the slug separator the
124
+ # poll snapshot uses for it (`owner/repo#7`, `owner/repo#pr16`).
125
+ LEDGER_LANES = (
126
+ ("spawns", "issue", "issue", "#"),
127
+ ("fix_spawns", "fix", "number", "#pr"),
128
+ ("maintain_spawns", "maintain", "number", "#pr"),
129
+ ("revert_spawns", "revert", "number", "#pr"),
130
+ )
131
+
132
+ # A stage holding one of these actions has a worker in flight on it whatever
133
+ # its owed verdict says: its session is never an orphan.
134
+ IN_FLIGHT_ACTIONS = frozenset({"spawned", "in-flight"})
135
+
136
+
137
+ def bare_session(name: "object") -> str:
138
+ """A session name without its `ali-` tmux prefix ("" for a non-name)."""
139
+ text = str(name or "")
140
+ return text[len(MANAGED_PREFIX):] if text.startswith(MANAGED_PREFIX) else text
141
+
142
+
143
+ def lane_sessions(
144
+ snapshot: "Mapping | None",
145
+ *,
146
+ ledgers: "Mapping | None" = None,
147
+ queues: "object" = None,
148
+ ) -> "frozenset[str] | None":
149
+ """The bare names of the sessions that back a lane the seat still holds
150
+ (issue #166): the session a stage that is owed or in flight names; every
151
+ session the spawn ledgers record on such a stage's lane (an earlier
152
+ attempt the lane may still resume); and every session the alissa queue
153
+ holds an undispatched item for. None before the first pass -- with no
154
+ snapshot nothing can be called an orphan, so every row stays managed."""
155
+ if snapshot is None:
156
+ return None
157
+ names: "set[str]" = set()
158
+ held: "set[tuple[str, str]]" = set()
159
+ for stage in snapshot.get("stages") or ():
160
+ if not isinstance(stage, Mapping):
161
+ continue
162
+ if not (stage_owed(stage) or stage.get("stage") in IN_FLIGHT_ACTIONS):
163
+ continue
164
+ held.add((str(stage.get("edge") or ""), str(stage.get("slug") or "")))
165
+ if stage.get("session"):
166
+ names.add(bare_session(stage["session"]))
167
+ for table, edge, column, sep in LEDGER_LANES:
168
+ for row in (ledgers or {}).get(table) or ():
169
+ lane = (edge, f"{row.get('repo_slug')}{sep}{row.get(column)}")
170
+ if lane in held and row.get("session"):
171
+ names.add(bare_session(row["session"]))
172
+ if isinstance(queues, list):
173
+ for queue in queues:
174
+ if not isinstance(queue, Mapping):
175
+ continue
176
+ if any(
177
+ isinstance(item, Mapping) and not item.get("lastDispatchedAt")
178
+ for item in queue.get("items") or ()
179
+ ):
180
+ names.add(bare_session(queue.get("session")))
181
+ names.discard("")
182
+ return frozenset(names)
183
+
184
+
114
185
  def roster_counts(
115
186
  roster: "Sequence[Mapping] | None",
116
- ) -> "tuple[int | None, int | None]":
117
- """`(live, managed)` from an ``alissa tmux ls --json`` listing, the
118
- fleet-vitals `sessions` rule: a row is live unless it says otherwise.
119
- `(None, None)` when the listing failed."""
187
+ lanes: "Collection[str] | None" = None,
188
+ ) -> "tuple[int | None, int | None, list[str] | None]":
189
+ """`(live, managed, gone)` from an ``alissa tmux ls --json`` listing.
190
+
191
+ A row is live unless it says otherwise (the fleet-vitals `sessions`
192
+ rule: its `live` flag, else `status != "gone"`). A row is MANAGED when it
193
+ is live or when its session backs a lane the seat still holds -- `lanes`,
194
+ the bare names `lane_sessions` returns. A dead row matching no lane is an
195
+ orphan: it is not managed, and its name is reported in `gone` instead
196
+ (issue #166 -- one such row kept orcloop's `managed == 0` from ever
197
+ holding). `lanes=None` (no completed pass to read lanes from) keeps every
198
+ row managed: unknown is never idle. `(None, None, None)` when the listing
199
+ failed."""
120
200
  if roster is None:
121
- return None, None
122
- rows = [r for r in roster if isinstance(r, Mapping)]
123
- live = sum(
124
- 1 for r in rows if bool(r.get("live", r.get("status") != "gone"))
125
- )
126
- return live, len(rows)
201
+ return None, None, None
202
+ live = managed = 0
203
+ gone: "list[str]" = []
204
+ for row in roster:
205
+ if not isinstance(row, Mapping):
206
+ continue
207
+ if bool(row.get("live", row.get("status") != "gone")):
208
+ live += 1
209
+ managed += 1
210
+ continue
211
+ name = bare_session(row.get("name") or row.get("session"))
212
+ if lanes is None or name in lanes or bare_session(row.get("session")) in lanes:
213
+ managed += 1
214
+ else:
215
+ gone.append(name)
216
+ return live, managed, sorted(gone)
127
217
 
128
218
 
129
219
  def idle_block(
@@ -134,10 +224,13 @@ def idle_block(
134
224
  queued: "int | None",
135
225
  drain: "Mapping | None",
136
226
  now: "float | int",
227
+ gone: "Sequence[str] | None" = None,
137
228
  ) -> dict:
138
229
  """The seat's idle block from its latest poll snapshot (None before the
139
230
  first pass) and the caller's live readings. `drain` is the holding drain
140
- row (`State.drain`) or None."""
231
+ row (`State.drain`) or None. `gone` is `roster_counts`' orphaned rows,
232
+ reported as `{count, sessions}` (null when the listing was not read) --
233
+ an observation for the operator, never a refusal."""
141
234
  stages: "Iterable[Mapping]" = (snapshot or {}).get("stages") or ()
142
235
  owed: "int | None" = None
143
236
  timers: "list[dict]" = []
@@ -157,6 +250,10 @@ def idle_block(
157
250
  "live": live,
158
251
  "managed": managed,
159
252
  "queued": queued,
253
+ "gone": (
254
+ {"count": len(gone), "sessions": list(gone)}
255
+ if gone is not None else None
256
+ ),
160
257
  "timers": timers,
161
258
  "drainedAt": iso(drain.get("drained_at")) if drain else None,
162
259
  }
@@ -1 +1 @@
1
- 0.8.42
1
+ 0.8.44
@@ -31,6 +31,7 @@ logic lives here -- the console only moves a clock the daemon already reads.
31
31
  from __future__ import annotations
32
32
 
33
33
  import json
34
+ import re
34
35
  import sqlite3
35
36
  import time
36
37
  import urllib.error
@@ -111,8 +112,20 @@ VERSION_CACHE_TTL = 600.0
111
112
  # non-terminal task corpus 1,440x/day.
112
113
  TASKS_CACHE_TTL = 300.0
113
114
  # The flag that narrows `alissa task list` to the calling actor's own rows
114
- # instead of the sponsor's corpus. Newer CLIs only -- see `_supports_self_scope`.
115
+ # instead of the sponsor's corpus. Newer CLIs only -- see `_task_list_flags`.
115
116
  TASKS_SELF_FLAG = "--self"
117
+ # The pane shows live work only, so the read asks for exactly those statuses
118
+ # rather than every non-terminal row: the pending_validation backlog alone was
119
+ # ~2,600 rows (~3.5 MB) per refresh on prod (issue #168).
120
+ TASKS_STATUS_FLAG = "--status"
121
+ TASKS_LIVE_STATUSES = ("draft", "committed", "in_progress", "blocked")
122
+ # A row cap, a small multiple of what the pane displays, for a CLI that offers
123
+ # one. Kept within the 1..100 the CLI's paged listings accept.
124
+ TASKS_LIMIT_FLAG = "--limit"
125
+ TASKS_LIMIT_FACTOR = 4
126
+ TASKS_LIMIT_MAX = 100
127
+ # The pane's displayed row count (`worker_tasks`' default limit).
128
+ TASKS_DISPLAYED = 25
116
129
  # PyPI JSON endpoint for the running-vs-latest drift chip.
117
130
  PYPI_URL = "https://pypi.org/pypi/alissa-tools-github-devloop/json"
118
131
  # How many minutes past the stale window retry-now ages a row: the daemon
@@ -194,6 +207,12 @@ def edge_of(name: "str | None") -> "str | None":
194
207
  return None
195
208
 
196
209
 
210
+ def _offers_flag(helptext: str, flag: str) -> bool:
211
+ """Whether a CLI `--help` text lists `flag` as an option of its own --
212
+ `--self` must not be read off a longer `--self-x` it happens to prefix."""
213
+ return re.search(re.escape(flag) + r"(?![\w-])", helptext) is not None
214
+
215
+
197
216
  def _default_http_get(url: str, timeout: float) -> "bytes | None":
198
217
  try:
199
218
  with urllib.request.urlopen(url, timeout=timeout) as resp: # noqa: S310
@@ -250,8 +269,8 @@ class Sources:
250
269
  self._rate_cache = _Cache(RATE_CACHE_TTL, clock)
251
270
  self._version_cache = _Cache(VERSION_CACHE_TTL, clock)
252
271
  self._tasks_cache = _Cache(TASKS_CACHE_TTL, clock)
253
- # Tri-state: None = not probed yet (see `_supports_self_scope`).
254
- self._self_scope: "bool | None" = None
272
+ # None = not probed yet (see `_task_list_flags`).
273
+ self._list_flags: "frozenset[str] | None" = None
255
274
  self.boot_wall = int(wall_clock())
256
275
 
257
276
  # -- thin, failure-tolerant subprocess helpers -------------------------
@@ -438,54 +457,89 @@ class Sources:
438
457
  a drain holds. A caller that already holds the listing (None when it
439
458
  failed) or the latest snapshot (None before the first pass) hands it
440
459
  in; anything not handed in is read here. A failed listing is null
441
- counts, never zeros."""
460
+ counts, never zeros. A dead row backing no lane the seat holds is
461
+ reported under `gone`, not counted as managed (issue #166)."""
442
462
  if latest is _UNREAD:
443
463
  snaps = self.snapshots(1)
444
464
  latest = snaps[0] if snaps else None
445
465
  if listing is _UNREAD:
446
466
  listing = self._safe_json(["alissa", "tmux", "ls", "--json"])
447
467
  roster = listing if isinstance(listing, list) else None
448
- live, managed = parking.roster_counts(roster)
468
+ snapshot = latest if isinstance(latest, dict) else None
469
+ queues = self._safe_json(["alissa", "tmux", "queue", "ls", "--json"])
470
+ # A dead row counts as managed only while it backs a lane the seat
471
+ # still holds (issue #166).
472
+ lanes = parking.lane_sessions(
473
+ snapshot, ledgers=self.ledgers(), queues=queues,
474
+ )
475
+ live, managed, gone = parking.roster_counts(roster, lanes)
449
476
  return parking.idle_block(
450
- latest if isinstance(latest, dict) else None,
451
- live=live, managed=managed, queued=self.queued(),
477
+ snapshot, live=live, managed=managed,
478
+ queued=parking.queued_items(queues), gone=gone,
452
479
  drain=self.drain(), now=self._wall(),
453
480
  )
454
481
 
455
- def _supports_self_scope(self) -> bool:
456
- """Whether the installed `alissa` CLI understands `task list --self`.
482
+ def _task_list_flags(self) -> "frozenset[str]":
483
+ """Which of the narrowing flags (`--self`, `--status`, `--limit`) the
484
+ installed `alissa` CLI offers on `task list`.
457
485
 
458
486
  Read off the CLI's own `--help`, which is local, tokenless and free.
459
- The alternative -- send the flag and fall back when the call fails --
487
+ The alternative -- send a flag and fall back when the call fails --
460
488
  cannot tell an unknown flag from an auth hiccup, so it would spend a
461
- second sponsor-scoped call on every unlucky refresh, which is the cost
462
- this whole path exists to avoid.
489
+ second, wider call on every unlucky refresh, which is the cost this
490
+ whole path exists to avoid.
463
491
 
464
492
  A probe that *answers* is memoized for the process (the CLI cannot
465
493
  change under a running console). A probe that fails is deliberately
466
494
  NOT memoized: a transient `alissa` failure then degrades this refresh
467
- only, instead of pinning the console to the expensive unscoped call
495
+ only, instead of pinning the console to the expensive unflagged call
468
496
  until someone restarts it."""
469
- if self._self_scope is None:
497
+ if self._list_flags is None:
470
498
  helptext = self._safe_run(
471
499
  ["alissa", "task", "list", "--help"], timeout=20
472
500
  )
473
501
  if helptext is None:
474
- return False
475
- self._self_scope = TASKS_SELF_FLAG in helptext
476
- return self._self_scope
502
+ return frozenset()
503
+ self._list_flags = frozenset(
504
+ flag for flag in
505
+ (TASKS_SELF_FLAG, TASKS_STATUS_FLAG, TASKS_LIMIT_FLAG)
506
+ if _offers_flag(helptext, flag)
507
+ )
508
+ return self._list_flags
477
509
 
478
- def worker_tasks(self, limit: int = 25) -> "list[dict]":
510
+ def _supports_self_scope(self) -> bool:
511
+ """Whether the installed `alissa` CLI understands `task list --self`."""
512
+ return TASKS_SELF_FLAG in self._task_list_flags()
513
+
514
+ def _task_list_argv(self) -> "list[str]":
515
+ """The `alissa task list` read the pane refreshes from: narrowed by
516
+ every flag the CLI offers -- the actor's own rows (`--self`), the live
517
+ statuses the pane shows (`--status`) and a row cap a small multiple
518
+ of the displayed count (`--limit`). A flag the CLI lacks is simply left
519
+ off; that per-flag degrade is the ONLY way the read widens."""
520
+ flags = self._task_list_flags()
521
+ argv = ["alissa", "task", "list", "--json"]
522
+ if TASKS_SELF_FLAG in flags:
523
+ argv.append(TASKS_SELF_FLAG)
524
+ if TASKS_STATUS_FLAG in flags:
525
+ argv += [TASKS_STATUS_FLAG, ",".join(TASKS_LIVE_STATUSES)]
526
+ if TASKS_LIMIT_FLAG in flags:
527
+ cap = min(TASKS_DISPLAYED * TASKS_LIMIT_FACTOR, TASKS_LIMIT_MAX)
528
+ argv += [TASKS_LIMIT_FLAG, str(cap)]
529
+ return argv
530
+
531
+ def worker_tasks(self, limit: int = TASKS_DISPLAYED) -> "list[dict]":
479
532
  """The worker actor's own tasks via `alissa task list` -- the
480
533
  actor-partition bridge (the console runs as the operator, whose actor
481
534
  owns the implementation tasks the dev sessions created). Best-effort:
482
535
  an old CLI or an auth hiccup just yields an empty list.
483
536
 
484
- Self-scoped (`--self`) wherever the installed CLI offers it, so a poll
485
- reads the console actor's own rows rather than the sponsor's whole
486
- non-terminal corpus -- the pane only ever displays the former. An
487
- older CLI, or a `--self` call that comes back empty-handed, degrades to
488
- the unflagged call: a slower, larger read beats a blank pane.
537
+ Narrowed (`_task_list_argv`) to what the pane displays wherever the
538
+ installed CLI offers the flags, so a refresh reads the console actor's
539
+ live rows rather than the sponsor's whole non-terminal corpus. Only a
540
+ CLI that lacks a flag reads wider. A narrowed call that fails is never
541
+ retried unflagged (issue #168: that fallback re-read the sponsor's
542
+ whole corpus), and a valid but empty `[]` is a real answer.
489
543
 
490
544
  Cached (TASKS_CACHE_TTL) exactly like the two other remote checks: this
491
545
  call rides the shared ALISSA_API_TOKEN the daemon uses, so on the ~10s
@@ -493,16 +547,7 @@ class Sources:
493
547
  the API budget. A fetch that fails returns None and keeps the last good
494
548
  list (the _Cache contract), never overwriting it with junk."""
495
549
  def fetch() -> "list[dict] | None":
496
- unscoped = ["alissa", "task", "list", "--json"]
497
- self_scoped = self._supports_self_scope()
498
- argv = (unscoped + [TASKS_SELF_FLAG]) if self_scoped else unscoped
499
- raw = self._safe_json(argv, timeout=45)
500
- if raw is None and self_scoped:
501
- # The CLI advertised the flag but the call brought nothing
502
- # back -- an API that does not serve the scope yet, say. One
503
- # unflagged retry keeps the pane populated. A valid but empty
504
- # `[]` is a real answer, not a failure, and is never retried.
505
- raw = self._safe_json(unscoped, timeout=45)
550
+ raw = self._safe_json(self._task_list_argv(), timeout=45)
506
551
  if not isinstance(raw, list):
507
552
  return None
508
553
  rows = [t for t in raw if isinstance(t, dict)]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-devloop
3
- Version: 0.8.42
3
+ Version: 0.8.44
4
4
  Summary: ALISSA-TOOLS-GITHUB-DEVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -194,6 +194,32 @@ resolved from the remote name or URL in the directory the push runs in
194
194
  no marker are untouched. `ALISSA_SHELL_GUARD=off` disables this guard with
195
195
  the rest.
196
196
 
197
+ ## Seat parking: what counts as managed (issue #166)
198
+
199
+ The idle block on `GET /api/state` (`parking.idle_block`, issue #153) is what
200
+ orcloop's D2 invariant reads before it drains and parks the seat: idle only
201
+ when `live == managed == queued == owed == 0` with no timer and a fresh pass.
202
+ `live` and `managed` come from `alissa tmux ls --json`:
203
+
204
+ - a row is **live** unless it says otherwise (its `live` flag, else
205
+ `status != "gone"`);
206
+ - a row is **managed** when it is live, **or** when it is dead but its
207
+ session still backs a lane the seat holds — the session named by a stage
208
+ of the latest poll snapshot that is owed or in flight (`spawned` /
209
+ `in-flight`), any session the spawn ledgers record on such a stage's lane
210
+ (an earlier attempt the lane may still resume), or a session the alissa
211
+ queue holds an undispatched item for;
212
+ - a dead row backing none of these is an **orphan**: it is not managed, and
213
+ is reported in the block's `gone` field as `{count, sessions}` so the
214
+ console and orcloop's logs still show it.
215
+
216
+ Unknown is never idle: a failed listing leaves `live`, `managed` and `gone`
217
+ null, and before the first completed pass (no snapshot to read lanes from)
218
+ every dead row stays managed. The console only reports an orphan — it never
219
+ kills or reaps one. The fleet-vitals push applies the same rule to
220
+ `sessions.managed` and `idle.managed` (Studio's park guard reads both) and
221
+ leaves `gone` out, since Studio's idle schema is strict.
222
+
197
223
  ## License
198
224
 
199
225
  Licensed under the
@@ -4,29 +4,29 @@ alissa/tools/github/devloop/alissa.py,sha256=TJU4eSZc2MvP35HewCwBKvhPAel32vKjtt3
4
4
  alissa/tools/github/devloop/alissa_client.py,sha256=wmpXYWf9ThcLeYnz6gGeDz7gj5BpYjduuVASMc-QOOY,14873
5
5
  alissa/tools/github/devloop/bows.py,sha256=pzDNMRk_S78FX7vMNYMlXHxW6VPHPo1Xd2EzFMgVCUM,17325
6
6
  alissa/tools/github/devloop/config.py,sha256=j6W0Wrz7smFdFdSs3g5S6xadcxfFS5YEhQDL64yAuJk,75109
7
- alissa/tools/github/devloop/fleet_vitals.py,sha256=Hl2xshweh7A9R0N7fyYRg2WzHN4aNefVlt0TiXCv8zk,24979
7
+ alissa/tools/github/devloop/fleet_vitals.py,sha256=5mWnRkJh8u9soSplud3UMxkelT6EqAiz-ivX8UmfIqk,25477
8
8
  alissa/tools/github/devloop/ghclient.py,sha256=tYUHwmoy9CBBt-jSOz9nuAl8lpMvm1LH4A-wHx25bss,71842
9
9
  alissa/tools/github/devloop/hubs.py,sha256=yjnsnSHLG7GNNP8X2hILeAMV6P5n4JGc_IaAxpSP_YQ,18974
10
10
  alissa/tools/github/devloop/janitor.py,sha256=R8REoHBeY2cfW7fYJVhKr-F5z9Mjlc-vmv-z4KtvBZc,27360
11
11
  alissa/tools/github/devloop/loop.py,sha256=uMfuomSdIPC8KkWUGF0OrW3RR9OSC1txKVnXGZ1SeIc,613973
12
12
  alissa/tools/github/devloop/loop_events.py,sha256=tmso0OALqY2yAPW2Fo6ae0sYetQIdIeIYooJ2Sc2cc4,37921
13
- alissa/tools/github/devloop/parking.py,sha256=2Sys1bh_hG8qbq7cO9RxW1F1kAu3dCWHnvwg_iAFVvY,7987
13
+ alissa/tools/github/devloop/parking.py,sha256=vEf-qprN14-I4vu4M53tIFaA3827FiHLpKetBAgN4LQ,12409
14
14
  alissa/tools/github/devloop/proc.py,sha256=nzfsxJSHFWRQcrXmfv588KazFysv8dVQrhMPH1q5eRk,3134
15
15
  alissa/tools/github/devloop/prompts.py,sha256=VzN6ec5eF2wwg7QSCRg52WLcxo-tGbGRgQbc4HSahUc,55169
16
16
  alissa/tools/github/devloop/state.py,sha256=uVwhbkogUTjWXXFq-COKzUeYrpuGbHzi05CjfEzAtPA,88668
17
- alissa/tools/github/devloop/version,sha256=AuempWUg4vcEdSLs24Raav8UvlQqlRrI2s1oyudhK4I,7
17
+ alissa/tools/github/devloop/version,sha256=va232A0BU1OdMrEBtZ5PyqpxusfsBdgt4whBySGjLHY,7
18
18
  alissa/tools/github/devloop/version.py,sha256=znuuOaitXGrBpbskNy78_rlVpFdtmKmk_3k1ACxZrl0,1418
19
19
  alissa/tools/github/devloop/webui/__init__.py,sha256=vdXYoyGYzD7BrJ5lV6XdH8Bpw_pej8-RK0hjfxop7-8,1513
20
20
  alissa/tools/github/devloop/webui/__main__.py,sha256=wORyW5AkgsniK0TZcrOrvm_P7Q4Mr-fH-4S6qj9XcgA,6137
21
21
  alissa/tools/github/devloop/webui/auth.py,sha256=g9YEDao9nKt-cBEENp7vFqwJ-T8olN6uLiCzVIN8yhA,7890
22
22
  alissa/tools/github/devloop/webui/page.py,sha256=4OD3e01ByvVM4Wo04AZyFuyOYTFEMT2lagktZAvyCKk,30862
23
23
  alissa/tools/github/devloop/webui/server.py,sha256=YuUmcng5qdWqSurcuyxH3FKKkBBfrpDtvtZLidJOGiU,26226
24
- alissa/tools/github/devloop/webui/sources.py,sha256=Ud8357-Ag4qC39V3mlepqnArui9p1fTJ058bAdN29Jc,36730
24
+ alissa/tools/github/devloop/webui/sources.py,sha256=JJXJEkLKh2qRbpTaIyQwCTu90DuKq_Z3nz6DwLhzlr4,38900
25
25
  alissa/tools/github/devloop/webui/sysinfo.py,sha256=ptlogH9alFv0vZ-pQfyUaA3w-K7LQS3tQHGv0d8Tdlk,9649
26
- alissa_tools_github_devloop-0.8.42.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
27
- alissa_tools_github_devloop-0.8.42.dist-info/licenses/NOTICE,sha256=FKstNs3uw_tQod4_1XcFLD1FUTJQXf4FC0oXJAOA_HE,260
28
- alissa_tools_github_devloop-0.8.42.dist-info/METADATA,sha256=ZITb9-7j5ZOmFkpcc4wmIz6DRT7_1HygSpTD7sHLp50,10759
29
- alissa_tools_github_devloop-0.8.42.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
30
- alissa_tools_github_devloop-0.8.42.dist-info/entry_points.txt,sha256=8aC7Ajzx4bu9ZRR37a40IDfuStacvZP5KsAxY1dbFKU,145
31
- alissa_tools_github_devloop-0.8.42.dist-info/top_level.txt,sha256=DodjDg-l-TWQnlxG-Vuc3G5sB4O7cWGQwe6XrVx3u-A,7
32
- alissa_tools_github_devloop-0.8.42.dist-info/RECORD,,
26
+ alissa_tools_github_devloop-0.8.44.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
27
+ alissa_tools_github_devloop-0.8.44.dist-info/licenses/NOTICE,sha256=FKstNs3uw_tQod4_1XcFLD1FUTJQXf4FC0oXJAOA_HE,260
28
+ alissa_tools_github_devloop-0.8.44.dist-info/METADATA,sha256=s-GkeBOMMoSQatUPgO0Hg_HgIyoe5TlcNi-8D-NwSYQ,12222
29
+ alissa_tools_github_devloop-0.8.44.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
30
+ alissa_tools_github_devloop-0.8.44.dist-info/entry_points.txt,sha256=8aC7Ajzx4bu9ZRR37a40IDfuStacvZP5KsAxY1dbFKU,145
31
+ alissa_tools_github_devloop-0.8.44.dist-info/top_level.txt,sha256=DodjDg-l-TWQnlxG-Vuc3G5sB4O7cWGQwe6XrVx3u-A,7
32
+ alissa_tools_github_devloop-0.8.44.dist-info/RECORD,,