agentgraph-server 0.5.3__tar.gz → 0.5.4__tar.gz
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.
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/.agents/skills/AgentGraph/SKILL.md +4 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/PKG-INFO +1 -1
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/backends/sqlite/backend.py +41 -19
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/cli.py +22 -8
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/config.py +1 -1
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/connectors/base.py +38 -34
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/demo.py +85 -45
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/mcp/server.py +34 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph_server.egg-info/PKG-INFO +1 -1
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/pyproject.toml +1 -1
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_cli.py +43 -36
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_config.py +1 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_launch_demo.py +57 -18
- agentgraph_server-0.5.4/tests/test_release_metadata.py +85 -0
- agentgraph_server-0.5.3/tests/test_release_metadata.py +0 -38
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/.agents/skills/AgentGraph/references/commands.md +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/.agents/skills/AgentGraph/references/data-model.md +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/.agents/skills/AgentGraph/references/operations.md +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/.agents/skills/slack-auth/SKILL.md +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/LICENSE +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/README.md +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/auth/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/auth/credentials.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/backends/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/backends/sqlite/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/backends/sqlite/schema.sql +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/backends/sqlite/vector.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/cli_query.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/cli_sync.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/connectors/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/connectors/command_effects.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/connectors/registry.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/connectors/status.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/core/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/core/context.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/core/runtime.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/core/storage.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/bookmark.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/delete.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/download.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/embeddings.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/expiration.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/fetch.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/link.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/operations.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/person.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/query.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/graph/upsert.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/logging.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/mcp/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/perf.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/__init__.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/app.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/cli_api.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/meta_api.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/observation.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/router.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/static/viewer.html +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/sync.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/server/sync_api.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph/skills.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph_server.egg-info/SOURCES.txt +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph_server.egg-info/dependency_links.txt +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph_server.egg-info/entry_points.txt +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph_server.egg-info/requires.txt +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/agentgraph_server.egg-info/top_level.txt +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/setup.cfg +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_auth.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_benchmarks.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_browse.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_command_effects.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_connectors.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_discord_attachments.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_docs_build.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_embeddings.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_expiration.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_fetch.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_graph_operations.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_logging.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_observation.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_query.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_registry.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_router.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_rss_connector.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_schema.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_server_imports.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_slack_auth_artifacts.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_slack_oauth.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_sync.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_upsert.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_viewer_layout_contract.py +0 -0
- {agentgraph_server-0.5.3 → agentgraph_server-0.5.4}/tests/test_web_connector.py +0 -0
|
@@ -13,6 +13,10 @@ Prefer the `agentgraph` CLI when a shell is available. Use the equivalent MCP to
|
|
|
13
13
|
when AgentGraph is connected directly to the agent. Do not query AgentGraph's SQLite
|
|
14
14
|
database or connector internals directly.
|
|
15
15
|
|
|
16
|
+
Use `agentgraph demo add` or `add_demo_tool` to add the fictional Atlas fixtures to
|
|
17
|
+
the configured graph. Remove them afterward with `agentgraph demo remove` or
|
|
18
|
+
`remove_demo_tool`.
|
|
19
|
+
|
|
16
20
|
## Commands that use localhost
|
|
17
21
|
|
|
18
22
|
`agentgraph poll` and connector or authentication commands that queue a poll or
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgraph-server
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: A local-first CLI and MCP server that turns selected digital sources into a searchable graph for the AI agent you already use.
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -208,9 +208,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
208
208
|
async def _run_migrations(self) -> None:
|
|
209
209
|
conn = self._conn_or_raise()
|
|
210
210
|
cursor = await conn.execute("PRAGMA table_info(entities)")
|
|
211
|
-
entity_columns = {
|
|
212
|
-
str(row["name"]): bool(row["notnull"]) for row in await cursor.fetchall()
|
|
213
|
-
}
|
|
211
|
+
entity_columns = {str(row["name"]): bool(row["notnull"]) for row in await cursor.fetchall()}
|
|
214
212
|
columns = set(entity_columns)
|
|
215
213
|
if "cumulative_dwell_ms" in columns:
|
|
216
214
|
await conn.execute(
|
|
@@ -249,9 +247,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
249
247
|
"UPDATE observations SET event_type = 'observation_threshold' "
|
|
250
248
|
"WHERE event_type = 'dwell_threshold'"
|
|
251
249
|
)
|
|
252
|
-
await conn.execute(
|
|
253
|
-
"UPDATE entities SET entity_type = 'Email' WHERE entity_type = 'Thread'"
|
|
254
|
-
)
|
|
250
|
+
await conn.execute("UPDATE entities SET entity_type = 'Email' WHERE entity_type = 'Thread'")
|
|
255
251
|
await conn.execute(
|
|
256
252
|
"CREATE INDEX IF NOT EXISTS idx_entities_bookmarked ON entities(bookmarked)"
|
|
257
253
|
)
|
|
@@ -352,10 +348,13 @@ class SQLiteBackend(StorageBackend):
|
|
|
352
348
|
"AND cumulative_observation_duration_ms > 0 THEN observed_at ELSE NULL END"
|
|
353
349
|
)
|
|
354
350
|
observed = (
|
|
355
|
-
"CASE WHEN platform = 'rss' AND entity_type = 'Folder' THEN NULL "
|
|
356
|
-
|
|
351
|
+
f"CASE WHEN platform = 'rss' AND entity_type = 'Folder' THEN NULL ELSE {observed} END"
|
|
352
|
+
)
|
|
353
|
+
duration = (
|
|
354
|
+
"cumulative_observation_duration_ms"
|
|
355
|
+
if "cumulative_observation_duration_ms" in columns
|
|
356
|
+
else "0"
|
|
357
357
|
)
|
|
358
|
-
duration = "cumulative_observation_duration_ms" if "cumulative_observation_duration_ms" in columns else "0"
|
|
359
358
|
bookmarked = "bookmarked" if "bookmarked" in columns else "0"
|
|
360
359
|
await conn.execute("PRAGMA foreign_keys=OFF")
|
|
361
360
|
try:
|
|
@@ -503,6 +502,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
503
502
|
for p in persons:
|
|
504
503
|
canonical_key = p.canonical_email or f"{p.platform}:{p.platform_user_id}"
|
|
505
504
|
meta: dict[str, str] = {}
|
|
505
|
+
meta.update({key: str(value) for key, value in p.metadata.items() if value is not None})
|
|
506
506
|
if p.canonical_email:
|
|
507
507
|
meta["canonical_email"] = p.canonical_email
|
|
508
508
|
meta[f"{p.platform}_user_id"] = p.platform_user_id
|
|
@@ -534,7 +534,9 @@ class SQLiteBackend(StorageBackend):
|
|
|
534
534
|
existing_metadata = json.loads(existing_row[3] or "{}") if existing_row else {}
|
|
535
535
|
merged_metadata = {**existing_metadata, **meta}
|
|
536
536
|
new_title = p.display_name if p.display_name is not None else existing_row[0]
|
|
537
|
-
new_content =
|
|
537
|
+
new_content = (
|
|
538
|
+
p.canonical_email if p.canonical_email is not None else existing_row[1]
|
|
539
|
+
)
|
|
538
540
|
new_embedding = emb_blob if emb_blob is not None else existing_row[2]
|
|
539
541
|
changed = (
|
|
540
542
|
new_title != existing_row[0]
|
|
@@ -647,9 +649,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
647
649
|
if parent_id is None:
|
|
648
650
|
parent_id = await self._resolve_existing_entity_id(conn, e.platform, parent_ref)
|
|
649
651
|
if parent_id is None:
|
|
650
|
-
raise ValueError(
|
|
651
|
-
f"Retention parent {e.platform}:{parent_ref} is not available"
|
|
652
|
-
)
|
|
652
|
+
raise ValueError(f"Retention parent {e.platform}:{parent_ref} is not available")
|
|
653
653
|
elif e.retention_parent_platform_entity_id is not None:
|
|
654
654
|
raise ValueError(
|
|
655
655
|
f"Entity {e.platform}:{e.platform_entity_id} has a parent but is not owned"
|
|
@@ -689,8 +689,12 @@ class SQLiteBackend(StorageBackend):
|
|
|
689
689
|
[e.platform, e.platform_entity_id],
|
|
690
690
|
)
|
|
691
691
|
existing_row = await existing_cursor.fetchone()
|
|
692
|
-
existing_title =
|
|
693
|
-
|
|
692
|
+
existing_title = (
|
|
693
|
+
str(existing_row["title"]) if existing_row and existing_row["title"] else ""
|
|
694
|
+
)
|
|
695
|
+
existing_content = (
|
|
696
|
+
str(existing_row["content"]) if existing_row and existing_row["content"] else ""
|
|
697
|
+
)
|
|
694
698
|
fts_title = e.title if e.title is not None else existing_title
|
|
695
699
|
fts_content = e.content if e.content is not None else existing_content
|
|
696
700
|
rewrite_fts = (
|
|
@@ -1734,7 +1738,14 @@ class SQLiteBackend(StorageBackend):
|
|
|
1734
1738
|
WHERE platform = ? AND platform_entity_id = ?
|
|
1735
1739
|
AND retention_policy = 'observed'
|
|
1736
1740
|
""",
|
|
1737
|
-
[
|
|
1741
|
+
[
|
|
1742
|
+
observation_duration_ms,
|
|
1743
|
+
now,
|
|
1744
|
+
observation_duration_ms,
|
|
1745
|
+
now,
|
|
1746
|
+
platform,
|
|
1747
|
+
platform_entity_id,
|
|
1748
|
+
],
|
|
1738
1749
|
)
|
|
1739
1750
|
|
|
1740
1751
|
async def record_observation_once(
|
|
@@ -1772,7 +1783,14 @@ class SQLiteBackend(StorageBackend):
|
|
|
1772
1783
|
WHERE platform = ? AND platform_entity_id = ?
|
|
1773
1784
|
AND retention_policy = 'observed'
|
|
1774
1785
|
""",
|
|
1775
|
-
[
|
|
1786
|
+
[
|
|
1787
|
+
observation_duration_ms,
|
|
1788
|
+
now,
|
|
1789
|
+
observation_duration_ms,
|
|
1790
|
+
now,
|
|
1791
|
+
platform,
|
|
1792
|
+
platform_entity_id,
|
|
1793
|
+
],
|
|
1776
1794
|
)
|
|
1777
1795
|
await conn.execute("COMMIT")
|
|
1778
1796
|
return inserted is not None
|
|
@@ -1871,8 +1889,12 @@ def _row_to_entity(row: Any) -> EntityResult:
|
|
|
1871
1889
|
"synced_at": row["synced_at"] if "synced_at" in keys else None,
|
|
1872
1890
|
"observed_at": row["observed_at"] if "observed_at" in keys else None,
|
|
1873
1891
|
"retention_policy": row["retention_policy"] if "retention_policy" in keys else "observed",
|
|
1874
|
-
"retention_parent_id": row["retention_parent_id"]
|
|
1875
|
-
|
|
1892
|
+
"retention_parent_id": row["retention_parent_id"]
|
|
1893
|
+
if "retention_parent_id" in keys
|
|
1894
|
+
else None,
|
|
1895
|
+
"cumulative_observation_duration_ms": row["cumulative_observation_duration_ms"]
|
|
1896
|
+
if "cumulative_observation_duration_ms" in keys
|
|
1897
|
+
else 0,
|
|
1876
1898
|
"bookmarked": bool(row["bookmarked"]) if "bookmarked" in keys else False,
|
|
1877
1899
|
"score": row["score"] if "score" in keys else None,
|
|
1878
1900
|
}
|
|
@@ -50,30 +50,44 @@ def _status_label(status: str) -> str:
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
@demo_app.command("
|
|
54
|
-
def
|
|
55
|
-
force: bool = typer.Option(False, "--force", help="Replace an existing demo database"),
|
|
53
|
+
@demo_app.command("add")
|
|
54
|
+
def add_demo_command(
|
|
56
55
|
json: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
57
56
|
) -> None:
|
|
58
|
-
"""
|
|
57
|
+
"""Add the fictional Atlas graph to the configured database."""
|
|
59
58
|
from agentgraph.config import get_config_paths
|
|
60
|
-
from agentgraph.demo import
|
|
59
|
+
from agentgraph.demo import add_demo
|
|
61
60
|
|
|
62
61
|
try:
|
|
63
|
-
result = asyncio.run(
|
|
64
|
-
except
|
|
62
|
+
result = asyncio.run(add_demo(get_config_paths()[0]))
|
|
63
|
+
except ValueError as exc:
|
|
65
64
|
typer.echo(str(exc), err=True)
|
|
66
65
|
raise typer.Exit(code=1) from exc
|
|
67
66
|
|
|
68
67
|
if json:
|
|
69
68
|
typer.echo(_json.dumps(result, indent=2))
|
|
70
69
|
return
|
|
71
|
-
typer.echo(f"
|
|
70
|
+
typer.echo(f"Added Atlas demo fixtures to {result['database']}")
|
|
72
71
|
typer.echo(
|
|
73
72
|
f"{result['entities']} entities, {result['persons']} people, {result['edges']} edges"
|
|
74
73
|
)
|
|
75
74
|
|
|
76
75
|
|
|
76
|
+
@demo_app.command("remove")
|
|
77
|
+
def remove_demo_command(
|
|
78
|
+
json: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Remove Atlas demo fixtures from the configured database."""
|
|
81
|
+
from agentgraph.config import get_config_paths
|
|
82
|
+
from agentgraph.demo import remove_demo
|
|
83
|
+
|
|
84
|
+
result = asyncio.run(remove_demo(get_config_paths()[0]))
|
|
85
|
+
if json:
|
|
86
|
+
typer.echo(_json.dumps(result, indent=2))
|
|
87
|
+
return
|
|
88
|
+
typer.echo(f"Removed {result['removed']} Atlas demo entities from {result['database']}")
|
|
89
|
+
|
|
90
|
+
|
|
77
91
|
def _remove_auth_credentials(provider: str, account_id: str | None) -> dict[str, object]:
|
|
78
92
|
from agentgraph.auth.credentials import remove_platform, remove_platform_account
|
|
79
93
|
|
|
@@ -125,7 +125,7 @@ class Settings(BaseSettings):
|
|
|
125
125
|
)
|
|
126
126
|
# Logging
|
|
127
127
|
log_level: str = Field(default="INFO")
|
|
128
|
-
log_file: Path = Field(
|
|
128
|
+
log_file: Path = Field(default_factory=lambda: get_config_paths()[0] / "agentgraph.log")
|
|
129
129
|
|
|
130
130
|
def __init__(self, **values: Any) -> None:
|
|
131
131
|
# Resolve the config-directory .env at instantiation time as well as
|
|
@@ -34,13 +34,13 @@ _URL_RE = re.compile(r"https?://\S+")
|
|
|
34
34
|
|
|
35
35
|
# Maps ResourceType values to entity_type strings stored in the DB
|
|
36
36
|
RESOURCE_TYPE_TO_ENTITY_TYPE: dict[str, str] = {
|
|
37
|
-
"channel":
|
|
38
|
-
"dm":
|
|
39
|
-
"document":
|
|
40
|
-
"folder":
|
|
41
|
-
"message":
|
|
37
|
+
"channel": "Channel",
|
|
38
|
+
"dm": "Channel",
|
|
39
|
+
"document": "Document",
|
|
40
|
+
"folder": "Folder",
|
|
41
|
+
"message": "Message",
|
|
42
42
|
"spreadsheet": "Spreadsheet",
|
|
43
|
-
"thread":
|
|
43
|
+
"thread": "Email",
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
|
|
@@ -76,10 +76,11 @@ class PersonRecord(BaseModel):
|
|
|
76
76
|
platform_username: str | None = None
|
|
77
77
|
canonical_email: str | None = None
|
|
78
78
|
display_name: str | None = None
|
|
79
|
+
metadata: dict[str, str | int | float | bool | None] = {}
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
class EntityRecord(BaseModel):
|
|
82
|
-
entity_type: str
|
|
83
|
+
entity_type: str # 'Message' | 'Document' | 'Channel' | 'Task'
|
|
83
84
|
platform: str
|
|
84
85
|
platform_entity_id: str
|
|
85
86
|
title: str | None = None
|
|
@@ -87,13 +88,13 @@ class EntityRecord(BaseModel):
|
|
|
87
88
|
source_created_at: datetime | None = None
|
|
88
89
|
source_updated_at: datetime | None = None
|
|
89
90
|
metadata: dict[str, str | int | float | bool | None] = {}
|
|
90
|
-
is_stub: bool = False
|
|
91
|
+
is_stub: bool = False # True → placeholder pending a full fetch; preserves synced_at=NULL
|
|
91
92
|
retention_policy: RetentionPolicy = "observed"
|
|
92
93
|
retention_parent_platform_entity_id: str | None = None
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
class EdgeRecord(BaseModel):
|
|
96
|
-
edge_type: str
|
|
97
|
+
edge_type: str # 'authored' | 'posted_in' | 'replied_to' | 'mentions'
|
|
97
98
|
source_platform_entity_id: str | None = None
|
|
98
99
|
source_platform_user_id: str | None = None
|
|
99
100
|
target_platform_entity_id: str | None = None
|
|
@@ -132,18 +133,22 @@ class EntityBatch(BaseModel):
|
|
|
132
133
|
if ref.source == entity.platform and ref.resource_id == entity.platform_entity_id:
|
|
133
134
|
continue
|
|
134
135
|
seen.add(key)
|
|
135
|
-
self.entities.append(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
136
|
+
self.entities.append(
|
|
137
|
+
EntityRecord(
|
|
138
|
+
entity_type=RESOURCE_TYPE_TO_ENTITY_TYPE[ref.resource_type],
|
|
139
|
+
platform=ref.source,
|
|
140
|
+
platform_entity_id=ref.resource_id,
|
|
141
|
+
is_stub=True,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
self.edges.append(
|
|
145
|
+
EdgeRecord(
|
|
146
|
+
edge_type="references",
|
|
147
|
+
source_platform_entity_id=entity.platform_entity_id,
|
|
148
|
+
target_platform_entity_id=ref.resource_id,
|
|
149
|
+
platform="cross",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
147
152
|
|
|
148
153
|
|
|
149
154
|
async def get_known_channel_syncs(
|
|
@@ -195,7 +200,6 @@ class ConnectorAccount(BaseModel):
|
|
|
195
200
|
metadata: dict[str, str] = Field(default_factory=dict)
|
|
196
201
|
|
|
197
202
|
|
|
198
|
-
|
|
199
203
|
class FetchPolicy:
|
|
200
204
|
"""Encapsulates refresh policy decisions for a resource."""
|
|
201
205
|
|
|
@@ -222,7 +226,7 @@ class FetchPolicy:
|
|
|
222
226
|
|
|
223
227
|
|
|
224
228
|
class BaseConnector(ABC):
|
|
225
|
-
source: ClassVar[str]
|
|
229
|
+
source: ClassVar[str] # platform name, e.g. "slack" — must be set by subclass
|
|
226
230
|
fetch_policy: ClassVar[FetchPolicy] # staleness policy — must be set by subclass
|
|
227
231
|
is_generic_url_fallback: ClassVar[bool] = False
|
|
228
232
|
"""True for broad fallback connectors that should not claim URLs during discovery."""
|
|
@@ -280,14 +284,16 @@ class BaseConnector(ABC):
|
|
|
280
284
|
user = cls.get_authenticated_user()
|
|
281
285
|
if user is None:
|
|
282
286
|
return []
|
|
283
|
-
return [
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
return [
|
|
288
|
+
ConnectorAccount(
|
|
289
|
+
account_id=cls.source,
|
|
290
|
+
label=user,
|
|
291
|
+
auth_group=cls.auth_label or cls.source,
|
|
292
|
+
source=cls.source,
|
|
293
|
+
user_id=user,
|
|
294
|
+
email=user if "@" in user else None,
|
|
295
|
+
)
|
|
296
|
+
]
|
|
291
297
|
|
|
292
298
|
@classmethod
|
|
293
299
|
async def verify_auth(cls, account_id: str | None = None) -> tuple[str, str | None]:
|
|
@@ -339,9 +345,7 @@ class BaseConnector(ABC):
|
|
|
339
345
|
_ = (args, result)
|
|
340
346
|
return ConnectorCommandEffects()
|
|
341
347
|
|
|
342
|
-
def normalise_fetch_id(
|
|
343
|
-
self, resource_id: str, entity_type: str
|
|
344
|
-
) -> tuple[str, ResourceType]:
|
|
348
|
+
def normalise_fetch_id(self, resource_id: str, entity_type: str) -> tuple[str, ResourceType]:
|
|
345
349
|
"""Map a stored resource_id + entity_type to the (id, resource_type) that fetch() expects.
|
|
346
350
|
|
|
347
351
|
The default maps entity_type to ResourceType using the standard table and
|
|
@@ -11,6 +11,8 @@ from agentgraph.backends.sqlite.backend import SQLiteBackend
|
|
|
11
11
|
from agentgraph.connectors.base import EdgeRecord, EntityBatch, EntityRecord, PersonRecord
|
|
12
12
|
|
|
13
13
|
DEMO_DATABASE_NAME = "agentgraph.db"
|
|
14
|
+
DEMO_FIXTURE_METADATA_KEY = "_agentgraph_fixture"
|
|
15
|
+
DEMO_FIXTURE_METADATA_VALUE = "atlas-demo"
|
|
14
16
|
DEMO_CREATED_AT = "2026-08-14T08:00:00Z"
|
|
15
17
|
WEBHOOK_ARTICLE_URL = (
|
|
16
18
|
"https://github.com/simonexmachina/agent-graph/blob/main/"
|
|
@@ -28,10 +30,7 @@ def utc(year: int, month: int, day: int, hour: int, minute: int = 0) -> datetime
|
|
|
28
30
|
|
|
29
31
|
def load_research_fixture(filename: str) -> str:
|
|
30
32
|
return (
|
|
31
|
-
files("agentgraph")
|
|
32
|
-
.joinpath("demo_fixtures", filename)
|
|
33
|
-
.read_text(encoding="utf-8")
|
|
34
|
-
.strip()
|
|
33
|
+
files("agentgraph").joinpath("demo_fixtures", filename).read_text(encoding="utf-8").strip()
|
|
35
34
|
)
|
|
36
35
|
|
|
37
36
|
|
|
@@ -44,6 +43,7 @@ def build_demo_batch() -> EntityBatch:
|
|
|
44
43
|
drive_folder_id = "atlas-program-folder"
|
|
45
44
|
drive_plan_id = "atlas-integration-plan"
|
|
46
45
|
|
|
46
|
+
fixture_metadata = {DEMO_FIXTURE_METADATA_KEY: DEMO_FIXTURE_METADATA_VALUE}
|
|
47
47
|
entities = [
|
|
48
48
|
EntityRecord(
|
|
49
49
|
entity_type="Channel",
|
|
@@ -53,6 +53,7 @@ def build_demo_batch() -> EntityBatch:
|
|
|
53
53
|
source_created_at=utc(2026, 7, 1, 9),
|
|
54
54
|
source_updated_at=utc(2026, 8, 10, 14),
|
|
55
55
|
metadata={
|
|
56
|
+
**fixture_metadata,
|
|
56
57
|
"web_url": "https://app.slack.com/client/TDEMO/CATLAS",
|
|
57
58
|
"workspace_name": "AgentGraph Demo",
|
|
58
59
|
},
|
|
@@ -69,7 +70,10 @@ def build_demo_batch() -> EntityBatch:
|
|
|
69
70
|
),
|
|
70
71
|
source_created_at=utc(2026, 8, 10, 10),
|
|
71
72
|
source_updated_at=utc(2026, 8, 10, 10),
|
|
72
|
-
metadata={
|
|
73
|
+
metadata={
|
|
74
|
+
**fixture_metadata,
|
|
75
|
+
"web_url": "https://app.slack.com/client/TDEMO/CATLAS/thread-1",
|
|
76
|
+
},
|
|
73
77
|
retention_policy="owned",
|
|
74
78
|
retention_parent_platform_entity_id=channel_id,
|
|
75
79
|
),
|
|
@@ -83,7 +87,10 @@ def build_demo_batch() -> EntityBatch:
|
|
|
83
87
|
),
|
|
84
88
|
source_created_at=utc(2026, 8, 10, 10, 30),
|
|
85
89
|
source_updated_at=utc(2026, 8, 10, 10, 30),
|
|
86
|
-
metadata={
|
|
90
|
+
metadata={
|
|
91
|
+
**fixture_metadata,
|
|
92
|
+
"web_url": "https://app.slack.com/client/TDEMO/CATLAS/thread-1-reply-1",
|
|
93
|
+
},
|
|
87
94
|
retention_policy="owned",
|
|
88
95
|
retention_parent_platform_entity_id=channel_id,
|
|
89
96
|
),
|
|
@@ -97,7 +104,10 @@ def build_demo_batch() -> EntityBatch:
|
|
|
97
104
|
),
|
|
98
105
|
source_created_at=utc(2026, 8, 10, 11),
|
|
99
106
|
source_updated_at=utc(2026, 8, 10, 11),
|
|
100
|
-
metadata={
|
|
107
|
+
metadata={
|
|
108
|
+
**fixture_metadata,
|
|
109
|
+
"web_url": "https://app.slack.com/client/TDEMO/CATLAS/thread-1-reply-2",
|
|
110
|
+
},
|
|
101
111
|
retention_policy="owned",
|
|
102
112
|
retention_parent_platform_entity_id=channel_id,
|
|
103
113
|
),
|
|
@@ -116,6 +126,7 @@ def build_demo_batch() -> EntityBatch:
|
|
|
116
126
|
source_created_at=utc(2026, 8, 9, 15),
|
|
117
127
|
source_updated_at=utc(2026, 8, 9, 15),
|
|
118
128
|
metadata={
|
|
129
|
+
**fixture_metadata,
|
|
119
130
|
"web_url": "https://mail.google.com/mail/u/0/#all/atlas-maya-thread",
|
|
120
131
|
"snippet": "For the September 30 Atlas launch...",
|
|
121
132
|
},
|
|
@@ -127,7 +138,10 @@ def build_demo_batch() -> EntityBatch:
|
|
|
127
138
|
title="Atlas program",
|
|
128
139
|
source_created_at=utc(2026, 7, 1, 8),
|
|
129
140
|
source_updated_at=utc(2026, 8, 6, 9),
|
|
130
|
-
metadata={
|
|
141
|
+
metadata={
|
|
142
|
+
**fixture_metadata,
|
|
143
|
+
"web_url": "https://drive.google.com/drive/folders/atlas-program-folder",
|
|
144
|
+
},
|
|
131
145
|
),
|
|
132
146
|
EntityRecord(
|
|
133
147
|
entity_type="Document",
|
|
@@ -143,6 +157,7 @@ def build_demo_batch() -> EntityBatch:
|
|
|
143
157
|
source_created_at=utc(2026, 8, 2, 9),
|
|
144
158
|
source_updated_at=utc(2026, 8, 6, 16),
|
|
145
159
|
metadata={
|
|
160
|
+
**fixture_metadata,
|
|
146
161
|
"web_url": "https://drive.google.com/file/d/atlas-integration-plan/view",
|
|
147
162
|
"mime_type": "text/markdown",
|
|
148
163
|
},
|
|
@@ -155,7 +170,7 @@ def build_demo_batch() -> EntityBatch:
|
|
|
155
170
|
content=load_research_fixture("reliable-webhooks.md"),
|
|
156
171
|
source_created_at=utc(2026, 8, 4, 9),
|
|
157
172
|
source_updated_at=utc(2026, 8, 4, 9),
|
|
158
|
-
metadata={"web_url": WEBHOOK_ARTICLE_URL, "author": "Nadia Okafor"},
|
|
173
|
+
metadata={**fixture_metadata, "web_url": WEBHOOK_ARTICLE_URL, "author": "Nadia Okafor"},
|
|
159
174
|
),
|
|
160
175
|
EntityRecord(
|
|
161
176
|
entity_type="Document",
|
|
@@ -165,7 +180,7 @@ def build_demo_batch() -> EntityBatch:
|
|
|
165
180
|
content=load_research_fixture("retry-guidance.md"),
|
|
166
181
|
source_created_at=utc(2026, 8, 8, 9),
|
|
167
182
|
source_updated_at=utc(2026, 8, 8, 9),
|
|
168
|
-
metadata={"web_url": RETRY_GUIDE_URL, "revision": "3"},
|
|
183
|
+
metadata={**fixture_metadata, "web_url": RETRY_GUIDE_URL, "revision": "3"},
|
|
169
184
|
),
|
|
170
185
|
]
|
|
171
186
|
|
|
@@ -176,12 +191,14 @@ def build_demo_batch() -> EntityBatch:
|
|
|
176
191
|
platform_username="alex",
|
|
177
192
|
canonical_email="alex@agentgraph.demo",
|
|
178
193
|
display_name="Alex Chen",
|
|
194
|
+
metadata=fixture_metadata,
|
|
179
195
|
),
|
|
180
196
|
PersonRecord(
|
|
181
197
|
platform="gdrive",
|
|
182
198
|
platform_user_id="alex@agentgraph.demo",
|
|
183
199
|
canonical_email="alex@agentgraph.demo",
|
|
184
200
|
display_name="Alex Chen",
|
|
201
|
+
metadata=fixture_metadata,
|
|
185
202
|
),
|
|
186
203
|
PersonRecord(
|
|
187
204
|
platform="slack",
|
|
@@ -189,12 +206,14 @@ def build_demo_batch() -> EntityBatch:
|
|
|
189
206
|
platform_username="priya",
|
|
190
207
|
canonical_email="priya@agentgraph.demo",
|
|
191
208
|
display_name="Priya Raman",
|
|
209
|
+
metadata=fixture_metadata,
|
|
192
210
|
),
|
|
193
211
|
PersonRecord(
|
|
194
212
|
platform="gmail",
|
|
195
213
|
platform_user_id="maya@customer.example",
|
|
196
214
|
canonical_email="maya@customer.example",
|
|
197
215
|
display_name="Maya Patel",
|
|
216
|
+
metadata=fixture_metadata,
|
|
198
217
|
),
|
|
199
218
|
]
|
|
200
219
|
|
|
@@ -284,21 +303,6 @@ def build_demo_batch() -> EntityBatch:
|
|
|
284
303
|
return EntityBatch(entities=entities, persons=persons, edges=edges)
|
|
285
304
|
|
|
286
305
|
|
|
287
|
-
def validate_demo_config_dir(config_dir: Path) -> Path:
|
|
288
|
-
resolved = config_dir.expanduser().resolve()
|
|
289
|
-
default_dir = (Path.home() / ".agentgraph").resolve()
|
|
290
|
-
if resolved == default_dir:
|
|
291
|
-
raise ValueError("Refusing to seed the default ~/.agentgraph directory")
|
|
292
|
-
return resolved
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def remove_demo_database(database_path: Path) -> None:
|
|
296
|
-
for suffix in ("", "-wal", "-shm"):
|
|
297
|
-
candidate = Path(f"{database_path}{suffix}")
|
|
298
|
-
if candidate.exists():
|
|
299
|
-
candidate.unlink()
|
|
300
|
-
|
|
301
|
-
|
|
302
306
|
def apply_demo_timestamps(database_path: Path) -> None:
|
|
303
307
|
observations = {
|
|
304
308
|
("slack", "TDEMO/CATLAS"): "2026-08-10T11:05:00Z",
|
|
@@ -308,29 +312,41 @@ def apply_demo_timestamps(database_path: Path) -> None:
|
|
|
308
312
|
}
|
|
309
313
|
with sqlite3.connect(database_path) as conn:
|
|
310
314
|
conn.execute(
|
|
311
|
-
"
|
|
312
|
-
|
|
315
|
+
"""
|
|
316
|
+
UPDATE entities
|
|
317
|
+
SET created_at = ?, updated_at = ?, synced_at = ?
|
|
318
|
+
WHERE json_extract(metadata, ?) = ?
|
|
319
|
+
""",
|
|
320
|
+
[
|
|
321
|
+
DEMO_CREATED_AT,
|
|
322
|
+
DEMO_CREATED_AT,
|
|
323
|
+
DEMO_CREATED_AT,
|
|
324
|
+
f"$.{DEMO_FIXTURE_METADATA_KEY}",
|
|
325
|
+
DEMO_FIXTURE_METADATA_VALUE,
|
|
326
|
+
],
|
|
313
327
|
)
|
|
314
328
|
for (platform, platform_entity_id), observed_at in observations.items():
|
|
315
329
|
conn.execute(
|
|
316
330
|
"""
|
|
317
331
|
UPDATE entities
|
|
318
332
|
SET observed_at = ?, cumulative_observation_duration_ms = 3000
|
|
319
|
-
WHERE platform = ?
|
|
333
|
+
WHERE platform = ?
|
|
334
|
+
AND platform_entity_id = ?
|
|
335
|
+
AND json_extract(metadata, ?) = ?
|
|
320
336
|
""",
|
|
321
|
-
[
|
|
337
|
+
[
|
|
338
|
+
observed_at,
|
|
339
|
+
platform,
|
|
340
|
+
platform_entity_id,
|
|
341
|
+
f"$.{DEMO_FIXTURE_METADATA_KEY}",
|
|
342
|
+
DEMO_FIXTURE_METADATA_VALUE,
|
|
343
|
+
],
|
|
322
344
|
)
|
|
323
345
|
|
|
324
346
|
|
|
325
|
-
async def
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if database_path.exists() and not force:
|
|
329
|
-
raise FileExistsError(f"Demo database already exists: {database_path}. Use --force to replace it.")
|
|
330
|
-
safe_dir.mkdir(parents=True, exist_ok=True)
|
|
331
|
-
if force:
|
|
332
|
-
remove_demo_database(database_path)
|
|
333
|
-
|
|
347
|
+
async def add_demo(config_dir: Path) -> dict[str, object]:
|
|
348
|
+
database_path = config_dir / DEMO_DATABASE_NAME
|
|
349
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
334
350
|
batch = build_demo_batch()
|
|
335
351
|
backend = SQLiteBackend(str(database_path), vector_mode="bm25-only")
|
|
336
352
|
await backend.initialize()
|
|
@@ -339,13 +355,11 @@ async def seed_demo(config_dir: Path, *, force: bool = False) -> dict[str, objec
|
|
|
339
355
|
finally:
|
|
340
356
|
await backend.close()
|
|
341
357
|
apply_demo_timestamps(database_path)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
)
|
|
348
|
-
edge_count = int(conn.execute("SELECT count(*) FROM edges").fetchone()[0])
|
|
358
|
+
entity_count = len(batch.entities)
|
|
359
|
+
person_count = len(
|
|
360
|
+
{p.canonical_email or f"{p.platform}:{p.platform_user_id}" for p in batch.persons}
|
|
361
|
+
)
|
|
362
|
+
edge_count = len(batch.edges)
|
|
349
363
|
return {
|
|
350
364
|
"database": str(database_path),
|
|
351
365
|
"entities": entity_count,
|
|
@@ -354,3 +368,29 @@ async def seed_demo(config_dir: Path, *, force: bool = False) -> dict[str, objec
|
|
|
354
368
|
"webhook_article": WEBHOOK_ARTICLE_URL,
|
|
355
369
|
"retry_guide": RETRY_GUIDE_URL,
|
|
356
370
|
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
async def remove_demo(config_dir: Path) -> dict[str, object]:
|
|
374
|
+
database_path = config_dir / DEMO_DATABASE_NAME
|
|
375
|
+
if not database_path.exists():
|
|
376
|
+
return {"database": str(database_path), "removed": 0}
|
|
377
|
+
|
|
378
|
+
with sqlite3.connect(database_path) as conn:
|
|
379
|
+
rows = conn.execute(
|
|
380
|
+
"""
|
|
381
|
+
SELECT id FROM entities
|
|
382
|
+
WHERE json_extract(metadata, ?) = ?
|
|
383
|
+
""",
|
|
384
|
+
[f"$.{DEMO_FIXTURE_METADATA_KEY}", DEMO_FIXTURE_METADATA_VALUE],
|
|
385
|
+
).fetchall()
|
|
386
|
+
entity_ids = [str(row[0]) for row in rows]
|
|
387
|
+
|
|
388
|
+
backend = SQLiteBackend(str(database_path), vector_mode="bm25-only")
|
|
389
|
+
await backend.initialize()
|
|
390
|
+
try:
|
|
391
|
+
for entity_id in entity_ids:
|
|
392
|
+
if await backend.get_entity_by_id(entity_id) is not None:
|
|
393
|
+
await backend.delete_entity(entity_id)
|
|
394
|
+
finally:
|
|
395
|
+
await backend.close()
|
|
396
|
+
return {"database": str(database_path), "removed": len(entity_ids)}
|
|
@@ -326,6 +326,40 @@ async def run_connector_command_tool(source: str, args: list[str]) -> str:
|
|
|
326
326
|
return json.dumps({"error": str(exc)})
|
|
327
327
|
|
|
328
328
|
|
|
329
|
+
@mcp.tool(
|
|
330
|
+
annotations=_tool_annotations(
|
|
331
|
+
"Add AgentGraph demo fixtures",
|
|
332
|
+
read_only=False,
|
|
333
|
+
destructive=False,
|
|
334
|
+
idempotent=True,
|
|
335
|
+
open_world=False,
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
async def add_demo_tool() -> str:
|
|
339
|
+
"""Add the fictional Atlas demo fixtures to the configured database."""
|
|
340
|
+
from agentgraph.config import get_config_paths
|
|
341
|
+
from agentgraph.demo import add_demo
|
|
342
|
+
|
|
343
|
+
return json.dumps(await add_demo(get_config_paths()[0]))
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@mcp.tool(
|
|
347
|
+
annotations=_tool_annotations(
|
|
348
|
+
"Remove AgentGraph demo fixtures",
|
|
349
|
+
read_only=False,
|
|
350
|
+
destructive=True,
|
|
351
|
+
idempotent=True,
|
|
352
|
+
open_world=False,
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
async def remove_demo_tool() -> str:
|
|
356
|
+
"""Remove marked Atlas demo fixtures from the configured database."""
|
|
357
|
+
from agentgraph.config import get_config_paths
|
|
358
|
+
from agentgraph.demo import remove_demo
|
|
359
|
+
|
|
360
|
+
return json.dumps(await remove_demo(get_config_paths()[0]))
|
|
361
|
+
|
|
362
|
+
|
|
329
363
|
async def _enrich_results(results: list[dict[str, Any]]) -> None:
|
|
330
364
|
"""Let each owning connector apply result presentation fixes in-place."""
|
|
331
365
|
from agentgraph.connectors.registry import bootstrap, get_connector
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgraph-server
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: A local-first CLI and MCP server that turns selected digital sources into a searchable graph for the AI agent you already use.
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|