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
8 changes: 6 additions & 2 deletions agentic_harness/core/verifier_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,10 +413,10 @@ def _add_globs(root: Path, candidates: set[Path], patterns: tuple[str, ...]) ->
(?:
\btest
|
\b(?:getByName|named)(?:<[^>]+>)?\(\s*["']test["']\s*\)(?:\.\s*configure)?
\b(?:getByName|named)(?:<[^>]+>)?\(\s*["']test["']\s*\)
)
(?:\s*\.\s*get\s*\(\s*\))?
(?:\s*\.\s*(?:configure|apply))?
(?:\.\s*(?P<method>[A-Za-z_][A-Za-z0-9_]*)\s*)?
\s*\{
"""
)
Expand Down Expand Up @@ -577,6 +577,7 @@ def _gradle_test_roots(root: Path, *, allow_dynamic: bool) -> set[Path]:
expressions: list[str] = []
unresolved_source_root = False
for match in _GRADLE_TEST_BLOCK.finditer(text):
method = match.group("method")
block = _balanced_brace_body(text, match.end() - 1)
source_matches = list(_GRADLE_SOURCE_ROOT_CALL.finditer(block))
expressions.extend(
Expand All @@ -586,6 +587,9 @@ def _gradle_test_roots(root: Path, *, allow_dynamic: bool) -> set[Path]:
unresolved_source_root = unresolved_source_root or (
len(_GRADLE_SOURCE_ROOT_NAME.findall(block)) != len(source_matches)
)
unresolved_source_root = unresolved_source_root or (
method is not None and method not in {"configure", "apply"}
)
for item in _GRADLE_DIRECT_TEST_CONFIG.finditer(text):
direct_config = item.group(0)
source_matches = list(_GRADLE_SOURCE_ROOT_CALL.finditer(direct_config))
Expand Down
47 changes: 40 additions & 7 deletions agentic_harness/gui/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1166,11 +1166,19 @@ function renderRecovery() {
const task = state.liveTask || state.currentTask || {};
const blockingForegroundReview = isBlockingForegroundReview(task);
const visibleState = blockingForegroundReview ? "needs_review" : readinessState;
const setupStates = [
"setup_required",
"credential_required",
"verification_required",
"connection_test_required",
];
const needsSetup = setupStates.includes(readinessState);
const canContinue = hasAction(task, "continue");
const canStop = hasAction(task, "stop");
const hasTask = Boolean(task?.id);
const show = blockingForegroundReview || state.readiness?.can_start === false
&& ["needs_review", "needs_attention", "blocked", "configuration_error"].includes(readinessState);
&& ["needs_review", "needs_attention", "blocked", "configuration_error", ...setupStates].includes(readinessState);
const canOpenSettings = needsSetup || readinessState === "configuration_error";

els.recoveryCard.hidden = !show;
if (!show) {
Expand All @@ -1186,6 +1194,8 @@ function renderRecovery() {
? guide.title || "Your result is ready"
: visibleState === "configuration_error"
? "Configuration needs repair"
: needsSetup
? "Setup needed before continuing"
: "Current task needs attention";
els.recoverySummary.textContent = visibleState === "needs_review"
? guide.explanation || "The assistant finished and stopped safely for your review."
Expand All @@ -1200,21 +1210,42 @@ function renderRecovery() {
: "Stop task";
els.recoveryOpenTaskButton.textContent = visibleState === "needs_review"
? "Review result"
: "Open current task";
: canOpenSettings
? "Open settings"
: "Open current task";
els.recoveryContinueButton.hidden = !hasTask || !canContinue;
els.recoveryStopButton.hidden = !hasTask || !canStop;
els.recoveryOpenTaskButton.hidden = !hasTask;
els.recoveryOpenTaskButton.hidden = !hasTask && !canOpenSettings;
els.recoveryStatus.textContent = hasTask
? canContinue || canStop
? "Use a button here, or open the task to review its details and result."
: "Open the current task to see the exact blocker and available decision."
: "Refresh once. If this remains blocked, the installation owner must repair the configuration.";
: canOpenSettings
? "Open Settings to resolve this setup step, then return to this screen."
: "Open the current task to see the exact blocker and available decision."
: canOpenSettings
? "Open Settings, complete this setup step, then come back."
: "Refresh once. If this remains blocked, the installation owner must repair the configuration.";
}

function openCurrentTaskFromRecovery() {
const readinessState = String(state.readiness?.state || "");
if (
[
"setup_required",
"credential_required",
"verification_required",
"connection_test_required",
"configuration_error",
].includes(readinessState)
) {
showView("settings", { focus: true });
return;
}
if (state.liveTask) {
state.viewingHistoryId = "";
renderTask(state.liveTask);
showView("tasks", { focus: true });
return;
}
showView("tasks", { focus: true });
}
Expand Down Expand Up @@ -2022,9 +2053,11 @@ function updateDemoCallout(task = null) {
}
els.demoButton.disabled = state.busy || active;
els.setupDemoButton.disabled = state.busy || active;
els.demoSetupButton.hidden = managedOverlay ? !isDemo || active : false;
els.demoSetupButton.hidden = active;
els.demoSetupButton.textContent = managedOverlay
? "Return to real workspace"
? isDemo
? "Return to real workspace"
: "Open settings"
: "Connect real work";
els.setupDemoButton.textContent = verified
? "Run safe demo again"
Expand Down
37 changes: 37 additions & 0 deletions tests/frontend_token_race_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,42 @@ async function testManagedExpectationStaysPlainAndMobileGetsUnavailableReasons()
assert.equal(newRouteCard.focusCount, 1);
}

async function testSetupRequiredStateShowsRecoverySettingsShortcut() {
const app = await runApp({
publicAccess: true,
setupPayload: {
contract: "agentic_harness.gui_setup.v1",
configured: false,
editable: true,
workspace: "/tmp/new-project",
suggested_check: "npm test",
},
healthPayload: {
ok: true,
readiness: {
state: "setup_required",
can_start: false,
summary: "Choose an AI connection.",
next_action: "Open Settings and connect an assistant before starting real work.",
},
},
taskPayload: {
status: "ready",
status_label: "Ready",
result_category: "in_progress",
summary: "Backend ready",
progress: { determinate: false, percent: null, label: "" },
},
});

assert.equal(app.elements.get("recoveryCard").hidden, false);
assert.equal(app.elements.get("recoveryOpenTaskButton").hidden, false);
assert.equal(app.elements.get("recoveryOpenTaskButton").textContent, "Open settings");
app.elements.get("recoveryOpenTaskButton").listeners.click();
await tick();
assert.equal(app.elements.get("settingsView").hidden, false);
}

async function testResetNeverSilentlyAppliesACloudDefaultRoute() {
const setupPayload = {
contract: "agentic_harness.gui_setup.v1",
Expand Down Expand Up @@ -1921,6 +1957,7 @@ async function testLostStartResponseReconnectsToTheAcceptedTask() {
await testLegacyHumanCanChooseEveryModeWithoutWritingACommand();
await testPredictableViewsConciseModesAndAccessSummaryKeepDraftState();
await testExpectationReflectsSetupReadinessInsteadOfClaimingReady();
await testSetupRequiredStateShowsRecoverySettingsShortcut();
await testExpectationUsesPlainDynamicFactsAndCardSelectionKeepsFocus();
await testManagedExpectationStaysPlainAndMobileGetsUnavailableReasons();
await testResetNeverSilentlyAppliesACloudDefaultRoute();
Expand Down
23 changes: 23 additions & 0 deletions tests/test_verified_tournament.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,29 @@ def test_unrecognized_gradle_test_alias_closure_fails_closed(tmp_path: Path) ->
tournament_module._freeze_verifier_assets(root, [["gradle", "test"]])


def test_sourceSets_named_with_convention_fails_closed(tmp_path: Path) -> None:
root, _ = _project(tmp_path)
(root / "verification").mkdir()
(root / "build.gradle.kts").write_text(
(
'plugins { java }\n'
'sourceSets.named("test").withConvention {\n'
' java.srcDir("verification")\n'
"}\n"
),
encoding="utf-8",
)
(root / "verification" / "BoundaryTest.java").write_text(
"class BoundaryTest {}\n",
encoding="utf-8",
)
_git(root, "add", ".")
_git(root, "commit", "-m", "add unsupported direct sourceSets closure")

with pytest.raises(ConfigError, match="cannot infer a Gradle test source root syntax"):
tournament_module._freeze_verifier_assets(root, [["gradle", "test"]])


@pytest.mark.parametrize(
"alias_expression",
[
Expand Down