Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cargento/skills/cargento/cargento_runtime/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ class HarnessSpec:
reports_rate: bool = False
reports_needs_input: bool = False
reports_needs_input_when: str | None = None
reports_turn_bounds: bool = True
usage: UsageProvider | None = None
usage_is_fetch: bool = False

Expand Down Expand Up @@ -415,6 +416,7 @@ def default_harnesses(*, usage_fetch_enabled: bool = True) -> tuple[HarnessSpec,
# feeds into it, so a wait posted from there would paint every Cursor
# row on its first command.
reports_needs_input=True,
reports_turn_bounds=False,
usage=cursor.usage if usage_fetch_enabled else None,
usage_is_fetch=True,
),
Expand Down Expand Up @@ -493,6 +495,11 @@ def _harness_row(spec: HarnessSpec, *, found: bool) -> dict[str, Any]:
# every row whose gate mechanism cannot be turned off, which is all but
# one; the page cannot derive either half.
"reports_needs_input_when": spec.reports_needs_input_when,
# Whether this harness can report turn bounds at all. Stated per
# harness because it is a property of the store and its collector;
# a non-working row distinguishes "no turn in progress" from a harness
# that never reports them (DRC-4548).
"reports_turn_bounds": spec.reports_turn_bounds,
"error": None,
}

Expand Down
12 changes: 11 additions & 1 deletion cargento/skills/cargento/cargento_runtime/web/next-observed.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ function nextObservedSession(source, asks, harness, generated, shared){
const turnElapsed = nextObservedString(turn.elapsed_h);
const turnEta = nextObservedString(turn.eta_h);
const turnText = turnElapsed ? `${turnElapsed} into turn` + (turnEta ? ` · ${turnEta} estimated remaining` : "") : "";
const turnReporter = Boolean(
harness && !harness.error && (
typeof harness.reports_turn_bounds === "boolean"
? harness.reports_turn_bounds
: (harness.key !== "cursor" && source.harness !== "cursor")
)
);
const turnReason = turnReporter
? (working ? "Turn bounds not published" : "No turn in progress")
: "Harness does not report turn bounds";
return {
sid: String(source.sid == null ? "" : source.sid),
harness: String(source.harness == null ? "" : source.harness),
Expand All @@ -146,7 +156,7 @@ function nextObservedSession(source, asks, harness, generated, shared){
stateKnown ? "Activity not published" : "No state published"),
...nextObservedPair("next", pending && pending.subject, "No pending step published"),
...nextObservedPair("where", "", "Exact location not published"),
...nextObservedPair("turn", turnText, "Harness does not report turn bounds"),
...nextObservedPair("turn", turnText, turnReason),
blockText: blocked ? "Waiting on you" : (reporter ? "No reported block" :
(gaps.includes("block state") ? "Block state could not be read" : "Harness does not report blocks")),
blockKnown,
Expand Down
2 changes: 1 addition & 1 deletion cargento/skills/cargento/tests/test_focus.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ def test_the_pinned_assembly_is_untouched(self) -> None:
# would fail on the reader rather than on an injected token.
self.assertNotIn(b'<meta name="cargento-focus"', assembled)
self.assertEqual(
"566decf4aed14c56703dc55679e3bcaf53c5a539608d297568508f2f7ad3532a",
"b6de0b2823cf47137f1cb901d891a490d4963574975269a95f87356f6131da05",
hashlib.sha256(assembled).hexdigest(),
)

Expand Down
8 changes: 8 additions & 0 deletions cargento/skills/cargento/tests/test_harness_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,14 @@ def test_only_the_six_harnesses_with_a_gate_path_declare_one(self) -> None:
{spec.key for spec in REGISTRY if spec.reports_needs_input},
)

def test_only_cursor_declares_no_turn_bounds_reporting(self) -> None:
# DRC-4548. Every harness collector except Cursor calls `turns.turn_progress`
# and reports turn bounds. Cursor records no turn bounds in its store.
self.assertEqual(
{"cursor"},
{spec.key for spec in REGISTRY if not spec.reports_turn_bounds},
)

def test_the_gate_flag_matches_the_harnesses_that_actually_have_a_path(self) -> None:
# The check that would have caught the defect this test was written for.
# `reports_needs_input` is a hand-set bool, and the first review of the
Expand Down
4 changes: 2 additions & 2 deletions cargento/skills/cargento/tests/test_next_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ def test_retired_next_query_is_not_a_page_alias(self) -> None:
def test_the_canonical_loader_is_the_released_ui_bundle(self) -> None:
page = frontend_page.load_page()

self.assertEqual(910_488, len(page))
self.assertEqual(910_849, len(page))
self.assertEqual(
"566decf4aed14c56703dc55679e3bcaf53c5a539608d297568508f2f7ad3532a",
"b6de0b2823cf47137f1cb901d891a490d4963574975269a95f87356f6131da05",
hashlib.sha256(page).hexdigest(),
)

Expand Down
45 changes: 45 additions & 0 deletions cargento/skills/cargento/tests/test_next_observed.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,3 +870,48 @@ def test_repeating_wait_unconfirmed_note_in_observed_block(self) -> None:
"Unconfirmed: no positive observation in 5m; prompt may still be standing",
out["unconfirmedNote"],
)

