agentgraph-server 0.8.0__tar.gz → 0.9.0__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.9.0/.agents/skills/agentgraph/SKILL.md +54 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/.agents/skills/agentgraph/references/commands.md +5 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/.agents/skills/agentgraph/references/operations.md +28 -14
- {agentgraph_server-0.8.0/agentgraph_server.egg-info → agentgraph_server-0.9.0}/PKG-INFO +14 -14
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/README.md +1 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/backends/sqlite/backend.py +99 -31
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/cli.py +11 -4
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/cli_query.py +16 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/base.py +2 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/command_effects.py +10 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/feed.py +12 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/status.py +1 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/core/storage.py +26 -0
- agentgraph_server-0.9.0/agentgraph/graph/delete.py +102 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/fetch.py +3 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/upsert.py +7 -2
- agentgraph_server-0.9.0/agentgraph/mcp/models.py +131 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/mcp/server.py +264 -177
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/query_client.py +14 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/graph_api.py +10 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/meta_api.py +2 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/static/viewer.html +175 -22
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0/agentgraph_server.egg-info}/PKG-INFO +14 -14
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph_server.egg-info/SOURCES.txt +1 -0
- agentgraph_server-0.9.0/agentgraph_server.egg-info/requires.txt +38 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/pyproject.toml +13 -13
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_auth.py +8 -9
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_browse.py +21 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_cli.py +69 -21
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_connector_discovery.py +16 -3
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_docs_build.py +15 -6
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_expiration.py +36 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_fetch.py +17 -2
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_mcp_transport.py +17 -4
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_observed_since.py +10 -1
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_query.py +246 -89
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_query_client.py +2 -2
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_release_metadata.py +2 -2
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_route_resolution.py +1 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_rss_connector.py +197 -230
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_schema.py +51 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_upsert.py +44 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_viewer_layout_contract.py +75 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_web_connector.py +23 -129
- agentgraph_server-0.8.0/.agents/skills/agentgraph/SKILL.md +0 -113
- agentgraph_server-0.8.0/agentgraph/graph/delete.py +0 -40
- agentgraph_server-0.8.0/agentgraph_server.egg-info/requires.txt +0 -38
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/.agents/skills/agentgraph/references/data-model.md +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/.agents/skills/slack-auth/SKILL.md +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/LICENSE +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/auth/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/auth/credentials.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/backends/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/backends/sqlite/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/backends/sqlite/schema.sql +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/backends/sqlite/vector.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/cli_sync.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/config.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/match_patterns.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/registry.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/core/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/core/context.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/core/runtime.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/demo.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/bookmark.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/download.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/embeddings.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/expiration.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/link.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/operations.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/person.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/graph/query.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/logging.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/mcp/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/perf.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/__init__.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/app.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/browse_api.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/observation.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/presentation.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/router.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/sync.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/sync_api.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/server/uds.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/skills.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph_server.egg-info/dependency_links.txt +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph_server.egg-info/entry_points.txt +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph_server.egg-info/top_level.txt +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/setup.cfg +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_auth_status_methods.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_benchmarks.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_command_effects.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_config.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_connectors.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_discord_attachments.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_embeddings.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_entity_types.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_feed_connectors.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_graph_operations.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_launch_demo.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_logging.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_match_patterns.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_observation.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_query_parity.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_registry.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_router.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_server_imports.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_server_lifespan.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_server_uds.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_slack_auth_artifacts.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_slack_oauth.py +0 -0
- {agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/tests/test_sync.py +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentgraph
|
|
3
|
+
description: Use AgentGraph through its CLI or MCP tools to search and traverse selected local context, retrieve full source-backed evidence, fetch missing or stale resources, and troubleshoot connector availability.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AgentGraph skill
|
|
7
|
+
|
|
8
|
+
AgentGraph is a local graph of selected messages, documents, people, feeds, pages,
|
|
9
|
+
and relationships. Use its CLI or connected MCP tools to investigate that context;
|
|
10
|
+
do not query its database or connector internals directly.
|
|
11
|
+
|
|
12
|
+
Prefer the connected MCP tools when AgentGraph is available through MCP. Use the
|
|
13
|
+
`agentgraph` CLI when MCP is unavailable or when a terminal script is the natural
|
|
14
|
+
interface. Do not query AgentGraph's SQLite database or connector internals directly.
|
|
15
|
+
|
|
16
|
+
## Investigate
|
|
17
|
+
|
|
18
|
+
1. Start with `search` for discovery. If the user supplied a graph ID, platform
|
|
19
|
+
reference, or indexed URL, use `get` directly instead.
|
|
20
|
+
2. Read the full entity before making a source-backed claim. Search results and graph
|
|
21
|
+
traversals contain bounded snippets.
|
|
22
|
+
3. Inspect direct edges or traverse only when relationships help answer the question.
|
|
23
|
+
4. Resolve a stub or fetch a known resource when missing or stale content blocks the
|
|
24
|
+
answer, then repeat the read.
|
|
25
|
+
5. Compare source dates and contents. Cite the source URL or entity ID and separate
|
|
26
|
+
source facts from conclusions drawn across sources.
|
|
27
|
+
|
|
28
|
+
Use search filters when the type, source, author, time window, observation, or
|
|
29
|
+
attachments are already known. A search without a query lists matching entities by
|
|
30
|
+
date; a query ranks them by relevance.
|
|
31
|
+
|
|
32
|
+
## Guardrails
|
|
33
|
+
|
|
34
|
+
- A stub has neither a title nor content.
|
|
35
|
+
- Use `source_created_at` and `source_updated_at` for source chronology; local
|
|
36
|
+
`created_at` and `updated_at` describe the graph record.
|
|
37
|
+
- Browser observation alone updates `observed_at`; fetch, poll, and ingest do not
|
|
38
|
+
indicate that the user viewed a resource.
|
|
39
|
+
- Check connector or authentication state only when availability, freshness, or a
|
|
40
|
+
graph operation calls it into question.
|
|
41
|
+
- Confirm Person merges, deletion, credential removal, and removal of bookmark
|
|
42
|
+
protection with the user.
|
|
43
|
+
- A multi-target deletion resolves every target before changing the graph; it either
|
|
44
|
+
deletes the resolved set or leaves the graph unchanged.
|
|
45
|
+
|
|
46
|
+
## References
|
|
47
|
+
|
|
48
|
+
- Read [references/commands.md](references/commands.md) for command syntax, MCP
|
|
49
|
+
mappings, filters, targets, stubs, and result limits.
|
|
50
|
+
- Read [references/data-model.md](references/data-model.md) for entity types,
|
|
51
|
+
relationships, attachments, timestamps, and retention.
|
|
52
|
+
- Read [references/operations.md](references/operations.md) for connector discovery,
|
|
53
|
+
authentication, fetching, downloads, polling, connector-owned commands, and server
|
|
54
|
+
transport.
|
{agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/.agents/skills/agentgraph/references/commands.md
RENAMED
|
@@ -43,6 +43,11 @@ MCP search defaults to `refresh=false`; set it only when fresh connector-owned
|
|
|
43
43
|
presentation metadata is needed. It does not replace a targeted source fetch for
|
|
44
44
|
stale content.
|
|
45
45
|
|
|
46
|
+
MCP tools return structured content: successes use `status="ok"` with a `data`
|
|
47
|
+
object; expected failures use `status="error"`, an error `code`, and `message`, with
|
|
48
|
+
the MCP error flag set. Search `data` includes `has_more`; entity payloads include
|
|
49
|
+
`is_stub` and, when known, `source_url`.
|
|
50
|
+
|
|
46
51
|
## Stubs
|
|
47
52
|
|
|
48
53
|
An entity is a stub when it has neither a title nor content. With the CLI, pass
|
|
@@ -16,6 +16,11 @@ receive the full installed entity type catalog in the `search_entities_tool`
|
|
|
16
16
|
description during tool discovery. Use `--verify` only when a live provider check is
|
|
17
17
|
needed.
|
|
18
18
|
|
|
19
|
+
Connector `last_synced_at` values come from successful poll timestamps in `sync_state`,
|
|
20
|
+
aggregated across account-scoped cursors. A connector command may reset its own cursor
|
|
21
|
+
through the generic command-effects path; after a reset, its status is `never` until the
|
|
22
|
+
next successful poll.
|
|
23
|
+
|
|
19
24
|
MCP equivalents are `list_connectors_tool`, `list_auth_providers_tool`,
|
|
20
25
|
`authenticate_provider_tool`, and `remove_auth_provider_tool`.
|
|
21
26
|
|
|
@@ -70,13 +75,15 @@ forms are `run_connector_command_tool("web", ["observe", "<url-or-prefix>"])` an
|
|
|
70
75
|
```bash
|
|
71
76
|
agentgraph download <entity-id|platform/ref> [--output <file-or-dir>] [--json]
|
|
72
77
|
agentgraph bookmark <entity-id|platform/ref|url> [--remove] [--json]
|
|
73
|
-
agentgraph delete <entity-id|platform/ref|url
|
|
78
|
+
agentgraph delete <entity-id|platform/ref|url>... [--json]
|
|
74
79
|
agentgraph unify-persons <primary-person-id> <duplicate-person-id>... [--json]
|
|
75
80
|
```
|
|
76
81
|
|
|
77
|
-
Use `download_entity_tool`, `bookmark_entity_tool`, `delete_entity_tool`,
|
|
78
|
-
`unify_persons_tool` through MCP. Confirm identity before
|
|
79
|
-
first Person is canonical and keeps its ID.
|
|
82
|
+
Use `download_entity_tool`, `bookmark_entity_tool`, `delete_entity_tool`,
|
|
83
|
+
`delete_entities_tool`, and `unify_persons_tool` through MCP. Confirm identity before
|
|
84
|
+
person unification. The first Person is canonical and keeps its ID. Multi-target
|
|
85
|
+
deletion resolves every target before deleting any of them, removes connected edges,
|
|
86
|
+
and returns compact references for the deleted entities.
|
|
80
87
|
|
|
81
88
|
## Server and skill setup
|
|
82
89
|
|
|
@@ -88,12 +95,17 @@ The server listens on a Unix socket (`AGENTGRAPH_SERVER_UDS_PATH`, default
|
|
|
88
95
|
`~/.agentgraph/agentgraph.sock`) as well as TCP, because most agent sandboxes deny
|
|
89
96
|
loopback TCP while permitting an allowlisted socket.
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
CLI polling and connector or authentication commands that queue a poll or ingest
|
|
99
|
+
contact the local server. Reads and `fetch`/`download` follow
|
|
100
|
+
`AGENTGRAPH_QUERY_TRANSPORT` (`auto` by default: socket, then TCP, then in-process),
|
|
101
|
+
so they keep working when the server is unreachable. The MCP server can run polling
|
|
102
|
+
and ingest locally when it cannot queue them on the local server.
|
|
103
|
+
|
|
104
|
+
Run ordinary reads with the default transport. To diagnose connectivity, use
|
|
105
|
+
`AGENTGRAPH_QUERY_TRANSPORT=server agentgraph search x --limit 1`; retry an affected
|
|
106
|
+
read with `AGENTGRAPH_QUERY_TRANSPORT=in-process` only after an actual sandbox
|
|
107
|
+
connection failure. See the Coding agent sandboxes section of the configuration docs
|
|
108
|
+
for Unix-socket allowlist settings.
|
|
97
109
|
|
|
98
110
|
```bash
|
|
99
111
|
agentgraph mcp-config
|
|
@@ -101,9 +113,11 @@ agentgraph mcp-serve
|
|
|
101
113
|
agentgraph install-skill [agentgraph] [--target user|project] [--no-claude] [--force] [--json]
|
|
102
114
|
```
|
|
103
115
|
|
|
104
|
-
`agentgraph mcp-config` prints local stdio setup for ChatGPT Desktop Work Mode
|
|
105
|
-
Claude Desktop
|
|
106
|
-
|
|
107
|
-
to its MCP
|
|
116
|
+
`agentgraph mcp-config` prints local stdio setup for ChatGPT Desktop Work Mode, Codex,
|
|
117
|
+
Claude Desktop, and Claude Code. Prefer these MCP connections when they are available.
|
|
118
|
+
In ChatGPT Desktop, enter the printed executable in **Command to launch** and
|
|
119
|
+
`mcp-serve` in **Arguments**; for Claude Desktop, add the printed JSON to its MCP
|
|
120
|
+
configuration file. Claude Code uses the printed user-scoped command so the server is
|
|
121
|
+
available across projects.
|
|
108
122
|
|
|
109
123
|
The skill installer is available only through the `agentgraph install-skill` CLI command.
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgraph-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
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
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: aiosqlite>=0.20.0
|
|
9
|
-
Requires-Dist: agentgraph-connector-web<0.
|
|
9
|
+
Requires-Dist: agentgraph-connector-web<0.8,>=0.7.0
|
|
10
10
|
Requires-Dist: apscheduler>=3.11.2
|
|
11
11
|
Requires-Dist: click<8.4
|
|
12
12
|
Requires-Dist: fastapi>=0.135.2
|
|
13
13
|
Requires-Dist: fastembed>=0.8.0
|
|
14
14
|
Requires-Dist: httpx>=0.28.1
|
|
15
|
-
Requires-Dist: mcp
|
|
15
|
+
Requires-Dist: mcp<2,>=1.26.0
|
|
16
16
|
Requires-Dist: numpy>=2.4.6
|
|
17
17
|
Requires-Dist: pydantic>=2.12.5
|
|
18
18
|
Requires-Dist: pydantic-settings>=2.13.1
|
|
@@ -22,21 +22,21 @@ Requires-Dist: sqlite-vec>=0.1.9
|
|
|
22
22
|
Requires-Dist: typer>=0.24.1
|
|
23
23
|
Requires-Dist: uvicorn>=0.42.0
|
|
24
24
|
Provides-Extra: google
|
|
25
|
-
Requires-Dist: agentgraph-connector-google<0.7,>=0.6.
|
|
25
|
+
Requires-Dist: agentgraph-connector-google<0.7,>=0.6.1; extra == "google"
|
|
26
26
|
Provides-Extra: slack
|
|
27
|
-
Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.
|
|
27
|
+
Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.1; extra == "slack"
|
|
28
28
|
Provides-Extra: discord
|
|
29
|
-
Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.
|
|
29
|
+
Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.1; extra == "discord"
|
|
30
30
|
Provides-Extra: rss
|
|
31
|
-
Requires-Dist: agentgraph-connector-rss<0.
|
|
31
|
+
Requires-Dist: agentgraph-connector-rss<0.8,>=0.7.0; extra == "rss"
|
|
32
32
|
Provides-Extra: web
|
|
33
|
-
Requires-Dist: agentgraph-connector-web<0.
|
|
33
|
+
Requires-Dist: agentgraph-connector-web<0.8,>=0.7.0; extra == "web"
|
|
34
34
|
Provides-Extra: all
|
|
35
|
-
Requires-Dist: agentgraph-connector-web<0.
|
|
36
|
-
Requires-Dist: agentgraph-connector-google<0.7,>=0.6.
|
|
37
|
-
Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.
|
|
38
|
-
Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.
|
|
39
|
-
Requires-Dist: agentgraph-connector-rss<0.
|
|
35
|
+
Requires-Dist: agentgraph-connector-web<0.8,>=0.7.0; extra == "all"
|
|
36
|
+
Requires-Dist: agentgraph-connector-google<0.7,>=0.6.1; extra == "all"
|
|
37
|
+
Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.1; extra == "all"
|
|
38
|
+
Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.1; extra == "all"
|
|
39
|
+
Requires-Dist: agentgraph-connector-rss<0.8,>=0.7.0; extra == "all"
|
|
40
40
|
Dynamic: license-file
|
|
41
41
|
|
|
42
42
|
# AgentGraph
|
|
@@ -122,7 +122,7 @@ Connect the agent you already use:
|
|
|
122
122
|
agentgraph mcp-config
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
Use the printed instructions to connect ChatGPT Desktop Work Mode
|
|
125
|
+
Use the printed instructions to connect ChatGPT Desktop Work Mode, Codex, Claude Desktop, or Claude Code to the local stdio server. MCP is the preferred connection; the installed skill provides CLI fallback workflows. See [Install](docs-src/install.md) for the complete setup path.
|
|
126
126
|
|
|
127
127
|
## What AgentGraph is and is not
|
|
128
128
|
|
|
@@ -81,7 +81,7 @@ Connect the agent you already use:
|
|
|
81
81
|
agentgraph mcp-config
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Use the printed instructions to connect ChatGPT Desktop Work Mode
|
|
84
|
+
Use the printed instructions to connect ChatGPT Desktop Work Mode, Codex, Claude Desktop, or Claude Code to the local stdio server. MCP is the preferred connection; the installed skill provides CLI fallback workflows. See [Install](docs-src/install.md) for the complete setup path.
|
|
85
85
|
|
|
86
86
|
## What AgentGraph is and is not
|
|
87
87
|
|
|
@@ -68,6 +68,7 @@ _LIST_PAGE_ORDER_BY = {
|
|
|
68
68
|
}
|
|
69
69
|
_COLUMN_FILTERS = {"platform", "platform_entity_id", "entity_type"}
|
|
70
70
|
_FTS_DELETE_CHUNK_SIZE = 500
|
|
71
|
+
_PLATFORM_ENTITY_ID_LOOKUP_CHUNK_SIZE = 900
|
|
71
72
|
_BUSY_TIMEOUT_MS = 5_000
|
|
72
73
|
_SCHEMA_VERSION = 6
|
|
73
74
|
|
|
@@ -869,11 +870,14 @@ class SQLiteBackend(StorageBackend):
|
|
|
869
870
|
str(existing_row["content"]) if existing_row and existing_row["content"] else ""
|
|
870
871
|
)
|
|
871
872
|
fts_title = e.title if e.title is not None else existing_title
|
|
872
|
-
fts_content =
|
|
873
|
+
fts_content = (
|
|
874
|
+
e.content if e.content_searchable and e.content is not None else ""
|
|
875
|
+
)
|
|
873
876
|
rewrite_fts = (
|
|
874
877
|
existing_row is None
|
|
875
878
|
or fts_title != existing_title
|
|
876
|
-
or fts_content != existing_content
|
|
879
|
+
or (e.content_searchable and fts_content != existing_content)
|
|
880
|
+
or not e.content_searchable
|
|
877
881
|
)
|
|
878
882
|
embedding = embeddings.get(e.platform_entity_id)
|
|
879
883
|
emb_blob = pack_embedding(embedding) if embedding else None
|
|
@@ -892,7 +896,10 @@ class SQLiteBackend(StorageBackend):
|
|
|
892
896
|
target_title = e.title if e.title is not None else existing_row["title"]
|
|
893
897
|
target_content = e.content if e.content is not None else existing_row["content"]
|
|
894
898
|
target_embedding = (
|
|
895
|
-
emb_blob if emb_blob is not None
|
|
899
|
+
emb_blob if e.content_searchable and emb_blob is not None
|
|
900
|
+
else existing_row["content_embedding"]
|
|
901
|
+
if e.content_searchable
|
|
902
|
+
else None
|
|
896
903
|
)
|
|
897
904
|
target_source_created = (
|
|
898
905
|
source_created
|
|
@@ -927,7 +934,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
927
934
|
entity_type = CASE WHEN entities.entity_type = 'Document' THEN EXCLUDED.entity_type ELSE entities.entity_type END,
|
|
928
935
|
title = COALESCE(EXCLUDED.title, entities.title),
|
|
929
936
|
content = COALESCE(EXCLUDED.content, entities.content),
|
|
930
|
-
content_embedding = COALESCE(EXCLUDED.content_embedding, entities.content_embedding),
|
|
937
|
+
content_embedding = CASE WHEN ? THEN COALESCE(EXCLUDED.content_embedding, entities.content_embedding) ELSE NULL END,
|
|
931
938
|
metadata = EXCLUDED.metadata,
|
|
932
939
|
source_created_at = COALESCE(EXCLUDED.source_created_at, entities.source_created_at),
|
|
933
940
|
source_updated_at = COALESCE(EXCLUDED.source_updated_at, entities.source_updated_at),
|
|
@@ -953,6 +960,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
953
960
|
local_updated,
|
|
954
961
|
e.retention_policy,
|
|
955
962
|
parent_id,
|
|
963
|
+
e.content_searchable,
|
|
956
964
|
local_updated,
|
|
957
965
|
],
|
|
958
966
|
)
|
|
@@ -1220,42 +1228,55 @@ class SQLiteBackend(StorageBackend):
|
|
|
1220
1228
|
|
|
1221
1229
|
async def delete_entity(self, entity_id: str) -> EntityResult:
|
|
1222
1230
|
"""Delete one entity by internal ID. Edges cascade; FTS rows are removed explicitly."""
|
|
1231
|
+
deleted = await self.delete_entities([entity_id])
|
|
1232
|
+
return deleted[0]
|
|
1233
|
+
|
|
1234
|
+
async def delete_entities(self, entity_ids: list[str]) -> list[EntityResult]:
|
|
1235
|
+
"""Atomically delete entities, their edges, and their FTS rows."""
|
|
1236
|
+
unique_ids = list(dict.fromkeys(entity_ids))
|
|
1237
|
+
if not unique_ids:
|
|
1238
|
+
raise ValueError("At least one entity ID is required")
|
|
1223
1239
|
assert self._write_lock is not None
|
|
1224
1240
|
async with self._write_lock:
|
|
1225
1241
|
conn = self._conn_or_raise()
|
|
1226
1242
|
await conn.execute("BEGIN IMMEDIATE")
|
|
1227
1243
|
try:
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1244
|
+
rows: list[aiosqlite.Row] = []
|
|
1245
|
+
for start in range(0, len(unique_ids), _FTS_DELETE_CHUNK_SIZE):
|
|
1246
|
+
ids = unique_ids[start : start + _FTS_DELETE_CHUNK_SIZE]
|
|
1247
|
+
placeholders = ",".join("?" * len(ids))
|
|
1248
|
+
await conn.execute(
|
|
1249
|
+
f"""
|
|
1250
|
+
UPDATE entities
|
|
1251
|
+
SET retention_parent_id = NULL, updated_at = ?
|
|
1252
|
+
WHERE retention_parent_id IN ({placeholders}) AND bookmarked = 1
|
|
1253
|
+
""",
|
|
1254
|
+
[_now(), *ids],
|
|
1255
|
+
)
|
|
1256
|
+
cursor = await conn.execute(
|
|
1257
|
+
f"""
|
|
1258
|
+
DELETE FROM entities
|
|
1259
|
+
WHERE id IN ({placeholders})
|
|
1260
|
+
RETURNING id, entity_type, platform, platform_entity_id,
|
|
1261
|
+
title, content, metadata, created_at, updated_at,
|
|
1262
|
+
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1263
|
+
retention_policy, retention_parent_id,
|
|
1264
|
+
cumulative_observation_duration_ms, bookmarked
|
|
1265
|
+
""",
|
|
1266
|
+
ids,
|
|
1267
|
+
)
|
|
1268
|
+
rows.extend(await cursor.fetchall())
|
|
1269
|
+
await conn.execute(
|
|
1270
|
+
f"DELETE FROM entities_fts WHERE id IN ({placeholders})", ids
|
|
1271
|
+
)
|
|
1272
|
+
if len(rows) != len(unique_ids):
|
|
1273
|
+
raise ValueError("One or more entities were not found")
|
|
1254
1274
|
await conn.execute("COMMIT")
|
|
1255
1275
|
except Exception:
|
|
1256
1276
|
await conn.execute("ROLLBACK")
|
|
1257
1277
|
raise
|
|
1258
|
-
|
|
1278
|
+
deleted_by_id = {str(row["id"]): _row_to_entity(row) for row in rows}
|
|
1279
|
+
return [deleted_by_id[entity_id] for entity_id in unique_ids]
|
|
1259
1280
|
|
|
1260
1281
|
# --- Read: entities ---
|
|
1261
1282
|
|
|
@@ -1546,6 +1567,30 @@ class SQLiteBackend(StorageBackend):
|
|
|
1546
1567
|
)
|
|
1547
1568
|
return _row_to_entity(row) if row else None
|
|
1548
1569
|
|
|
1570
|
+
async def get_existing_platform_entity_ids(
|
|
1571
|
+
self,
|
|
1572
|
+
platform: str,
|
|
1573
|
+
platform_entity_ids: list[str],
|
|
1574
|
+
) -> set[str]:
|
|
1575
|
+
if not platform_entity_ids:
|
|
1576
|
+
return set()
|
|
1577
|
+
|
|
1578
|
+
existing: set[str] = set()
|
|
1579
|
+
for offset in range(0, len(platform_entity_ids), _PLATFORM_ENTITY_ID_LOOKUP_CHUNK_SIZE):
|
|
1580
|
+
chunk = platform_entity_ids[
|
|
1581
|
+
offset : offset + _PLATFORM_ENTITY_ID_LOOKUP_CHUNK_SIZE
|
|
1582
|
+
]
|
|
1583
|
+
placeholders = ",".join("?" for _ in chunk)
|
|
1584
|
+
rows = await self._fetchall(
|
|
1585
|
+
f"""
|
|
1586
|
+
SELECT platform_entity_id FROM entities
|
|
1587
|
+
WHERE platform = ? AND platform_entity_id IN ({placeholders})
|
|
1588
|
+
""",
|
|
1589
|
+
[platform, *chunk],
|
|
1590
|
+
)
|
|
1591
|
+
existing.update(str(row["platform_entity_id"]) for row in rows)
|
|
1592
|
+
return existing
|
|
1593
|
+
|
|
1549
1594
|
async def list_entities(
|
|
1550
1595
|
self,
|
|
1551
1596
|
entity_types: list[str] | None,
|
|
@@ -1992,6 +2037,9 @@ class SQLiteBackend(StorageBackend):
|
|
|
1992
2037
|
[source, json.dumps(cursor), _now()],
|
|
1993
2038
|
)
|
|
1994
2039
|
|
|
2040
|
+
async def clear_cursor(self, source: str) -> None:
|
|
2041
|
+
await self._execute("DELETE FROM sync_state WHERE source = ?", [source])
|
|
2042
|
+
|
|
1995
2043
|
# --- Connector support ---
|
|
1996
2044
|
|
|
1997
2045
|
async def increment_observation_duration(
|
|
@@ -2129,6 +2177,26 @@ class SQLiteBackend(StorageBackend):
|
|
|
2129
2177
|
result[row["platform"]] = datetime.fromisoformat(val) if val else None
|
|
2130
2178
|
return result
|
|
2131
2179
|
|
|
2180
|
+
async def get_sources_last_synced_at(
|
|
2181
|
+
self, sources: list[str]
|
|
2182
|
+
) -> dict[str, datetime | None]:
|
|
2183
|
+
if not sources:
|
|
2184
|
+
return {}
|
|
2185
|
+
rows = await self._fetchall(
|
|
2186
|
+
"SELECT source, updated_at FROM sync_state"
|
|
2187
|
+
)
|
|
2188
|
+
result: dict[str, datetime | None] = dict.fromkeys(sources, None)
|
|
2189
|
+
for source in sources:
|
|
2190
|
+
matching = [
|
|
2191
|
+
row["updated_at"]
|
|
2192
|
+
for row in rows
|
|
2193
|
+
if row["source"] == source or row["source"].startswith(f"{source}:")
|
|
2194
|
+
]
|
|
2195
|
+
values = [datetime.fromisoformat(value) for value in matching if value]
|
|
2196
|
+
if values:
|
|
2197
|
+
result[source] = max(values)
|
|
2198
|
+
return result
|
|
2199
|
+
|
|
2132
2200
|
async def reset_synced_at(self, platform: str, platform_entity_id: str) -> None:
|
|
2133
2201
|
await self._execute(
|
|
2134
2202
|
"UPDATE entities SET synced_at = NULL WHERE platform = ? AND platform_entity_id = ?",
|
|
@@ -437,6 +437,13 @@ def connector_command(
|
|
|
437
437
|
try:
|
|
438
438
|
result = type(connector).run_cli_command(command_args)
|
|
439
439
|
effects = type(connector).command_effects(command_args, result)
|
|
440
|
+
if effects.reset_cursors:
|
|
441
|
+
from agentgraph.cli_query import run_graph_operation
|
|
442
|
+
from agentgraph.connectors.command_effects import execute_cursor_resets
|
|
443
|
+
|
|
444
|
+
result["reset_cursors"] = run_graph_operation(
|
|
445
|
+
lambda: execute_cursor_resets(effects)
|
|
446
|
+
)
|
|
440
447
|
if effects.delete_entities:
|
|
441
448
|
from agentgraph.cli_query import run_graph_operation
|
|
442
449
|
from agentgraph.connectors.command_effects import execute_deletions
|
|
@@ -737,13 +744,13 @@ def bookmark(
|
|
|
737
744
|
|
|
738
745
|
@app.command("delete")
|
|
739
746
|
def delete_cmd(
|
|
740
|
-
|
|
747
|
+
targets: list[str] = typer.Argument(..., help="Entity IDs, UUID prefixes, platform refs, or URLs"),
|
|
741
748
|
json: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
742
749
|
) -> None:
|
|
743
|
-
"""Delete
|
|
750
|
+
"""Delete one or more entities from the graph."""
|
|
744
751
|
from agentgraph.cli_query import cmd_delete
|
|
745
752
|
|
|
746
|
-
cmd_delete(
|
|
753
|
+
cmd_delete(targets=targets, as_json=json)
|
|
747
754
|
|
|
748
755
|
|
|
749
756
|
@app.command("unify-persons")
|
|
@@ -842,7 +849,7 @@ def mcp_config() -> None:
|
|
|
842
849
|
typer.echo()
|
|
843
850
|
typer.echo("Claude Code:")
|
|
844
851
|
typer.echo(" Register the server from your terminal:\n")
|
|
845
|
-
typer.echo(f" claude mcp add agentgraph -- {binary} mcp-serve")
|
|
852
|
+
typer.echo(f" claude mcp add --transport stdio --scope user agentgraph -- {binary} mcp-serve")
|
|
846
853
|
typer.echo()
|
|
847
854
|
typer.echo("Every client above runs the same server. Reads follow")
|
|
848
855
|
typer.echo("AGENTGRAPH_QUERY_TRANSPORT: by default the local server when one is")
|
|
@@ -342,7 +342,22 @@ def cmd_bookmark(target: str, bookmarked: bool, as_json: bool) -> None:
|
|
|
342
342
|
console.print(f"[green]{action}:[/green] {label} [{result['id'][:8]}]")
|
|
343
343
|
|
|
344
344
|
|
|
345
|
-
def cmd_delete(
|
|
345
|
+
def cmd_delete(targets: list[str], as_json: bool) -> None:
|
|
346
|
+
if len(targets) == 1:
|
|
347
|
+
_cmd_delete_one(targets[0], as_json)
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
async def operation(client: QueryClient) -> dict[str, Any]:
|
|
351
|
+
return await client.delete_many(targets)
|
|
352
|
+
|
|
353
|
+
result = _run_with_client(operation)
|
|
354
|
+
if as_json:
|
|
355
|
+
console.print_json(json.dumps(result, default=str))
|
|
356
|
+
return
|
|
357
|
+
console.print(f"[green]Deleted {result['deleted_count']} entities.[/green]")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _cmd_delete_one(target: str, as_json: bool) -> None:
|
|
346
361
|
async def operation(client: QueryClient) -> dict[str, Any]:
|
|
347
362
|
return await client.delete(target)
|
|
348
363
|
|
|
@@ -105,6 +105,7 @@ class ConnectorCommandEffects:
|
|
|
105
105
|
ingest_account_id: str | None = None
|
|
106
106
|
delete_entities: tuple[EntityReference, ...] = ()
|
|
107
107
|
fetch_references: tuple[SourceReference, ...] = ()
|
|
108
|
+
reset_cursors: tuple[str, ...] = ()
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
class PersonRecord(BaseModel):
|
|
@@ -122,6 +123,7 @@ class EntityRecord(BaseModel):
|
|
|
122
123
|
platform_entity_id: str
|
|
123
124
|
title: str | None = None
|
|
124
125
|
content: str | None = None
|
|
126
|
+
content_searchable: bool = True
|
|
125
127
|
source_created_at: datetime | None = None
|
|
126
128
|
source_updated_at: datetime | None = None
|
|
127
129
|
metadata: dict[str, str | int | float | bool | None] = {}
|
{agentgraph_server-0.8.0 → agentgraph_server-0.9.0}/agentgraph/connectors/command_effects.py
RENAMED
|
@@ -39,6 +39,16 @@ async def execute_deletions(effects: ConnectorCommandEffects) -> list[dict[str,
|
|
|
39
39
|
return deleted
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
async def execute_cursor_resets(effects: ConnectorCommandEffects) -> list[str]:
|
|
43
|
+
"""Clear connector-requested cursors and return the sources that were reset."""
|
|
44
|
+
from agentgraph.core.context import get_backend
|
|
45
|
+
|
|
46
|
+
backend = get_backend()
|
|
47
|
+
for source in effects.reset_cursors:
|
|
48
|
+
await backend.clear_cursor(source)
|
|
49
|
+
return list(effects.reset_cursors)
|
|
50
|
+
|
|
51
|
+
|
|
42
52
|
async def execute_fetches(effects: ConnectorCommandEffects) -> list[dict[str, Any]]:
|
|
43
53
|
"""Fetch connector-owned references and persist their returned batches."""
|
|
44
54
|
from agentgraph.connectors.registry import get_connector
|
|
@@ -92,6 +92,13 @@ class TombstoneMutation(BaseModel):
|
|
|
92
92
|
target: MutationTarget
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
class TombstoneBatchMutation(BaseModel):
|
|
96
|
+
event_id: UUID = Field(default_factory=uuid4)
|
|
97
|
+
kind: Literal["tombstone_batch"] = "tombstone_batch"
|
|
98
|
+
occurred_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
99
|
+
targets: list[MutationTarget] = Field(min_length=1)
|
|
100
|
+
|
|
101
|
+
|
|
95
102
|
class EntityUpsertMutation(BaseModel):
|
|
96
103
|
event_id: UUID = Field(default_factory=uuid4)
|
|
97
104
|
kind: Literal["upsert"] = "upsert"
|
|
@@ -102,7 +109,11 @@ class EntityUpsertMutation(BaseModel):
|
|
|
102
109
|
|
|
103
110
|
|
|
104
111
|
type MutationEvent = (
|
|
105
|
-
ObservationMutation
|
|
112
|
+
ObservationMutation
|
|
113
|
+
| BookmarkMutation
|
|
114
|
+
| TombstoneMutation
|
|
115
|
+
| TombstoneBatchMutation
|
|
116
|
+
| EntityUpsertMutation
|
|
106
117
|
)
|
|
107
118
|
|
|
108
119
|
|
|
@@ -174,7 +174,7 @@ async def connector_status_items(
|
|
|
174
174
|
for delegated_source in type(connector).poll_delegates:
|
|
175
175
|
poll_delegators.setdefault(delegated_source, []).append(connector.source)
|
|
176
176
|
|
|
177
|
-
last_synced_by_platform = await backend.
|
|
177
|
+
last_synced_by_platform = await backend.get_sources_last_synced_at(
|
|
178
178
|
[connector.source for connector in connectors]
|
|
179
179
|
)
|
|
180
180
|
|
|
@@ -76,6 +76,11 @@ class StorageBackend(ABC):
|
|
|
76
76
|
"""Delete an entity and return the deleted entity."""
|
|
77
77
|
...
|
|
78
78
|
|
|
79
|
+
@abstractmethod
|
|
80
|
+
async def delete_entities(self, entity_ids: list[str]) -> list[EntityResult]:
|
|
81
|
+
"""Atomically delete entities and return their deleted snapshots."""
|
|
82
|
+
...
|
|
83
|
+
|
|
79
84
|
# --- Read: entities ---
|
|
80
85
|
|
|
81
86
|
@abstractmethod
|
|
@@ -127,6 +132,15 @@ class StorageBackend(ABC):
|
|
|
127
132
|
content_limit: int | None = None,
|
|
128
133
|
) -> EntityResult | None: ...
|
|
129
134
|
|
|
135
|
+
@abstractmethod
|
|
136
|
+
async def get_existing_platform_entity_ids(
|
|
137
|
+
self,
|
|
138
|
+
platform: str,
|
|
139
|
+
platform_entity_ids: list[str],
|
|
140
|
+
) -> set[str]:
|
|
141
|
+
"""Return the supplied platform entity IDs that are already stored."""
|
|
142
|
+
...
|
|
143
|
+
|
|
130
144
|
@abstractmethod
|
|
131
145
|
async def list_entities(
|
|
132
146
|
self,
|
|
@@ -234,6 +248,11 @@ class StorageBackend(ABC):
|
|
|
234
248
|
@abstractmethod
|
|
235
249
|
async def save_cursor(self, source: str, cursor: dict[str, Any]) -> None: ...
|
|
236
250
|
|
|
251
|
+
@abstractmethod
|
|
252
|
+
async def clear_cursor(self, source: str) -> None:
|
|
253
|
+
"""Remove a source cursor so its next poll starts from an empty state."""
|
|
254
|
+
...
|
|
255
|
+
|
|
237
256
|
# --- Connector support ---
|
|
238
257
|
|
|
239
258
|
@abstractmethod
|
|
@@ -284,6 +303,13 @@ class StorageBackend(ABC):
|
|
|
284
303
|
for platform in platforms
|
|
285
304
|
}
|
|
286
305
|
|
|
306
|
+
@abstractmethod
|
|
307
|
+
async def get_sources_last_synced_at(
|
|
308
|
+
self, sources: list[str]
|
|
309
|
+
) -> dict[str, datetime | None]:
|
|
310
|
+
"""Return the latest successful poll timestamp for each connector source."""
|
|
311
|
+
...
|
|
312
|
+
|
|
287
313
|
@abstractmethod
|
|
288
314
|
async def reset_synced_at(
|
|
289
315
|
self, platform: str, platform_entity_id: str
|