agentgraph-server 0.7.2__tar.gz → 0.8.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.
Files changed (115) hide show
  1. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/SKILL.md +26 -10
  2. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/references/commands.md +7 -2
  3. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/references/data-model.md +6 -1
  4. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/references/operations.md +4 -2
  5. {agentgraph_server-0.7.2/agentgraph_server.egg-info → agentgraph_server-0.8.0}/PKG-INFO +12 -12
  6. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/backend.py +6 -0
  7. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/cli.py +21 -3
  8. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/cli_query.py +2 -0
  9. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/base.py +73 -31
  10. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/feed.py +10 -6
  11. agentgraph_server-0.8.0/agentgraph/connectors/registry.py +175 -0
  12. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/status.py +6 -1
  13. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/storage.py +1 -0
  14. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/link.py +3 -2
  15. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/query.py +3 -0
  16. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/mcp/server.py +40 -30
  17. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/query_client.py +5 -0
  18. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/graph_api.py +2 -0
  19. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/meta_api.py +2 -3
  20. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/static/viewer.html +12 -4
  21. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/skills.py +22 -7
  22. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0/agentgraph_server.egg-info}/PKG-INFO +12 -12
  23. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/SOURCES.txt +2 -0
  24. agentgraph_server-0.8.0/agentgraph_server.egg-info/requires.txt +38 -0
  25. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/pyproject.toml +12 -12
  26. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_browse.py +3 -1
  27. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_cli.py +104 -1
  28. agentgraph_server-0.8.0/tests/test_connector_discovery.py +83 -0
  29. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_entity_types.py +66 -3
  30. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_observation.py +13 -0
  31. agentgraph_server-0.8.0/tests/test_observed_since.py +200 -0
  32. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_query.py +23 -0
  33. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_registry.py +78 -1
  34. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_release_metadata.py +2 -2
  35. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_viewer_layout_contract.py +20 -1
  36. agentgraph_server-0.7.2/agentgraph/connectors/registry.py +0 -86
  37. agentgraph_server-0.7.2/agentgraph_server.egg-info/requires.txt +0 -38
  38. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/slack-auth/SKILL.md +0 -0
  39. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/LICENSE +0 -0
  40. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/README.md +0 -0
  41. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/__init__.py +0 -0
  42. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/auth/__init__.py +0 -0
  43. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/auth/credentials.py +0 -0
  44. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/__init__.py +0 -0
  45. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/__init__.py +0 -0
  46. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/schema.sql +0 -0
  47. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/vector.py +0 -0
  48. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/cli_sync.py +0 -0
  49. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/config.py +0 -0
  50. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/__init__.py +0 -0
  51. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/command_effects.py +0 -0
  52. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/match_patterns.py +0 -0
  53. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/__init__.py +0 -0
  54. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/context.py +0 -0
  55. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/runtime.py +0 -0
  56. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/demo.py +0 -0
  57. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
  58. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
  59. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/__init__.py +0 -0
  60. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/bookmark.py +0 -0
  61. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/delete.py +0 -0
  62. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/download.py +0 -0
  63. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/embeddings.py +0 -0
  64. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/expiration.py +0 -0
  65. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/fetch.py +0 -0
  66. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/operations.py +0 -0
  67. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/person.py +0 -0
  68. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/upsert.py +0 -0
  69. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/logging.py +0 -0
  70. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/mcp/__init__.py +0 -0
  71. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/perf.py +0 -0
  72. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/__init__.py +0 -0
  73. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/app.py +0 -0
  74. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/browse_api.py +0 -0
  75. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/observation.py +0 -0
  76. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/presentation.py +0 -0
  77. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/router.py +0 -0
  78. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/sync.py +0 -0
  79. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/sync_api.py +0 -0
  80. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/uds.py +0 -0
  81. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/dependency_links.txt +0 -0
  82. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/entry_points.txt +0 -0
  83. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/top_level.txt +0 -0
  84. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/setup.cfg +0 -0
  85. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_auth.py +0 -0
  86. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_auth_status_methods.py +0 -0
  87. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_benchmarks.py +0 -0
  88. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_command_effects.py +0 -0
  89. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_config.py +0 -0
  90. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_connectors.py +0 -0
  91. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_discord_attachments.py +0 -0
  92. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_docs_build.py +0 -0
  93. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_embeddings.py +0 -0
  94. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_expiration.py +0 -0
  95. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_feed_connectors.py +0 -0
  96. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_fetch.py +0 -0
  97. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_graph_operations.py +0 -0
  98. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_launch_demo.py +0 -0
  99. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_logging.py +0 -0
  100. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_match_patterns.py +0 -0
  101. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_mcp_transport.py +0 -0
  102. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_query_client.py +0 -0
  103. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_query_parity.py +0 -0
  104. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_route_resolution.py +0 -0
  105. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_router.py +0 -0
  106. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_rss_connector.py +0 -0
  107. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_schema.py +0 -0
  108. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_server_imports.py +0 -0
  109. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_server_lifespan.py +0 -0
  110. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_server_uds.py +0 -0
  111. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_slack_auth_artifacts.py +0 -0
  112. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_slack_oauth.py +0 -0
  113. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_sync.py +0 -0
  114. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_upsert.py +0 -0
  115. {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_web_connector.py +0 -0
@@ -13,10 +13,6 @@ 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
-
20
16
  ## Commands that use the local server
21
17
 
22
18
  `agentgraph poll` and connector or authentication commands that queue a poll or
@@ -24,6 +20,10 @@ historical ingest always call the local AgentGraph server. Reads (`search`, `get
24
20
  `edges`, `traverse`) and `fetch`/`download` use it when it is reachable and
25
21
  otherwise read the database directly, so they work with no server running.
26
22
 
23
+ Run ordinary commands without setting `AGENTGRAPH_QUERY_TRANSPORT`, even when the
24
+ server is stopped. The default `auto` transport tries the Unix
25
+ socket, then TCP, then falls back to in-process automatically.
26
+
27
27
  Many agent sandboxes deny loopback TCP but allow an allowlisted Unix socket, so the
28
28
  server listens on both and clients prefer the socket. If a command fails because the
29
29
  sandbox blocked it:
@@ -31,9 +31,12 @@ sandbox blocked it:
31
31
  - For `poll`, ingest, or auth commands there is no fallback — request permission to
32
32
  contact the local server, or ask the user to allowlist the socket
33
33
  (`~/.agentgraph/agentgraph.sock`), then retry.
34
- - For reads, `AGENTGRAPH_QUERY_TRANSPORT=in-process` bypasses the server entirely.
35
- - `AGENTGRAPH_QUERY_TRANSPORT=server agentgraph search x --limit 1` reports plainly
36
- whether the server is reachable, instead of silently falling back.
34
+ - For reads, retry with `AGENTGRAPH_QUERY_TRANSPORT=in-process` only after an actual
35
+ sandbox connection failure. This bypasses server probes for that retry.
36
+
37
+ To diagnose server connectivity explicitly, use
38
+ `AGENTGRAPH_QUERY_TRANSPORT=server agentgraph search x --limit 1`; it reports whether
39
+ the server is reachable instead of silently falling back.
37
40
 
38
41
  See the Coding agent sandboxes section of the configuration docs for the per-agent
39
42
  allowlist settings.
@@ -55,12 +58,25 @@ allowlist settings.
55
58
  Start with search unless the user already supplied a graph ID, platform reference, or
56
59
  known indexed URL. `search` covers both jobs: pass a query string for ranked
57
60
  discovery, and add or use only its filters (`--type`, `--platform`, `--filter`,
58
- `--since`, `--mine`, `--has-attachments`) when the entity type or constraints are
59
- already known. Omitting the query string turns it into a deterministic listing
60
- ordered by date.
61
+ `--since`, `--observed-since`, `--mine`, `--has-attachments`) when the entity type or
62
+ constraints are already known. Omitting the query string turns it into a
63
+ deterministic listing ordered by date.
64
+
65
+ Use `--observed-since 2d` (MCP: `observed_since="2d"`) for entities observed in
66
+ the browser within the last two days. It accepts the same relative durations and
67
+ ISO timestamps as `--since`, which filters `updated_at`. Both cutoffs are inclusive;
68
+ when combined, both must match. Never-observed entities are excluded by
69
+ `--observed-since`. Add `--order-by observed_at` to sort by observation time.
61
70
 
62
71
  ## Context lifecycle
63
72
 
73
+ `agentgraph list-connectors --json` (MCP: `list_connectors_tool`) reports each
74
+ connector's declared resource `entity_types`, including standard graph types,
75
+ with their names, resource types, and source-specific descriptions. Person
76
+ identities are handled separately. An empty list means the connector has not
77
+ declared resource types; it does not mean the source has no indexed entities.
78
+ The CLI table displays `-` for this empty list.
79
+
64
80
  - **Connect** is setup: installed connectors and their authentication/configuration
65
81
  determine which selected sources AgentGraph can access.
66
82
  - **Observe** records human attention to a supported browser URL and triggers a
@@ -3,7 +3,7 @@
3
3
  ## CLI
4
4
 
5
5
  ```bash
6
- agentgraph search ["<query>"] [--type <type>] [--platform <platform>] [--filter key=value] [--since 12h|30m|2d] [--mine] [--has-attachments] [--limit N] [--order-by created_at|updated_at|source_created_at|source_updated_at|observed_at|synced_at] [--min-score N] [--json]
6
+ agentgraph search [--type <type>] [--platform <platform>] [--filter key=value] [--since 12h|30m|2d] [--observed-since 12h|30m|2d] [--mine] [--has-attachments] [--limit N] [--order-by created_at|updated_at|source_created_at|source_updated_at|observed_at|synced_at] [--min-score N] [--json]
7
7
  agentgraph get <entity-id|platform/ref|url> [--resolve] [--json]
8
8
  agentgraph edges <entity-id|platform/ref> [--type <edge-type>] [--direction in|out|both] [--json]
9
9
  agentgraph traverse <entity-id|platform/ref> [--resolve] [--depth 0..4] [--json]
@@ -14,6 +14,11 @@ filter is applied as a hard constraint (`--limit` defaults to 10). Without one t
14
14
  is no ranking: the filters select the entities, `--order-by` sorts them newest first,
15
15
  `--min-score` is ignored, and `--limit` defaults to 50.
16
16
 
17
+ `--since` filters `updated_at`; `--observed-since` filters browser observation time
18
+ (`observed_at`), excluding never-observed entities. Both accept ISO timestamps or
19
+ relative durations and include the cutoff itself. When supplied together, both
20
+ must match. Neither flag changes ordering.
21
+
17
22
  `get` and `traverse` do not resolve stubs unless `--resolve` is supplied. Depth 0
18
23
  returns only the starting entity; depths 1 through 4 include that many relationship
19
24
  hops.
@@ -28,7 +33,7 @@ accept full UUIDs, unambiguous UUID prefixes, and platform references.
28
33
  ## MCP equivalents
29
34
 
30
35
  ```text
31
- agentgraph search ... -> search_entities_tool(query, entity_types, platform, filters, since, authored_by_me, has_attachments, limit, order_by, min_score, refresh)
36
+ agentgraph search ... -> search_entities_tool(query, entity_types, platform, filters, since, authored_by_me, has_attachments, limit, order_by, min_score, refresh, observed_since)
32
37
  agentgraph get ... -> get_entity_tool(entity_id, resolve)
33
38
  agentgraph edges ... -> get_edges_tool(entity_id, edge_type, direction)
34
39
  agentgraph traverse ... -> traverse_graph_tool(entity_id, max_depth, resolve)
@@ -14,7 +14,12 @@
14
14
  | `Task` | Tracked work items such as Jira issues. Issue key, status, and assignee live in `metadata`. |
15
15
  | `Video` | Recorded videos such as Looms. The transcript is indexed as content and `metadata.web_url` links to the video. |
16
16
 
17
- The valid core model does not currently include `Project`.
17
+ Installed connectors may add shared entity type names such as `Project`. The complete
18
+ alphabetical catalog, including connector descriptions, is embedded in the
19
+ `search_entities_tool` MCP description. With the CLI, connector-specific declarations
20
+ are available from `agentgraph list-connectors --json`. Undeclared type strings remain
21
+ storable and searchable for compatibility, but do not receive automatic discovery or
22
+ reference routing.
18
23
 
19
24
  ## Relationships
20
25
 
@@ -10,8 +10,10 @@ agentgraph auth remove <provider> [--account <account-id>] [--json]
10
10
  ```
11
11
 
12
12
  Use `agentgraph list-connectors --json` to discover installed connector source names,
13
- valid platform values, URL ownership, polling delegation, and sync state. Do not rely
14
- on a hardcoded platform list. Use `--verify` only when a live provider check is
13
+ valid platform values, connector-declared entity types, URL ownership, polling
14
+ delegation, and sync state. Do not rely on a hardcoded platform list. MCP clients
15
+ receive the full installed entity type catalog in the `search_entities_tool`
16
+ description during tool discovery. Use `--verify` only when a live provider check is
15
17
  needed.
16
18
 
17
19
  MCP equivalents are `list_connectors_tool`, `list_auth_providers_tool`,
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentgraph-server
3
- Version: 0.7.2
3
+ Version: 0.8.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.7,>=0.5.5
9
+ Requires-Dist: agentgraph-connector-web<0.7,>=0.6.0
10
10
  Requires-Dist: apscheduler>=3.11.2
11
11
  Requires-Dist: click<8.4
12
12
  Requires-Dist: fastapi>=0.135.2
@@ -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.5.5; extra == "google"
25
+ Requires-Dist: agentgraph-connector-google<0.7,>=0.6.0; extra == "google"
26
26
  Provides-Extra: slack
27
- Requires-Dist: agentgraph-connector-slack<0.7,>=0.5.5; extra == "slack"
27
+ Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.0; extra == "slack"
28
28
  Provides-Extra: discord
29
- Requires-Dist: agentgraph-connector-discord<0.7,>=0.5.5; extra == "discord"
29
+ Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.0; extra == "discord"
30
30
  Provides-Extra: rss
31
- Requires-Dist: agentgraph-connector-rss<0.7,>=0.5.5; extra == "rss"
31
+ Requires-Dist: agentgraph-connector-rss<0.7,>=0.6.0; extra == "rss"
32
32
  Provides-Extra: web
33
- Requires-Dist: agentgraph-connector-web<0.7,>=0.5.5; extra == "web"
33
+ Requires-Dist: agentgraph-connector-web<0.7,>=0.6.0; extra == "web"
34
34
  Provides-Extra: all
35
- Requires-Dist: agentgraph-connector-web<0.7,>=0.5.5; extra == "all"
36
- Requires-Dist: agentgraph-connector-google<0.7,>=0.5.5; extra == "all"
37
- Requires-Dist: agentgraph-connector-slack<0.7,>=0.5.5; extra == "all"
38
- Requires-Dist: agentgraph-connector-discord<0.7,>=0.5.5; extra == "all"
39
- Requires-Dist: agentgraph-connector-rss<0.7,>=0.5.5; extra == "all"
35
+ Requires-Dist: agentgraph-connector-web<0.7,>=0.6.0; extra == "all"
36
+ Requires-Dist: agentgraph-connector-google<0.7,>=0.6.0; extra == "all"
37
+ Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.0; extra == "all"
38
+ Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.0; extra == "all"
39
+ Requires-Dist: agentgraph-connector-rss<0.7,>=0.6.0; extra == "all"
40
40
  Dynamic: license-file
41
41
 
42
42
  # AgentGraph
@@ -116,6 +116,7 @@ def _entity_predicates(
116
116
  since: datetime | None = None,
117
117
  authored_by: list[str] | None = None,
118
118
  has_attachments: bool = False,
119
+ observed_since: datetime | None = None,
119
120
  ) -> tuple[str, list[Any], str, list[Any]]:
120
121
  """Build the entity-selection predicates shared by every leg of a search.
121
122
 
@@ -143,6 +144,9 @@ def _entity_predicates(
143
144
  if since:
144
145
  clauses.append("e.updated_at >= ?")
145
146
  params.append(_utc_stamp(since))
147
+ if observed_since:
148
+ clauses.append("e.observed_at >= ?")
149
+ params.append(_utc_stamp(observed_since))
146
150
  if has_attachments:
147
151
  clauses.append(
148
152
  "json_extract(e.metadata, '$.attachments') IS NOT NULL"
@@ -1269,6 +1273,7 @@ class SQLiteBackend(StorageBackend):
1269
1273
  has_attachments: bool = False,
1270
1274
  order_by: str | None = None,
1271
1275
  content_limit: int | None = None,
1276
+ observed_since: datetime | None = None,
1272
1277
  ) -> list[EntityResult]:
1273
1278
  """Select entities by predicate, ranked by relevance or ordered by date.
1274
1279
 
@@ -1286,6 +1291,7 @@ class SQLiteBackend(StorageBackend):
1286
1291
  since,
1287
1292
  authored_by,
1288
1293
  has_attachments,
1294
+ observed_since=observed_since,
1289
1295
  )
1290
1296
 
1291
1297
  if query_text is None or query_vec is None:
@@ -484,7 +484,7 @@ def list_connectors(
484
484
  False, "--verify", help="Live-check connector credentials with provider APIs"
485
485
  ),
486
486
  ) -> None:
