Skip to content

feat(flow-v2): autonomous research digest, agentic RAG, graph query, FlowIsland, Chrome extension#9

Merged
Sekoya88 merged 67 commits into
mainfrom
feat/flow-v2-autonomous
May 23, 2026
Merged

feat(flow-v2): autonomous research digest, agentic RAG, graph query, FlowIsland, Chrome extension#9
Sekoya88 merged 67 commits into
mainfrom
feat/flow-v2-autonomous

Conversation

@Sekoya88
Copy link
Copy Markdown
Owner

@Sekoya88 Sekoya88 commented May 23, 2026

Summary

  • Research Digest: daily arXiv + HuggingFace paper ingestion, AI scoring, Obsidian export, Qdrant embed-as-knowledge (fixed uuid5 point IDs)
  • Agentic RAG: Qdrant hybrid search (BM25 + dense, RRF fusion), toggled per-agent via Knowledge & Tools tab
  • Knowledge Graph: natural-language query with keyword fallback, cascade node delete (edges + Qdrant), graph path detection
  • Chrome Extension: full digest feed with status tabs, delete, mark read, SSE live log, Run Now
  • FlowIsland: macOS notch client with live skill graph, agent picker, knowledge tab
  • Bug fixes: gpt-5.4-mini typo → gpt-4o-mini (5 occurrences), key_insights list→str coercion, export-obsidian 400 removed, summarize crash fixed
  • Docs: rewritten README, new experience.md setup guide, removed stale Electron menubar app

Test plan

  • docker compose up --build — all services start cleanly
  • Register + onboarding flow on web
  • Research → Run now → papers appear in Unread tab
  • Select paper → Summarize (no crash)
  • Select paper → Export to Obsidian (requires vault mount)
  • Select paper → Embed as Knowledge → Qdrant dashboard shows points
  • Graph → Query → returns results for any topic
  • Graph → click node → delete button removes node + edges
  • Research → paper card → BookX (vault delete) + Trash (DB delete)
  • Agents → Config → Knowledge & Tools tab → toggles persist
  • Chrome extension → loads, shows digest, delete/mark-read work

Summary by cubic

Ships Flow v2: autonomous research digest, hybrid agentic RAG, natural-language graph query, and two new clients — FlowIsland (macOS notch) and a Chrome extension — with live observability, MCP tool integration, and a refreshed skills experience. Includes CI cleanups (ruff/ESLint) and minor naming fixes.

  • New Features

    • Research Digest: fetch from arXiv + HuggingFace, relevance scoring, TLDRs, Obsidian export, Qdrant embed, global SSE events, web dashboard with unread/read tabs.
    • Agentic RAG: Qdrant hybrid search (BM25 + dense, RRF fusion) toggled per agent under Knowledge & Tools.
    • Knowledge Graph: NL query with keyword fallback, cascade delete, paper nodes, related-paper edges, path detection.
    • Observability: workspace-wide live stream on Logs → Live; per-agent live panel (subagents, todos); local discovery + WS channel for desktop.
    • FlowIsland (macOS): notch UI with agent picker, live skill graph, event feed, and quick links.
    • Chrome Extension: quick capture (text/image), selection tooltip, agent launcher, full digest feed, Run Now, MCP vault status.
    • MCP: server registry (CRUD, ping, list/invoke tools), standalone SSE service; settings page to manage servers.
    • Skills: category-first hub, Create Skill sheet (Template/Vibe/Library), vibe-modify streaming; seed scripts for templates and memory.
    • Autonomy: SkillBandit (Thompson sampling), GenomeEvolver loop, MetaCognition journal/skill scoring, workflow snapshot export.
    • Infra/Docs: deterministic Qdrant IDs, .env.example additions, docker-compose services (mcp, minio), new experience guide; ruff/ESLint fixes and formatting.
  • Migration

    • Copy .env.example.env, set at least FLOW_OPENAI_API_KEY and FLOW_JWT_SECRET; optionally set Obsidian/GitHub/MinIO vars.
    • Run make update to build/start api, worker, web, mcp, minio, apply migrations, and seed data.
    • Ensure an Obsidian vault is available: set FLOW_OBSIDIAN_MODE and mount FLOW_OBSIDIAN_VAULT_PATH (filesystem) or configure API/cloud.
    • Optional: build the Chrome extension (apps/chrome-extension) and load unpacked; open apps/mac/FlowIsland.xcodeproj in Xcode to run FlowIsland.

Written for commit f6815eb. Summary will update on new commits. Review in cubic

Sekoya88 added 30 commits May 19, 2026 17:28
- Add MetaCogService for skill grading and journal tracking
- Implement GenomeEvolver and SkillBandit for RL-lite loop
- Add SkillLoader for XML prompt injection and progressive disclosure
- Create WorkflowSnapshot for agent state portability
- Register evolution API routes
- Add corresponding DB migrations
…bservability

