aline-ai 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl
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.
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/METADATA +1 -1
- aline_ai-0.5.6.dist-info/RECORD +95 -0
- realign/__init__.py +1 -1
- realign/adapters/antigravity.py +28 -20
- realign/adapters/base.py +46 -50
- realign/adapters/claude.py +14 -14
- realign/adapters/codex.py +7 -7
- realign/adapters/gemini.py +11 -11
- realign/adapters/registry.py +14 -10
- realign/claude_detector.py +2 -2
- realign/claude_hooks/__init__.py +3 -3
- realign/claude_hooks/permission_request_hook_installer.py +31 -32
- realign/claude_hooks/stop_hook.py +4 -1
- realign/claude_hooks/stop_hook_installer.py +30 -31
- realign/cli.py +23 -4
- realign/codex_detector.py +11 -11
- realign/commands/add.py +88 -65
- realign/commands/config.py +3 -12
- realign/commands/context.py +3 -1
- realign/commands/export_shares.py +86 -127
- realign/commands/import_shares.py +145 -155
- realign/commands/init.py +166 -30
- realign/commands/restore.py +18 -6
- realign/commands/search.py +14 -42
- realign/commands/upgrade.py +155 -11
- realign/commands/watcher.py +98 -219
- realign/commands/worker.py +29 -6
- realign/config.py +25 -20
- realign/context.py +1 -3
- realign/dashboard/app.py +34 -24
- realign/dashboard/screens/__init__.py +10 -1
- realign/dashboard/screens/create_agent.py +244 -0
- realign/dashboard/screens/create_event.py +3 -1
- realign/dashboard/screens/event_detail.py +14 -6
- realign/dashboard/screens/help_screen.py +114 -0
- realign/dashboard/screens/session_detail.py +3 -1
- realign/dashboard/screens/share_import.py +7 -3
- realign/dashboard/tmux_manager.py +54 -9
- realign/dashboard/widgets/config_panel.py +85 -1
- realign/dashboard/widgets/events_table.py +314 -70
- realign/dashboard/widgets/header.py +2 -1
- realign/dashboard/widgets/search_panel.py +37 -27
- realign/dashboard/widgets/sessions_table.py +404 -85
- realign/dashboard/widgets/terminal_panel.py +155 -175
- realign/dashboard/widgets/watcher_panel.py +6 -2
- realign/dashboard/widgets/worker_panel.py +10 -1
- realign/db/__init__.py +1 -1
- realign/db/base.py +5 -15
- realign/db/locks.py +0 -1
- realign/db/migration.py +82 -76
- realign/db/schema.py +2 -6
- realign/db/sqlite_db.py +23 -41
- realign/events/__init__.py +0 -1
- realign/events/event_summarizer.py +27 -15
- realign/events/session_summarizer.py +29 -15
- realign/file_lock.py +1 -0
- realign/hooks.py +150 -60
- realign/logging_config.py +12 -15
- realign/mcp_server.py +30 -51
- realign/mcp_watcher.py +0 -1
- realign/models/event.py +29 -20
- realign/prompts/__init__.py +7 -7
- realign/prompts/presets.py +15 -11
- realign/redactor.py +99 -59
- realign/triggers/__init__.py +9 -9
- realign/triggers/antigravity_trigger.py +30 -28
- realign/triggers/base.py +4 -3
- realign/triggers/claude_trigger.py +104 -85
- realign/triggers/codex_trigger.py +15 -5
- realign/triggers/gemini_trigger.py +57 -47
- realign/triggers/next_turn_trigger.py +3 -1
- realign/triggers/registry.py +6 -2
- realign/triggers/turn_status.py +3 -1
- realign/watcher_core.py +306 -131
- realign/watcher_daemon.py +8 -8
- realign/worker_core.py +3 -1
- realign/worker_daemon.py +3 -1
- aline_ai-0.5.4.dist-info/RECORD +0 -93
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/WHEEL +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/top_level.txt +0 -0
|
@@ -154,7 +154,7 @@ def _find_events_by_uuid(selector: str, events: List) -> List[int]:
|
|
|
154
154
|
|
|
155
155
|
for i, event in enumerate(events, 1):
|
|
156
156
|
# Support both 'id' and 'event_id' attributes
|
|
157
|
-
eid = getattr(event,
|
|
157
|
+
eid = getattr(event, "event_id", None) or getattr(event, "id", None) or ""
|
|
158
158
|
eid = eid.lower()
|
|
159
159
|
if eid.startswith(part) or eid == part:
|
|
160
160
|
if i not in indices:
|
|
@@ -452,9 +452,7 @@ def _compact_message_content(content: Any, config: ExportCompactionConfig) -> An
|
|
|
452
452
|
cmd = input_obj.get("command")
|
|
453
453
|
if isinstance(cmd, str):
|
|
454
454
|
input_obj = dict(input_obj)
|
|
455
|
-
input_obj["command"] = _truncate_string(
|
|
456
|
-
cmd, config.max_tool_command_chars
|
|
457
|
-
)
|
|
455
|
+
input_obj["command"] = _truncate_string(cmd, config.max_tool_command_chars)
|
|
458
456
|
block = dict(block)
|
|
459
457
|
block["input"] = input_obj
|
|
460
458
|
compacted.append(block)
|
|
@@ -527,9 +525,7 @@ def _compact_jsonl_record(
|
|
|
527
525
|
if not isinstance(item, dict):
|
|
528
526
|
continue
|
|
529
527
|
t = item.get("type")
|
|
530
|
-
if t in ("input_text", "output_text") and isinstance(
|
|
531
|
-
item.get("text"), str
|
|
532
|
-
):
|
|
528
|
+
if t in ("input_text", "output_text") and isinstance(item.get("text"), str):
|
|
533
529
|
texts.append(item["text"])
|
|
534
530
|
compact_message: Dict[str, Any] = {}
|
|
535
531
|
if isinstance(role, str):
|
|
@@ -573,7 +569,11 @@ def _compact_jsonl_record(
|
|
|
573
569
|
if isinstance(role, str):
|
|
574
570
|
compact_message["role"] = role
|
|
575
571
|
compacted_content = _compact_message_content(content, config)
|
|
576
|
-
if
|
|
572
|
+
if (
|
|
573
|
+
isinstance(compacted_content, str)
|
|
574
|
+
and config.omit_thinking_blocks
|
|
575
|
+
and compacted_content.startswith("[Thinking]")
|
|
576
|
+
):
|
|
577
577
|
return None
|
|
578
578
|
compact_message["content"] = compacted_content
|
|
579
579
|
|
|
@@ -651,9 +651,7 @@ def _build_export_size_report(
|
|
|
651
651
|
content = msg.get("content")
|
|
652
652
|
if isinstance(content, list):
|
|
653
653
|
for block in content:
|
|
654
|
-
if isinstance(block, dict) and isinstance(
|
|
655
|
-
block.get("type"), str
|
|
656
|
-
):
|
|
654
|
+
if isinstance(block, dict) and isinstance(block.get("type"), str):
|
|
657
655
|
block_types.append(block["type"])
|
|
658
656
|
if block.get("type") == "tool_use":
|
|
659
657
|
tool_use_id = block.get("id")
|
|
@@ -669,7 +667,11 @@ def _build_export_size_report(
|
|
|
669
667
|
command = input_obj["command"]
|
|
670
668
|
if isinstance(input_obj, dict):
|
|
671
669
|
input_keys = sorted(
|
|
672
|
-
[
|
|
670
|
+
[
|
|
671
|
+
k
|
|
672
|
+
for k in input_obj.keys()
|
|
673
|
+
if isinstance(k, str)
|
|
674
|
+
]
|
|
673
675
|
)
|
|
674
676
|
for k, v in input_obj.items():
|
|
675
677
|
if isinstance(k, str) and isinstance(v, str):
|
|
@@ -681,20 +683,24 @@ def _build_export_size_report(
|
|
|
681
683
|
break
|
|
682
684
|
tool_use_index[tool_use_id] = {
|
|
683
685
|
"tool_name": block.get("name"),
|
|
684
|
-
"description":
|
|
685
|
-
|
|
686
|
-
|
|
686
|
+
"description": (
|
|
687
|
+
input_obj.get("description")
|
|
688
|
+
if isinstance(input_obj, dict)
|
|
689
|
+
else None
|
|
690
|
+
),
|
|
687
691
|
"input_keys": input_keys,
|
|
688
692
|
"input_string_lengths": input_string_lengths,
|
|
689
693
|
"path_hint": input_path_hint,
|
|
690
|
-
"command_snippet":
|
|
691
|
-
command, 200
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
"command_chars":
|
|
696
|
-
|
|
697
|
-
|
|
694
|
+
"command_snippet": (
|
|
695
|
+
_truncate_string(command, 200)
|
|
696
|
+
if isinstance(command, str)
|
|
697
|
+
else None
|
|
698
|
+
),
|
|
699
|
+
"command_chars": (
|
|
700
|
+
len(command)
|
|
701
|
+
if isinstance(command, str)
|
|
702
|
+
else None
|
|
703
|
+
),
|
|
698
704
|
}
|
|
699
705
|
if block.get("type") == "tool_result":
|
|
700
706
|
tool_use_id = block.get("tool_use_id")
|
|
@@ -718,11 +724,7 @@ def _build_export_size_report(
|
|
|
718
724
|
"session_id": session.session_id,
|
|
719
725
|
"turn_id": turn.id,
|
|
720
726
|
"turn_number": turn.turn_number,
|
|
721
|
-
"timestamp": (
|
|
722
|
-
turn.timestamp.isoformat()
|
|
723
|
-
if turn.timestamp
|
|
724
|
-
else None
|
|
725
|
-
),
|
|
727
|
+
"timestamp": (turn.timestamp.isoformat() if turn.timestamp else None),
|
|
726
728
|
"record_type": kind,
|
|
727
729
|
"role": role,
|
|
728
730
|
"content_block_types": sorted(set(block_types))[:6],
|
|
@@ -780,9 +782,7 @@ def build_enhanced_conversation_data(
|
|
|
780
782
|
Dictionary with v2.0 structure including event metadata, session metadata,
|
|
781
783
|
and structured turns with messages
|
|
782
784
|
"""
|
|
783
|
-
logger.info(
|
|
784
|
-
f"Building enhanced conversation data (v2.0) for event: {selected_event.title}"
|
|
785
|
-
)
|
|
785
|
+
logger.info(f"Building enhanced conversation data (v2.0) for event: {selected_event.title}")
|
|
786
786
|
|
|
787
787
|
# Build event data
|
|
788
788
|
event_data = {
|
|
@@ -792,9 +792,7 @@ def build_enhanced_conversation_data(
|
|
|
792
792
|
"event_type": selected_event.event_type,
|
|
793
793
|
"status": selected_event.status,
|
|
794
794
|
"created_at": None, # Will be populated if available from EventRecord
|
|
795
|
-
"updated_at": selected_event.updated_at.isoformat()
|
|
796
|
-
if selected_event.updated_at
|
|
797
|
-
else None,
|
|
795
|
+
"updated_at": selected_event.updated_at.isoformat() if selected_event.updated_at else None,
|
|
798
796
|
"metadata": {},
|
|
799
797
|
}
|
|
800
798
|
|
|
@@ -834,9 +832,7 @@ def build_enhanced_conversation_data(
|
|
|
834
832
|
continue
|
|
835
833
|
messages.append(msg)
|
|
836
834
|
except json.JSONDecodeError:
|
|
837
|
-
logger.warning(
|
|
838
|
-
f"Failed to parse JSONL line in turn {turn.id}"
|
|
839
|
-
)
|
|
835
|
+
logger.warning(f"Failed to parse JSONL line in turn {turn.id}")
|
|
840
836
|
continue
|
|
841
837
|
|
|
842
838
|
# Build turn data (V9: includes creator fields)
|
|
@@ -844,9 +840,9 @@ def build_enhanced_conversation_data(
|
|
|
844
840
|
"turn_id": turn.id,
|
|
845
841
|
"turn_number": turn.turn_number,
|
|
846
842
|
"content_hash": turn.content_hash,
|
|
847
|
-
"timestamp":
|
|
848
|
-
|
|
849
|
-
|
|
843
|
+
"timestamp": (
|
|
844
|
+
turn.timestamp.isoformat() if turn.timestamp else datetime.now().isoformat()
|
|
845
|
+
),
|
|
850
846
|
"llm_title": turn.llm_title or "",
|
|
851
847
|
"llm_description": turn.llm_description,
|
|
852
848
|
"user_message": (
|
|
@@ -862,7 +858,11 @@ def build_enhanced_conversation_data(
|
|
|
862
858
|
_collapse_high_entropy_runs(turn.assistant_summary, compaction),
|
|
863
859
|
compaction.max_turn_assistant_summary_chars,
|
|
864
860
|
)
|
|
865
|
-
if (
|
|
861
|
+
if (
|
|
862
|
+
compaction
|
|
863
|
+
and compaction.enabled
|
|
864
|
+
and isinstance(turn.assistant_summary, str)
|
|
865
|
+
)
|
|
866
866
|
else turn.assistant_summary
|
|
867
867
|
),
|
|
868
868
|
"model_name": turn.model_name,
|
|
@@ -880,12 +880,10 @@ def build_enhanced_conversation_data(
|
|
|
880
880
|
"workspace_path": session.workspace_path,
|
|
881
881
|
"session_title": session.session_title,
|
|
882
882
|
"session_summary": session.session_summary,
|
|
883
|
-
"started_at": session.started_at.isoformat()
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if session.last_activity_at
|
|
888
|
-
else None,
|
|
883
|
+
"started_at": session.started_at.isoformat() if session.started_at else None,
|
|
884
|
+
"last_activity_at": (
|
|
885
|
+
session.last_activity_at.isoformat() if session.last_activity_at else None
|
|
886
|
+
),
|
|
889
887
|
"creator_name": session.creator_name,
|
|
890
888
|
"creator_id": session.creator_id,
|
|
891
889
|
"turns": turns_data,
|
|
@@ -938,9 +936,7 @@ def get_unpushed_commits(repo_root: Path) -> List[UnpushedCommit]:
|
|
|
938
936
|
Returns empty list since git tracking is no longer used.
|
|
939
937
|
Use get_sessions_for_export() instead.
|
|
940
938
|
"""
|
|
941
|
-
logger.warning(
|
|
942
|
-
"get_unpushed_commits is deprecated, use get_sessions_for_export instead"
|
|
943
|
-
)
|
|
939
|
+
logger.warning("get_unpushed_commits is deprecated, use get_sessions_for_export instead")
|
|
944
940
|
return []
|
|
945
941
|
|
|
946
942
|
|
|
@@ -1212,11 +1208,7 @@ def display_sessions_for_selection(sessions: List[ExportableSession]) -> None:
|
|
|
1212
1208
|
if title:
|
|
1213
1209
|
title = title[:80] + ("..." if len(title) > 80 else "")
|
|
1214
1210
|
|
|
1215
|
-
last_ts = (
|
|
1216
|
-
session.last_activity_at.isoformat()
|
|
1217
|
-
if session.last_activity_at
|
|
1218
|
-
else "unknown"
|
|
1219
|
-
)
|
|
1211
|
+
last_ts = session.last_activity_at.isoformat() if session.last_activity_at else "unknown"
|
|
1220
1212
|
turns = session.turn_count
|
|
1221
1213
|
meta = f"{session.session_type} | turns={turns} | last={last_ts}"
|
|
1222
1214
|
if title:
|
|
@@ -1254,11 +1246,7 @@ def display_events_for_selection(events: List[ExportableEvent]) -> None:
|
|
|
1254
1246
|
# First line: [索引] event_id... | 会话数 | 更新时间
|
|
1255
1247
|
event_id_short = event.event_id[:8]
|
|
1256
1248
|
session_count = len(event.sessions)
|
|
1257
|
-
updated = (
|
|
1258
|
-
event.updated_at.strftime("%Y-%m-%d %H:%M")
|
|
1259
|
-
if event.updated_at
|
|
1260
|
-
else "unknown"
|
|
1261
|
-
)
|
|
1249
|
+
updated = event.updated_at.strftime("%Y-%m-%d %H:%M") if event.updated_at else "unknown"
|
|
1262
1250
|
|
|
1263
1251
|
first_line = f" [{event.index}] {event_id_short}... | {session_count} sessions | {updated}"
|
|
1264
1252
|
if use_rich:
|
|
@@ -1451,7 +1439,10 @@ def export_shares_command(
|
|
|
1451
1439
|
indices_list = parse_indices(indices_input)
|
|
1452
1440
|
except ValueError as e:
|
|
1453
1441
|
print(f"Error: Invalid selector format: {e}", file=sys.stderr)
|
|
1454
|
-
print(
|
|
1442
|
+
print(
|
|
1443
|
+
f"Valid formats: 1-{len(events)}, abc123de (UUID prefix), or abc123de,def456gh (multiple UUIDs)",
|
|
1444
|
+
file=sys.stderr,
|
|
1445
|
+
)
|
|
1455
1446
|
logger.error(f"Invalid selector format: {e}")
|
|
1456
1447
|
return 1
|
|
1457
1448
|
|
|
@@ -1540,9 +1531,7 @@ def encrypt_conversation_data(data: dict, password: str) -> dict:
|
|
|
1540
1531
|
包含加密数据的字典: {encrypted_data, salt, nonce, password_hash}
|
|
1541
1532
|
"""
|
|
1542
1533
|
if not CRYPTO_AVAILABLE:
|
|
1543
|
-
raise RuntimeError(
|
|
1544
|
-
"cryptography package not installed. Run: pip install cryptography"
|
|
1545
|
-
)
|
|
1534
|
+
raise RuntimeError("cryptography package not installed. Run: pip install cryptography")
|
|
1546
1535
|
|
|
1547
1536
|
# 生成盐值和随机数
|
|
1548
1537
|
salt = os.urandom(32)
|
|
@@ -1627,7 +1616,9 @@ def upload_to_backend(
|
|
|
1627
1616
|
encrypted_data_size = len(encrypted_data.encode("utf-8"))
|
|
1628
1617
|
|
|
1629
1618
|
# Always print payload size for debugging
|
|
1630
|
-
print(
|
|
1619
|
+
print(
|
|
1620
|
+
f"📊 Payload size: {payload_size / 1024 / 1024:.2f}MB (threshold: {CHUNKED_UPLOAD_THRESHOLD / 1024 / 1024:.2f}MB)"
|
|
1621
|
+
)
|
|
1631
1622
|
|
|
1632
1623
|
# Decide upload method based on size
|
|
1633
1624
|
# Use chunked upload if either the full payload or encrypted_data exceeds threshold
|
|
@@ -1724,9 +1715,7 @@ def _upload_chunks_and_complete(
|
|
|
1724
1715
|
# Upload each chunk
|
|
1725
1716
|
for i, chunk in enumerate(chunks):
|
|
1726
1717
|
if progress_callback:
|
|
1727
|
-
progress_callback(
|
|
1728
|
-
i + 1, total_chunks + 2, f"Uploading chunk {i + 1}/{total_chunks}..."
|
|
1729
|
-
)
|
|
1718
|
+
progress_callback(i + 1, total_chunks + 2, f"Uploading chunk {i + 1}/{total_chunks}...")
|
|
1730
1719
|
|
|
1731
1720
|
try:
|
|
1732
1721
|
chunk_payload = {
|
|
@@ -1912,7 +1901,9 @@ def upload_to_backend_unencrypted(
|
|
|
1912
1901
|
payload_size = len(payload_json.encode("utf-8"))
|
|
1913
1902
|
|
|
1914
1903
|
# Always print payload size for debugging
|
|
1915
|
-
print(
|
|
1904
|
+
print(
|
|
1905
|
+
f"📊 Payload size: {payload_size / 1024 / 1024:.2f}MB (threshold: {CHUNKED_UPLOAD_THRESHOLD / 1024 / 1024:.2f}MB)"
|
|
1906
|
+
)
|
|
1916
1907
|
|
|
1917
1908
|
# Decide upload method based on size
|
|
1918
1909
|
if payload_size > CHUNKED_UPLOAD_THRESHOLD:
|
|
@@ -2398,9 +2389,7 @@ Return the content in JSON format."""
|
|
|
2398
2389
|
|
|
2399
2390
|
except Exception as e:
|
|
2400
2391
|
logger.error(f"LLM UI metadata generation failed: {e}", exc_info=True)
|
|
2401
|
-
print(
|
|
2402
|
-
f" ⚠️ LLM generation failed: {e}, using default UI text", file=sys.stderr
|
|
2403
|
-
)
|
|
2392
|
+
print(f" ⚠️ LLM generation failed: {e}, using default UI text", file=sys.stderr)
|
|
2404
2393
|
return None, None
|
|
2405
2394
|
|
|
2406
2395
|
|
|
@@ -2629,9 +2618,7 @@ def _export_by_events_interactive(
|
|
|
2629
2618
|
|
|
2630
2619
|
if not events:
|
|
2631
2620
|
print("No events available yet.")
|
|
2632
|
-
print(
|
|
2633
|
-
"Tip: events are auto-generated by session_summarizer during watcher sessions"
|
|
2634
|
-
)
|
|
2621
|
+
print("Tip: events are auto-generated by session_summarizer during watcher sessions")
|
|
2635
2622
|
return 1
|
|
2636
2623
|
|
|
2637
2624
|
# Default: show 3-7 most recent events, but allow showing all (tree view).
|
|
@@ -2642,15 +2629,11 @@ def _export_by_events_interactive(
|
|
|
2642
2629
|
)
|
|
2643
2630
|
default_limit = 7
|
|
2644
2631
|
show_choice = (
|
|
2645
|
-
input(
|
|
2646
|
-
f"Show [r]ecent events ({min(default_limit, len(events_sorted))}) or [a]ll? [r]: "
|
|
2647
|
-
)
|
|
2632
|
+
input(f"Show [r]ecent events ({min(default_limit, len(events_sorted))}) or [a]ll? [r]: ")
|
|
2648
2633
|
.strip()
|
|
2649
2634
|
.lower()
|
|
2650
2635
|
)
|
|
2651
|
-
events_to_show = (
|
|
2652
|
-
events_sorted if show_choice.startswith("a") else events_sorted[:default_limit]
|
|
2653
|
-
)
|
|
2636
|
+
events_to_show = events_sorted if show_choice.startswith("a") else events_sorted[:default_limit]
|
|
2654
2637
|
|
|
2655
2638
|
def _event_path(ev) -> List[str]:
|
|
2656
2639
|
ui = ev.ui_metadata or {}
|
|
@@ -2714,9 +2697,7 @@ def _export_by_events_interactive(
|
|
|
2714
2697
|
"idx": idx,
|
|
2715
2698
|
"label": key,
|
|
2716
2699
|
"commit_count": commit_count,
|
|
2717
|
-
"desc": desc
|
|
2718
|
-
if (desc and not desc.startswith("Auto-added"))
|
|
2719
|
-
else "",
|
|
2700
|
+
"desc": desc if (desc and not desc.startswith("Auto-added")) else "",
|
|
2720
2701
|
}
|
|
2721
2702
|
)
|
|
2722
2703
|
else:
|
|
@@ -2773,15 +2754,9 @@ def _export_by_events_interactive(
|
|
|
2773
2754
|
console.print(Padding(_render_markdown_lite(line), (0, 0, 0, 2 * indent)))
|
|
2774
2755
|
desc = (item.get("desc") or "").strip()
|
|
2775
2756
|
if desc:
|
|
2776
|
-
console.print(
|
|
2777
|
-
Padding(_render_markdown_lite(desc), (0, 0, 1, 2 * (indent + 1)))
|
|
2778
|
-
)
|
|
2757
|
+
console.print(Padding(_render_markdown_lite(desc), (0, 0, 1, 2 * (indent + 1))))
|
|
2779
2758
|
else:
|
|
2780
|
-
console.print(
|
|
2781
|
-
Padding(
|
|
2782
|
-
_render_markdown_lite(str(item["label"])), (0, 0, 0, 2 * indent)
|
|
2783
|
-
)
|
|
2784
|
-
)
|
|
2759
|
+
console.print(Padding(_render_markdown_lite(str(item["label"])), (0, 0, 0, 2 * indent)))
|
|
2785
2760
|
console.print()
|
|
2786
2761
|
|
|
2787
2762
|
# Get user selection
|
|
@@ -2807,9 +2782,7 @@ def _export_by_events_interactive(
|
|
|
2807
2782
|
else:
|
|
2808
2783
|
# Fall back to numeric indices
|
|
2809
2784
|
indices_list = parse_commit_indices(selection)
|
|
2810
|
-
selected_events = [
|
|
2811
|
-
index_to_event[i] for i in indices_list if i in index_to_event
|
|
2812
|
-
]
|
|
2785
|
+
selected_events = [index_to_event[i] for i in indices_list if i in index_to_event]
|
|
2813
2786
|
|
|
2814
2787
|
if not selected_events:
|
|
2815
2788
|
print("No valid events selected.")
|
|
@@ -2817,7 +2790,9 @@ def _export_by_events_interactive(
|
|
|
2817
2790
|
|
|
2818
2791
|
except ValueError as e:
|
|
2819
2792
|
print(f"Error: Invalid selector format: {e}", file=sys.stderr)
|
|
2820
|
-
print(
|
|
2793
|
+
print(
|
|
2794
|
+
"Valid formats: 1,3,5-7, all, abc123de (UUID prefix), or abc123de,def456gh (multiple UUIDs)"
|
|
2795
|
+
)
|
|
2821
2796
|
return 1
|
|
2822
2797
|
|
|
2823
2798
|
all_commit_hashes = set()
|
|
@@ -2833,9 +2808,7 @@ def _export_by_events_interactive(
|
|
|
2833
2808
|
loaded = get_commits_by_hashes(shadow_git, missing_hashes)
|
|
2834
2809
|
selected_commits.extend(loaded)
|
|
2835
2810
|
except Exception as e:
|
|
2836
|
-
logger.error(
|
|
2837
|
-
f"Failed to load commits by hashes for events: {e}", exc_info=True
|
|
2838
|
-
)
|
|
2811
|
+
logger.error(f"Failed to load commits by hashes for events: {e}", exc_info=True)
|
|
2839
2812
|
print(
|
|
2840
2813
|
f"Error: Failed to load commits for selected events: {e}",
|
|
2841
2814
|
file=sys.stderr,
|
|
@@ -2868,11 +2841,7 @@ def _export_by_events_interactive(
|
|
|
2868
2841
|
selected_commits = sorted(selected_commits, key=lambda c: c.timestamp, reverse=True)
|
|
2869
2842
|
print("\n📋 Commits included:\n")
|
|
2870
2843
|
for i, c in enumerate(selected_commits, 1):
|
|
2871
|
-
summary = (
|
|
2872
|
-
c.llm_summary
|
|
2873
|
-
if c.llm_summary and c.llm_summary != "(No summary)"
|
|
2874
|
-
else c.message
|
|
2875
|
-
)
|
|
2844
|
+
summary = c.llm_summary if c.llm_summary and c.llm_summary != "(No summary)" else c.message
|
|
2876
2845
|
print(f" [{i}] {c.hash} - {summary}")
|
|
2877
2846
|
print()
|
|
2878
2847
|
|
|
@@ -2888,9 +2857,7 @@ def _export_by_events_interactive(
|
|
|
2888
2857
|
try:
|
|
2889
2858
|
keep = parse_commit_indices(subset)
|
|
2890
2859
|
selected_commits = [
|
|
2891
|
-
selected_commits[i - 1]
|
|
2892
|
-
for i in keep
|
|
2893
|
-
if 0 < i <= len(selected_commits)
|
|
2860
|
+
selected_commits[i - 1] for i in keep if 0 < i <= len(selected_commits)
|
|
2894
2861
|
]
|
|
2895
2862
|
except Exception as e:
|
|
2896
2863
|
print(f"Error: Invalid indices format: {e}", file=sys.stderr)
|
|
@@ -3101,9 +3068,7 @@ def export_shares_interactive_command(
|
|
|
3101
3068
|
|
|
3102
3069
|
from .. import get_realign_dir
|
|
3103
3070
|
|
|
3104
|
-
shadow_git = get_realign_dir(
|
|
3105
|
-
repo_root
|
|
3106
|
-
) # used for log/output placement (not a git repo)
|
|
3071
|
+
shadow_git = get_realign_dir(repo_root) # used for log/output placement (not a git repo)
|
|
3107
3072
|
|
|
3108
3073
|
# Always show all events, don't filter by workspace
|
|
3109
3074
|
events = get_events_for_export(workspace_path=None, limit=200)
|
|
@@ -3122,9 +3087,7 @@ def export_shares_interactive_command(
|
|
|
3122
3087
|
from rich.console import Console
|
|
3123
3088
|
|
|
3124
3089
|
console = Console()
|
|
3125
|
-
console.print(
|
|
3126
|
-
"[yellow]Enter event number to export (single event only):[/yellow]"
|
|
3127
|
-
)
|
|
3090
|
+
console.print("[yellow]Enter event number to export (single event only):[/yellow]")
|
|
3128
3091
|
except ImportError:
|
|
3129
3092
|
print("Enter event number to export (single event only):")
|
|
3130
3093
|
indices = input("Event #: ").strip()
|
|
@@ -3236,7 +3199,9 @@ def export_shares_interactive_command(
|
|
|
3236
3199
|
and full_event.share_url
|
|
3237
3200
|
):
|
|
3238
3201
|
reuse_share_url = full_event.share_url
|
|
3239
|
-
reuse_share_id = getattr(full_event, "share_id", None) or _extract_share_id_from_url(
|
|
3202
|
+
reuse_share_id = getattr(full_event, "share_id", None) or _extract_share_id_from_url(
|
|
3203
|
+
reuse_share_url
|
|
3204
|
+
)
|
|
3240
3205
|
reuse_admin_token = getattr(full_event, "share_admin_token", None)
|
|
3241
3206
|
|
|
3242
3207
|
# Fast path: if we're not debugging, don't rebuild payload—just extend expiry.
|
|
@@ -3312,9 +3277,7 @@ def export_shares_interactive_command(
|
|
|
3312
3277
|
compaction=None,
|
|
3313
3278
|
)
|
|
3314
3279
|
conversation_data_full_for_debug = conversation_data_full
|
|
3315
|
-
full_bytes = len(
|
|
3316
|
-
json.dumps(conversation_data_full, ensure_ascii=False).encode("utf-8")
|
|
3317
|
-
)
|
|
3280
|
+
full_bytes = len(json.dumps(conversation_data_full, ensure_ascii=False).encode("utf-8"))
|
|
3318
3281
|
|
|
3319
3282
|
conversation_data_compact = build_enhanced_conversation_data(
|
|
3320
3283
|
selected_event=selected_events[0],
|
|
@@ -3334,9 +3297,7 @@ def export_shares_interactive_command(
|
|
|
3334
3297
|
|
|
3335
3298
|
full_mb = full_bytes / 1024 / 1024
|
|
3336
3299
|
compact_mb = compact_bytes / 1024 / 1024
|
|
3337
|
-
reduction = (
|
|
3338
|
-
(1 - (compact_bytes / full_bytes)) * 100 if full_bytes else 0.0
|
|
3339
|
-
)
|
|
3300
|
+
reduction = (1 - (compact_bytes / full_bytes)) * 100 if full_bytes else 0.0
|
|
3340
3301
|
print("\n=== Export Payload Size (pre-encryption) ===")
|
|
3341
3302
|
print(f"No compact: {full_mb:.2f}MB")
|
|
3342
3303
|
print(f"Compact: {compact_mb:.2f}MB ({reduction:.1f}% smaller)")
|
|
@@ -3351,7 +3312,9 @@ def export_shares_interactive_command(
|
|
|
3351
3312
|
print("\nLargest lines (no content):")
|
|
3352
3313
|
for item in report["top_lines"]:
|
|
3353
3314
|
size_kb = item["size_bytes"] / 1024
|
|
3354
|
-
blocks =
|
|
3315
|
+
blocks = (
|
|
3316
|
+
",".join(item["content_block_types"]) if item["content_block_types"] else "-"
|
|
3317
|
+
)
|
|
3355
3318
|
tools = item.get("tools") or []
|
|
3356
3319
|
tool_hint = ""
|
|
3357
3320
|
if tools:
|
|
@@ -3663,9 +3626,7 @@ def export_shares_interactive_command(
|
|
|
3663
3626
|
db = get_database()
|
|
3664
3627
|
db.update_event_share_metadata(
|
|
3665
3628
|
event_id=selected_event.event_id,
|
|
3666
|
-
preset_questions=ui_metadata.get("preset_questions")
|
|
3667
|
-
if ui_metadata
|
|
3668
|
-
else None,
|
|
3629
|
+
preset_questions=ui_metadata.get("preset_questions") if ui_metadata else None,
|
|
3669
3630
|
slack_message=ui_metadata.get("slack_message") if ui_metadata else None,
|
|
3670
3631
|
share_url=result.get("share_url"),
|
|
3671
3632
|
share_id=result.get("share_id")
|
|
@@ -3706,7 +3667,5 @@ def export_shares_interactive_command(
|
|
|
3706
3667
|
)
|
|
3707
3668
|
|
|
3708
3669
|
if not json_output:
|
|
3709
|
-
logger.info(
|
|
3710
|
-
f"======== Interactive export completed: {result['share_url']} ========"
|
|
3711
|
-
)
|
|
3670
|
+
logger.info(f"======== Interactive export completed: {result['share_url']} ========")
|
|
3712
3671
|
return 0
|