@tasksai/install 0.1.29 → 0.1.30
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.
- package/README.md +12 -0
- package/package.json +1 -1
- package/runtime/server.py +73 -8
package/README.md
CHANGED
|
@@ -75,3 +75,15 @@ folder. The save tool supports `.docx` and Markdown output, sanitizes filenames,
|
|
|
75
75
|
and avoids overwriting existing files unless explicitly told to overwrite.
|
|
76
76
|
Generated document content is handled by the customer's AI framework and local
|
|
77
77
|
MCP runtime; it is not sent to TasksAI websites or APIs.
|
|
78
|
+
|
|
79
|
+
## Realtor jurisdiction sources
|
|
80
|
+
|
|
81
|
+
For jurisdiction-sensitive RealtorTasksAI workflows, the local runtime sends
|
|
82
|
+
only the selected skill ID and general state, county, city, public-authority,
|
|
83
|
+
and MLS labels to the TasksAI API. It never sends the user's prompt, property
|
|
84
|
+
address, client facts, uploaded records, or generated document.
|
|
85
|
+
|
|
86
|
+
The API returns a reviewed source pack when one exists. If it does not, the
|
|
87
|
+
licensed runtime can request a controlled official-web lookup. Live results are
|
|
88
|
+
cached, source-linked, and visibly labeled `provisional`; they never substitute
|
|
89
|
+
for private MLS rules, brokerage policy, approved forms, or professional review.
|
package/package.json
CHANGED
package/runtime/server.py
CHANGED
|
@@ -125,7 +125,7 @@ if not LICENSE_KEY:
|
|
|
125
125
|
print("Find your key in your purchase confirmation email.", file=sys.stderr, flush=True)
|
|
126
126
|
sys.exit(1)
|
|
127
127
|
|
|
128
|
-
SERVER_VERSION = "2.
|
|
128
|
+
SERVER_VERSION = "2.5.0"
|
|
129
129
|
|
|
130
130
|
AUTH_HEADERS = {
|
|
131
131
|
"Authorization": f"Bearer {LICENSE_KEY}",
|
|
@@ -509,8 +509,8 @@ async def api_get(path):
|
|
|
509
509
|
return resp.json()
|
|
510
510
|
|
|
511
511
|
|
|
512
|
-
async def api_post(path, payload):
|
|
513
|
-
async with httpx.AsyncClient(timeout=
|
|
512
|
+
async def api_post(path, payload, *, timeout=10.0):
|
|
513
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
514
514
|
resp = await client.post(
|
|
515
515
|
f"{API_BASE}{path}",
|
|
516
516
|
json=payload,
|
|
@@ -523,20 +523,58 @@ async def api_post(path, payload):
|
|
|
523
523
|
def jurisdiction_sources_path(arguments):
|
|
524
524
|
"""Build a metadata-only source lookup path without customer or property data."""
|
|
525
525
|
skill_id = (arguments.get("skill_id") or "").strip()
|
|
526
|
-
state = (arguments.get("state")
|
|
526
|
+
state = general_authority_label(arguments.get("state"), "state")
|
|
527
527
|
if not skill_id:
|
|
528
528
|
raise ValueError("skill_id is required.")
|
|
529
529
|
|
|
530
530
|
params = {"skill_id": skill_id}
|
|
531
531
|
if state:
|
|
532
532
|
params["state"] = state
|
|
533
|
-
for field in ("county", "locality", "district"):
|
|
534
|
-
value = (arguments.get(field)
|
|
533
|
+
for field in ("county", "locality", "district", "mls"):
|
|
534
|
+
value = general_authority_label(arguments.get(field), field)
|
|
535
535
|
if value:
|
|
536
536
|
params[field] = value
|
|
537
537
|
return f"/v1/skills/jurisdiction-sources?{urlencode(params)}"
|
|
538
538
|
|
|
539
539
|
|
|
540
|
+
_GENERAL_AUTHORITY_LABEL = re.compile(r"^[A-Za-z0-9 .,'&()/-]{1,240}$")
|
|
541
|
+
_LIKELY_STREET_ADDRESS = re.compile(
|
|
542
|
+
r"^\d+\s+.+\b(?:street|st|avenue|ave|road|rd|drive|dr|lane|ln|boulevard|blvd|court|ct|way)\b",
|
|
543
|
+
re.IGNORECASE,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def general_authority_label(value, field):
|
|
548
|
+
"""Normalize a public authority label and reject likely customer data."""
|
|
549
|
+
normalized = " ".join(str(value or "").strip().split())
|
|
550
|
+
if not normalized:
|
|
551
|
+
return None
|
|
552
|
+
if (
|
|
553
|
+
not _GENERAL_AUTHORITY_LABEL.fullmatch(normalized)
|
|
554
|
+
or _LIKELY_STREET_ADDRESS.search(normalized)
|
|
555
|
+
):
|
|
556
|
+
raise ValueError(
|
|
557
|
+
f"{field} must be a general authority label, not an address or customer record."
|
|
558
|
+
)
|
|
559
|
+
return normalized
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def jurisdiction_research_payload(arguments):
|
|
563
|
+
"""Build the privacy-safe research request sent only after reviewed lookup misses."""
|
|
564
|
+
payload = {}
|
|
565
|
+
for field in ("skill_id", "state", "county", "locality", "district", "mls"):
|
|
566
|
+
value = (
|
|
567
|
+
(arguments.get(field) or "").strip()
|
|
568
|
+
if field == "skill_id"
|
|
569
|
+
else general_authority_label(arguments.get(field), field)
|
|
570
|
+
)
|
|
571
|
+
if value:
|
|
572
|
+
payload[field] = value
|
|
573
|
+
if not payload.get("skill_id") or not payload.get("state"):
|
|
574
|
+
raise ValueError("skill_id and state are required for live jurisdiction research.")
|
|
575
|
+
return payload
|
|
576
|
+
|
|
577
|
+
|
|
540
578
|
def format_jurisdiction_source_pack(pack):
|
|
541
579
|
"""Render the public source pack as compact instructions for the local AI."""
|
|
542
580
|
status = pack.get("status", "unsupported")
|
|
@@ -548,6 +586,7 @@ def format_jurisdiction_source_pack(pack):
|
|
|
548
586
|
f"- Skill: `{pack.get('skill_id', '')}`",
|
|
549
587
|
f"- Registry reviewed: {pack.get('registry_reviewed_on', 'not supplied')}",
|
|
550
588
|
f"- Registry refresh due: {pack.get('registry_refresh_due_on', 'not supplied')}",
|
|
589
|
+
f"- Resolution: **{pack.get('resolution_source', 'packaged')}**",
|
|
551
590
|
]
|
|
552
591
|
if pack.get("state_name"):
|
|
553
592
|
lines.append(f"- State: {pack['state_name']} ({pack.get('state_code', '')})")
|
|
@@ -566,6 +605,8 @@ def format_jurisdiction_source_pack(pack):
|
|
|
566
605
|
lines.extend(["", "## Sources"])
|
|
567
606
|
for source in sources:
|
|
568
607
|
effective = source.get("effective_date") or "not specified"
|
|
608
|
+
verification = source.get("verification_status", "reviewed")
|
|
609
|
+
date_label = "Machine researched" if verification == "machine_researched" else "Reviewed"
|
|
569
610
|
lines.extend(
|
|
570
611
|
[
|
|
571
612
|
"",
|
|
@@ -573,7 +614,8 @@ def format_jurisdiction_source_pack(pack):
|
|
|
573
614
|
f"- Authority: {source.get('authority', '')}",
|
|
574
615
|
f"- Jurisdiction: {source.get('jurisdiction', '')} ({source.get('scope_type', 'scope not supplied')})",
|
|
575
616
|
f"- Effective date: {effective}",
|
|
576
|
-
f"-
|
|
617
|
+
f"- Verification: {verification}",
|
|
618
|
+
f"- {date_label}: {source.get('reviewed_on', '')}",
|
|
577
619
|
f"- Refresh due: {source.get('refresh_due_on', '')}",
|
|
578
620
|
f"- URL: {source.get('url', '')}",
|
|
579
621
|
f"- Use: {source.get('purpose', '')}",
|
|
@@ -1672,8 +1714,9 @@ def build_tools(prefix, product_name, occupation):
|
|
|
1672
1714
|
Tool(
|
|
1673
1715
|
name=f"{prefix}_jurisdiction_sources",
|
|
1674
1716
|
description=(
|
|
1675
|
-
f"Retrieve the
|
|
1717
|
+
f"Retrieve the current jurisdiction-and-authority sources for a jurisdiction-sensitive {product_name} workflow. "
|
|
1676
1718
|
"Use after the applicable state is known and before applying state, local, district, regulator, form, or policy requirements. "
|
|
1719
|
+
"For RealtorTasksAI, a missing reviewed pack can trigger a licensed, privacy-safe official-web lookup whose results are explicitly provisional. "
|
|
1677
1720
|
"Send only the skill ID and general authority labels; never send an address, person name, student or client facts, documents, or generated content."
|
|
1678
1721
|
),
|
|
1679
1722
|
inputSchema={
|
|
@@ -1699,6 +1742,10 @@ def build_tools(prefix, product_name, occupation):
|
|
|
1699
1742
|
"type": "string",
|
|
1700
1743
|
"description": "Optional public authority or school-district name only; do not include a person, student, or record identifier.",
|
|
1701
1744
|
},
|
|
1745
|
+
"mls": {
|
|
1746
|
+
"type": "string",
|
|
1747
|
+
"description": "Optional MLS or Realtor-association name only; never send credentials, listing data, or member-only content.",
|
|
1748
|
+
},
|
|
1702
1749
|
},
|
|
1703
1750
|
"required": ["skill_id"],
|
|
1704
1751
|
},
|
|
@@ -1905,6 +1952,24 @@ async def call_tool(name, arguments):
|
|
|
1905
1952
|
}:
|
|
1906
1953
|
path = jurisdiction_sources_path(arguments or {})
|
|
1907
1954
|
result = await api_get(path)
|
|
1955
|
+
if (
|
|
1956
|
+
prefix == "realtortasksai"
|
|
1957
|
+
and (arguments or {}).get("state")
|
|
1958
|
+
and result.get("status") not in {"ready", "provisional"}
|
|
1959
|
+
):
|
|
1960
|
+
try:
|
|
1961
|
+
result = await api_post(
|
|
1962
|
+
"/v1/skills/jurisdiction-research",
|
|
1963
|
+
jurisdiction_research_payload(arguments or {}),
|
|
1964
|
+
timeout=90.0,
|
|
1965
|
+
)
|
|
1966
|
+
except Exception:
|
|
1967
|
+
result = dict(result)
|
|
1968
|
+
instructions = list(result.get("instructions") or [])
|
|
1969
|
+
instructions.append(
|
|
1970
|
+
"Controlled live research was unavailable. Continue only with intake or professional-supplied current official sources; do not substitute another jurisdiction."
|
|
1971
|
+
)
|
|
1972
|
+
result["instructions"] = instructions
|
|
1908
1973
|
return [TextContent(type="text", text=format_jurisdiction_source_pack(result))]
|
|
1909
1974
|
|
|
1910
1975
|
# ── Save Document ───────────────────────────────────────────────────
|