- New Electron app (apps/menubar) rendering inside macOS hardware notch
  - Pill at y=0 with screen-saver window level (above menubar)
  - Expands to glass panel on hover with skill graph + live event feed
  - Auto-discovers running agents via /api/v1/local/active-agents (no auth)
  - WebSocket bridge streams agent events (skills, metacog, bandit) in real-time
  - Context-aware footer: This Agent / Runs / Memory links
  - menubarH sent from main process via IPC for accurate panel positioning

- Backend: unauthenticated local discovery endpoint + observability WebSocket
  - GET /api/v1/local/active-agents — Redis key scan (active_agent:* TTL 2h)
  - WS  /api/v1/agents/{id}/ws-observability — pub/sub Redis channel forwarding
  - ExecutionStreamHub: publish_agent_event + get_active_agent_ids
  - graph/nodes.py: emit skills_matched, metacog_evaluated, skill_arm_updated events
NSPanel-based app living inside the hardware notch via safeAreaInsets.top.
Auto-discovers running agents via /api/v1/local/active-agents, connects
WebSocket for live skill graph and event feed. Hover to expand dark glass
panel (NSVisualEffectView), click Open to launch web app.

- FlowIslandApp: @NSApplicationDelegateAdaptor, no Dock icon (LSUIElement)
- AppDelegate: NSPanel at mainMenu+3 level, global mouse monitor
- AgentDiscovery: polls localhost:18000 every 5s, auto-connects first agent
- WebSocketClient: AgentState, SkillNode, event dispatch (skills/metacog/bandit)
- ContentView: PillView (transparent) + PanelView (dark glass, 420×560)
- SkillGraphView: Canvas-based node graph with score-scaled radii
- EventFeedView: live events with icons, relative timestamps
Rewrites the notch app with boring.notch-style animation:
- NSPanel always fixed at full expanded size, never resized at runtime
- SwiftUI content morphs via spring(.42, .8) open / spring(.45, 1.0) close
- NotchShape: Shape, Animatable with topRadius/bottomRadius AnimatablePair
  gives the Dynamic Island squish effect each frame during the spring
- panel.ignoresMouseEvents=true when closed (no menubar interference)
- Global NSEvent monitor detects pill entry (open) and panel exit (close)
- Combine observes notchState to sync ignoresMouseEvents automatically
- Background morphs black pill → NSVisualEffectView dark glass on open
…Island

Remove NSVisualEffectView material that was creating subtle border reflections.
Apple's Dynamic Island is opaque black with no glass effect.
Binary UI state file (open tabs, scroll positions) — per-developer,
never belongs in version control.
…etups

Three bugs caused the pill to appear on the wrong screen:
1. NSScreen.main follows keyboard focus — replaced with CGDisplayIsBuiltin()
   which identifies the MacBook's LCD panel at the hardware level
2. Panel x used screen.frame.width without adding screen.frame.minX offset,
   miscentering the panel when the built-in display isn't at x=0
3. Same minX offset missing in pillZone() and panelZone() hit-test rects,
   causing hover detection to target the wrong screen coordinates
…hboard link

- Two-phase hover: isHoveringPill triggers scale+glow at entry, notchState.open after 80ms
- Tab switcher in panel footer (This Agent / Runs / Memory) with compact inline views
- RunsTabView: fetches last 5 executions from /api/v1/local/agent-executions/{id}
- MemoryTabView: fetches last 10 entries from /api/v1/local/agent-memory/{id}
- Open button navigates to /dashboard directly (skip public landing page)
- Multi-monitor fix: CGDisplayIsBuiltin() pins panel to MacBook built-in screen
- Backend: add agent-executions and agent-memory unauthenticated local endpoints
…e avatar in header

- Root page now redirects to /dashboard when token exists (no more public landing for authenticated users)
- AppShell header shows user email initial as avatar chip in top-right when logged in
- Backend: active-agents falls back to DB when Redis empty, returns agent name
- WebSocketClient: stores agentName, passes through connect(agentId:name:)
- AgentDiscovery: parses name from response, passes to WS connect
- ContentView: shows agent name in header (falls back to UUID prefix)
- AppDelegate: haptic tap (NSHapticFeedbackManager .alignment) on notch hover
- Backend: /agents list, /agent-skills/{id} with bandit scores, higher content limits
- AppStore: availableAgents list populated every 5s
- AgentDiscovery: polls /agents + passes store ref for agent list
- ContentView: clickable agent picker popover with all agents, LIVE badge on skill graph
- SkillGraphView: loads real DB skills when no live WS events, expanded node capacity
- RunsTabView: status icons, duration, relative time, 10 runs shown
- MemoryTabView: relative time, 3-line content, 20 entries, better empty states
- Haptic: switched to .generic for strongest available pattern
- SkillGraphView: dark starfield bg, indigo agent hub, cyan skill nodes,
  glow halos, score bars, elliptical orbit layout matching web color scheme
