flock-core 0.5.0b63__py3-none-any.whl → 0.5.0b70__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (62) hide show
  1. flock/agent.py +205 -27
  2. flock/cli.py +74 -2
  3. flock/dashboard/websocket.py +13 -2
  4. flock/engines/dspy_engine.py +70 -13
  5. flock/examples.py +4 -1
  6. flock/frontend/README.md +15 -1
  7. flock/frontend/package-lock.json +11 -21
  8. flock/frontend/package.json +1 -1
  9. flock/frontend/src/App.tsx +74 -6
  10. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +4 -5
  11. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +7 -3
  12. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  13. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  14. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  15. flock/frontend/src/components/filters/FilterPills.module.css +186 -45
  16. flock/frontend/src/components/filters/FilterPills.test.tsx +115 -99
  17. flock/frontend/src/components/filters/FilterPills.tsx +120 -44
  18. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  19. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  20. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  21. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  22. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  23. flock/frontend/src/components/filters/TimeRangeFilter.module.css +24 -0
  24. flock/frontend/src/components/filters/TimeRangeFilter.tsx +6 -1
  25. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  26. flock/frontend/src/components/graph/GraphCanvas.tsx +24 -0
  27. flock/frontend/src/components/layout/DashboardLayout.css +13 -0
  28. flock/frontend/src/components/layout/DashboardLayout.tsx +8 -24
  29. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  30. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +460 -0
  31. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  32. flock/frontend/src/components/modules/ModuleRegistry.ts +7 -1
  33. flock/frontend/src/components/modules/registerModules.ts +9 -10
  34. flock/frontend/src/hooks/useModules.ts +11 -1
  35. flock/frontend/src/services/api.ts +140 -0
  36. flock/frontend/src/services/indexeddb.ts +56 -2
  37. flock/frontend/src/services/websocket.ts +129 -0
  38. flock/frontend/src/store/filterStore.test.ts +105 -185
  39. flock/frontend/src/store/filterStore.ts +173 -26
  40. flock/frontend/src/store/graphStore.test.ts +19 -0
  41. flock/frontend/src/store/graphStore.ts +166 -27
  42. flock/frontend/src/types/filters.ts +34 -1
  43. flock/frontend/src/types/graph.ts +7 -0
  44. flock/frontend/src/utils/artifacts.ts +24 -0
  45. flock/mcp/client.py +25 -1
  46. flock/mcp/config.py +1 -10
  47. flock/mcp/manager.py +34 -3
  48. flock/mcp/types/callbacks.py +4 -1
  49. flock/orchestrator.py +56 -5
  50. flock/service.py +146 -9
  51. flock/store.py +971 -24
  52. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/METADATA +27 -1
  53. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/RECORD +56 -49
  54. flock/frontend/src/components/filters/FilterBar.module.css +0 -29
  55. flock/frontend/src/components/filters/FilterBar.test.tsx +0 -133
  56. flock/frontend/src/components/filters/FilterBar.tsx +0 -33
  57. flock/frontend/src/components/modules/EventLogModule.test.tsx +0 -401
  58. flock/frontend/src/components/modules/EventLogModule.tsx +0 -396
  59. flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +0 -17
  60. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/WHEEL +0 -0
  61. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/entry_points.txt +0 -0
  62. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/licenses/LICENSE +0 -0
flock/service.py CHANGED
@@ -3,13 +3,15 @@ from __future__ import annotations
3
3
 
4
4
  """HTTP control plane for the blackboard orchestrator."""
5
5
 
6
+ from datetime import datetime
6
7
  from typing import TYPE_CHECKING, Any
7
8
  from uuid import UUID
8
9
 
9
- from fastapi import FastAPI, HTTPException
10
+ from fastapi import FastAPI, HTTPException, Query
10
11
  from fastapi.responses import PlainTextResponse
11
12
 
12
13
  from flock.registry import type_registry
14
+ from flock.store import ArtifactEnvelope, ConsumptionRecord, FilterConfig
13
15
 
14
16
 
15
17
  if TYPE_CHECKING:
@@ -26,6 +28,64 @@ class BlackboardHTTPService:
26
28
  app = self.app
27
29
  orchestrator = self.orchestrator
28
30
 
31
+ def _serialize_artifact(
32
+ artifact,
33
+ consumptions: list[ConsumptionRecord] | None = None,
34
+ ) -> dict[str, Any]:
35
+ data = {
36
+ "id": str(artifact.id),
37
+ "type": artifact.type,
38
+ "payload": artifact.payload,
39
+ "produced_by": artifact.produced_by,
40
+ "visibility": artifact.visibility.model_dump(mode="json"),
41
+ "visibility_kind": getattr(artifact.visibility, "kind", "Unknown"),
42
+ "created_at": artifact.created_at.isoformat(),
43
+ "correlation_id": str(artifact.correlation_id) if artifact.correlation_id else None,
44
+ "partition_key": artifact.partition_key,
45
+ "tags": sorted(artifact.tags),
46
+ "version": artifact.version,
47
+ }
48
+ if consumptions is not None:
49
+ data["consumptions"] = [
50
+ {
51
+ "artifact_id": str(record.artifact_id),
52
+ "consumer": record.consumer,
53
+ "run_id": record.run_id,
54
+ "correlation_id": record.correlation_id,
55
+ "consumed_at": record.consumed_at.isoformat(),
56
+ }
57
+ for record in consumptions
58
+ ]
59
+ data["consumed_by"] = sorted({record.consumer for record in consumptions})
60
+ return data
61
+
62
+ def _parse_datetime(value: str | None, label: str) -> datetime | None:
63
+ if value is None:
64
+ return None
65
+ try:
66
+ return datetime.fromisoformat(value)
67
+ except ValueError as exc: # pragma: no cover - FastAPI converts
68
+ raise HTTPException(status_code=400, detail=f"Invalid {label}: {value}") from exc
69
+
70
+ def _make_filter_config(
71
+ type_names: list[str] | None,
72
+ produced_by: list[str] | None,
73
+ correlation_id: str | None,
74
+ tags: list[str] | None,
75
+ visibility: list[str] | None,
76
+ start: str | None,
77
+ end: str | None,
78
+ ) -> FilterConfig:
79
+ return FilterConfig(
80
+ type_names=set(type_names) if type_names else None,
81
+ produced_by=set(produced_by) if produced_by else None,
82
+ correlation_id=correlation_id,
83
+ tags=set(tags) if tags else None,
84
+ visibility=set(visibility) if visibility else None,
85
+ start=_parse_datetime(start, "from"),
86
+ end=_parse_datetime(end, "to"),
87
+ )
88
+
29
89
  @app.post("/api/v1/artifacts")
