@tasksai/install 0.1.23 → 0.1.25
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/package.json +1 -1
- package/runtime/server.py +380 -18
- package/runtime/skill_matcher.py +8 -0
package/package.json
CHANGED
package/runtime/server.py
CHANGED
|
@@ -28,6 +28,7 @@ import httpx
|
|
|
28
28
|
import uuid
|
|
29
29
|
from datetime import datetime
|
|
30
30
|
from pathlib import Path
|
|
31
|
+
from urllib.parse import urlencode
|
|
31
32
|
|
|
32
33
|
# The runtime is installed as a pair of adjacent Python files and is also
|
|
33
34
|
# loaded directly by path in focused tests, so make the sibling matcher
|
|
@@ -124,7 +125,7 @@ if not LICENSE_KEY:
|
|
|
124
125
|
print("Find your key in your purchase confirmation email.", file=sys.stderr, flush=True)
|
|
125
126
|
sys.exit(1)
|
|
126
127
|
|
|
127
|
-
SERVER_VERSION = "2.
|
|
128
|
+
SERVER_VERSION = "2.4.1"
|
|
128
129
|
|
|
129
130
|
AUTH_HEADERS = {
|
|
130
131
|
"Authorization": f"Bearer {LICENSE_KEY}",
|
|
@@ -519,6 +520,79 @@ async def api_post(path, payload):
|
|
|
519
520
|
return resp.json()
|
|
520
521
|
|
|
521
522
|
|
|
523
|
+
def jurisdiction_sources_path(arguments):
|
|
524
|
+
"""Build a metadata-only source lookup path without customer or property data."""
|
|
525
|
+
skill_id = (arguments.get("skill_id") or "").strip()
|
|
526
|
+
state = (arguments.get("state") or "").strip()
|
|
527
|
+
if not skill_id:
|
|
528
|
+
raise ValueError("skill_id is required.")
|
|
529
|
+
|
|
530
|
+
params = {"skill_id": skill_id}
|
|
531
|
+
if state:
|
|
532
|
+
params["state"] = state
|
|
533
|
+
for field in ("county", "locality", "district"):
|
|
534
|
+
value = (arguments.get(field) or "").strip()
|
|
535
|
+
if value:
|
|
536
|
+
params[field] = value
|
|
537
|
+
return f"/v1/skills/jurisdiction-sources?{urlencode(params)}"
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def format_jurisdiction_source_pack(pack):
|
|
541
|
+
"""Render the public source pack as compact instructions for the local AI."""
|
|
542
|
+
status = pack.get("status", "unsupported")
|
|
543
|
+
lines = [
|
|
544
|
+
"# Official Jurisdiction and Authority Source Pack",
|
|
545
|
+
"",
|
|
546
|
+
f"- Status: **{status}**",
|
|
547
|
+
f"- Coverage: **{pack.get('coverage_level', 'intake_only')}**",
|
|
548
|
+
f"- Skill: `{pack.get('skill_id', '')}`",
|
|
549
|
+
f"- Registry reviewed: {pack.get('registry_reviewed_on', 'not supplied')}",
|
|
550
|
+
f"- Registry refresh due: {pack.get('registry_refresh_due_on', 'not supplied')}",
|
|
551
|
+
]
|
|
552
|
+
if pack.get("state_name"):
|
|
553
|
+
lines.append(f"- State: {pack['state_name']} ({pack.get('state_code', '')})")
|
|
554
|
+
if pack.get("county"):
|
|
555
|
+
lines.append(f"- County: {pack['county']}")
|
|
556
|
+
if pack.get("locality"):
|
|
557
|
+
lines.append(f"- Locality: {pack['locality']}")
|
|
558
|
+
if pack.get("district"):
|
|
559
|
+
lines.append(f"- District: {pack['district']}")
|
|
560
|
+
authority_layers = pack.get("authority_layers") or []
|
|
561
|
+
if authority_layers:
|
|
562
|
+
lines.append(f"- Authority layers: {', '.join(authority_layers)}")
|
|
563
|
+
|
|
564
|
+
sources = pack.get("sources") or []
|
|
565
|
+
if sources:
|
|
566
|
+
lines.extend(["", "## Sources"])
|
|
567
|
+
for source in sources:
|
|
568
|
+
effective = source.get("effective_date") or "not specified"
|
|
569
|
+
lines.extend(
|
|
570
|
+
[
|
|
571
|
+
"",
|
|
572
|
+
f"### {source.get('title', 'Official source')}",
|
|
573
|
+
f"- Authority: {source.get('authority', '')}",
|
|
574
|
+
f"- Jurisdiction: {source.get('jurisdiction', '')} ({source.get('scope_type', 'scope not supplied')})",
|
|
575
|
+
f"- Effective date: {effective}",
|
|
576
|
+
f"- Reviewed: {source.get('reviewed_on', '')}",
|
|
577
|
+
f"- Refresh due: {source.get('refresh_due_on', '')}",
|
|
578
|
+
f"- URL: {source.get('url', '')}",
|
|
579
|
+
f"- Use: {source.get('purpose', '')}",
|
|
580
|
+
f"- Limit: {source.get('limits', '')}",
|
|
581
|
+
]
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
required_authorities = pack.get("required_user_authorities") or []
|
|
585
|
+
if required_authorities:
|
|
586
|
+
lines.extend(["", "## Professional-Supplied Authority Still Needed"])
|
|
587
|
+
lines.extend(f"- {item}" for item in required_authorities)
|
|
588
|
+
|
|
589
|
+
instructions = pack.get("instructions") or []
|
|
590
|
+
if instructions:
|
|
591
|
+
lines.extend(["", "## Required handling"])
|
|
592
|
+
lines.extend(f"- {instruction}" for instruction in instructions)
|
|
593
|
+
return "\n".join(lines)
|
|
594
|
+
|
|
595
|
+
|
|
522
596
|
def get_install_id() -> str:
|
|
523
597
|
"""Stable anonymous install id for attribution; stored locally only."""
|
|
524
598
|
global INSTALL_ID
|
|
@@ -828,10 +902,57 @@ def is_markdown_table_start(lines, index):
|
|
|
828
902
|
return bool(header and markdown_table_alignments(separator))
|
|
829
903
|
|
|
830
904
|
|
|
905
|
+
def inferred_plain_heading_level(lines, index):
|
|
906
|
+
"""Infer conservative headings when an AI omits Markdown # markers."""
|
|
907
|
+
text = lines[index].strip()
|
|
908
|
+
if not text or len(text) > 90 or "|" in text or ":" in text:
|
|
909
|
+
return None
|
|
910
|
+
if text.endswith((".", "?", "!", ";")):
|
|
911
|
+
return None
|
|
912
|
+
|
|
913
|
+
letters = [char for char in text if char.isalpha()]
|
|
914
|
+
if len(letters) >= 4 and all(char.isupper() for char in letters):
|
|
915
|
+
return 2
|
|
916
|
+
|
|
917
|
+
next_index = index + 1
|
|
918
|
+
while next_index < len(lines) and not lines[next_index].strip():
|
|
919
|
+
next_index += 1
|
|
920
|
+
next_line = lines[next_index] if next_index < len(lines) else ""
|
|
921
|
+
introduces_list = bool(
|
|
922
|
+
re.match(r"^\s*[-*+]\s+\S", next_line)
|
|
923
|
+
or re.match(r"^\s*\d+\.\s+\S", next_line)
|
|
924
|
+
)
|
|
925
|
+
introduces_facts = bool(re.match(r"^\s*[^:\n]{1,55}:\s+\S", next_line))
|
|
926
|
+
if introduces_list or introduces_facts or text.istitle():
|
|
927
|
+
return 3
|
|
928
|
+
return None
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
def labeled_fact(line):
|
|
932
|
+
"""Return a label/value pair for a short professional fact line."""
|
|
933
|
+
match = re.match(r"^([^:\n]{1,55}):\s+(.+)$", line.strip())
|
|
934
|
+
if not match:
|
|
935
|
+
return None
|
|
936
|
+
return match.group(1).strip(), match.group(2).strip()
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def add_labeled_text(paragraph, label, value):
|
|
940
|
+
"""Render a label/value pair in an existing Word paragraph."""
|
|
941
|
+
label_run = paragraph.add_run(f"{label}: ")
|
|
942
|
+
label_run.bold = True
|
|
943
|
+
add_markdown_text(paragraph, value)
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def add_labeled_fact(doc, label, value):
|
|
947
|
+
"""Render a fact line with a bold label and distinct Word paragraph."""
|
|
948
|
+
paragraph = doc.add_paragraph()
|
|
949
|
+
add_labeled_text(paragraph, label, value)
|
|
950
|
+
|
|
951
|
+
|
|
831
952
|
def iter_inline_markdown(text):
|
|
832
953
|
"""Yield (text, marks) spans for common inline Markdown."""
|
|
833
954
|
token_re = re.compile(
|
|
834
|
-
r"(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|\*[^*\s][^*]*\*|_[^_\s][^_]*_|\[[^\]]+\]\([^)]+\))"
|
|
955
|
+
r"(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|\*[^*\s][^*]*\*|(?<!\w)_[^_\s][^_]*_(?!\w)|\[[^\]]+\]\([^)]+\))"
|
|
835
956
|
)
|
|
836
957
|
pos = 0
|
|
837
958
|
for match in token_re.finditer(text):
|
|
@@ -934,10 +1055,81 @@ def add_horizontal_rule(paragraph):
|
|
|
934
1055
|
borders.append(bottom)
|
|
935
1056
|
|
|
936
1057
|
|
|
1058
|
+
def set_cell_shading(cell, fill):
|
|
1059
|
+
"""Apply a solid background color to a Word table cell."""
|
|
1060
|
+
from docx.oxml import OxmlElement
|
|
1061
|
+
from docx.oxml.ns import qn
|
|
1062
|
+
|
|
1063
|
+
cell_properties = cell._tc.get_or_add_tcPr()
|
|
1064
|
+
shading = cell_properties.find(qn("w:shd"))
|
|
1065
|
+
if shading is None:
|
|
1066
|
+
shading = OxmlElement("w:shd")
|
|
1067
|
+
cell_properties.append(shading)
|
|
1068
|
+
shading.set(qn("w:fill"), fill)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def set_cell_margins(cell, top=70, start=90, bottom=70, end=90):
|
|
1072
|
+
"""Set compact, readable table cell margins in twentieths of a point."""
|
|
1073
|
+
from docx.oxml import OxmlElement
|
|
1074
|
+
from docx.oxml.ns import qn
|
|
1075
|
+
|
|
1076
|
+
cell_properties = cell._tc.get_or_add_tcPr()
|
|
1077
|
+
margins = cell_properties.first_child_found_in("w:tcMar")
|
|
1078
|
+
if margins is None:
|
|
1079
|
+
margins = OxmlElement("w:tcMar")
|
|
1080
|
+
cell_properties.append(margins)
|
|
1081
|
+
for name, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
|
|
1082
|
+
node = margins.find(qn(f"w:{name}"))
|
|
1083
|
+
if node is None:
|
|
1084
|
+
node = OxmlElement(f"w:{name}")
|
|
1085
|
+
margins.append(node)
|
|
1086
|
+
node.set(qn("w:w"), str(value))
|
|
1087
|
+
node.set(qn("w:type"), "dxa")
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def repeat_table_header(row):
|
|
1091
|
+
"""Repeat the first row of a long table on subsequent pages."""
|
|
1092
|
+
from docx.oxml import OxmlElement
|
|
1093
|
+
from docx.oxml.ns import qn
|
|
1094
|
+
|
|
1095
|
+
row_properties = row._tr.get_or_add_trPr()
|
|
1096
|
+
repeat = row_properties.find(qn("w:tblHeader"))
|
|
1097
|
+
if repeat is None:
|
|
1098
|
+
repeat = OxmlElement("w:tblHeader")
|
|
1099
|
+
row_properties.append(repeat)
|
|
1100
|
+
repeat.set(qn("w:val"), "true")
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
def prevent_table_row_split(row):
|
|
1104
|
+
"""Keep a table row together when Word paginates the document."""
|
|
1105
|
+
from docx.oxml import OxmlElement
|
|
1106
|
+
from docx.oxml.ns import qn
|
|
1107
|
+
|
|
1108
|
+
row_properties = row._tr.get_or_add_trPr()
|
|
1109
|
+
if row_properties.find(qn("w:cantSplit")) is None:
|
|
1110
|
+
row_properties.append(OxmlElement("w:cantSplit"))
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
def add_page_number(paragraph):
|
|
1114
|
+
"""Add a dynamic PAGE field to a footer paragraph."""
|
|
1115
|
+
from docx.oxml import OxmlElement
|
|
1116
|
+
from docx.oxml.ns import qn
|
|
1117
|
+
|
|
1118
|
+
run = paragraph.add_run()
|
|
1119
|
+
begin = OxmlElement("w:fldChar")
|
|
1120
|
+
begin.set(qn("w:fldCharType"), "begin")
|
|
1121
|
+
instruction = OxmlElement("w:instrText")
|
|
1122
|
+
instruction.set(qn("xml:space"), "preserve")
|
|
1123
|
+
instruction.text = " PAGE "
|
|
1124
|
+
end = OxmlElement("w:fldChar")
|
|
1125
|
+
end.set(qn("w:fldCharType"), "end")
|
|
1126
|
+
run._r.extend((begin, instruction, end))
|
|
1127
|
+
|
|
1128
|
+
|
|
937
1129
|
def add_markdown_table(doc, rows, alignments):
|
|
938
1130
|
"""Render a Markdown pipe table as a native Word table."""
|
|
939
1131
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
940
|
-
from docx.shared import Pt
|
|
1132
|
+
from docx.shared import Pt, RGBColor
|
|
941
1133
|
|
|
942
1134
|
if not rows:
|
|
943
1135
|
return
|
|
@@ -947,15 +1139,22 @@ def add_markdown_table(doc, rows, alignments):
|
|
|
947
1139
|
table = doc.add_table(rows=len(normalized), cols=width)
|
|
948
1140
|
table.style = "Table Grid"
|
|
949
1141
|
table.autofit = True
|
|
950
|
-
|
|
1142
|
+
repeat_table_header(table.rows[0])
|
|
951
1143
|
for row_index, row in enumerate(normalized):
|
|
952
1144
|
for column_index, value in enumerate(row):
|
|
953
1145
|
cell = table.cell(row_index, column_index)
|
|
1146
|
+
set_cell_margins(cell)
|
|
1147
|
+
if row_index == 0:
|
|
1148
|
+
set_cell_shading(cell, "17365D")
|
|
1149
|
+
elif row_index % 2 == 0:
|
|
1150
|
+
set_cell_shading(cell, "EEF3F8")
|
|
954
1151
|
paragraph = cell.paragraphs[0]
|
|
1152
|
+
paragraph.paragraph_format.space_after = Pt(0)
|
|
955
1153
|
add_markdown_text(paragraph, value)
|
|
956
1154
|
if row_index == 0:
|
|
957
1155
|
for run in paragraph.runs:
|
|
958
1156
|
run.bold = True
|
|
1157
|
+
run.font.color.rgb = RGBColor(255, 255, 255)
|
|
959
1158
|
alignment = alignments[column_index] if column_index < len(alignments) else "left"
|
|
960
1159
|
if alignment == "right":
|
|
961
1160
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
@@ -968,16 +1167,70 @@ def add_markdown_table(doc, rows, alignments):
|
|
|
968
1167
|
|
|
969
1168
|
|
|
970
1169
|
def write_docx(path, title, content_markdown, product_name):
|
|
971
|
-
"""Write a
|
|
1170
|
+
"""Write a professionally styled DOCX file locally."""
|
|
972
1171
|
try:
|
|
973
1172
|
from docx import Document
|
|
974
|
-
from docx.
|
|
1173
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
1174
|
+
from docx.shared import Inches, Pt, RGBColor
|
|
975
1175
|
except Exception as exc:
|
|
976
1176
|
raise RuntimeError(
|
|
977
1177
|
"python-docx is not installed. Re-run the official installer or install python-docx in the MCP runtime environment."
|
|
978
1178
|
) from exc
|
|
979
1179
|
|
|
980
1180
|
doc = Document()
|
|
1181
|
+
doc.core_properties.title = title or f"{product_name} Work Product"
|
|
1182
|
+
doc.core_properties.author = product_name
|
|
1183
|
+
|
|
1184
|
+
section = doc.sections[0]
|
|
1185
|
+
section.top_margin = Inches(0.55)
|
|
1186
|
+
section.bottom_margin = Inches(0.55)
|
|
1187
|
+
section.left_margin = Inches(0.7)
|
|
1188
|
+
section.right_margin = Inches(0.7)
|
|
1189
|
+
section.header_distance = Inches(0.3)
|
|
1190
|
+
section.footer_distance = Inches(0.3)
|
|
1191
|
+
|
|
1192
|
+
styles = doc.styles
|
|
1193
|
+
normal = styles["Normal"]
|
|
1194
|
+
normal.font.name = "Aptos"
|
|
1195
|
+
normal.font.size = Pt(9.3)
|
|
1196
|
+
normal.font.color.rgb = RGBColor(31, 41, 55)
|
|
1197
|
+
normal.paragraph_format.space_after = Pt(4)
|
|
1198
|
+
normal.paragraph_format.line_spacing = 1.05
|
|
1199
|
+
|
|
1200
|
+
title_style = styles["Title"]
|
|
1201
|
+
title_style.font.name = "Aptos Display"
|
|
1202
|
+
title_style.font.size = Pt(19)
|
|
1203
|
+
title_style.font.bold = True
|
|
1204
|
+
title_style.font.color.rgb = RGBColor(23, 54, 93)
|
|
1205
|
+
title_style.paragraph_format.space_after = Pt(5)
|
|
1206
|
+
|
|
1207
|
+
heading_sizes = {1: 14, 2: 11.5, 3: 10.5, 4: 9.5}
|
|
1208
|
+
for level, size in heading_sizes.items():
|
|
1209
|
+
style = styles[f"Heading {level}"]
|
|
1210
|
+
style.font.name = "Aptos Display"
|
|
1211
|
+
style.font.size = Pt(size)
|
|
1212
|
+
style.font.bold = True
|
|
1213
|
+
style.font.color.rgb = RGBColor(23, 54, 93)
|
|
1214
|
+
style.paragraph_format.space_before = Pt(8 if level <= 2 else 5)
|
|
1215
|
+
style.paragraph_format.space_after = Pt(3)
|
|
1216
|
+
style.paragraph_format.keep_with_next = True
|
|
1217
|
+
|
|
1218
|
+
header = section.header.paragraphs[0]
|
|
1219
|
+
header.text = f"{product_name} | Professional Work Product"
|
|
1220
|
+
header.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
1221
|
+
for run in header.runs:
|
|
1222
|
+
run.font.name = "Aptos"
|
|
1223
|
+
run.font.size = Pt(7.5)
|
|
1224
|
+
run.font.color.rgb = RGBColor(100, 116, 139)
|
|
1225
|
+
|
|
1226
|
+
footer = section.footer.paragraphs[0]
|
|
1227
|
+
footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
1228
|
+
footer_run = footer.add_run(f"Generated locally by {product_name} | Private local output | Page ")
|
|
1229
|
+
footer_run.font.name = "Aptos"
|
|
1230
|
+
footer_run.font.size = Pt(7.5)
|
|
1231
|
+
footer_run.font.color.rgb = RGBColor(100, 116, 139)
|
|
1232
|
+
add_page_number(footer)
|
|
1233
|
+
|
|
981
1234
|
if title:
|
|
982
1235
|
doc.add_heading(title, level=0)
|
|
983
1236
|
|
|
@@ -987,16 +1240,24 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
987
1240
|
def flush_paragraph():
|
|
988
1241
|
if not paragraph_buffer:
|
|
989
1242
|
return
|
|
990
|
-
|
|
1243
|
+
text_parts = []
|
|
1244
|
+
for part in paragraph_buffer:
|
|
1245
|
+
clean = part.strip()
|
|
1246
|
+
if not clean:
|
|
1247
|
+
continue
|
|
1248
|
+
text_parts.append(clean)
|
|
1249
|
+
text_parts.append("\n" if part.endswith(" ") else " ")
|
|
1250
|
+
text = "".join(text_parts).rstrip()
|
|
991
1251
|
paragraph_buffer.clear()
|
|
992
1252
|
if text:
|
|
993
1253
|
add_markdown_text(doc.add_paragraph(), text)
|
|
994
1254
|
|
|
995
1255
|
in_code_block = False
|
|
1256
|
+
keep_checklist_together = False
|
|
996
1257
|
index = 0
|
|
997
1258
|
while index < len(lines):
|
|
998
1259
|
raw_line = lines[index]
|
|
999
|
-
line = raw_line.rstrip()
|
|
1260
|
+
line = raw_line.rstrip("\r\n")
|
|
1000
1261
|
stripped = line.strip()
|
|
1001
1262
|
if stripped.startswith("```"):
|
|
1002
1263
|
flush_paragraph()
|
|
@@ -1016,10 +1277,34 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1016
1277
|
continue
|
|
1017
1278
|
|
|
1018
1279
|
heading = re.match(r"^(#{1,4})\s+(.+)$", stripped)
|
|
1280
|
+
standalone_bold_heading = re.fullmatch(r"\*\*([^*\n]{1,120})\*\*", stripped)
|
|
1281
|
+
inferred_heading = inferred_plain_heading_level(lines, index)
|
|
1019
1282
|
bullet = re.match(r"^(\s*)[-*+]\s+(.+)$", line)
|
|
1020
1283
|
numbered = re.match(r"^(\s*)\d+\.\s+(.+)$", line)
|
|
1021
1284
|
quote = re.match(r"^>\s?(.+)$", stripped)
|
|
1285
|
+
fact = (
|
|
1286
|
+
labeled_fact(stripped)
|
|
1287
|
+
if not paragraph_buffer
|
|
1288
|
+
and not line.endswith(" ")
|
|
1289
|
+
and not bullet
|
|
1290
|
+
and not numbered
|
|
1291
|
+
and not quote
|
|
1292
|
+
else None
|
|
1293
|
+
)
|
|
1022
1294
|
|
|
1295
|
+
if re.fullmatch(r"<!--\s*[^\n]*?\s*-->", stripped):
|
|
1296
|
+
# Public-copy delimiters and other machine-readable Markdown
|
|
1297
|
+
# comments are useful in the source but must not appear in the
|
|
1298
|
+
# downloadable customer document.
|
|
1299
|
+
flush_paragraph()
|
|
1300
|
+
index += 1
|
|
1301
|
+
continue
|
|
1302
|
+
if stripped == ">":
|
|
1303
|
+
# A blank line inside a Markdown blockquote is structural spacing,
|
|
1304
|
+
# not a literal greater-than character in the Word document.
|
|
1305
|
+
flush_paragraph()
|
|
1306
|
+
index += 1
|
|
1307
|
+
continue
|
|
1023
1308
|
if is_markdown_table_start(lines, index):
|
|
1024
1309
|
flush_paragraph()
|
|
1025
1310
|
header = markdown_table_row(lines[index])
|
|
@@ -1036,7 +1321,28 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1036
1321
|
continue
|
|
1037
1322
|
elif heading:
|
|
1038
1323
|
flush_paragraph()
|
|
1039
|
-
|
|
1324
|
+
heading_text = heading.group(2).strip()
|
|
1325
|
+
if not (
|
|
1326
|
+
title
|
|
1327
|
+
and index == 0
|
|
1328
|
+
and re.sub(r"\W+", "", heading_text).casefold()
|
|
1329
|
+
== re.sub(r"\W+", "", title).casefold()
|
|
1330
|
+
):
|
|
1331
|
+
doc.add_heading(heading_text, level=min(len(heading.group(1)), 4))
|
|
1332
|
+
keep_checklist_together = "checklist" in heading_text.casefold()
|
|
1333
|
+
index += 1
|
|
1334
|
+
elif standalone_bold_heading:
|
|
1335
|
+
flush_paragraph()
|
|
1336
|
+
doc.add_heading(standalone_bold_heading.group(1).strip(), level=3)
|
|
1337
|
+
index += 1
|
|
1338
|
+
elif inferred_heading:
|
|
1339
|
+
flush_paragraph()
|
|
1340
|
+
doc.add_heading(stripped, level=inferred_heading)
|
|
1341
|
+
keep_checklist_together = "checklist" in stripped.casefold()
|
|
1342
|
+
index += 1
|
|
1343
|
+
elif fact:
|
|
1344
|
+
flush_paragraph()
|
|
1345
|
+
add_labeled_fact(doc, fact[0], fact[1])
|
|
1040
1346
|
index += 1
|
|
1041
1347
|
elif bullet:
|
|
1042
1348
|
flush_paragraph()
|
|
@@ -1047,7 +1353,19 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1047
1353
|
checkbox = re.match(r"^\[([ xX])\]\s+(.+)$", text)
|
|
1048
1354
|
if checkbox:
|
|
1049
1355
|
text = f"[{checkbox.group(1).lower()}] {checkbox.group(2).strip()}"
|
|
1050
|
-
|
|
1356
|
+
bullet_fact = labeled_fact(text)
|
|
1357
|
+
if bullet_fact:
|
|
1358
|
+
add_labeled_text(para, bullet_fact[0], bullet_fact[1])
|
|
1359
|
+
else:
|
|
1360
|
+
add_markdown_text(para, text)
|
|
1361
|
+
next_is_bullet = bool(
|
|
1362
|
+
index + 1 < len(lines)
|
|
1363
|
+
and re.match(r"^\s*[-*+]\s+\S", lines[index + 1])
|
|
1364
|
+
)
|
|
1365
|
+
if keep_checklist_together and next_is_bullet:
|
|
1366
|
+
para.paragraph_format.keep_with_next = True
|
|
1367
|
+
elif keep_checklist_together:
|
|
1368
|
+
keep_checklist_together = False
|
|
1051
1369
|
index += 1
|
|
1052
1370
|
elif numbered:
|
|
1053
1371
|
flush_paragraph()
|
|
@@ -1070,17 +1388,11 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
1070
1388
|
add_horizontal_rule(doc.add_paragraph())
|
|
1071
1389
|
index += 1
|
|
1072
1390
|
else:
|
|
1073
|
-
paragraph_buffer.append(
|
|
1391
|
+
paragraph_buffer.append(line)
|
|
1074
1392
|
index += 1
|
|
1075
1393
|
|
|
1076
1394
|
flush_paragraph()
|
|
1077
1395
|
|
|
1078
|
-
doc.add_paragraph()
|
|
1079
|
-
footer = doc.add_paragraph()
|
|
1080
|
-
run = footer.add_run(f"Generated locally by {product_name}. Content was not sent to TasksAI servers.")
|
|
1081
|
-
run.italic = True
|
|
1082
|
-
run.font.size = Pt(8)
|
|
1083
|
-
|
|
1084
1396
|
doc.save(path)
|
|
1085
1397
|
|
|
1086
1398
|
|
|
@@ -1145,7 +1457,7 @@ def build_tools(prefix, product_name, occupation):
|
|
|
1145
1457
|
domain_adjective = normalize_domain_adjective(occ_key)
|
|
1146
1458
|
examples = _examples.get(domain_adjective, _examples.get(product_name.replace('TasksAI','').lower().strip(), f"e.g. a {domain_adjective} task"))
|
|
1147
1459
|
|
|
1148
|
-
|
|
1460
|
+
tools = [
|
|
1149
1461
|
Tool(
|
|
1150
1462
|
name=f"{prefix}_search",
|
|
1151
1463
|
description=(
|
|
@@ -1250,6 +1562,46 @@ def build_tools(prefix, product_name, occupation):
|
|
|
1250
1562
|
),
|
|
1251
1563
|
]
|
|
1252
1564
|
|
|
1565
|
+
if prefix in {"realtortasksai", "farmertasksai", "teachertasksai"}:
|
|
1566
|
+
tools.insert(
|
|
1567
|
+
2,
|
|
1568
|
+
Tool(
|
|
1569
|
+
name=f"{prefix}_jurisdiction_sources",
|
|
1570
|
+
description=(
|
|
1571
|
+
f"Retrieve the curated current jurisdiction-and-authority source pack for a jurisdiction-sensitive {product_name} workflow. "
|
|
1572
|
+
"Use after the applicable state is known and before applying state, local, district, regulator, form, or policy requirements. "
|
|
1573
|
+
"Send only the skill ID and general authority labels; never send an address, person name, student or client facts, documents, or generated content."
|
|
1574
|
+
),
|
|
1575
|
+
inputSchema={
|
|
1576
|
+
"type": "object",
|
|
1577
|
+
"properties": {
|
|
1578
|
+
"skill_id": {
|
|
1579
|
+
"type": "string",
|
|
1580
|
+
"description": "Exact workflow ID requesting a task-specific source pack.",
|
|
1581
|
+
},
|
|
1582
|
+
"state": {
|
|
1583
|
+
"type": "string",
|
|
1584
|
+
"description": "Property state name or two-letter code. Omit only to obtain a blocking missing-jurisdiction response.",
|
|
1585
|
+
},
|
|
1586
|
+
"county": {
|
|
1587
|
+
"type": "string",
|
|
1588
|
+
"description": "Optional county name only; do not include an address.",
|
|
1589
|
+
},
|
|
1590
|
+
"locality": {
|
|
1591
|
+
"type": "string",
|
|
1592
|
+
"description": "Optional city or municipality name only; do not include an address.",
|
|
1593
|
+
},
|
|
1594
|
+
"district": {
|
|
1595
|
+
"type": "string",
|
|
1596
|
+
"description": "Optional public authority or school-district name only; do not include a person, student, or record identifier.",
|
|
1597
|
+
},
|
|
1598
|
+
},
|
|
1599
|
+
"required": ["skill_id"],
|
|
1600
|
+
},
|
|
1601
|
+
),
|
|
1602
|
+
)
|
|
1603
|
+
return tools
|
|
1604
|
+
|
|
1253
1605
|
|
|
1254
1606
|
def normalize_audience_label(label):
|
|
1255
1607
|
"""Return a natural audience phrase without naive pluralization."""
|
|
@@ -1441,6 +1793,16 @@ async def call_tool(name, arguments):
|
|
|
1441
1793
|
f"---\n*Credits remaining: {credits_remaining}*"
|
|
1442
1794
|
))]
|
|
1443
1795
|
|
|
1796
|
+
# ── Jurisdiction Sources ─────────────────────────────────────────────
|
|
1797
|
+
elif name == f"{prefix}_jurisdiction_sources" and prefix in {
|
|
1798
|
+
"realtortasksai",
|
|
1799
|
+
"farmertasksai",
|
|
1800
|
+
"teachertasksai",
|
|
1801
|
+
}:
|
|
1802
|
+
path = jurisdiction_sources_path(arguments or {})
|
|
1803
|
+
result = await api_get(path)
|
|
1804
|
+
return [TextContent(type="text", text=format_jurisdiction_source_pack(result))]
|
|
1805
|
+
|
|
1444
1806
|
# ── Save Document ───────────────────────────────────────────────────
|
|
1445
1807
|
elif name == f"{prefix}_save_document":
|
|
1446
1808
|
output_path, fmt = save_document(arguments or {}, product_name)
|
package/runtime/skill_matcher.py
CHANGED
|
@@ -57,6 +57,12 @@ DEFAULT_CONTEXT_WORDS = {
|
|
|
57
57
|
DEFAULT_THRESHOLDS = {"high": 90, "medium": 45}
|
|
58
58
|
DEFAULT_RESULT_COUNT = 3
|
|
59
59
|
MAXIMUM_RESULT_COUNT = 10
|
|
60
|
+
TRIGGER_EVIDENCE_RANK = {
|
|
61
|
+
"trigger_exact": 4,
|
|
62
|
+
"trigger_complete": 3,
|
|
63
|
+
"trigger_query_complete": 2,
|
|
64
|
+
"trigger_partial": 1,
|
|
65
|
+
}
|
|
60
66
|
|
|
61
67
|
|
|
62
68
|
def normalize_text(value: object) -> str:
|
|
@@ -386,6 +392,7 @@ def _score_skill(
|
|
|
386
392
|
"score": score,
|
|
387
393
|
"confidence": confidence,
|
|
388
394
|
"evidence": evidence,
|
|
395
|
+
"_trigger_rank": TRIGGER_EVIDENCE_RANK.get(trigger_evidence, 0),
|
|
389
396
|
"_trigger_specificity": trigger_specificity,
|
|
390
397
|
"_matched_tokens": matched_tokens,
|
|
391
398
|
"_description_normalized": normalize_text(skill.get("description", "")),
|
|
@@ -395,6 +402,7 @@ def _score_skill(
|
|
|
395
402
|
|
|
396
403
|
def _sort_key(result: Mapping[str, object]) -> tuple:
|
|
397
404
|
return (
|
|
405
|
+
-result["_trigger_rank"],
|
|
398
406
|
-result["score"],
|
|
399
407
|
-result["_trigger_specificity"],
|
|
400
408
|
-len(result["_matched_tokens"]),
|