- Memory tab redesigned as Agent Knowledge: skills grid (name + score bar
  + use count) + episodic memory entries, both sourced from DB
- Skill chips in 2-column grid with cyan border and score progress bar
…in LazyVGrid

- Tighter orbit radius (0.26x) prevents nodes from reaching canvas edges
- Labels clamped to [10, height-10] / [30, width-30] — no more clipping
- SkillChipView score bar uses overlay+GeometryReader instead of ZStack
  to fix layout collapse inside LazyVGrid
- FlowIsland: @published currentAgentId so graph fetches on agent selection
- AppStore: Combine forwarding so ContentView re-renders on wsClient changes
- SkillGraphView: @ObservedObject wsClient directly, error state + retry
- local.py: agent-graph endpoint returning skills, tools, system prompt, memory
- Web: fix CORS origins to include :13000, wrap refresh in try-catch
- AgentDetailDrawer: TOOL_DEFAULTS so new agents show correct initial state
Sekoya88 added 24 commits May 22, 2026 14:06
filter_by_interest now calls Haiku/gpt-4o-mini to score each paper
0.0-1.0 against workspace categories (falls back to 0.6 if no LLM).
format_obsidian generates full YAML frontmatter (arxiv_id, authors,
categories, relevance_score, flow_knowledge_id, status, type) with
TL;DR callout block, emoji section headers, and daily digest wikilink.
GET /api/v1/digest/history returns per-day aggregate stats via SQL
LATERAL unnest. POST /api/v1/mcp/servers/{id}/tools/{name}/invoke
proxies arbitrary JSON args to the MCP server's /invoke endpoint.
mcp-client.ts: thin wrappers for listMCPServers, pingMCPServer,
listMCPTools via Flow REST API. VaultStatus.tsx: ping-driven status
indicator showing each MCP server as online/offline in the popup.
test_research_digest_graph.py covers fetch_sources, filter_by_interest
(category mismatch, LLM scoring, no-LLM fallback), summarize_papers,
and format_obsidian YAML frontmatter. test_obsidian_vault.py covers all
LocalVaultService CRUD operations with a tmp_path fixture (10 tests).
research/page.tsx and settings/mcp/page.tsx import Dialog, DialogContent,
DialogHeader, DialogTitle, DialogTrigger — none existed. Component mirrors
the existing alert-dialog.tsx pattern with base-ui primitives.
base-ui uses render={element} where Radix uses asChild. DialogTrigger
now accepts asChild and forwards the child element as render prop so
existing usage in research/page.tsx and settings/mcp/page.tsx compiles.
…ty wsId

- Replace all repo.pool → repo._pool in mcp.py and digest.py routes;
  FlowRepository stores the pool as self._pool (private)
- Update Makefile update target to include mcp and minio services;
  add logs-mcp convenience target
- Add if (!wsId) return guard in DigestConfigModal.save() and
  AddMCPServerModal.create() to prevent 422 on empty workspace ID
- Fix test mocks to use repo._pool in test_mcp_routes and test_digest_routes
Dockerfile copied files to /app/ directly; uvicorn loaded server as a
top-level module with no __package__, so all relative imports failed.

Copy to /app/flow_mcp/ instead and invoke as flow_mcp.server:app so
Python sets __package__ = "flow_mcp" and all .auth/.tools/.* imports
resolve correctly. Named flow_mcp (not mcp) to avoid shadowing the
installed mcp library.
Without Content-Type: application/json, FastAPI passes the raw string
to Pydantic which rejects it with model_attributes_type 422. The
apiFetch json: shorthand sets the header and stringifies correctly.
…/update

asyncpg has no dict→jsonb encoder; pass json.dumps(dict) so the string
is cast to jsonb by Postgres. Also add ::jsonb cast in dynamic PATCH
SET clause for the metadata column.
…icons

Vite outputs popup to dist/src/popup/index.html (preserves source path).
Manifest had dist/popup/index.html which caused Chrome to fail loading.
Added indigo placeholder PNGs for icon16/48/128 required by Chrome.
…context

Chrome extension resolves /popup.js as chrome-extension://<id>/popup.js
but Vite outputs to dist/popup.js. Setting base:"" makes Vite emit
relative paths (../../popup.js) that resolve correctly from dist/src/popup/.
Replace all inline style={{}} objects with a single popup.css file using
exact Flow design tokens (warm neutral palette, JetBrains Mono, --f-* vars).

