Repository navigation
Show current Campus work and GitHub Issue #36
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
Changes from all commits
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 |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # Campus project work summary + GitHub Issue v1 | ||
|
|
||
| Status: LOCKED | ||
| Issue: https://github.com/pirajoke/agent-dashboard/issues/34 | ||
|
|
||
| ## Goal | ||
|
|
||
| The read-only Pixel Verse Campus project inspector shows a concise description | ||
| of the current verified work and a direct GitHub Issue link for owner-view live | ||
| tasks, without publishing private task content. | ||
|
|
||
| ## Acceptance criteria | ||
|
|
||
| - AC-01: A live project inspector can render `Над чем работаем` from a bounded, | ||
| privacy-safe `work_summary` value. | ||
| - AC-02: A live project inspector can render one keyboard-operable external | ||
| GitHub Issue link whose visible label is `Issue #N`. | ||
| - AC-03: Owner-view canonical Bridge tasks derive `work_summary` only from the | ||
| typed metadata `objective`, never from raw `description`, `result`, messages, | ||
| issue bodies, or logs. | ||
| - AC-04: Owner-view issue data is accepted only when metadata contains an | ||
| integer `github_issue_number`, a matching `github_issue_url`, and an exact | ||
| `github_repo` identity. The URL must be HTTPS `github.com/{owner}/{repo}/issues/N` | ||
| with no credentials, port, query, or fragment. | ||
| - AC-05: Verified MAIN MANAGER pixel events may carry the same owner-only | ||
| fields through the projection after identical validation. | ||
| - AC-06: An anonymous public-host `/api/manager/departments` response never | ||
| contains `work_summary`, `issue_url`, or `issue_number`; a view authenticated | ||
| with the existing dashboard owner token may contain those owner-only fields, | ||
| including when accessed through the public host. | ||
|
|
||
| ## Edge cases | ||
|
|
||
| - EC-01: Missing summary or issue data hides only the corresponding row. | ||
| - EC-02: Summary text is whitespace-normalized and bounded without breaking | ||
| the existing responsive inspector. | ||
| - EC-03: The link opens in a new tab with `rel="noreferrer"` and remains | ||
| read-only and keyboard accessible. | ||
|
|
||
| ## Errors and privacy | ||
|
|
||
| - ERR-01: A mismatched issue number/repository, non-GitHub URL, credentials, | ||
| port, query, fragment, malformed type, or unsafe summary fails closed. | ||
| - ERR-02: Raw task `description`, result, prompt/body, local paths, tokens, | ||
| credentials, logs, and private metadata never enter the projection or DOM. | ||
| - ERR-03: Existing project details, focus return, Escape close, empty state, | ||
| three-lane cap, and public privacy behavior remain unchanged. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - No new dependency. | ||
| - Source changes stay in `builder/`. | ||
| - Read-only UI only; no dispatch, edit, merge, deploy, or publication action. | ||
| - Draft PR only. Merge and production deployment are separate stages. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -699,10 +699,15 @@ def _github_bridge_reconciliation(issues: list[dict], bridge_tasks: list[dict]) | |
| } | ||
|
|
||
|
|
||
| def _dashboard_run_token() -> str: | ||
| def _dashboard_run_token(*, create_if_missing: bool = True) -> str: | ||
| token = os.environ.get("DASHBOARD_RUN_TOKEN", "").strip() | ||
| if token: | ||
| return token | ||
| if not create_if_missing: | ||
| try: | ||
| return JARVIS_DASHBOARD_RUN_TOKEN_FILE.read_text().strip() | ||
| except FileNotFoundError: | ||
| return "" | ||
| JARVIS_DASHBOARD_RUN_TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True) | ||
| if not JARVIS_DASHBOARD_RUN_TOKEN_FILE.exists() or not JARVIS_DASHBOARD_RUN_TOKEN_FILE.read_text().strip(): | ||
| JARVIS_DASHBOARD_RUN_TOKEN_FILE.write_text(secrets.token_urlsafe(24) + "\n") | ||
|
|
@@ -1250,7 +1255,7 @@ def _campus_bridge_event(task: dict) -> dict | None: | |
| if not isinstance(task_id, str): | ||
| return None | ||
| zone = DEPARTMENT_ZONES[project["department_id"]] | ||
| return { | ||
| event = { | ||
| "event_id": task_id, | ||
| "task_id": task_id, | ||
| "department_id": project["department_id"], | ||
|
|
@@ -1265,16 +1270,27 @@ def _campus_bridge_event(task: dict) -> dict | None: | |
| "ephemeral": True, | ||
| "zone_id": zone["zone_id"], | ||
| } | ||
| metadata = _manager_metadata(task) | ||
| event["work_summary"] = metadata.get("objective") | ||
| event["github_repo"] = metadata.get("github_repo") | ||
| event["github_issue_number"] = metadata.get("github_issue_number") | ||
| event["github_issue_url"] = metadata.get("github_issue_url") | ||
| return event | ||
|
|
||
|
|
||
| def _department_campus_payload(data: object, *, now: datetime | None = None) -> dict: | ||
| def _department_campus_payload( | ||
| data: object, | ||
| *, | ||
| now: datetime | None = None, | ||
| owner_view: bool = False, | ||
| ) -> dict: | ||
| """Project a verified manager snapshot or safe canonical Bridge tasks.""" | ||
| current = now or datetime.now(timezone.utc) | ||
| if current.tzinfo is None: | ||
| current = current.replace(tzinfo=timezone.utc) | ||
| current = current.astimezone(timezone.utc) | ||
| if not isinstance(data, dict) or not isinstance(data.get("tasks"), list): | ||
| return department_campus_projection(None, now=current) | ||
| return department_campus_projection(None, now=current, owner_view=owner_view) | ||
|
|
||
| candidates: list[tuple[int, datetime, list]] = [] | ||
| malformed_verified_snapshot = False | ||
|
|
@@ -1307,21 +1323,31 @@ def _department_campus_payload(data: object, *, now: datetime | None = None) -> | |
|
|
||
| if not candidates: | ||
| if malformed_verified_snapshot: | ||
| return department_campus_projection(None, now=current) | ||
| return department_campus_projection(None, now=current, owner_view=owner_view) | ||
| events = [ | ||
| event | ||
| for task in data["tasks"] | ||
| if isinstance(task, dict) | ||
| for event in [_campus_bridge_event(task)] | ||
| if event is not None | ||
| ] | ||
| return department_campus_projection(events, now=current, max_tasks=3) | ||
| return department_campus_projection( | ||
| events, | ||
| now=current, | ||
| max_tasks=3, | ||
| owner_view=owner_view, | ||
| ) | ||
|
|
||
| # max() preserves the first source item when timestamps tie. | ||
| _, snapshot_time, events = max(candidates, key=lambda item: item[1]) | ||
| if (current - snapshot_time).total_seconds() > 30 * 60: | ||
| return _department_campus_state("stale", now=current) | ||
| return department_campus_projection(events, now=current, max_tasks=3) | ||
| return department_campus_projection( | ||
| events, | ||
| now=current, | ||
| max_tasks=3, | ||
| owner_view=owner_view, | ||
| ) | ||
|
|
||
|
|
||
| def _runtime_asset_block(filename: str, start_marker: str, end_marker: str) -> str: | ||
|
|
@@ -1392,9 +1418,19 @@ def _read_json_body(self, max_bytes: int = 4096) -> dict: | |
| def _dashboard_run_authorized(self) -> bool: | ||
| if not self._is_public_request(): | ||
| return True | ||
| expected = _dashboard_run_token() | ||
| write_method = getattr(self, "command", "").upper() in { | ||
| "POST", | ||
| "PUT", | ||
| "PATCH", | ||
| "DELETE", | ||
| } | ||
| expected = _dashboard_run_token(create_if_missing=write_method) | ||
|
Comment on lines
+1421
to
+1427
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.
On a fresh deployment or after the token file is removed, every GET now calls Useful? React with 👍 / 👎. |
||
| provided = self.headers.get("X-Dashboard-Run-Token", "").strip() | ||
| return bool(provided) and secrets.compare_digest(provided, expected) | ||
| return ( | ||
| bool(expected) | ||
| and bool(provided) | ||
| and secrets.compare_digest(provided, expected) | ||
| ) | ||
|
|
||
| def _require_dashboard_run_auth(self) -> bool: | ||
| if self._dashboard_run_authorized(): | ||
|
|
@@ -2244,7 +2280,11 @@ def do_GET(self): | |
| if parsed.path == '/api/manager/departments': | ||
| try: | ||
| data = _bridge_request("GET", "/api/tasks?limit=24&include_messages=1") | ||
| self._json_response(200, _department_campus_payload(data)) | ||
| if self._dashboard_run_authorized(): | ||
| payload = _department_campus_payload(data, owner_view=True) | ||
| else: | ||
| payload = _department_campus_payload(data) | ||
| self._json_response(200, payload) | ||
| except Exception: | ||
| self._json_response( | ||
| 200, | ||
|
|
||
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.
When the supported dashboard is opened from
file://,PIXEL_AGENTS_BASEloads this script in an iframe fromhttps://command.meshly.fr, but the saved run token belongs to the parent file origin. Readingwindow.localStoragehere therefore cannot see that token, so every Campus refresh receives the anonymous projection and the new summary/Issue rows remain unavailable to the owner; explicitly pass the token from the parent to the iframe or perform the authenticated request in the parent.Useful? React with 👍 / 👎.