487
- """List installed connectors and their sync status."""
487
+ """List installed connectors, their resource entity types, and sync status."""
488
488
  from agentgraph.connectors.registry import bootstrap, get_all_connectors
489
489
  from agentgraph.connectors.status import connector_status_items
490
490
  from agentgraph.core.runtime import backend_context
@@ -505,6 +505,7 @@ def list_connectors(
505
505
  table = Table(title="Connectors", show_lines=True)
506
506
  table.add_column("Connector", style="bold", no_wrap=True)
507
507
  table.add_column("Description", ratio=1)
508
+ table.add_column("Types")
508
509
  table.add_column("Auth")
509
510
  table.add_column("Sync")
510
511
  table.add_column("Last sync", no_wrap=True)
@@ -513,6 +514,11 @@ def list_connectors(
513
514
  table.add_row(
514
515
  str(item["source"]),
515
516
  str(desc),
517
+ ", ".join(
518
+ str(definition["name"])
519
+ for definition in cast(list[dict[str, object]], item["entity_types"])
520
+ )
521
+ or "-",
516
522
  _connector_auth_label(item),
517
523
  str(item["sync"]),
518
524
  str(item["last_sync"]),
@@ -591,7 +597,7 @@ def search(
591
597
  None,
592
598
  "--since",
593
599
  "-s",
594
- help="Only results after this time: ISO timestamp or relative (12h, 30m, 2d)",
600
+ help="Only results updated at or after this time: ISO timestamp or relative (12h, 30m, 2d)",
595
601
  ),
596
602
  mine: bool = typer.Option(False, "--mine", "-m", help="Only entities authored by me"),
597
603
  has_attachments: bool = typer.Option(
@@ -613,6 +619,11 @@ def search(
613
619
  0.03, "--min-score", help="Minimum relevance score (0–1); ignored without a query"
614
620
  ),
615
621
  json: bool = typer.Option(False, "--json", help="Output as JSON"),
622
+ observed_since: str | None = typer.Option(
623
+ None,
624
+ "--observed-since",
625
+ help="Only results observed at or after this time: ISO timestamp or relative (12h, 30m, 2d)",
626
+ ),
616
627
  ) -> None:
617
628
  """Search the knowledge graph, or list entities matching filters alone."""
618
629
  from agentgraph.cli_query import cmd_search
@@ -623,6 +634,7 @@ def search(
623
634
  platform=platform,
624
635
  filters=dict(f.split("=", 1) for f in filter if "=" in f),
625
636
  since=since,
637
+ observed_since=observed_since,
626
638
  authored_by_me=mine,
627
639
  has_attachments=has_attachments,
628
640
  limit=limit,
@@ -874,7 +886,13 @@ def install_skill(
874
886
 
875
887
  typer.echo(f"Installed AgentGraph skill '{result.skill}' to {result.destination}")
876
888
  if result.claude_destination is not None:
877
- typer.echo(f"Linked Claude skill to {result.claude_destination}")
889
+ if result.claude_linked:
890
+ typer.echo(f"Linked Claude skill to {result.claude_destination}")
891
+ else:
892
+ typer.echo(
893
+ f"Claude reads {result.claude_destination} already; "
894
+ "its skill directory is the same one."
895
+ )
878
896
 
879
897
 
880
898
  @app.command()
@@ -109,6 +109,7 @@ def cmd_search(
109
109
  authored_by_me: bool = False,
110
110
  has_attachments: bool = False,
111
111
  order_by: str | None = None,
112
+ observed_since: str | None = None,
112
113
  ) -> None:
113
114
  # An empty or whitespace-only argument is an absent query, not a query for
114
115
  # nothing: this keeps the limit default, the renderer, and the backend's
@@ -127,6 +128,7 @@ def cmd_search(
127
128
  platform,
128
129
  filters=filters,
129
130
  since=since,
131
+ observed_since=observed_since,
130
132
  authored_by_me=authored_by_me,
131
133
  has_attachments=has_attachments,
132
134
  order_by=order_by,
@@ -11,24 +11,13 @@ from dataclasses import dataclass
11
11
  from datetime import UTC, datetime, timedelta
12
12
  from typing import Any, ClassVar, Literal
13
13
 
14
- from pydantic import BaseModel, Field
15
-
16
- # All resource_type values understood by the connector layer.
17
- # Each value maps to a distinct fetch strategy within a connector.
18
- ResourceType = Literal[
19
- "channel",
20
- "dm",
21
- "document",
22
- "folder",
23
- "message",
24
- "spreadsheet",
25
- "thread",
26
- "video",
27
- "work-item",
28
- ]
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+ # Resource types name connector-local fetch strategies. Connectors may extend them.
17
+ type ResourceType = str
29
18
  RetentionPolicy = Literal["observed", "owned", "connected", "persistent"]
30
19
 
31
- # All valid entity_type values stored in the DB.
20
+ # Built-in entity type vocabulary. Storage also accepts connector-defined names.
32
21
  ENTITY_TYPES: tuple[str, ...] = (
33
22
  "Channel",
34
23
  "Document",
@@ -41,6 +30,18 @@ ENTITY_TYPES: tuple[str, ...] = (
41
30
  "Video",
42
31
  )
43
32
 
33
+ ENTITY_TYPE_DESCRIPTIONS: dict[str, str] = {
34
+ "Channel": "Chat channels and direct-message threads.",
35
+ "Document": "Text documents, web pages, files, and attachment stubs.",
36
+ "Email": "Email threads.",
37
+ "Folder": "Containers such as Drive folders and RSS feeds.",
38
+ "Message": "Chat messages, including chat uploads stored in metadata.",
39
+ "Person": "Source identities and confirmed cross-source identity merges.",
40
+ "Spreadsheet": "Spreadsheets and tabular workbook resources.",
41
+ "Task": "Tracked work items such as issues and tasks.",
42
+ "Video": "Recorded videos whose searchable content may include a transcript.",
43
+ }
44
+
44
45
  # Broad URL extractor — classify_url does fine-grained matching
45
46
  _URL_RE = re.compile(r"https?://\S+")
46
47
 
@@ -57,6 +58,27 @@ RESOURCE_TYPE_TO_ENTITY_TYPE: dict[str, str] = {
57
58
  "work-item": "Task",
58
59
  }
59
60
 
61
+ _ENTITY_TYPE_TO_RESOURCE_TYPE: dict[str, ResourceType] = {
62
+ "Channel": "channel",
63
+ "Document": "document",
64
+ "Email": "thread",
65
+ "Folder": "folder",
66
+ "Message": "message",
67
+ "Spreadsheet": "spreadsheet",
68
+ "Task": "work-item",
69
+ "Video": "video",
70
+ }
71
+
72
+
73
+ class EntityTypeDefinition(BaseModel):
74
+ """A connector-local fetch resource mapped to a shared graph entity type."""
75
+
76
+ model_config = ConfigDict(frozen=True)
77
+
78
+ name: str = Field(pattern=r"^[A-Z][A-Za-z0-9]*$")
79
+ resource_type: str = Field(pattern=r"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$")
80
+ description: str = Field(min_length=1)
81
+
60
82
 
61
83
  @dataclass(frozen=True)
62
84
  class SourceReference:
@@ -147,6 +169,7 @@ class EntityBatch(BaseModel):
147
169
  """
148
170
  if not entity.content:
149
171
  return
172
+ from agentgraph.connectors.registry import entity_type_for_reference
150
173
  from agentgraph.server.router import classify_url
151
174
 
152
175
  seen: set[str] = set()
@@ -163,7 +186,7 @@ class EntityBatch(BaseModel):
163
186
  seen.add(key)
164
187
  self.entities.append(
165
188
  EntityRecord(
166
- entity_type=RESOURCE_TYPE_TO_ENTITY_TYPE[ref.resource_type],
189
+ entity_type=entity_type_for_reference(ref),
167
190
  platform=ref.source,
168
191
  platform_entity_id=ref.resource_id,
169
192
  is_stub=True,
@@ -260,6 +283,13 @@ class ResourceUnavailableError(RuntimeError):
260
283
  class BaseConnector(ABC):
261
284
  source: ClassVar[str] # platform name, e.g. "slack" — must be set by subclass
262
285
  fetch_policy: ClassVar[FetchPolicy] # staleness policy — must be set by subclass
286
+ entity_types: ClassVar[tuple[EntityTypeDefinition, ...]] = ()
287
+ """Resource entity types exposed by this connector, including core vocabulary.
288
+
289
+ Used for capability discovery and to extend or override core resource mappings.
290
+ Person identities are emitted separately through EntityBatch.persons.
291
+ """
292
+
263
293
  is_generic_url_fallback: ClassVar[bool] = False
264
294
  """True for broad fallback connectors that should not claim URLs during discovery."""
265
295
 
@@ -387,21 +417,33 @@ class BaseConnector(ABC):
387
417
  def normalise_fetch_id(self, resource_id: str, entity_type: str) -> tuple[str, ResourceType]:
388
418
  """Map a stored resource_id + entity_type to the (id, resource_type) that fetch() expects.
389
419
 
390
- The default maps entity_type to ResourceType using the standard table and
391
- returns resource_id unchanged. Connectors override this when their stored IDs
392
- differ from their fetchable IDs (e.g. Discord message IDs encode the channel).
420
+ The default prefers connector-declared mappings, falls back to the core
421
+ vocabulary, and returns resource_id unchanged. Connectors override this when
422
+ their stored IDs differ from their fetchable IDs (e.g. Discord message IDs
423
+ encode the channel).
393
424
  """
394
- resource_type_map: dict[str, ResourceType] = {
395
- "Document": "document",
396
- "Folder": "folder",
397
- "Spreadsheet": "spreadsheet",
398
- "Channel": "channel",
399
- "Message": "message",
400
- "Email": "thread",
401
- "Task": "work-item",
402
- "Video": "video",
403
- }
404
- return resource_id, resource_type_map.get(entity_type, "document") # type: ignore[return-value]
425
+ return resource_id, type(self).resource_type_for_entity_type(entity_type)
426
+
427
+ @classmethod
428
+ def entity_type_for_resource_type(cls, resource_type: ResourceType) -> str:
429
+ """Map this connector's fetch resource type to a stored entity type."""
430
+ for definition in cls.entity_types:
431
+ if definition.resource_type == resource_type:
432
+ return definition.name
433
+ try:
434
+ return RESOURCE_TYPE_TO_ENTITY_TYPE[resource_type]
435
+ except KeyError as exc:
436
+ raise ValueError(
437
+ f"Connector {cls.source!r} does not declare resource type {resource_type!r}"
438
+ ) from exc
439
+
440
+ @classmethod
441
+ def resource_type_for_entity_type(cls, entity_type: str) -> ResourceType:
442
+ """Map a stored entity type to this connector's canonical fetch resource type."""
443
+ for definition in cls.entity_types:
444
+ if definition.name == entity_type:
445
+ return definition.resource_type
446
+ return _ENTITY_TYPE_TO_RESOURCE_TYPE.get(entity_type, "document")
405
447
 
406
448
  @classmethod
407
449
  def current_user_id(cls) -> str | None:
@@ -13,11 +13,7 @@ from uuid import UUID, uuid4
13
13
 
14
14
  from pydantic import BaseModel, Field
15
15
 
16
- from agentgraph.connectors.base import (
17
- RESOURCE_TYPE_TO_ENTITY_TYPE,
18
- BaseConnector,
19
- ResourceType,
20
- )
16
+ from agentgraph.connectors.base import BaseConnector, ResourceType
21
17
 
22
18
  logger = logging.getLogger(__name__)
23
19
 
@@ -201,11 +197,19 @@ def mutation_target_from_reference(
201
197
  url: str,
202
198
  ) -> MutationTarget:
203
199
  """Build a target from a connector-owned source reference."""
200
+ from agentgraph.connectors.base import SourceReference
201
+ from agentgraph.connectors.registry import entity_type_for_reference
204
202
 
205
203
  return MutationTarget(
206
204
  platform=platform,
207
205
  platform_entity_id=platform_entity_id,
208
- entity_type=RESOURCE_TYPE_TO_ENTITY_TYPE[resource_type],
206
+ entity_type=entity_type_for_reference(
207
+ SourceReference(
208
+ source=platform,
209
+ resource_type=resource_type,
210
+ resource_id=platform_entity_id,
211
+ )
212
+ ),
209
213
  resource_type=resource_type,
210
214
  url=url,
211
215
  )
@@ -0,0 +1,175 @@
1
+ """Connector registry: maps source names to connector instances.
2
+
3
+ Built-in connectors are discovered via Python entry points
4
+ (``agentgraph.connectors`` group). Third-party connectors can be installed
5
+ as separate packages that declare the same entry point group.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.metadata
11
+ import logging
12
+ from collections.abc import Iterable, Sequence
13
+ from typing import cast
14
+
15
+ from agentgraph.connectors.base import (
16
+ ENTITY_TYPE_DESCRIPTIONS,
17
+ ENTITY_TYPES,
18
+ RESOURCE_TYPE_TO_ENTITY_TYPE,
19
+ BaseConnector,
20
+ EntityTypeDefinition,
21
+ SourceReference,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ _registry: dict[str, BaseConnector] = {}
27
+ _entry_points: dict[str, importlib.metadata.EntryPoint] = {}
28
+ _load_errors: dict[str, str] = {}
29
+ _bootstrapped = False
30
+
31
+
32
+ def register(connector: BaseConnector) -> None:
33
+ _validate_entity_types(connector)
34
+ _registry[connector.source] = connector
35
+
36
+
37
+ def _validate_entity_types(connector: BaseConnector) -> None:
38
+ raw_definitions: object = getattr(type(connector), "entity_types", None)
39
+ if not isinstance(raw_definitions, tuple):
40
+ raise TypeError(f"Connector {connector.source!r} entity_types must be a tuple")
41
+ definitions = cast(tuple[object, ...], raw_definitions)
42
+
43
+ names: set[str] = set()
44
+ resource_types: set[str] = set()
45
+ for definition in definitions:
46
+ if not isinstance(definition, EntityTypeDefinition):
47
+ raise TypeError(
48
+ f"Connector {connector.source!r} entity_types must contain "
49
+ "EntityTypeDefinition values"
50
+ )
51
+ if definition.name in names:
52
+ raise ValueError(
53
+ f"Connector {connector.source!r} declares entity type "
54
+ f"{definition.name!r} more than once"
55
+ )
56
+ if definition.resource_type in resource_types:
57
+ raise ValueError(
58
+ f"Connector {connector.source!r} declares resource type "
59
+ f"{definition.resource_type!r} more than once"
60
+ )
61
+ names.add(definition.name)
62
+ resource_types.add(definition.resource_type)
63
+
64
+
65
+ def get_entity_type_names(connectors: Sequence[BaseConnector] | None = None) -> list[str]:
66
+ """Return the sorted union of core and installed connector entity type names."""
67
+ resolved = list(connectors) if connectors is not None else get_all_connectors()
68
+ names = set(ENTITY_TYPES)
69
+ for connector in resolved:
70
+ names.update(definition.name for definition in _entity_type_definitions(connector))
71
+ return sorted(names)
72
+
73
+
74
+ def get_entity_type_catalog(
75
+ connectors: Sequence[BaseConnector] | None = None,
76
+ ) -> list[tuple[str, list[tuple[str, str]]]]:
77
+ """Return entity types with their core or connector-labelled descriptions."""
78
+ resolved = list(connectors) if connectors is not None else get_all_connectors()
79
+ descriptions: dict[str, list[tuple[str, str]]] = {
80
+ name: [("core", ENTITY_TYPE_DESCRIPTIONS[name])] for name in ENTITY_TYPES
81
+ }
82
+ for connector in resolved:
83
+ for definition in _entity_type_definitions(connector):
84
+ entries = descriptions.setdefault(definition.name, [])
85
+ detail = (connector.source, definition.description)
86
+ if detail not in entries:
87
+ entries.append(detail)
88
+ catalog: list[tuple[str, list[tuple[str, str]]]] = []
89
+ for name in sorted(descriptions):
90
+ entries = descriptions[name]
91
+ entries.sort(key=lambda item: (item[0] != "core", item[0], item[1]))
92
+ catalog.append((name, entries))
93
+ return catalog
94
+
95
+
96
+ def _entity_type_definitions(connector: BaseConnector) -> tuple[EntityTypeDefinition, ...]:
97
+ return cast(
98
+ tuple[EntityTypeDefinition, ...],
99
+ getattr(type(connector), "entity_types", ()),
100
+ )
101
+
102
+
103
+ def entity_type_for_reference(reference: SourceReference) -> str:
104
+ """Resolve a source-scoped resource type to the stored graph entity type."""
105
+ connector = get_connector(reference.source)
106
+ if connector is not None:
107
+ return type(connector).entity_type_for_resource_type(reference.resource_type)
108
+ try:
109
+ return RESOURCE_TYPE_TO_ENTITY_TYPE[reference.resource_type]
110
+ except KeyError as exc:
111
+ raise ValueError(
112
+ f"No connector registered for source {reference.source!r} and resource type "
113
+ f"{reference.resource_type!r} is not a core resource type"
114
+ ) from exc
115
+
116
+
117
+ def get_connector(source: str) -> BaseConnector | None:
118
+ bootstrap()
119
+ if source not in _registry and source in _entry_points:
120
+ _load_entry_point(source, _entry_points[source])
121
+ return _registry.get(source)
122
+
123
+
124
+ def get_connector_load_error(source: str) -> str | None:
125
+ """Return the recorded entry-point import error for a connector, if any."""
126
+ return _load_errors.get(source)
127
+
128
+
129
+ def registered_sources() -> list[str]:
130
+ bootstrap()
131
+ return list(dict.fromkeys([*_registry.keys(), *_entry_points.keys()]))
132
+
133
+
134
+ def get_all_connectors() -> list[BaseConnector]:
135
+ bootstrap()
136
+ for name, ep in list(_entry_points.items()):
137
+ if name not in _registry:
138
+ _load_entry_point(name, ep)
139
+ return list(_registry.values())
140
+
141
+
142
+ def bootstrap() -> None:
143
+ """Discover connector entry points without importing connector packages."""
144
+ global _bootstrapped
145
+ if _bootstrapped:
146
+ return
147
+ _bootstrapped = True
148
+
149
+ for ep in _connector_entry_points():
150
+ _entry_points.setdefault(ep.name, ep)
151
+
152
+ if not _entry_points and not _registry:
153
+ logger.warning(
154
+ "No connectors discovered. Install connector packages (e.g. pip install agentgraph[all]) "
155
+ "or declare entry points in the 'agentgraph.connectors' group."
156
+ )
157
+
158
+
159
+ def _connector_entry_points() -> Iterable[importlib.metadata.EntryPoint]:
160
+ return importlib.metadata.entry_points(group="agentgraph.connectors")
161
+
162
+
163
+ def _load_entry_point(name: str, ep: importlib.metadata.EntryPoint) -> None:
164
+ try:
165
+ connector_class: type[BaseConnector] = ep.load()
166
+ connector = connector_class()
167
+ register(connector)
168
+ _load_errors.pop(name, None)
169
+ logger.debug("Loaded connector %r from %s", ep.name, ep.value)
170
+ if connector.source != name:
171
+ _entry_points.pop(name, None)
172
+ except Exception as exc:
173
+ _entry_points.pop(name, None)
174
+ _load_errors[name] = str(exc)
175
+ logger.warning("Failed to load connector %r: %s", ep.name, exc)