Raise memory discovery limits

This commit is contained in:
HumbleTurtle
2026-05-07 07:11:51 -03:00
parent a073f22403
commit c0d3c0c89a
2 changed files with 111 additions and 23 deletions
+51 -23
View File
@@ -50,6 +50,11 @@ from datetime import datetime, timedelta, timezone
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
DISCOVERY_VIEWS = {"paths", "summary", "metadata", "full"}
DISCOVERY_DEFAULT_LIMIT = 25
DISCOVERY_COMPACT_MAX_LIMIT = 10_000
DISCOVERY_FULL_MAX_LIMIT = 10_000
DISCOVERY_SOURCE_PAGE_LIMIT = 10_000
DISCOVERY_CURSOR_GUARD_PAGES = 100
ACTIVE_EXCLUDED_STATUSES = {
"completed",
"complete_verified_uncommitted",
@@ -92,6 +97,10 @@ SUMMARY_METADATA_KEYS = {
KANBAN_DONE_STATUSES = {"done", "cancelled", "archived"}
DEFAULT_KANBAN_COLUMNS = ["backlog", "todo", "doing", "review", "done"]
def discovery_max_limit(view):
return DISCOVERY_FULL_MAX_LIMIT if view == "full" else DISCOVERY_COMPACT_MAX_LIMIT
from napseer_spake2 import (
relay_aad,
relay_context_hash,
@@ -5196,7 +5205,7 @@ def summarize_node(node, view="summary", preview_chars=240):
def discovery_envelope(project_id, items, args, *, view=None, next_cursor=None, has_more=None, diagnostics=None, query_analysis=None, warnings=None):
view = view or normalize_discovery_view(args)[0]
limit = normalize_int(args.get("limit"), 25, 1, 100 if view != "full" else 200)
limit = normalize_int(args.get("limit"), DISCOVERY_DEFAULT_LIMIT, 1, discovery_max_limit(view))
preview_chars = normalize_int(args.get("preview_chars"), 240, 80, 1000)
summarized = [summarize_node(node, view=view, preview_chars=preview_chars) for node in items]
response = {
@@ -5208,7 +5217,7 @@ def discovery_envelope(project_id, items, args, *, view=None, next_cursor=None,
"has_more": bool(has_more) if has_more is not None else bool(next_cursor),
"truncated": False,
"omitted_fields": [] if view == "full" else ["content_text"],
"budget": {"limit": limit, "max_limit": 100 if view != "full" else 200, "preview_chars": preview_chars},
"budget": {"limit": limit, "max_limit": discovery_max_limit(view), "preview_chars": preview_chars},
}
if as_bool(args.get("verbose"), False):
if query_analysis is not None:
@@ -7327,7 +7336,7 @@ def list_project_nodes(args):
project_id = resolve_project_id(args)
view, view_warnings = normalize_discovery_view(args)
filters = status_filter_from_args(args)
limit = normalize_int(args.get("limit"), 25, 1, 100 if view != "full" else 200)
limit = normalize_int(args.get("limit"), DISCOVERY_DEFAULT_LIMIT, 1, discovery_max_limit(view))
allowed = {"tag", "folder_path", "q", "limit", "cursor", "updated_before", "updated_after", "type", "include_archived", "archived_only"}
base_query = {key: value for key, value in args.items() if key in allowed and value not in (None, "")}
base_query["limit"] = limit
@@ -7394,7 +7403,7 @@ def list_active_plans(args):
"include_archived": False,
"archived_only": False,
"view": view,
"limit": normalize_int(args.get("limit"), 25, 1, 100),
"limit": normalize_int(args.get("limit"), DISCOVERY_DEFAULT_LIMIT, 1, DISCOVERY_COMPACT_MAX_LIMIT),
}
return list_project_nodes(query)
@@ -7469,7 +7478,7 @@ def list_kanban_cards(args):
view, view_warnings = normalize_discovery_view(args)
if view == "full":
raise RuntimeError("nap_kanban_list supports paths, summary, or metadata views; use nap_cat for full card bodies")
limit = normalize_int(args.get("limit"), 25, 1, 100)
limit = normalize_int(args.get("limit"), DISCOVERY_DEFAULT_LIMIT, 1, DISCOVERY_COMPACT_MAX_LIMIT)
preview_chars = normalize_int(args.get("preview_chars"), 240, 80, 1000)
active_only = as_bool(args.get("active_only"), True)
column_filter = set(normalize_string_list(args.get("column") or args.get("columns")))
@@ -7483,17 +7492,35 @@ def list_kanban_cards(args):
if group_by not in {"column", "status", "priority", "owner", "blocked"}:
raise RuntimeError("group_by must be column, status, priority, owner, or blocked")
source_args = {
source_query = {
"q": "/kanban",
"limit": limit,
"limit": DISCOVERY_SOURCE_PAGE_LIMIT,
"view": "full",
"active_only": False,
"include_archived": not active_only,
"archived_only": False,
}
raw = list_project_nodes(source_args)
source_items = []
source_truncated = False
source_pages = 0
seen_cursors = set()
while True:
suffix = f"?{urllib.parse.urlencode(source_query)}"
raw = request_json("GET", f"/v1/projects/{project_id}/nodes{suffix}")
page_items = decrypt_nodes_for_return(filter_project_nodes(raw.get("items", [])), project_id=project_id)
source_items.extend(page_items)
source_pages += 1
next_cursor = raw.get("next_cursor")
if not next_cursor:
source_truncated = bool(raw.get("truncated") or raw.get("has_more"))
break
if next_cursor in seen_cursors or source_pages >= DISCOVERY_CURSOR_GUARD_PAGES:
source_truncated = True
break
seen_cursors.add(next_cursor)
source_query["cursor"] = next_cursor
items = []
for node in raw.get("items", []):
for node in source_items:
full_path = normalize_node_path(node.get("full_path") or "")
if not full_path.startswith("/kanban/"):
continue
@@ -7524,11 +7551,10 @@ def list_kanban_cards(args):
enriched["owner"] = owner
enriched["blocked"] = blocked
items.append(enriched)
if len(items) >= limit:
break
summarized = [summarize_node(node, view=view, preview_chars=preview_chars) for node in items]
for item, node in zip(summarized, items):
returned_items = items[:limit]
summarized = [summarize_node(node, view=view, preview_chars=preview_chars) for node in returned_items]
for item, node in zip(summarized, returned_items):
if node.get("column") is not None:
item["column"] = node["column"]
if node.get("priority") is not None:
@@ -7537,7 +7563,7 @@ def list_kanban_cards(args):
item["owner"] = node["owner"]
item["blocked"] = bool(node.get("blocked"))
groups = {}
for item, node in zip(summarized, items):
for item, node in zip(summarized, returned_items):
if group_by == "column":
key = node.get("column") or "uncategorized"
elif group_by == "status":
@@ -7559,9 +7585,9 @@ def list_kanban_cards(args):
"groups": groups,
"next_cursor": None,
"has_more": False,
"truncated": bool(raw.get("truncated") or raw.get("has_more") or raw.get("next_cursor")),
"truncated": bool(source_truncated or len(items) > limit),
"omitted_fields": ["content_text"],
"budget": {"limit": limit, "max_limit": 100, "preview_chars": preview_chars},
"budget": {"limit": limit, "max_limit": DISCOVERY_COMPACT_MAX_LIMIT, "preview_chars": preview_chars},
"filters": {
"active_only": active_only,
"column": sorted(column_filter),
@@ -7574,8 +7600,10 @@ def list_kanban_cards(args):
if as_bool(args.get("verbose"), False):
response["diagnostics"] = {
"source": "nodes",
"source_query": {"q": "/kanban", "view": "full"},
"source_item_count": len(raw.get("items", [])),
"source_query": {"q": "/kanban", "view": "full", "limit": DISCOVERY_SOURCE_PAGE_LIMIT},
"source_page_count": source_pages,
"source_item_count": len(source_items),
"filtered_item_count": len(items),
"default_columns": DEFAULT_KANBAN_COLUMNS,
}
if view_warnings:
@@ -9495,7 +9523,7 @@ def raw_tools():
"tag": {"type": "string"},
"q": {"type": "string"},
"cursor": {"type": "string"},
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
"limit": {"type": "integer", "minimum": 1, "maximum": 10000},
"view": {"type": "string", "enum": ["paths", "summary", "metadata", "full"], "default": "summary"},
"include_content": {"type": "boolean", "default": False, "description": "Legacy; use view='full' for body text. Ignored when view is set."},
"include_archived": {"type": "boolean", "default": False},
@@ -9518,7 +9546,7 @@ def raw_tools():
"folder_path": {"type": "string", "default": "/plans"},
"status": {"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]},
"exclude_status": {"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]},
"limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25},
"limit": {"type": "integer", "minimum": 1, "maximum": 10000, "default": 25},
"cursor": {"type": "string"},
"view": {"type": "string", "enum": ["paths", "summary", "metadata"], "default": "summary"},
"verbose": {"type": "boolean", "default": False},
@@ -9544,7 +9572,7 @@ def raw_tools():
"blocking": {"type": "boolean"},
"active_only": {"type": "boolean", "default": True},
"group_by": {"type": "string", "enum": ["column", "status", "priority", "owner", "blocked"], "default": "column"},
"limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25},
"limit": {"type": "integer", "minimum": 1, "maximum": 10000, "default": 25},
"view": {"type": "string", "enum": ["paths", "summary", "metadata"], "default": "summary"},
"verbose": {"type": "boolean", "default": False},
"preview_chars": {"type": "integer", "minimum": 80, "maximum": 1000, "default": 240},
@@ -9921,7 +9949,7 @@ def raw_tools():
"folder_path": {"type": "string"},
"q": {"type": "string", "description": "Search phrase. Distinctive nouns, paths, tags, and quoted phrases work best; long sentences are decomposed automatically."},
"cursor": {"type": "string"},
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
"limit": {"type": "integer", "minimum": 1, "maximum": 10000},
"view": {"type": "string", "enum": ["paths", "summary", "metadata", "full"], "default": "summary"},
"include_content": {"type": "boolean", "default": False, "description": "Legacy; use view='full' for body text. Ignored when view is set."},
"include_archived": {"type": "boolean", "default": False},