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
29 changes: 29 additions & 0 deletions src/main/java/dev/talos/runtime/toolcall/ToolSurfacePlanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ && verifyOnlyDirectoryAwarePathCheck(contract)) {
descriptor -> isFileReadTool(descriptor) || isDirectoryListingTool(descriptor),
"verify-only path check with directory targets");
}
if (contract != null
&& !contract.mutationAllowed()
&& readOnlyPathExistenceCheck(contract)) {
return select(
registry,
descriptor -> isFileReadTool(descriptor) || isDirectoryListingTool(descriptor),
"read-only path existence surface");
}
if (contract != null
&& !contract.mutationAllowed()
&& !contract.expectedTargets().isEmpty()) {
Expand Down Expand Up @@ -109,6 +117,10 @@ public static List<String> defaultVisibleToolNames(TaskContract contract, Execut
&& verifyOnlyDirectoryAwarePathCheck(contract)) {
return List.of("talos.list_dir", "talos.read_file");
}
if (!contract.mutationAllowed()
&& readOnlyPathExistenceCheck(contract)) {
return List.of("talos.list_dir", "talos.read_file");
}
if (contract.mutationAllowed() && phase == ExecutionPhase.APPLY) {
var workspaceOperation = WorkspaceOperationIntent.detect(contract);
if (workspaceOperation.isPresent() && !requiresFileWriteForExactExpectation(contract)) {
Expand Down Expand Up @@ -286,6 +298,23 @@ private static boolean verifyOnlyDirectoryAwarePathCheck(TaskContract contract)
return mentionsDirectory && asksPathStatus;
}

private static boolean readOnlyPathExistenceCheck(TaskContract contract) {
if (contract == null || contract.mutationAllowed() || contract.expectedTargets().isEmpty()) {
return false;
}
String request = contract.originalUserRequest();
if (request == null || request.isBlank()) return false;
String lower = request.toLowerCase(Locale.ROOT);
boolean asksExistence = lower.contains("exists")
|| lower.contains("exist")
|| lower.contains("present")
|| lower.contains("is there")
|| lower.contains("are there");
Comment on lines +308 to +312

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep “present” from widening ordinary read prompts

For prompts like “Present README.md as bullet points,” TaskContractResolver extracts README.md as an expected target and this substring check treats the imperative verb “present” as an existence question. That changes the surface from the narrow expected target read path to talos.list_dir + talos.read_file, allowing directory enumeration even though the user only asked to format/read a named file. Please restrict this to status phrasing such as is/are present rather than any occurrence of present.

Useful? React with 👍 / 👎.

boolean asksPathStatus = lower.contains("path")
&& (lower.contains("check") || lower.contains("verify") || lower.contains("whether"));
return asksExistence || asksPathStatus;
}

private static boolean containsExtensionlessSlashPath(String request) {
if (request == null || request.isBlank()) return false;
Matcher matcher = SLASH_PATH_CANDIDATE.matcher(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,25 @@ void namedReadTargetSurfaceUsesFileTargetMetadataForProtectedAndPublicReads() {
}
}

@Test
void fileExistenceQuestionsExposeDirectoryAndFileReadEvidenceTools() {
var contract = TaskContractResolver.fromUserRequest(
"Check whether scripts.js exists and whether script.js exists. Do not change anything.");

ToolSurfacePlanner.Plan plan = ToolSurfacePlanner.plan(contract, ExecutionPhase.INSPECT, registry());

List<String> names = plan.nativeToolNames();
assertEquals("read-only path existence surface", plan.reason());
assertTrue(names.contains("talos.list_dir"), names.toString());
assertTrue(names.contains("talos.read_file"), names.toString());
assertFalse(names.contains("talos.write_file"), names.toString());
assertFalse(names.contains("talos.edit_file"), names.toString());
assertFalse(names.contains("talos.run_command"), names.toString());
assertEquals(
List.of("talos.list_dir", "talos.read_file"),
ToolSurfacePlanner.defaultVisibleToolNames(contract, ExecutionPhase.INSPECT));
}

@Test
void verifyOnlyMixedFileAndDirectoryPathChecksExposeReadFileAndListDirOnly() {
var contract = TaskContractResolver.fromUserRequest(
Expand Down