agentic-dev-loop 1.0.0a50__tar.gz → 1.0.0a51__tar.gz
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.
Potentially problematic release.
This version of agentic-dev-loop might be problematic. Click here for more details.
- {agentic_dev_loop-1.0.0a50/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a51}/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/graph.py +16 -2
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/viz/server.py +48 -12
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51/src/agentic_dev_loop.egg-info}/PKG-INFO +1 -1
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_graph.py +20 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_viz.py +113 -6
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/README.md +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/config.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/stages.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_041_brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_046_brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_containers.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_verdict.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentic-dev-loop"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0a51"
|
|
8
8
|
description = "Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
@@ -8,6 +8,7 @@ steer the retry cycle, and a SQLite checkpointer makes runs crash-resumable
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import operator
|
|
11
|
+
import time
|
|
11
12
|
import uuid
|
|
12
13
|
import webbrowser
|
|
13
14
|
from pathlib import Path
|
|
@@ -279,12 +280,14 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
279
280
|
if bus is not None:
|
|
280
281
|
start_state = initial if initial is not None \
|
|
281
282
|
else graph.get_state(config).values
|
|
283
|
+
run_start = time.monotonic()
|
|
282
284
|
bus.publish({
|
|
283
285
|
"type": "run_started",
|
|
284
286
|
"subject": start_state["subject"],
|
|
285
287
|
"max_attempts": start_state["max_attempts"],
|
|
286
288
|
"thread_id": thread_id,
|
|
287
289
|
"attempt": start_state["attempt"],
|
|
290
|
+
"started_at": time.time(),
|
|
288
291
|
})
|
|
289
292
|
watch = viz.NodeWatch()
|
|
290
293
|
capture = viz.ConsoleCapture(bus, watch)
|
|
@@ -292,6 +295,7 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
292
295
|
heartbeat = viz.HeartbeatThread(watch, bus)
|
|
293
296
|
heartbeat.start()
|
|
294
297
|
final = None
|
|
298
|
+
step_starts: dict[str, float] = {}
|
|
295
299
|
try:
|
|
296
300
|
for part in graph.stream(initial, config,
|
|
297
301
|
stream_mode=["tasks", "updates"],
|
|
@@ -299,8 +303,13 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
299
303
|
for event in viz.translate(part):
|
|
300
304
|
if event["type"] == "node_started":
|
|
301
305
|
watch.start(event["node"])
|
|
306
|
+
step_starts[event["node"]] = time.monotonic()
|
|
302
307
|
elif event["type"] == "node_finished":
|
|
303
308
|
watch.finish(event["node"])
|
|
309
|
+
if event["node"] in step_starts:
|
|
310
|
+
event["elapsed_seconds"] = max(
|
|
311
|
+
0, int(round(time.monotonic()
|
|
312
|
+
- step_starts[event["node"]])))
|
|
304
313
|
bus.publish(event)
|
|
305
314
|
final = graph.get_state(config).values
|
|
306
315
|
finally:
|
|
@@ -317,8 +326,13 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
317
326
|
else:
|
|
318
327
|
success = False
|
|
319
328
|
attempts = 0
|
|
320
|
-
bus.publish({
|
|
321
|
-
|
|
329
|
+
bus.publish({
|
|
330
|
+
"type": "run_finished",
|
|
331
|
+
"success": success,
|
|
332
|
+
"attempts": attempts,
|
|
333
|
+
"elapsed_seconds": max(
|
|
334
|
+
0, int(round(time.monotonic() - run_start))),
|
|
335
|
+
})
|
|
322
336
|
capture.detach()
|
|
323
337
|
else:
|
|
324
338
|
final = graph.invoke(initial, config)
|
|
@@ -466,7 +466,7 @@ PAGE = """<!doctype html>
|
|
|
466
466
|
#thread code { font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
467
467
|
background: #f5f5f5; padding: 2px 6px; border-radius: 4px;
|
|
468
468
|
cursor: pointer; user-select: all; }
|
|
469
|
-
#
|
|
469
|
+
#total { font-size: 14px; color: #1976d2; display: none; }
|
|
470
470
|
#verdicts { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
471
471
|
.badge { font-size: 12px; padding: 3px 8px; border-radius: 10px;
|
|
472
472
|
background: #f0f0f0; color: #333; }
|
|
@@ -477,12 +477,15 @@ PAGE = """<!doctype html>
|
|
|
477
477
|
#stages { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex;
|
|
478
478
|
flex-direction: column; gap: 8px; }
|
|
479
479
|
.stage { border: 1px solid #e0e0e0; border-radius: 6px; overflow: hidden; }
|
|
480
|
-
.stage-header { display:
|
|
480
|
+
.stage-header { display: flex; align-items: baseline; width: 100%;
|
|
481
|
+
text-align: left; cursor: pointer;
|
|
481
482
|
font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
482
483
|
Roboto, Helvetica, Arial, sans-serif; padding: 6px 10px;
|
|
483
484
|
border: 0; background: #f7f7f7; }
|
|
484
485
|
.stage-glyph { display: inline-block; width: 1.1em; }
|
|
485
486
|
.stage-status { margin-left: 0.4em; }
|
|
487
|
+
.stage-time { margin-left: auto; color: #555;
|
|
488
|
+
font-variant-numeric: tabular-nums; }
|
|
486
489
|
.status-pending { color: #555; }
|
|
487
490
|
.status-running { color: #1976d2; }
|
|
488
491
|
.status-pass { color: #2e7d32; }
|
|
@@ -531,7 +534,7 @@ PAGE = """<!doctype html>
|
|
|
531
534
|
<h2 id="subject">agentic-dev-loop pipeline</h2>
|
|
532
535
|
<div id="attempt">attempt ?/?</div>
|
|
533
536
|
<div id="thread">thread id: <code id="thread-id" title="click to select">\u2014</code></div>
|
|
534
|
-
<div id="
|
|
537
|
+
<div id="total"></div>
|
|
535
538
|
<div id="verdicts"></div>
|
|
536
539
|
<div id="stages"></div>
|
|
537
540
|
<ul id="run-log"></ul>
|
|
@@ -554,6 +557,10 @@ PAGE = """<!doctype html>
|
|
|
554
557
|
var renderCount = 0;
|
|
555
558
|
var mermaidFailed = !!window.__mermaidFailed;
|
|
556
559
|
var pendingCompletion = null;
|
|
560
|
+
var stepTimes = {};
|
|
561
|
+
var liveElapsed = {};
|
|
562
|
+
var runStartWall = null;
|
|
563
|
+
var totalTimer = null;
|
|
557
564
|
var topologyReady = false;
|
|
558
565
|
var pendingEvents = [];
|
|
559
566
|
var MAX_CONSOLE = 500;
|
|
@@ -602,10 +609,16 @@ PAGE = """<!doctype html>
|
|
|
602
609
|
return m + ":" + (s < 10 ? "0" : "") + s;
|
|
603
610
|
}
|
|
604
611
|
|
|
605
|
-
function
|
|
606
|
-
var line = el("
|
|
607
|
-
|
|
608
|
-
|
|
612
|
+
function renderTotal() {
|
|
613
|
+
var line = el("total");
|
|
614
|
+
if (runStartWall === null) {
|
|
615
|
+
line.textContent = "";
|
|
616
|
+
line.style.display = "none";
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
line.textContent = "total " + mmss(Math.max(0,
|
|
620
|
+
Math.round(Date.now() / 1000 - runStartWall)));
|
|
621
|
+
line.style.display = "block";
|
|
609
622
|
}
|
|
610
623
|
|
|
611
624
|
function statusOf(n) {
|
|
@@ -723,6 +736,9 @@ PAGE = """<!doctype html>
|
|
|
723
736
|
header.appendChild(glyph);
|
|
724
737
|
header.appendChild(name);
|
|
725
738
|
header.appendChild(status);
|
|
739
|
+
var time = document.createElement("span");
|
|
740
|
+
time.className = "stage-time";
|
|
741
|
+
header.appendChild(time);
|
|
726
742
|
|
|
727
743
|
var console = document.createElement("pre");
|
|
728
744
|
console.className = "stage-console";
|
|
@@ -768,6 +784,10 @@ PAGE = """<!doctype html>
|
|
|
768
784
|
else if (v === "env") { suffix = " (verdict: ENV)"; }
|
|
769
785
|
}
|
|
770
786
|
status.textContent = (word ? " " + word : "") + suffix;
|
|
787
|
+
header.querySelector(".stage-time").textContent =
|
|
788
|
+
(word === "completed" && stepTimes[node]) ? mmss(stepTimes[node])
|
|
789
|
+
: (word === "running" && liveElapsed[node] !== undefined)
|
|
790
|
+
? mmss(liveElapsed[node]) : "";
|
|
771
791
|
header.className = "stage-header status-" + renderStatusOf(node);
|
|
772
792
|
}
|
|
773
793
|
|
|
@@ -1033,6 +1053,13 @@ PAGE = """<!doctype html>
|
|
|
1033
1053
|
switch (ev.type) {
|
|
1034
1054
|
case "run_started":
|
|
1035
1055
|
flushPending();
|
|
1056
|
+
stepTimes = {};
|
|
1057
|
+
liveElapsed = {};
|
|
1058
|
+
if (totalTimer !== null) { clearInterval(totalTimer); totalTimer = null; }
|
|
1059
|
+
runStartWall = (typeof ev.started_at === "number")
|
|
1060
|
+
? ev.started_at : null;
|
|
1061
|
+
if (runStartWall !== null) { totalTimer = setInterval(renderTotal, 1000); }
|
|
1062
|
+
renderTotal();
|
|
1036
1063
|
clearConsoles();
|
|
1037
1064
|
resetVerdicts();
|
|
1038
1065
|
if (ev.attempt !== undefined) {
|
|
@@ -1051,14 +1078,18 @@ PAGE = """<!doctype html>
|
|
|
1051
1078
|
if (!nodeIds.has(ev.node)) { break; }
|
|
1052
1079
|
nodeStatus[ev.node] = "running";
|
|
1053
1080
|
stageWord[ev.node] = "running";
|
|
1081
|
+
liveElapsed = {};
|
|
1082
|
+
liveElapsed[ev.node] = 0;
|
|
1054
1083
|
Array.from(nodeIds).forEach(function (n) {
|
|
1055
1084
|
if (n === ev.node) { expandStage(n); } else { collapseStage(n); }
|
|
1056
1085
|
});
|
|
1057
1086
|
renderGraph();
|
|
1058
1087
|
break;
|
|
1059
1088
|
case "node_finished":
|
|
1060
|
-
clearElapsed();
|
|
1061
1089
|
if (!nodeIds.has(ev.node)) { break; }
|
|
1090
|
+
if (ev.elapsed_seconds !== undefined) {
|
|
1091
|
+
stepTimes[ev.node] = (stepTimes[ev.node] || 0) + ev.elapsed_seconds;
|
|
1092
|
+
}
|
|
1062
1093
|
if (!fatalNodes.has(ev.node)) {
|
|
1063
1094
|
nodeStatus[ev.node] = ev.ok ? "pass" : "fail";
|
|
1064
1095
|
}
|
|
@@ -1071,9 +1102,9 @@ PAGE = """<!doctype html>
|
|
|
1071
1102
|
renderGraph();
|
|
1072
1103
|
break;
|
|
1073
1104
|
case "heartbeat":
|
|
1074
|
-
if (nodeStatus[ev.node] === "running") {
|
|
1075
|
-
|
|
1076
|
-
|
|
1105
|
+
if (nodeIds.has(ev.node) && nodeStatus[ev.node] === "running") {
|
|
1106
|
+
liveElapsed[ev.node] = ev.elapsed_seconds;
|
|
1107
|
+
renderStageHeader(ev.node);
|
|
1077
1108
|
}
|
|
1078
1109
|
break;
|
|
1079
1110
|
case "state":
|
|
@@ -1106,7 +1137,12 @@ PAGE = """<!doctype html>
|
|
|
1106
1137
|
updateAttempt();
|
|
1107
1138
|
break;
|
|
1108
1139
|
case "run_finished":
|
|
1109
|
-
|
|
1140
|
+
if (totalTimer !== null) { clearInterval(totalTimer); totalTimer = null; }
|
|
1141
|
+
if (ev.elapsed_seconds !== undefined) {
|
|
1142
|
+
runStartWall = null;
|
|
1143
|
+
el("total").textContent = "total " + mmss(ev.elapsed_seconds);
|
|
1144
|
+
el("total").style.display = "block";
|
|
1145
|
+
}
|
|
1110
1146
|
flushPending();
|
|
1111
1147
|
if (!ev.success) {
|
|
1112
1148
|
Object.keys(nodeStatus).forEach(function (n) {
|
|
@@ -7,6 +7,7 @@ everything else - files, git, checkpoints - is real."""
|
|
|
7
7
|
|
|
8
8
|
import argparse
|
|
9
9
|
import queue
|
|
10
|
+
import time
|
|
10
11
|
import sqlite3
|
|
11
12
|
import subprocess
|
|
12
13
|
from pathlib import Path
|
|
@@ -453,6 +454,25 @@ class TestVizStream:
|
|
|
453
454
|
assert events[-1]["success"] is True
|
|
454
455
|
assert events[-1]["attempts"] == 1
|
|
455
456
|
|
|
457
|
+
def test_run_events_carry_timings(self, repo, fake_agents):
|
|
458
|
+
script_all_pass(fake_agents)
|
|
459
|
+
before = time.time()
|
|
460
|
+
bus = VizBus()
|
|
461
|
+
ok, attempts = run_pipeline("pipeline.yaml", args(), viz_bus=bus)
|
|
462
|
+
after = time.time()
|
|
463
|
+
assert (ok, attempts) == (True, 1)
|
|
464
|
+
events = drain_events(bus)
|
|
465
|
+
run_started = next(e for e in events if e["type"] == "run_started")
|
|
466
|
+
assert isinstance(run_started["started_at"], (int, float))
|
|
467
|
+
assert before <= run_started["started_at"] <= after
|
|
468
|
+
run_finished = next(e for e in events if e["type"] == "run_finished")
|
|
469
|
+
assert isinstance(run_finished["elapsed_seconds"], int)
|
|
470
|
+
assert run_finished["elapsed_seconds"] >= 0
|
|
471
|
+
for event in events:
|
|
472
|
+
if event["type"] == "node_finished":
|
|
473
|
+
assert isinstance(event["elapsed_seconds"], int)
|
|
474
|
+
assert event["elapsed_seconds"] >= 0
|
|
475
|
+
|
|
456
476
|
def test_stream_path_failure_returns_same(self, repo, fake_agents):
|
|
457
477
|
fake_agents.set("check", [("write", "ci-fix.md", "VERDICT: FAIL")])
|
|
458
478
|
bus = VizBus()
|
|
@@ -764,9 +764,22 @@ class TestServer:
|
|
|
764
764
|
assert "elapsed_seconds" in PAGE
|
|
765
765
|
assert "elapsed" in PAGE
|
|
766
766
|
|
|
767
|
-
def
|
|
768
|
-
assert "
|
|
769
|
-
assert
|
|
767
|
+
def test_page_constant_has_total(self):
|
|
768
|
+
assert 'id="total"' in PAGE
|
|
769
|
+
assert "total " in PAGE
|
|
770
|
+
|
|
771
|
+
def test_page_constant_has_step_time(self):
|
|
772
|
+
assert "stepTimes" in PAGE
|
|
773
|
+
assert ".stage-time" in PAGE
|
|
774
|
+
assert "stage-time" in PAGE
|
|
775
|
+
assert "liveElapsed" in PAGE
|
|
776
|
+
|
|
777
|
+
def test_page_constant_has_loop_total(self):
|
|
778
|
+
assert "renderTotal" in PAGE
|
|
779
|
+
assert "started_at" in PAGE
|
|
780
|
+
assert "setInterval" in PAGE
|
|
781
|
+
assert "clearInterval" in PAGE
|
|
782
|
+
assert "total " in PAGE
|
|
770
783
|
|
|
771
784
|
def test_page_constant_buffers_sse_until_topology_ready(self):
|
|
772
785
|
assert "topologyReady" in PAGE
|
|
@@ -933,7 +946,7 @@ El.prototype.querySelectorAll = function (sel) {
|
|
|
933
946
|
};
|
|
934
947
|
El.prototype.scrollIntoView = function () {};
|
|
935
948
|
|
|
936
|
-
var STATIC_IDS = ["subject", "attempt", "thread-id", "
|
|
949
|
+
var STATIC_IDS = ["subject", "attempt", "thread-id", "total", "verdicts",
|
|
937
950
|
"stages", "run-log", "mermaid-container", "run-banner",
|
|
938
951
|
"divider", "sidebar"];
|
|
939
952
|
STATIC_IDS.forEach(function (id) { registry[id] = new El(id); });
|
|
@@ -961,6 +974,7 @@ var esInstance = null;
|
|
|
961
974
|
globalThis.document = document;
|
|
962
975
|
globalThis.window = window;
|
|
963
976
|
var copiedText = null;
|
|
977
|
+
var intervalFn = null;
|
|
964
978
|
// node 21+ ships a read-only `navigator` global, so defineProperty is needed
|
|
965
979
|
Object.defineProperty(globalThis, "navigator", {
|
|
966
980
|
configurable: true,
|
|
@@ -990,6 +1004,10 @@ globalThis.fetch = function () {
|
|
|
990
1004
|
};
|
|
991
1005
|
globalThis.requestAnimationFrame = function (fn) { fn(); };
|
|
992
1006
|
globalThis.setTimeout = function () { return 0; };
|
|
1007
|
+
globalThis.setInterval = function (fn) { intervalFn = fn; return 1; };
|
|
1008
|
+
globalThis.clearInterval = function () {};
|
|
1009
|
+
var fakeNow = 0;
|
|
1010
|
+
Date.now = function () { return fakeNow; };
|
|
993
1011
|
globalThis.EventSource = function (url) {
|
|
994
1012
|
this.url = url;
|
|
995
1013
|
this.onmessage = null;
|
|
@@ -1012,15 +1030,37 @@ function nodeClasses() {
|
|
|
1012
1030
|
}
|
|
1013
1031
|
function fire(ev) { esInstance.onmessage({ data: JSON.stringify(ev) }); }
|
|
1014
1032
|
|
|
1033
|
+
function stageTimes() {
|
|
1034
|
+
var out = {};
|
|
1035
|
+
["dev", "test", "review"].forEach(function (n) {
|
|
1036
|
+
var section = document.getElementById("stage-" + n);
|
|
1037
|
+
var header = section.querySelector(".stage-header");
|
|
1038
|
+
var t = header.querySelector(".stage-time");
|
|
1039
|
+
out[n] = t ? t.textContent : null;
|
|
1040
|
+
});
|
|
1041
|
+
return out;
|
|
1042
|
+
}
|
|
1043
|
+
function totalText() {
|
|
1044
|
+
var total = document.getElementById("total");
|
|
1045
|
+
return { text: total.textContent, display: total.style.display };
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1015
1048
|
var snapshots = [];
|
|
1016
1049
|
async function main() {
|
|
1017
1050
|
// let the topology fetch microtask chain resolve so events are handled
|
|
1018
1051
|
// live (synchronously inside es.onmessage) instead of buffered
|
|
1019
1052
|
await new Promise(function (res) { setImmediate(res); });
|
|
1020
1053
|
EVENTS.forEach(function (ev) {
|
|
1054
|
+
if (ev.type === "__tick") {
|
|
1055
|
+
fakeNow = ev.now;
|
|
1056
|
+
if (intervalFn) { intervalFn(); }
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1021
1059
|
fire(ev);
|
|
1022
1060
|
if (ev.type === "state") {
|
|
1023
|
-
snapshots.push({ ev: ev, badges: badges(), classes: nodeClasses()
|
|
1061
|
+
snapshots.push({ ev: ev, badges: badges(), classes: nodeClasses(),
|
|
1062
|
+
times: stageTimes(), total: totalText(),
|
|
1063
|
+
timer: intervalFn !== null });
|
|
1024
1064
|
}
|
|
1025
1065
|
});
|
|
1026
1066
|
// exercise the copy button on the dev console
|
|
@@ -1029,7 +1069,9 @@ async function main() {
|
|
|
1029
1069
|
copyBtn.listeners["click"]();
|
|
1030
1070
|
await new Promise(function (res) { setImmediate(res); });
|
|
1031
1071
|
snapshots.push({ copy: { text: copiedText,
|
|
1032
|
-
title: copyBtn.getAttribute("title") }
|
|
1072
|
+
title: copyBtn.getAttribute("title") },
|
|
1073
|
+
times: stageTimes(), total: totalText(),
|
|
1074
|
+
timer: intervalFn !== null });
|
|
1033
1075
|
console.log(JSON.stringify(snapshots));
|
|
1034
1076
|
}
|
|
1035
1077
|
main();
|
|
@@ -1167,6 +1209,71 @@ main();
|
|
|
1167
1209
|
assert snaps[-1]["copy"]["title"] == "Nothing to copy"
|
|
1168
1210
|
|
|
1169
1211
|
|
|
1212
|
+
def test_step_header_shows_accumulated_time(self, node, tmp_path):
|
|
1213
|
+
events = [
|
|
1214
|
+
{"type": "run_started", "subject": "s", "max_attempts": 5,
|
|
1215
|
+
"attempt": 1, "thread_id": "t", "started_at": 1000},
|
|
1216
|
+
{"type": "node_started", "node": "dev"},
|
|
1217
|
+
{"type": "node_finished", "node": "dev", "ok": True,
|
|
1218
|
+
"elapsed_seconds": 135},
|
|
1219
|
+
{"type": "state", "attempt": 1, "failed": False},
|
|
1220
|
+
{"type": "node_finished", "node": "dev", "ok": True,
|
|
1221
|
+
"elapsed_seconds": 45},
|
|
1222
|
+
{"type": "state", "attempt": 1, "failed": False},
|
|
1223
|
+
]
|
|
1224
|
+
snaps = self._run_page(node, events, tmp_path)
|
|
1225
|
+
|
|
1226
|
+
assert snaps[-1]["times"]["dev"] == "3:00"
|
|
1227
|
+
assert snaps[-1]["times"]["test"] == ""
|
|
1228
|
+
assert snaps[-1]["times"]["review"] == ""
|
|
1229
|
+
|
|
1230
|
+
def test_running_stage_shows_live_heartbeat_time(self, node, tmp_path):
|
|
1231
|
+
events = [
|
|
1232
|
+
{"type": "run_started", "subject": "s", "max_attempts": 5,
|
|
1233
|
+
"attempt": 1, "thread_id": "t", "started_at": 1000},
|
|
1234
|
+
{"type": "node_started", "node": "dev"},
|
|
1235
|
+
{"type": "heartbeat", "node": "dev", "elapsed_seconds": 27},
|
|
1236
|
+
{"type": "state", "attempt": 1, "failed": False},
|
|
1237
|
+
{"type": "node_finished", "node": "dev", "ok": True,
|
|
1238
|
+
"elapsed_seconds": 28},
|
|
1239
|
+
{"type": "state", "attempt": 1, "failed": False},
|
|
1240
|
+
]
|
|
1241
|
+
snaps = self._run_page(node, events, tmp_path)
|
|
1242
|
+
|
|
1243
|
+
assert snaps[0]["times"]["dev"] == "0:27"
|
|
1244
|
+
assert snaps[1]["times"]["dev"] == "0:28"
|
|
1245
|
+
|
|
1246
|
+
def test_loop_total_ticks_then_freezes(self, node, tmp_path):
|
|
1247
|
+
events = [
|
|
1248
|
+
{"type": "run_started", "subject": "s", "max_attempts": 5,
|
|
1249
|
+
"attempt": 1, "thread_id": "t", "started_at": 1000},
|
|
1250
|
+
{"type": "__tick", "now": 1065000},
|
|
1251
|
+
{"type": "state", "attempt": 1, "failed": False},
|
|
1252
|
+
{"type": "run_finished", "success": True, "attempts": 1,
|
|
1253
|
+
"elapsed_seconds": 200},
|
|
1254
|
+
]
|
|
1255
|
+
snaps = self._run_page(node, events, tmp_path)
|
|
1256
|
+
|
|
1257
|
+
assert snaps[0]["total"]["text"] == "total 1:05"
|
|
1258
|
+
assert snaps[0]["total"]["display"] == "block"
|
|
1259
|
+
assert snaps[-1]["total"]["text"] == "total 3:20"
|
|
1260
|
+
|
|
1261
|
+
def test_loop_total_fallback_without_started_at(self, node, tmp_path):
|
|
1262
|
+
events = [
|
|
1263
|
+
{"type": "run_started", "subject": "s", "max_attempts": 5,
|
|
1264
|
+
"attempt": 1, "thread_id": "t"},
|
|
1265
|
+
{"type": "state", "attempt": 1, "failed": False},
|
|
1266
|
+
{"type": "run_finished", "success": True, "attempts": 1,
|
|
1267
|
+
"elapsed_seconds": 95},
|
|
1268
|
+
]
|
|
1269
|
+
snaps = self._run_page(node, events, tmp_path)
|
|
1270
|
+
|
|
1271
|
+
assert snaps[0]["timer"] is False
|
|
1272
|
+
assert snaps[0]["total"] == {"text": "", "display": "none"}
|
|
1273
|
+
assert snaps[-1]["total"]["text"] == "total 1:35"
|
|
1274
|
+
assert snaps[-1]["total"]["display"] == "block"
|
|
1275
|
+
|
|
1276
|
+
|
|
1170
1277
|
class TestDemo:
|
|
1171
1278
|
def test_run_started_carries_attempt(self, monkeypatch):
|
|
1172
1279
|
import viz_demo
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/results.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/core/verdict.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/allowlist.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/brief_prompt.md
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/brief_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/containers.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/egress_policy.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/egress_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/forbidden.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/hostboundary.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/loaders.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/mounts.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/presets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/secrets.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/engine/sync_back.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/viz/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop/viz/__main__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/src/agentic_dev_loop.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a50 → agentic_dev_loop-1.0.0a51}/tests/test_hostboundary_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|