-
Notifications
You must be signed in to change notification settings - Fork 0
feat(discovery): early "detected tools" pre-report for snappy live UI #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Changes from all commits
534dbf9
0069f6d
b5ad81a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1747,6 +1747,12 @@ def time_step(name: str, phase: str) -> Iterator[None]: | |
| logger.info("Detecting AI tools...") | ||
| all_tools = [] # Store all unique tools across all users | ||
| tools_by_user = {} # Track which tools belong to which user | ||
| # Per-user tool-key sets, used to scope the early detected-tools | ||
| # preview report below to the tools each user actually has. Review | ||
| # feedback (#130): the previous version sent the device-wide union | ||
| # to every user, creating phantom tiles for tools that user didn't | ||
| # own (e.g. Alice gets a Cursor tile because Bob has Cursor). | ||
| tool_keys_by_user = {u: set() for u in all_users} | ||
|
|
||
| for user in all_users: | ||
| if platform.system() == "Darwin": | ||
|
|
@@ -1769,6 +1775,7 @@ def time_step(name: str, phase: str) -> Iterator[None]: | |
|
|
||
| # Track which user this tool belongs to | ||
| tool_key = f"{tool_name}:{tool_path}" | ||
| tool_keys_by_user[user].add(tool_key) | ||
| if tool_key not in tools_by_user: | ||
| tools_by_user[tool_key] = tool | ||
| all_tools.append(tool) | ||
|
|
@@ -1780,6 +1787,76 @@ def time_step(name: str, phase: str) -> Iterator[None]: | |
| logger.info(f"Detection complete: {len(tools)} unique tool(s) found across all users") | ||
| logger.info("") | ||
|
|
||
| # --- Early "detected" pre-report (snappy live UI) ------------------- | ||
| # Every tool is already known here, BEFORE the slow per-tool | ||
| # extraction (rules / MCP / skills / projects). Send a lightweight | ||
| # tools-only report (no projects) per user up front so the dashboard | ||
| # shows every agent tile within seconds of scan start, instead of one | ||
| # tile every ~tool-extraction. The full per-tool reports below | ||
| # supersede these (the backend keys installs by | ||
| # device + tool + home_user, retire-then-recreate), so this only | ||
| # changes *when* a tile first appears, not the final data. | ||
| if tools: | ||
| logger.info("Sending early detected-tools report (for live UI)...") | ||
| # Build preview stubs keyed by tool_key so we can scope to per-user | ||
| # attribution below. Phantom-tile fix (#130 review): a user only | ||
| # receives stubs for tools their OWN detection pass found, never | ||
| # the device-wide union. | ||
| # | ||
| # IMPORTANT: the preview must OMIT the `projects` key entirely (not | ||
| # set it to []). The server uses key-absence as the only | ||
| # unambiguous "this is a preview" signal — a full per-tool report | ||
| # from a user with zero projects legitimately sends `projects: []`, | ||
| # so an explicit empty list here would be indistinguishable from a | ||
| # real zero-projects report and the server's short-circuit would | ||
| # misroute it, leaving stale projects on returning users. | ||
| # Companion change: ai-gateway-data#1945 | ||
| # (webapp/services/ai_tools_service.py preview-only short-circuit). | ||
| preview_tool_by_key = {} | ||
| for t in tools: | ||
| pt = {k: v for k, v in t.items() | ||
| if not k.startswith('_') and k != 'projects'} | ||
| tool_key = f"{t.get('name', 'Unknown')}:{t.get('install_path', 'Unknown path')}" | ||
| preview_tool_by_key[tool_key] = pt | ||
| with time_step("send_detected_preview", "send"): | ||
| for user_name in all_users: | ||
| user_keys = tool_keys_by_user.get(user_name, set()) | ||
| user_preview_tools = [ | ||
| preview_tool_by_key[k] | ||
| for k in user_keys | ||
| if k in preview_tool_by_key | ||
| ] | ||
| if not user_preview_tools: | ||
| # User has no tools — skip rather than send an empty | ||
| # preview that would create an empty tile. | ||
| logger.info( | ||
| f" -- detected-tools preview skipped for " | ||
| f"{user_name} (no tools)" | ||
| ) | ||
| continue | ||
| preview_report = { | ||
| "home_user": user_name, | ||
| "system_user": system_user, | ||
| "device_id": device_id, | ||
| "run_id": run_id, | ||
| "tools": user_preview_tools, | ||
| } | ||
| try: | ||
| ok, _ = send_report_to_backend( | ||
| args.domain, args.api_key, preview_report, | ||
| args.app_name, sentry_context=sentry_ctx, | ||
| ) | ||
| logger.info( | ||
| f" {'OK' if ok else 'XX'} detected-tools preview " | ||
| f"for {user_name} ({len(user_preview_tools)} tool(s))" | ||
| ) | ||
| except Exception as e: | ||
| logger.warning( | ||
| f" detected-tools preview failed for " | ||
| f"{user_name}: {e}" | ||
| ) | ||
| logger.info("") | ||
|
Comment on lines
+1799
to
+1858
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The preview fires the backend's retire-then-recreate for every |
||
|
|
||
| # Process each tool, then explore all users for that tool and send reports | ||
| for tool in tools: | ||
| tool_name = tool.get('name', 'Unknown') | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code comment explicitly states the server needs
ai-gateway-data#1945to distinguish a preview payload (absentprojectskey) from a genuine zero-project report. Without that companion change the server has no "preview-only short-circuit," so it processes the incoming payload as a regular tool report — which on a returning user's device would either overwrite or drop their existing project data for ~80 s per tool while the full extraction runs. The PR description claims "zero server-side changes needed," but the inline comment contradicts that for the re-scan case. There is no guard, feature-flag, or version check here that ensuresai-gateway-data#1945is deployed before this code path is exercised.