- LoginGate: violet glow backdrop, Flow wordmark, token hint text
- App: 4-tab layout (Capture / Agents / Digest / MCP), workspace name chip
- QuickCapture: current page badge, ⌘↵ shortcut
- AgentLauncher: empty state instead of null, select caret, ⌘↵ shortcut
- DigestFeed: color-coded relevance scores (emerald/amber/violet), scrollable list
- VaultStatus: full MCP tab with server URL, status dots, Refresh button
…raction

- fix listAgents: use path param /workspaces/{id}/agents, unwrap .agents
- fix runAgent: POST /agents/{id}/execute with {message} only
- fix ingestKnowledge: POST /knowledge with {body} field
- add flowUpload helper for multipart (no Content-Type override)
- add ingestImageKnowledge for /knowledge/from-image
- VaultStatus: direct browser fetch to /health URL instead of backend API ping (avoids Docker localhost resolution issue)
- QuickCapture: paste handler, image preview with clear button, dual save path (text vs image)
- popup.css: img-preview-wrap/img-preview/img-preview-clear classes
- knowledge.py: POST /from-image route — Claude Haiku vision extraction + ingest_document
…ntegration

- digest.py: remove spurious redis_url arg from get_arq_pool() (TypeError on every Run Now)
- worker.py: same fix in research_digest_tick cron handler
- docker-compose: mount FLOW_OBSIDIAN_VAULT_PATH to /vault in worker service
- research/page.tsx: extend DigestConfig type + Configure modal with obsidian_mode
  and obsidian_vault_path fields (filesystem mode with /vault path hint)
…r KG nodes

- Research page: surface actual API errors instead of swallowing them;
  add 65s auto-refresh + queued banner after clicking Run Now
- HuggingFace fetcher: switch from huggingface.co/papers (HTML) to
  huggingface.co/api/daily_papers (JSON); parse paper.paper wrapper
- Global SSE: add publish_global() to ExecutionStreamHub, new
  GET /api/v1/stream endpoint for workspace-scoped activity stream;
  emit digest.start / digest.complete events from worker
- Logs page: add Live tab that subscribes to the global SSE stream via
  fetch() + ReadableStream (supports Bearer auth unlike EventSource)
- Knowledge graph: persist node now inserts kg_nodes (type=paper) and
  co_category edges between papers sharing arXiv categories; migration
  0026 extends node_type CHECK constraint
- Graph page: add Papers filter tab (orange, size 6) throughout the
  node type system (types.ts, graphColors.ts, canvas, graph page)
…nks, Tavily, on-demand summarize

- Migration 0027: UNIQUE (workspace_id, title) on digest_papers enables ON CONFLICT target
- research_digest_graph.py: fix ON CONFLICT clause, await publish_global, add Tavily enrichment,
  add category/author wikilinks, daily index.md, related-papers second pass
- execution_streams.py: publish_global now async def (no create_task race at task return)
- digest.py: POST /papers/{id}/summarize endpoint re-runs LLM and updates tldr/key_insights
- research/page.tsx: Summarize button per paper with in-flight state
…scade delete, UI

- fix(digest): coerce key_insights list→str before asyncpg UPDATE (summarize crash)
- fix(digest): remove obsidian_mode hard-check from export endpoint; generate md on-the-fly
- fix(qdrant): use uuid5 deterministic point IDs — fixes embed-knowledge writing 0 points
- fix(kg): keyword extraction + OR-based to_tsquery in _text_search — graph query now matches any topic
- fix(kg): cascade-delete incident edges before removing node in delete_kg_node
- fix(agents): replace gpt-5.4-mini typo with gpt-4o-mini across nodes/graph/routes
- feat(web/research): add BookX vault-delete button alongside DB-delete on paper cards
- feat(web/graph): add Trash2 delete button to NodeDetailPanel with optimistic edge removal
- feat(web/agents): add Knowledge & Tools tab with 7 toggle flags (retrieve, memory, tools)
- Replace verbose README with focused version (3 apps, quick start, capabilities)
- Add experience.md: full setup guide for Web, FlowIsland, Chrome extension
- Remove apps/menubar/ (Electron companion, superseded by FlowIsland)
…tch, SSE live log

- Extension DigestFeed: unread/read/all tabs, delete paper, mark read, Run now button
- flow-api.ts: add deletePaper, patchPaper, runDigest, fix listPapers to accept status param
- popup.css: styles for digest tabs, log entries, score badges
- fix(web/logs): minor display fixes in logs page
- fix(kg): KnowledgeGraphCanvas edge rendering fix
- docs(api): update services/api/README.md
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Stale comment

Pull request risk assessment

