feat(webui): add project workspaces and access controls (#4007)
* feat(webui): add project workspaces and access controls * feat(webui): add project workspaces and access controls * refactor(tools): centralize workspace access resolution * refactor(webui): remove unused workspace host state * fix(webui): hide estimated file edit label * fix(webui): clarify file edit deletion feedback * fix(webui): label deleted file activity * fix(webui): flatten file edit activity rows * fix(core): remove path-only patch deletion * fix(core): keep apply patch non-destructive * refactor(webui): trim workspace host plumbing * fix(tools): register exec with tools config
This commit is contained in:
@@ -299,6 +299,7 @@ def build_file_edit_end_event(
|
||||
deleted=deleted,
|
||||
approximate=False,
|
||||
binary=(after.binary or after.oversized or after.unreadable) and not counted,
|
||||
operation="delete" if tracker.before.exists and not after.exists else None,
|
||||
)
|
||||
|
||||
|
||||
@@ -324,6 +325,7 @@ def build_file_edit_live_event(
|
||||
*,
|
||||
added: int,
|
||||
deleted: int = 0,
|
||||
operation: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build an approximate in-progress event while tool-call arguments stream."""
|
||||
return _event_payload(
|
||||
@@ -333,6 +335,7 @@ def build_file_edit_live_event(
|
||||
added=added,
|
||||
deleted=deleted,
|
||||
approximate=True,
|
||||
operation=operation,
|
||||
)
|
||||
|
||||
|
||||
@@ -454,15 +457,14 @@ class StreamingFileEditTracker:
|
||||
segment_end = path_matches[i + 1].start() if i + 1 < len(path_matches) else len(state.arguments)
|
||||
segment = state.arguments[segment_start:segment_end]
|
||||
|
||||
action_match = re.search(r'"action"\s*:\s*"(replace|add|delete)"', segment)
|
||||
action_match = re.search(r'"action"\s*:\s*"(replace|add)"', segment)
|
||||
action = action_match.group(1) if action_match else "replace"
|
||||
|
||||
old_text = _extract_json_string_prefix(segment, "old_text") or ""
|
||||
new_text = _extract_json_string_prefix(segment, "new_text") or ""
|
||||
|
||||
added = _text_line_count(new_text) if action in ("replace", "add") else 0
|
||||
deleted = _text_line_count(old_text) if action in ("replace", "delete") else 0
|
||||
delete_file = action == "delete"
|
||||
deleted = _text_line_count(old_text) if action == "replace" else 0
|
||||
|
||||
file_state = state.patch_files.get(raw_path)
|
||||
if file_state is None:
|
||||
@@ -475,8 +477,6 @@ class StreamingFileEditTracker:
|
||||
)
|
||||
file_state = _StreamingPatchFileState(tracker=tracker)
|
||||
state.patch_files[raw_path] = file_state
|
||||
if delete_file and added == 0 and deleted == 0 and file_state.tracker.before.countable:
|
||||
deleted = _text_line_count(file_state.tracker.before.text or "")
|
||||
if not file_state.should_emit(added, deleted, now):
|
||||
continue
|
||||
file_state.mark_emitted(added, deleted, now)
|
||||
@@ -916,6 +916,7 @@ def _event_payload(
|
||||
deleted: int,
|
||||
approximate: bool,
|
||||
binary: bool = False,
|
||||
operation: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
payload: dict[str, Any] = {
|
||||
"version": 1,
|
||||
@@ -931,6 +932,8 @@ def _event_payload(
|
||||
}
|
||||
if binary:
|
||||
payload["binary"] = True
|
||||
if operation:
|
||||
payload["operation"] = operation
|
||||
return payload
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user