30
90
  async def publish_artifact(body: dict[str, Any]) -> dict[str, str]:
31
91
  type_name = body.get("type")
@@ -38,19 +98,73 @@ class BlackboardHTTPService:
38
98
  raise HTTPException(status_code=400, detail=str(exc)) from exc
39
99
  return {"status": "accepted"}
40
100
 
101
+ @app.get("/api/v1/artifacts")
102
+ async def list_artifacts(
103
+ type_names: list[str] | None = Query(None, alias="type"),
104
+ produced_by: list[str] | None = Query(None),
105
+ correlation_id: str | None = None,
106
+ tag: list[str] | None = Query(None),
107
+ start: str | None = Query(None, alias="from"),
108
+ end: str | None = Query(None, alias="to"),
109
+ visibility: list[str] | None = Query(None),
110
+ limit: int = Query(50, ge=1, le=500),
111
+ offset: int = Query(0, ge=0),
112
+ embed_meta: bool = Query(False, alias="embed_meta"),
113
+ ) -> dict[str, Any]:
114
+ filters = _make_filter_config(
115
+ type_names,
116
+ produced_by,
117
+ correlation_id,
118
+ tag,
119
+ visibility,
120
+ start,
121
+ end,
122
+ )
123
+ artifacts, total = await orchestrator.store.query_artifacts(
124
+ filters,
125
+ limit=limit,
126
+ offset=offset,
127
+ embed_meta=embed_meta,
128
+ )
129
+ items: list[dict[str, Any]] = []
130
+ for artifact in artifacts:
131
+ if isinstance(artifact, ArtifactEnvelope):
132
+ items.append(_serialize_artifact(artifact.artifact, artifact.consumptions))
133
+ else:
134
+ items.append(_serialize_artifact(artifact))
135
+ return {
136
+ "items": items,
137
+ "pagination": {"limit": limit, "offset": offset, "total": total},
138
+ }
139
+
140
+ @app.get("/api/v1/artifacts/summary")
141
+ async def summarize_artifacts(
142
+ type_names: list[str] | None = Query(None, alias="type"),
143
+ produced_by: list[str] | None = Query(None),
144
+ correlation_id: str | None = None,
145
+ tag: list[str] | None = Query(None),
146
+ start: str | None = Query(None, alias="from"),
147
+ end: str | None = Query(None, alias="to"),
148
+ visibility: list[str] | None = Query(None),
149
+ ) -> dict[str, Any]:
150
+ filters = _make_filter_config(
151
+ type_names,
152
+ produced_by,
153
+ correlation_id,
154
+ tag,
155
+ visibility,
156
+ start,
157
+ end,
158
+ )
159
+ summary = await orchestrator.store.summarize_artifacts(filters)
160
+ return {"summary": summary}
161
+
41
162
  @app.get("/api/v1/artifacts/{artifact_id}")
42
163
  async def get_artifact(artifact_id: UUID) -> dict[str, Any]:
43
164
  artifact = await orchestrator.store.get(artifact_id)
44
165
  if artifact is None:
45
166
  raise HTTPException(status_code=404, detail="artifact not found")
46
- return {
47
- "id": str(artifact.id),
48
- "type": artifact.type,
49
- "payload": artifact.payload,
50
- "produced_by": artifact.produced_by,
51
- "visibility": artifact.visibility.model_dump(mode="json"),
52
- "created_at": artifact.created_at.isoformat(),
53
- }
167
+ return _serialize_artifact(artifact)
54
168
 
55
169
  @app.post("/api/v1/agents/{name}/run")
56
170
  async def run_agent(name: str, body: dict[str, Any]) -> dict[str, Any]:
@@ -110,6 +224,29 @@ class BlackboardHTTPService:
110
224
  ]
111
225
  }
112
226
 
227
+ @app.get("/api/v1/agents/{agent_id}/history-summary")
228
+ async def agent_history(
229
+ agent_id: str,
230
+ type_names: list[str] | None = Query(None, alias="type"),
231
+ produced_by: list[str] | None = Query(None),
232
+ correlation_id: str | None = None,
233
+ tag: list[str] | None = Query(None),
234
+ start: str | None = Query(None, alias="from"),
235
+ end: str | None = Query(None, alias="to"),
236
+ visibility: list[str] | None = Query(None),
237
+ ) -> dict[str, Any]:
238
+ filters = _make_filter_config(
239
+ type_names,
240
+ produced_by,
241
+ correlation_id,
242
+ tag,
243
+ visibility,
244
+ start,
245
+ end,
246
+ )
247
+ summary = await orchestrator.store.agent_history_summary(agent_id, filters)
248
+ return {"agent_id": agent_id, "summary": summary}
249
+
113
250
  @app.get("/health")
114
251
  async def health() -> dict[str, str]: # pragma: no cover - trivial
115
252
  return {"status": "ok"}