def test_non_working_row_distinguishes_no_turn_in_progress_from_unsupported_harness(
self,
) -> None:
# DRC-4548. A non-working row (idle or needs_input) on a harness that reports
# turn bounds renders "No turn in progress", whereas a harness that never
# reports turn bounds (e.g. Cursor) renders "Harness does not report turn bounds".
out = self._run_page_js(
"""
const obs = nextObserved({
harnesses: [
{key: "claude", reports_turn_bounds: true},
{key: "cursor", reports_turn_bounds: false}
],
sessions: [
{harness: "claude", sid: "c-idle", project: "repo", state: "idle"},
{harness: "claude", sid: "c-needs", project: "repo", state: "needs_input"},
{harness: "cursor", sid: "cur-idle", project: "repo", state: "idle"},
{harness: "cursor", sid: "cur-work", project: "repo", state: "working"}
]
});
const bySid = Object.fromEntries(obs.sessions.map(s => [s.sid, {
turnText: s.turnText,
turnKnown: s.turnKnown
}]));
console.log(JSON.stringify(bySid));
"""
)
assert isinstance(out, dict)
self.assertEqual(
{"turnText": "No turn in progress", "turnKnown": False},
out["c-idle"],
)
self.assertEqual(
{"turnText": "No turn in progress", "turnKnown": False},
out["c-needs"],
)
self.assertEqual(
{"turnText": "Harness does not report turn bounds", "turnKnown": False},
out["cur-idle"],
)
self.assertEqual(
{"turnText": "Harness does not report turn bounds", "turnKnown": False},
out["cur-work"],
)
8 changes: 4 additions & 4 deletions cargento/skills/cargento/tests/test_next_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,8 @@ def test_load_page_preserves_its_byte_oracles(self) -> None:
"b71d627fe8cc06bc4210d23c76c0ee5b2ab644ce15a44c9d617ba66fe398847d",
),
"next-observed.js": (
30_697,
"7e2716980a691f0a0f3a1a3f979d78cc333b09deaa5d3adbacf992a1f75662fe",
31_058,
"c4f46faa78327849dbc1dd2aa410cc53b59cda482d87c00b3ab3cb6f1f79f9e0",
),
"next-attention.js": (
56_558,
Expand Down Expand Up @@ -707,9 +707,9 @@ def test_load_page_preserves_its_byte_oracles(self) -> None:
)

assembled = frontend_page.load_page()
self.assertEqual(910_488, len(assembled))
self.assertEqual(910_849, len(assembled))
self.assertEqual(
"566decf4aed14c56703dc55679e3bcaf53c5a539608d297568508f2f7ad3532a",
"b6de0b2823cf47137f1cb901d891a490d4963574975269a95f87356f6131da05",
hashlib.sha256(assembled).hexdigest(),
)

Expand Down
35 changes: 35 additions & 0 deletions cargento/skills/cargento/tests/test_next_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,41 @@ def test_absent_facts_are_reasons_and_never_placeholder_values(self) -> None:
self.assertNotIn("—", html)
self.assertNotRegex(html, r">\s*0\s*<")

def test_turn_bounds_absence_distinguishes_no_turn_in_progress_from_unsupported_harness(
self,
) -> None:
# DRC-4548. On non-working rows, a turn-reporting harness renders "No turn in progress",
# whereas a harness that does not report turn bounds renders "Harness does not report turn bounds".
out = self._run_page_js("""
__els.app = {innerHTML: ""};
nextData = {
generated: 10000,
harnesses: [
{key: "claude", label: "Claude Code", reports_turn_bounds: true},
{key: "cursor", label: "Cursor", reports_turn_bounds: false}
],
sessions: [
{harness: "claude", sid: "claude-idle", project: "repo", state: "idle"},
{harness: "claude", sid: "claude-needs", project: "repo", state: "needs_input"},
{harness: "cursor", sid: "cursor-idle", project: "repo", state: "idle"}
]
};
const results = {};
for(const sid of ["claude-idle", "claude-needs", "cursor-idle"]){
const s = nextData.sessions.find(row => row.sid === sid);
nextRoute = {view: "session", project: "repo", harness: s.harness, session: sid};
renderNext();
results[sid] = __els.app.innerHTML;
}
console.log(JSON.stringify(results));
""")
assert isinstance(out, dict)
self.assertIn('class="next-session-absent">No turn in progress</', out["claude-idle"])
self.assertIn('class="next-session-absent">No turn in progress</', out["claude-needs"])
self.assertIn(
'class="next-session-absent">Harness does not report turn bounds</', out["cursor-idle"]
)

def test_raise_is_only_offered_to_a_waiting_reachable_session_with_capability(self) -> None:
out = self.render("""
const query = document.querySelector;
Expand Down