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.
- flock/agent.py +205 -27
- flock/cli.py +74 -2
- flock/dashboard/websocket.py +13 -2
- flock/engines/dspy_engine.py +70 -13
- flock/examples.py +4 -1
- flock/frontend/README.md +15 -1
- flock/frontend/package-lock.json +11 -21
- flock/frontend/package.json +1 -1
- flock/frontend/src/App.tsx +74 -6
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +4 -5
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +7 -3
- flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
- flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
- flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
- flock/frontend/src/components/filters/FilterPills.module.css +186 -45
- flock/frontend/src/components/filters/FilterPills.test.tsx +115 -99
- flock/frontend/src/components/filters/FilterPills.tsx +120 -44
- flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
- flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
- flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
- flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
- flock/frontend/src/components/filters/TagFilter.tsx +21 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +24 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +6 -1
- flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +24 -0
- flock/frontend/src/components/layout/DashboardLayout.css +13 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +8 -24
- flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +460 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +7 -1
- flock/frontend/src/components/modules/registerModules.ts +9 -10
- flock/frontend/src/hooks/useModules.ts +11 -1
- flock/frontend/src/services/api.ts +140 -0
- flock/frontend/src/services/indexeddb.ts +56 -2
- flock/frontend/src/services/websocket.ts +129 -0
- flock/frontend/src/store/filterStore.test.ts +105 -185
- flock/frontend/src/store/filterStore.ts +173 -26
- flock/frontend/src/store/graphStore.test.ts +19 -0
- flock/frontend/src/store/graphStore.ts +166 -27
- flock/frontend/src/types/filters.ts +34 -1
- flock/frontend/src/types/graph.ts +7 -0
- flock/frontend/src/utils/artifacts.ts +24 -0
- flock/mcp/client.py +25 -1
- flock/mcp/config.py +1 -10
- flock/mcp/manager.py +34 -3
- flock/mcp/types/callbacks.py +4 -1
- flock/orchestrator.py +56 -5
- flock/service.py +146 -9
- flock/store.py +971 -24
- {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/METADATA +27 -1
- {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/RECORD +56 -49
- flock/frontend/src/components/filters/FilterBar.module.css +0 -29
- flock/frontend/src/components/filters/FilterBar.test.tsx +0 -133
- flock/frontend/src/components/filters/FilterBar.tsx +0 -33
- flock/frontend/src/components/modules/EventLogModule.test.tsx +0 -401
- flock/frontend/src/components/modules/EventLogModule.tsx +0 -396
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +0 -17
- {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/entry_points.txt +0 -0
- {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"}
|