Risk level: High
Code review: Required
Automation decision: Do not approve

This assessment is based only on the diff (18f1f41…f69ee85, 153 files, +15,459 / −401). PR title/description claims are not used as evidence.

Why High (not Medium-High)

Area Evidence
Blast radius Touches API core (LangGraph nodes, skill loader, digest graph), persistence, queue worker, new HTTP routes, web app shell/pages, Docker infra, and two new client surfaces (Chrome extension + macOS FlowIsland).
Data model Eight Alembic migrations (00200027): new tables (rl_episodes, metacog_journal, mcp_servers, digest_papers, …), agent_versions lineage columns, kg_nodes CHECK rewrite, data backfill (0024), and a UNIQUE constraint on existing digest_papers (0027) — rollback and deploy ordering matter.
Security / auth New MCP service (JWT-validated tools, GitHub Actions dispatch, web research, Obsidian/MinIO vault paths); Chrome extension JWT in chrome.storage.local; /set-token?t= writes JWT to localStorage; observability WebSocket accepts connections without auth (see inline). User-supplied MCP server URLs in API (/api/v1/mcp/servers) expand SSRF surface.
Prompts / agent behavior Large skill template seed (+771 lines), MCP research-digest prompt, planner skill-loading refactor — material model-behavior change.
Infra New mcp + minio services, vault volume mounts, host ports 18001 / 19000.
CI API — lint (ruff) and Web — ESLint + type-check are failing on this PR; API — pytest skipped after lint failure.

Actions taken

  • Not approved (High risk — automation never approves High).
  • Reviewers: No additional GitHub collaborators on Sekoya88/Flow besides the author; no reviewer requests were added (would not satisfy meaningful independent review).
  • CODEOWNERS: None configured on this repository.

Merge guidance

  1. Fix CI (ruff + web lint/type-check) and ensure pytest runs green.
  2. Human review focused on: migration deploy plan, MCP/SSRF boundaries, WebSocket + extension auth, and production secrets handling.
  3. Treat Agentic Security Review (if enabled) as blocking for auth/MCP surfaces.

Cursor automation — risk assessment from diff evidence only.

Open in Web View Automation 

Sent by Cursor Automation: Assign PR reviewers

# token: str = Query(...)
):
"""Real-time stream of agent events (MetaCog, skills, RL) for the macOS App."""
await websocket.accept()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security (High): This endpoint calls websocket.accept() without JWT/workspace checks (comment acknowledges “for now”). Any client that can reach the API can subscribe to agent_events:{agent_id} and receive MetaCog/skill/RL events. Gate on the same auth pattern as other agent routes (query-token or session) before merge to any shared/staging/prod network.

