feat(flow-v2): autonomous research digest, agentic RAG, graph query, FlowIsland, Chrome extension#9
Conversation
- 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
…r stronger sensation
- 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
…bility WS channel
…ow, thinner edges, labels on tap only
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
There was a problem hiding this comment.
Stale comment
Pull request risk assessment
Risk level: High
Code review: Required
Automation decision: Do not approveThis 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 ( 0020–0027): new tables (rl_episodes,metacog_journal,mcp_servers,digest_papers, …),agent_versionslineage columns,kg_nodesCHECK rewrite, data backfill (0024), and a UNIQUE constraint on existingdigest_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 tolocalStorage; 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+minioservices, vault volume mounts, host ports18001/19000.CI API — lint (ruff)andWeb — ESLint + type-checkare failing on this PR;API — pytestskipped after lint failure.Actions taken
- Not approved (High risk — automation never approves High).
- Reviewers: No additional GitHub collaborators on
Sekoya88/Flowbesides the author; no reviewer requests were added (would not satisfy meaningful independent review).- CODEOWNERS: None configured on this repository.
Merge guidance
- Fix CI (ruff + web lint/type-check) and ensure pytest runs green.
- Human review focused on: migration deploy plan, MCP/SSRF boundaries, WebSocket + extension auth, and production secrets handling.
- Treat Agentic Security Review (if enabled) as blocking for auth/MCP surfaces.
Cursor automation — risk assessment from diff evidence only.
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() |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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(""" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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`); |
There was a problem hiding this comment.
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>
| 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, | ||
| }), | ||
| }); |
There was a problem hiding this comment.
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>
| 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 |
There was a problem hiding this comment.
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`, |
There was a problem hiding this comment.
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 ?? ""; |
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 (f69ee85 → f6815eb)
| 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 (0020–0027 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/Flowhas 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
- Human review before merge: migrations deploy order, MCP/SSRF boundaries, WebSocket + extension auth, production secrets.
- Fix remaining correctness issues flagged by cubic (e.g.
repo.poolvsrepo._poolon MCP tools route). - Treat Agentic Security Review as blocking where enabled.
Cursor automation — risk assessment from diff evidence only.
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() |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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>
| 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) |


Summary
gpt-5.4-minitypo →gpt-4o-mini(5 occurrences),key_insightslist→str coercion, export-obsidian 400 removed, summarize crash fixedexperience.mdsetup guide, removed stale Electron menubar appTest plan
docker compose up --build— all services start cleanlySummary 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
.env.exampleadditions, docker-compose services (mcp,minio), new experience guide; ruff/ESLint fixes and formatting.Migration
.env.example→.env, set at leastFLOW_OPENAI_API_KEYandFLOW_JWT_SECRET; optionally set Obsidian/GitHub/MinIO vars.make updateto build/startapi,worker,web,mcp,minio, apply migrations, and seed data.FLOW_OBSIDIAN_MODEand mountFLOW_OBSIDIAN_VAULT_PATH(filesystem) or configure API/cloud.apps/chrome-extension) and load unpacked; openapps/mac/FlowIsland.xcodeprojin Xcode to run FlowIsland.Written for commit f6815eb. Summary will update on new commits. Review in cubic