useEffect(() => {
const t = params.get("t");
if (t) localStorage.setItem("flow_token", t);
router.replace("/dashboard");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security: JWT is taken from the t query parameter and persisted to localStorage. That pattern leaks via browser history, Referer, and server/proxy logs. Prefer a short-lived exchange code, POST body, or fragment-only handoff; align with how SSE already passes tokens today and track hardening in a follow-up if intentional for the extension.



def upgrade() -> None:
op.execute("""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Migration risk: Adding UNIQUE (workspace_id, title) on digest_papers will fail if duplicate titles already exist in any environment. Run a pre-migration dedupe check in staging before production deploy.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

9 issues found across 153 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/chrome-extension/src/background/service-worker.ts">

<violation number="1" location="apps/chrome-extension/src/background/service-worker.ts:37">
P2: Check the ingest API response status before showing success; otherwise failed saves can be reported as successful.</violation>
</file>

<file name="apps/mac/FlowIsland/AgentDiscovery.swift">

<violation number="1" location="apps/mac/FlowIsland/AgentDiscovery.swift:24">
P2: Overlapping `/active-agents` poll responses can be applied out of order, causing stale agent reconnections.</violation>
</file>

<file name="apps/chrome-extension/src/content/inject.ts">

<violation number="1" location="apps/chrome-extension/src/content/inject.ts:22">
P2: For a fixed-position tooltip, adding `window.scrollY` to `rect.top` misplaces the tooltip on scrolled pages. Use viewport coordinates directly.</violation>
</file>

<file name="apps/web/src/app/set-token/page.tsx">

<violation number="1" location="apps/web/src/app/set-token/page.tsx:9">
P1: Avoid accepting auth tokens via query parameters; this exposes credentials in URL-based logs/history before redirect. Use a non-URL channel (e.g., POST or hash fragment exchange) for token handoff.</violation>
</file>

<file name="apps/chrome-extension/src/popup/App.tsx">

<violation number="1" location="apps/chrome-extension/src/popup/App.tsx:43">
P2: Guard against users with no workspaces before rendering workspace-dependent tabs; passing an empty `workspaceId` can break downstream API requests.</violation>
</file>

<file name="apps/chrome-extension/src/popup/components/QuickCapture.tsx">

<violation number="1" location="apps/chrome-extension/src/popup/components/QuickCapture.tsx:27">
P2: Revoke the previous preview URL before replacing `pastedImage`; otherwise repeated pastes leak object URLs.</violation>
</file>

<file name="apps/chrome-extension/src/popup/components/AgentLauncher.tsx">

<violation number="1" location="apps/chrome-extension/src/popup/components/AgentLauncher.tsx:17">
P2: Do not swallow `listAgents` errors; this currently shows a false "No agents in this workspace" state when loading fails.</violation>
</file>

<file name="apps/chrome-extension/src/lib/mcp-client.ts">

<violation number="1" location="apps/chrome-extension/src/lib/mcp-client.ts:28">
P1: `listMCPTools` is wired to a backend endpoint that currently crashes (`repo.pool` vs `repo._pool`), so this new call will fail at runtime instead of returning tools.</violation>
</file>

<file name="apps/chrome-extension/src/lib/flow-api.ts">

<violation number="1" location="apps/chrome-extension/src/lib/flow-api.ts:22">
P2: `flowFetch` incorrectly spreads `init.headers`; `Headers` inputs can be lost. Normalize with `new Headers(...)` (or `Object.fromEntries`) before merging.</violation>
</file>

Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.

Re-trigger cubic

const router = useRouter();
const params = useSearchParams();
useEffect(() => {
const t = params.get("t");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Avoid accepting auth tokens via query parameters; this exposes credentials in URL-based logs/history before redirect. Use a non-URL channel (e.g., POST or hash fragment exchange) for token handoff.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/app/set-token/page.tsx, line 9:

<comment>Avoid accepting auth tokens via query parameters; this exposes credentials in URL-based logs/history before redirect. Use a non-URL channel (e.g., POST or hash fragment exchange) for token handoff.</comment>

<file context>
@@ -0,0 +1,18 @@
+  const router = useRouter();
+  const params = useSearchParams();
+  useEffect(() => {
+    const t = params.get("t");
+    if (t) localStorage.setItem("flow_token", t);
+    router.replace("/dashboard");
</file context>

}

export async function listMCPTools(serverId: string): Promise<MCPTool[]> {
return flowFetch(`/api/v1/mcp/servers/${serverId}/tools`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: listMCPTools is wired to a backend endpoint that currently crashes (repo.pool vs repo._pool), so this new call will fail at runtime instead of returning tools.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/src/lib/mcp-client.ts, line 28:

<comment>`listMCPTools` is wired to a backend endpoint that currently crashes (`repo.pool` vs `repo._pool`), so this new call will fail at runtime instead of returning tools.</comment>

<file context>
@@ -0,0 +1,29 @@
+}
+
+export async function listMCPTools(serverId: string): Promise<MCPTool[]> {
+  return flowFetch(`/api/v1/mcp/servers/${serverId}/tools`);
+}
</file context>

Comment on lines +37 to +49
await fetch(`${baseUrl}/api/v1/knowledge/ingest`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
workspace_id: wsId,
title: tab?.title ?? "Web capture",
content: text,
source_url: tab?.url,
}),
});
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: Check the ingest API response status before showing success; otherwise failed saves can be reported as successful.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/src/background/service-worker.ts, line 37:

<comment>Check the ingest API response status before showing success; otherwise failed saves can be reported as successful.</comment>

<file context>
@@ -0,0 +1,65 @@
+    const wsId = me.workspaces?.[0]?.id;
+    if (!wsId) return;
+
+    await fetch(`${baseUrl}/api/v1/knowledge/ingest`, {
+      method: "POST",
+      headers: {
</file context>
Suggested change
await fetch(`${baseUrl}/api/v1/knowledge/ingest`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
workspace_id: wsId,
title: tab?.title ?? "Web capture",
content: text,
source_url: tab?.url,
}),
});
const ingestRes = await fetch(`${baseUrl}/api/v1/knowledge/ingest`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
workspace_id: wsId,
title: tab?.title ?? "Web capture",
content: text,
source_url: tab?.url,
}),
});
if (!ingestRes.ok) throw new Error("ingest");


private func poll() {
guard let url = URL(string: "\(API_BASE)/active-agents") else { return }
URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
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: Overlapping /active-agents poll responses can be applied out of order, causing stale agent reconnections.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mac/FlowIsland/AgentDiscovery.swift, line 24:

<comment>Overlapping `/active-agents` poll responses can be applied out of order, causing stale agent reconnections.</comment>

<file context>
@@ -0,0 +1,64 @@
+
+    private func poll() {
+        guard let url = URL(string: "\(API_BASE)/active-agents") else { return }
+        URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
+            guard let self, let data else { return }
+            guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
</file context>

tooltip.textContent = "📥 Save to Flow";
Object.assign(tooltip.style, {
position: "fixed",
top: `${rect.top + window.scrollY - 36}px`,
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: For a fixed-position tooltip, adding window.scrollY to rect.top misplaces the tooltip on scrolled pages. Use viewport coordinates directly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/src/content/inject.ts, line 22:

<comment>For a fixed-position tooltip, adding `window.scrollY` to `rect.top` misplaces the tooltip on scrolled pages. Use viewport coordinates directly.</comment>

<file context>
@@ -0,0 +1,49 @@
+  tooltip.textContent = "📥 Save to Flow";
+  Object.assign(tooltip.style, {
+    position: "fixed",
+    top: `${rect.top + window.scrollY - 36}px`,
+    left: `${rect.left + rect.width / 2}px`,
+    transform: "translateX(-50%)",
</file context>

if (!checked) return null;
if (!me) return <LoginGate onLogin={() => getMe().then(setMe)} />;

const wsId = me.workspaces[0]?.id ?? "";
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: Guard against users with no workspaces before rendering workspace-dependent tabs; passing an empty workspaceId can break downstream API requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/src/popup/App.tsx, line 43:

<comment>Guard against users with no workspaces before rendering workspace-dependent tabs; passing an empty `workspaceId` can break downstream API requests.</comment>

<file context>
@@ -0,0 +1,79 @@
+  if (!checked) return null;
+  if (!me) return <LoginGate onLogin={() => getMe().then(setMe)} />;
+
+  const wsId = me.workspaces[0]?.id ?? "";
+  const wsName = me.workspaces[0]?.name ?? "workspace";
+
</file context>

const blob = item.getAsFile();
if (!blob) return;
const preview = URL.createObjectURL(blob);
setPastedImage({ blob, preview });
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: Revoke the previous preview URL before replacing pastedImage; otherwise repeated pastes leak object URLs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/src/popup/components/QuickCapture.tsx, line 27:

<comment>Revoke the previous preview URL before replacing `pastedImage`; otherwise repeated pastes leak object URLs.</comment>

<file context>
@@ -0,0 +1,107 @@
+    const blob = item.getAsFile();
+    if (!blob) return;
+    const preview = URL.createObjectURL(blob);
+    setPastedImage({ blob, preview });
+  }
+
</file context>

setAgents(a);
if (a[0]) setSelectedId(a[0].id);
})
.catch(() => null)
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: Do not swallow listAgents errors; this currently shows a false "No agents in this workspace" state when loading fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/src/popup/components/AgentLauncher.tsx, line 17:

<comment>Do not swallow `listAgents` errors; this currently shows a false "No agents in this workspace" state when loading fails.</comment>

<file context>
@@ -0,0 +1,89 @@
+        setAgents(a);
+        if (a[0]) setSelectedId(a[0].id);
+      })
+      .catch(() => null)
+      .finally(() => setLoaded(true));
+  }, [workspaceId]);
</file context>

headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...init?.headers,
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: flowFetch incorrectly spreads init.headers; Headers inputs can be lost. Normalize with new Headers(...) (or Object.fromEntries) before merging.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/src/lib/flow-api.ts, line 22:

<comment>`flowFetch` incorrectly spreads `init.headers`; `Headers` inputs can be lost. Normalize with `new Headers(...)` (or `Object.fromEntries`) before merging.</comment>

<file context>
@@ -0,0 +1,123 @@
+    headers: {
+      "Content-Type": "application/json",
+      ...(token ? { Authorization: `Bearer ${token}` } : {}),
+      ...init?.headers,
+    },
+  });
</file context>

- Fix E741: rename ambiguous var `l` → `ln` in nodes.py
- Fix B905: add strict=False to zip() in research_digest_graph.py
- Fix F401/I001: remove unused imports, reorder import blocks (auto)
- Format: reformat 25 files via ruff format
- Fix ESLint: move connectRef.current assignment into useEffect to avoid
  refs-during-render violation in useAgentStream.ts
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Pull request risk assessment (updated)

Risk level: High
Code review: Required
Automation decision: Do not approve

Re-evaluated on synchronize to f6815eb (diff base 18f1f41…f6815eb, 153 files, +15,397 / −401). Assessment uses diff evidence only.

Delta since last automation run (f69ee85f6815eb)

Change Impact on risk
fix(ci): resolve all ruff and ESLint failures — 31 files, mostly lint/refactor in digest graph, routes, tests CI posture improved (ruff + web lint now green; pytest passing on latest run)
No rollback of migrations, MCP service, extension, FlowIsland, or auth surfaces Blast radius unchanged

Risk classification unchanged: High. Green CI reduces merge friction but does not shrink operational or security exposure.

Why High

Area Evidence
Blast radius Core API (LangGraph nodes.py, digest graph, skill loader), queue worker, 8 Alembic migrations (00200027 incl. backfill 0024, UNIQUE on digest_papers 0027), new services/mcp + MinIO in docker-compose.yml, large web surfaces (research, skills, MCP settings), Chrome extension, macOS FlowIsland
Security / auth User-configurable MCP server URLs with server-side httpx ping/invoke (SSRF class); observability WebSocket accepts without auth; /set-token?t= JWT handoff via query string; extension stores JWT in chrome.storage.local; MCP tools include GitHub Actions dispatch
Prompts / agent behavior Large skill_templates.py seed, MCP research-digest prompt, planner/skill-loading changes in graph nodes
Data model New tables (mcp_servers, digest_papers, RL/metacog/bandit) + constraint changes on existing data

Actions taken

  • Not approved (High risk — automation never self-approves High).
  • Reviewers: Sekoya88/Flow has no other collaborators besides the author; no reviewer requests added.
  • CODEOWNERS: None configured.
  • Prior approval: None from this automation; no dismissal required.
  • Slack: Summary could not be posted — no Slack webhook or Slack MCP is configured in this agent environment.

Merge guidance

  1. Human review before merge: migrations deploy order, MCP/SSRF boundaries, WebSocket + extension auth, production secrets.
  2. Fix remaining correctness issues flagged by cubic (e.g. repo.pool vs repo._pool on MCP tools route).
  3. Treat Agentic Security Review as blocking where enabled.

Cursor automation — risk assessment from diff evidence only.

Open in Web View Automation 

Sent by Cursor Automation: Assign PR reviewers

# token: str = Query(...)
):
"""Real-time stream of agent events (MetaCog, skills, RL) for the macOS App."""
await websocket.accept()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security (High-risk surface): This endpoint calls websocket.accept() without JWT or workspace checks. Any client that can reach the API can subscribe to agent_events:{agent_id} for arbitrary UUIDs — acceptable only behind strict network isolation; gate before production.

const params = useSearchParams();
useEffect(() => {
const t = params.get("t");
if (t) localStorage.setItem("flow_token", t);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security: JWT in query string (?t=) is logged in proxies, browser history, and Referer. Prefer POST body, hash-fragment exchange, or extension-only chrome.runtime messaging for token handoff.

repo: Annotated[FlowRepository, Depends(get_repo)],
) -> list:
"""List tools available on an MCP server by reading the tool assignment table."""
row = await repo.pool.fetchrow("SELECT * FROM mcp_servers WHERE id = $1", server_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correctness: repo.pool will raise at runtime — other routes in this file use repo._pool. Same bug cubic flagged for the Chrome extension MCP tools call.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 31 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="services/api/flow/interfaces/http/routes/mcp.py">

<violation number="1" location="services/api/flow/interfaces/http/routes/mcp.py:151">
P1: AttributeError at runtime: repo.pool does not exist on FlowRepository. The private attribute is repo._pool (with underscore). This will raise an AttributeError when list_mcp_server_tools is called.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

repo: Annotated[FlowRepository, Depends(get_repo)],
) -> list:
"""List tools available on an MCP server by reading the tool assignment table."""
row = await repo.pool.fetchrow("SELECT * FROM mcp_servers WHERE id = $1", server_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: AttributeError at runtime: repo.pool does not exist on FlowRepository. The private attribute is repo._pool (with underscore). This will raise an AttributeError when list_mcp_server_tools is called.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At services/api/flow/interfaces/http/routes/mcp.py, line 151:

<comment>AttributeError at runtime: repo.pool does not exist on FlowRepository. The private attribute is repo._pool (with underscore). This will raise an AttributeError when list_mcp_server_tools is called.</comment>

<file context>
@@ -161,9 +148,7 @@ async def list_mcp_server_tools(
-    row = await repo.pool.fetchrow(
-        "SELECT * FROM mcp_servers WHERE id = $1", server_id
-    )
+    row = await repo.pool.fetchrow("SELECT * FROM mcp_servers WHERE id = $1", server_id)
     if not row:
         raise HTTPException(status_code=404, detail="MCP server not found")
</file context>
Suggested change
row = await repo.pool.fetchrow("SELECT * FROM mcp_servers WHERE id = $1", server_id)
row = await repo._pool.fetchrow("SELECT * FROM mcp_servers WHERE id = $1", server_id)

@Sekoya88 Sekoya88 merged commit b9f3ef9 into main May 23, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant