aline-ai 0.6.2__py3-none-any.whl → 0.6.4__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.6.2.dist-info → aline_ai-0.6.4.dist-info}/METADATA +1 -1
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/RECORD +38 -37
- realign/__init__.py +1 -1
- realign/adapters/__init__.py +0 -3
- realign/adapters/codex.py +14 -9
- realign/cli.py +42 -236
- realign/codex_detector.py +72 -32
- realign/codex_home.py +85 -0
- realign/codex_terminal_linker.py +172 -0
- realign/commands/__init__.py +2 -2
- realign/commands/add.py +89 -9
- realign/commands/doctor.py +495 -0
- realign/commands/export_shares.py +154 -226
- realign/commands/init.py +66 -4
- realign/commands/watcher.py +30 -80
- realign/config.py +9 -46
- realign/dashboard/app.py +7 -11
- realign/dashboard/screens/event_detail.py +0 -3
- realign/dashboard/screens/session_detail.py +0 -1
- realign/dashboard/tmux_manager.py +129 -4
- realign/dashboard/widgets/config_panel.py +175 -241
- realign/dashboard/widgets/events_table.py +71 -128
- realign/dashboard/widgets/sessions_table.py +77 -136
- realign/dashboard/widgets/terminal_panel.py +349 -27
- realign/dashboard/widgets/watcher_panel.py +0 -2
- realign/db/sqlite_db.py +77 -2
- realign/events/event_summarizer.py +76 -35
- realign/events/session_summarizer.py +73 -32
- realign/hooks.py +334 -647
- realign/llm_client.py +201 -520
- realign/triggers/__init__.py +0 -2
- realign/triggers/next_turn_trigger.py +4 -5
- realign/triggers/registry.py +1 -4
- realign/watcher_core.py +53 -35
- realign/adapters/antigravity.py +0 -159
- realign/triggers/antigravity_trigger.py +0 -140
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/WHEEL +0 -0
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.4.dist-info}/top_level.txt +0 -0
realign/commands/watcher.py
CHANGED
|
@@ -524,7 +524,6 @@ def watcher_status_command(verbose: bool = False) -> int:
|
|
|
524
524
|
"claude": "Claude Code",
|
|
525
525
|
"codex": "Codex",
|
|
526
526
|
"gemini": "Gemini",
|
|
527
|
-
"antigravity": "Antigravity",
|
|
528
527
|
}
|
|
529
528
|
source = source_map.get(session_type, session_type)
|
|
530
529
|
|
|
@@ -583,7 +582,6 @@ def watcher_status_command(verbose: bool = False) -> int:
|
|
|
583
582
|
"claude": "Claude Code",
|
|
584
583
|
"codex": "Codex",
|
|
585
584
|
"gemini": "Gemini",
|
|
586
|
-
"antigravity": "Antigravity",
|
|
587
585
|
}
|
|
588
586
|
source = source_map.get(session_type, session_type)
|
|
589
587
|
|
|
@@ -1069,7 +1067,6 @@ def _get_session_tracking_status_batch(
|
|
|
1069
1067
|
"claude": registry.get_adapter("claude"),
|
|
1070
1068
|
"codex": registry.get_adapter("codex"),
|
|
1071
1069
|
"gemini": registry.get_adapter("gemini"),
|
|
1072
|
-
"antigravity": registry.get_adapter("antigravity"),
|
|
1073
1070
|
}
|
|
1074
1071
|
|
|
1075
1072
|
def _infer_adapter_name(session_file: Path) -> str:
|
|
@@ -1081,9 +1078,6 @@ def _get_session_tracking_status_batch(
|
|
|
1081
1078
|
return "codex"
|
|
1082
1079
|
if ".gemini" in parts:
|
|
1083
1080
|
return "gemini"
|
|
1084
|
-
# Antigravity "sessions" may be directories with markdown artifacts.
|
|
1085
|
-
if session_file.is_dir():
|
|
1086
|
-
return "antigravity"
|
|
1087
1081
|
return "unknown"
|
|
1088
1082
|
|
|
1089
1083
|
session_infos = []
|
|
@@ -1258,7 +1252,7 @@ def _get_session_tracking_status(session_file: Path, config: ReAlignConfig, db=N
|
|
|
1258
1252
|
- committed_turns: int
|
|
1259
1253
|
- total_turns: int
|
|
1260
1254
|
- session_id: str
|
|
1261
|
-
- source: str (claude/codex/gemini
|
|
1255
|
+
- source: str (claude/codex/gemini)
|
|
1262
1256
|
- project_name: str | None
|
|
1263
1257
|
- last_activity: datetime
|
|
1264
1258
|
- session_file: Path
|
|
@@ -1449,7 +1443,8 @@ def watcher_session_list_command(
|
|
|
1449
1443
|
console.print("[yellow]No sessions discovered.[/yellow]")
|
|
1450
1444
|
console.print("[dim]Sessions are discovered from:[/dim]")
|
|
1451
1445
|
console.print("[dim] • Claude Code: ~/.claude/projects/[/dim]")
|
|
1452
|
-
console.print("[dim] • Codex: ~/.codex/sessions/[/dim]")
|
|
1446
|
+
console.print("[dim] • Codex (legacy): ~/.codex/sessions/[/dim]")
|
|
1447
|
+
console.print("[dim] • Codex (isolated): ~/.aline/codex_homes/*/sessions/[/dim]")
|
|
1453
1448
|
console.print("[dim] • Gemini: ~/.gemini/tmp/*/chats/[/dim]")
|
|
1454
1449
|
console.print("[dim] • Imported shares: Database[/dim]")
|
|
1455
1450
|
return 0
|
|
@@ -1653,16 +1648,9 @@ def watcher_session_list_command(
|
|
|
1653
1648
|
]
|
|
1654
1649
|
|
|
1655
1650
|
if detect_turns:
|
|
1656
|
-
# Handle Antigravity sessions using timestamps as turn counts
|
|
1657
1651
|
total_turns = info.get("total_turns") or 0
|
|
1658
1652
|
committed_turns = info.get("committed_turns") or 0
|
|
1659
|
-
|
|
1660
|
-
# Normalize display for timestamp-based tracking
|
|
1661
|
-
norm_total = 1
|
|
1662
|
-
norm_committed = 1 if committed_turns > 0 else 0
|
|
1663
|
-
turns_str = f"{norm_committed}/{norm_total}"
|
|
1664
|
-
else:
|
|
1665
|
-
turns_str = f"{committed_turns}/{total_turns}"
|
|
1653
|
+
turns_str = f"{committed_turns}/{total_turns}"
|
|
1666
1654
|
row_data.append(turns_str)
|
|
1667
1655
|
|
|
1668
1656
|
row_data.extend(
|
|
@@ -2212,7 +2200,6 @@ def watcher_event_list_command(
|
|
|
2212
2200
|
def watcher_event_revise_slack_command(
|
|
2213
2201
|
input_json: dict,
|
|
2214
2202
|
instruction: str,
|
|
2215
|
-
provider: str = "auto",
|
|
2216
2203
|
json_output: bool = False,
|
|
2217
2204
|
) -> int:
|
|
2218
2205
|
"""
|
|
@@ -2227,7 +2214,6 @@ def watcher_event_revise_slack_command(
|
|
|
2227
2214
|
- slack_message: Current Slack message
|
|
2228
2215
|
- password: Optional password
|
|
2229
2216
|
instruction: User's revision instructions
|
|
2230
|
-
provider: LLM provider (auto, claude, openai)
|
|
2231
2217
|
json_output: If True, output JSON (same format as input)
|
|
2232
2218
|
|
|
2233
2219
|
Returns:
|
|
@@ -2237,7 +2223,7 @@ def watcher_event_revise_slack_command(
|
|
|
2237
2223
|
|
|
2238
2224
|
try:
|
|
2239
2225
|
from ..db import get_database
|
|
2240
|
-
from ..
|
|
2226
|
+
from ..llm_client import call_llm_cloud
|
|
2241
2227
|
|
|
2242
2228
|
# Extract data from input JSON
|
|
2243
2229
|
event_id = input_json.get("event_id")
|
|
@@ -2258,24 +2244,14 @@ def watcher_event_revise_slack_command(
|
|
|
2258
2244
|
console.print("[red]Error: Missing 'slack_message' in input JSON[/red]")
|
|
2259
2245
|
return 1
|
|
2260
2246
|
|
|
2261
|
-
# Load prompt
|
|
2247
|
+
# Load optional custom prompt
|
|
2248
|
+
custom_prompt = None
|
|
2262
2249
|
prompt_path = Path.home() / ".aline" / "prompts" / "slack_share_revise.md"
|
|
2263
|
-
if not prompt_path.exists():
|
|
2264
|
-
# Fallback to example file
|
|
2265
|
-
prompt_path = Path.home() / ".aline" / "prompts" / "slack_share_revise.md.example"
|
|
2266
|
-
|
|
2267
|
-
if not prompt_path.exists():
|
|
2268
|
-
if not json_output:
|
|
2269
|
-
console.print(f"[red]Prompt file not found: {prompt_path}[/red]")
|
|
2270
|
-
console.print("[dim]Run 'aline init' to initialize prompts[/dim]")
|
|
2271
|
-
return 1
|
|
2272
|
-
|
|
2273
2250
|
try:
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
return 1
|
|
2251
|
+
if prompt_path.exists():
|
|
2252
|
+
custom_prompt = prompt_path.read_text(encoding="utf-8").strip()
|
|
2253
|
+
except Exception:
|
|
2254
|
+
pass
|
|
2279
2255
|
|
|
2280
2256
|
# Build event context from input JSON
|
|
2281
2257
|
context_parts = []
|
|
@@ -2286,35 +2262,21 @@ def watcher_event_revise_slack_command(
|
|
|
2286
2262
|
|
|
2287
2263
|
event_context = "\n".join(context_parts) if context_parts else "No additional context"
|
|
2288
2264
|
|
|
2289
|
-
#
|
|
2290
|
-
user_prompt = f"""**Original Event Context:**
|
|
2291
|
-
```
|
|
2292
|
-
{event_context}
|
|
2293
|
-
```
|
|
2294
|
-
|
|
2295
|
-
**Previous Message:**
|
|
2296
|
-
```
|
|
2297
|
-
{slack_message}
|
|
2298
|
-
```
|
|
2299
|
-
|
|
2300
|
-
**Revision Request:**
|
|
2301
|
-
```
|
|
2302
|
-
{instruction}
|
|
2303
|
-
```
|
|
2304
|
-
"""
|
|
2305
|
-
|
|
2306
|
-
# Call LLM to revise the message
|
|
2265
|
+
# Call cloud LLM to revise the message
|
|
2307
2266
|
if not json_output:
|
|
2308
2267
|
console.print(f"→ Revising Slack message for event: [cyan]{event_title}[/cyan]")
|
|
2309
|
-
logger.info(f"Calling LLM to revise Slack message for event {event_id}")
|
|
2268
|
+
logger.info(f"Calling cloud LLM to revise Slack message for event {event_id}")
|
|
2310
2269
|
|
|
2311
2270
|
try:
|
|
2312
|
-
model_name,
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2271
|
+
model_name, result = call_llm_cloud(
|
|
2272
|
+
task="revise_slack_message",
|
|
2273
|
+
payload={
|
|
2274
|
+
"event_context": event_context,
|
|
2275
|
+
"current_message": slack_message,
|
|
2276
|
+
"revision_instruction": instruction,
|
|
2277
|
+
},
|
|
2278
|
+
custom_prompt=custom_prompt,
|
|
2279
|
+
silent=True,
|
|
2318
2280
|
)
|
|
2319
2281
|
except Exception as e:
|
|
2320
2282
|
if not json_output:
|
|
@@ -2322,27 +2284,18 @@ def watcher_event_revise_slack_command(
|
|
|
2322
2284
|
logger.error(f"LLM invocation failed: {e}", exc_info=True)
|
|
2323
2285
|
return 1
|
|
2324
2286
|
|
|
2325
|
-
if not
|
|
2287
|
+
if not result:
|
|
2326
2288
|
if not json_output:
|
|
2327
2289
|
console.print("[red]LLM did not return a response[/red]")
|
|
2328
2290
|
logger.error("LLM returned empty response")
|
|
2329
2291
|
return 1
|
|
2330
2292
|
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
# Fallback: use the raw response if JSON parsing fails or no message field
|
|
2338
|
-
revised_message = response_text
|
|
2339
|
-
logger.warning(
|
|
2340
|
-
"Response did not contain expected 'message' field, using raw response"
|
|
2341
|
-
)
|
|
2342
|
-
except Exception as e:
|
|
2343
|
-
# Fallback: use the raw response if JSON parsing fails
|
|
2344
|
-
revised_message = response_text
|
|
2345
|
-
logger.warning(f"Failed to parse JSON response: {e}, using raw response")
|
|
2293
|
+
revised_message = result.get("message", "")
|
|
2294
|
+
if not revised_message:
|
|
2295
|
+
if not json_output:
|
|
2296
|
+
console.print("[red]LLM response missing 'message' field[/red]")
|
|
2297
|
+
logger.error("LLM response missing 'message' field")
|
|
2298
|
+
return 1
|
|
2346
2299
|
|
|
2347
2300
|
# Update the event in the database
|
|
2348
2301
|
db = get_database()
|
|
@@ -3201,8 +3154,7 @@ def watcher_session_show_command(
|
|
|
3201
3154
|
"claude": "Claude Code",
|
|
3202
3155
|
"codex": "Codex",
|
|
3203
3156
|
"gemini": "Gemini",
|
|
3204
|
-
|
|
3205
|
-
}
|
|
3157
|
+
}
|
|
3206
3158
|
source = source_map.get(info["source"], info["source"])
|
|
3207
3159
|
|
|
3208
3160
|
# Build turns data
|
|
@@ -3301,7 +3253,6 @@ def watcher_session_show_command(
|
|
|
3301
3253
|
"claude": "Claude Code",
|
|
3302
3254
|
"codex": "Codex",
|
|
3303
3255
|
"gemini": "Gemini",
|
|
3304
|
-
"antigravity": "Antigravity",
|
|
3305
3256
|
}
|
|
3306
3257
|
source = source_map.get(info["source"], info["source"])
|
|
3307
3258
|
|
|
@@ -3662,7 +3613,6 @@ def watcher_session_delete_command(
|
|
|
3662
3613
|
"claude": "Claude Code",
|
|
3663
3614
|
"codex": "Codex",
|
|
3664
3615
|
"gemini": "Gemini",
|
|
3665
|
-
"antigravity": "Antigravity",
|
|
3666
3616
|
}
|
|
3667
3617
|
source = source_map.get(info.get("source", ""), info.get("source", "unknown"))
|
|
3668
3618
|
|
realign/config.py
CHANGED
|
@@ -20,7 +20,6 @@ class ReAlignConfig:
|
|
|
20
20
|
auto_detect_claude: bool = True # Enable Claude Code session auto-detection
|
|
21
21
|
auto_detect_codex: bool = True # Enable Codex session auto-detection
|
|
22
22
|
auto_detect_gemini: bool = True # Enable Gemini CLI session auto-detection
|
|
23
|
-
auto_detect_antigravity: bool = False # Enable Antigravity IDE brain artifact monitoring
|
|
24
23
|
mcp_auto_commit: bool = True # Enable watcher auto-commit after each user request completes
|
|
25
24
|
enable_temp_turn_titles: bool = True # Generate temporary turn titles on user prompt submit
|
|
26
25
|
share_backend_url: str = (
|
|
@@ -34,16 +33,9 @@ class ReAlignConfig:
|
|
|
34
33
|
# Session catch-up settings
|
|
35
34
|
max_catchup_sessions: int = 3 # Max sessions to auto-import on watcher startup
|
|
36
35
|
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# LLM Model Configuration
|
|
42
|
-
llm_anthropic_model: str = "claude-3-5-haiku-20241022" # Claude model to use
|
|
43
|
-
llm_openai_model: str = "gpt-4o-mini" # OpenAI model to use
|
|
44
|
-
llm_openai_use_responses: bool = False # Use OpenAI Responses API for reasoning models
|
|
45
|
-
llm_max_tokens: int = 1000 # Default max tokens
|
|
46
|
-
llm_temperature: float = 0.0 # Default temperature (0.0 = deterministic)
|
|
36
|
+
# Terminal auto-close settings
|
|
37
|
+
auto_close_stale_terminals: bool = False # Auto-close terminals inactive for 24+ hours
|
|
38
|
+
stale_terminal_hours: int = 24 # Hours of inactivity before auto-closing
|
|
47
39
|
|
|
48
40
|
@classmethod
|
|
49
41
|
def load(cls, config_path: Optional[Path] = None) -> "ReAlignConfig":
|
|
@@ -87,38 +79,29 @@ class ReAlignConfig:
|
|
|
87
79
|
"auto_detect_claude": os.getenv("REALIGN_AUTO_DETECT_CLAUDE"),
|
|
88
80
|
"auto_detect_codex": os.getenv("REALIGN_AUTO_DETECT_CODEX"),
|
|
89
81
|
"auto_detect_gemini": os.getenv("REALIGN_AUTO_DETECT_GEMINI"),
|
|
90
|
-
"auto_detect_antigravity": os.getenv("REALIGN_AUTO_DETECT_ANTIGRAVITY"),
|
|
91
82
|
"mcp_auto_commit": os.getenv("REALIGN_MCP_AUTO_COMMIT"),
|
|
92
83
|
"enable_temp_turn_titles": os.getenv("REALIGN_ENABLE_TEMP_TURN_TITLES"),
|
|
93
84
|
"share_backend_url": os.getenv("REALIGN_SHARE_BACKEND_URL"),
|
|
94
85
|
"user_name": os.getenv("REALIGN_USER_NAME"),
|
|
95
86
|
"uid": os.getenv("REALIGN_UID"),
|
|
96
87
|
"max_catchup_sessions": os.getenv("REALIGN_MAX_CATCHUP_SESSIONS"),
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"llm_anthropic_model": os.getenv("REALIGN_ANTHROPIC_MODEL"),
|
|
100
|
-
"llm_openai_model": os.getenv("REALIGN_OPENAI_MODEL"),
|
|
101
|
-
"llm_openai_use_responses": os.getenv("REALIGN_OPENAI_USE_RESPONSES"),
|
|
102
|
-
"llm_max_tokens": os.getenv("REALIGN_LLM_MAX_TOKENS"),
|
|
103
|
-
"llm_temperature": os.getenv("REALIGN_LLM_TEMPERATURE"),
|
|
88
|
+
"auto_close_stale_terminals": os.getenv("REALIGN_AUTO_CLOSE_STALE_TERMINALS"),
|
|
89
|
+
"stale_terminal_hours": os.getenv("REALIGN_STALE_TERMINAL_HOURS"),
|
|
104
90
|
}
|
|
105
91
|
|
|
106
92
|
for key, value in env_overrides.items():
|
|
107
93
|
if value is not None:
|
|
108
|
-
if key in ["summary_max_chars", "max_catchup_sessions", "
|
|
94
|
+
if key in ["summary_max_chars", "max_catchup_sessions", "stale_terminal_hours"]:
|
|
109
95
|
config_dict[key] = int(value)
|
|
110
|
-
elif key in ["llm_temperature"]:
|
|
111
|
-
config_dict[key] = float(value)
|
|
112
96
|
elif key in [
|
|
113
97
|
"redact_on_match",
|
|
114
98
|
"use_LLM",
|
|
115
99
|
"auto_detect_claude",
|
|
116
100
|
"auto_detect_codex",
|
|
117
101
|
"auto_detect_gemini",
|
|
118
|
-
"auto_detect_antigravity",
|
|
119
102
|
"mcp_auto_commit",
|
|
120
103
|
"enable_temp_turn_titles",
|
|
121
|
-
"
|
|
104
|
+
"auto_close_stale_terminals",
|
|
122
105
|
]:
|
|
123
106
|
config_dict[key] = value.lower() in ("true", "1", "yes")
|
|
124
107
|
else:
|
|
@@ -150,20 +133,14 @@ class ReAlignConfig:
|
|
|
150
133
|
"auto_detect_claude": self.auto_detect_claude,
|
|
151
134
|
"auto_detect_codex": self.auto_detect_codex,
|
|
152
135
|
"auto_detect_gemini": self.auto_detect_gemini,
|
|
153
|
-
"auto_detect_antigravity": self.auto_detect_antigravity,
|
|
154
136
|
"mcp_auto_commit": self.mcp_auto_commit,
|
|
155
137
|
"enable_temp_turn_titles": self.enable_temp_turn_titles,
|
|
156
138
|
"share_backend_url": self.share_backend_url,
|
|
157
139
|
"user_name": self.user_name,
|
|
158
140
|
"uid": self.uid,
|
|
159
141
|
"max_catchup_sessions": self.max_catchup_sessions,
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"llm_anthropic_model": self.llm_anthropic_model,
|
|
163
|
-
"llm_openai_model": self.llm_openai_model,
|
|
164
|
-
"llm_openai_use_responses": self.llm_openai_use_responses,
|
|
165
|
-
"llm_max_tokens": self.llm_max_tokens,
|
|
166
|
-
"llm_temperature": self.llm_temperature,
|
|
142
|
+
"auto_close_stale_terminals": self.auto_close_stale_terminals,
|
|
143
|
+
"stale_terminal_hours": self.stale_terminal_hours,
|
|
167
144
|
}
|
|
168
145
|
|
|
169
146
|
with open(config_path, "w", encoding="utf-8") as f:
|
|
@@ -200,7 +177,6 @@ use_LLM: true # Whether to use a cloud LLM to generate summar
|
|
|
200
177
|
llm_provider: "auto" # LLM provider: "auto" (try Claude then OpenAI), "claude", or "openai"
|
|
201
178
|
auto_detect_claude: true # Automatically detect Claude Code session directory (~/.claude/projects/)
|
|
202
179
|
auto_detect_codex: true # Automatically detect Codex session files (~/.codex/sessions/)
|
|
203
|
-
auto_detect_antigravity: false # Automatically detect Antigravity IDE brain artifacts (~/.gemini/antigravity/brain/)
|
|
204
180
|
mcp_auto_commit: true # Enable watcher to auto-commit after each user request completes
|
|
205
181
|
enable_temp_turn_titles: true # Generate temporary turn titles on user prompt submit
|
|
206
182
|
share_backend_url: "https://realign-server.vercel.app" # Backend URL for interactive share export
|
|
@@ -211,19 +187,6 @@ max_catchup_sessions: 3 # Max sessions to auto-import on watcher
|
|
|
211
187
|
# Use 'aline watcher session list' to see all sessions
|
|
212
188
|
# Use 'aline watcher session import <id>' to import specific sessions
|
|
213
189
|
|
|
214
|
-
# LLM API Keys (configured in this file only, NOT from environment variables):
|
|
215
|
-
# anthropic_api_key: "your-anthropic-api-key" # For Claude (Anthropic)
|
|
216
|
-
# openai_api_key: "your-openai-api-key" # For OpenAI (GPT)
|
|
217
|
-
# Note: API keys are read ONLY from this config file, not from system environment variables
|
|
218
|
-
# Alternative: Use REALIGN_ANTHROPIC_API_KEY or REALIGN_OPENAI_API_KEY env vars to override
|
|
219
|
-
|
|
220
|
-
# LLM Model Configuration:
|
|
221
|
-
llm_anthropic_model: "claude-3-5-haiku-20241022" # Claude model to use
|
|
222
|
-
llm_openai_model: "gpt-4o-mini" # OpenAI model to use
|
|
223
|
-
llm_openai_use_responses: false # Use OpenAI Responses API for reasoning models (GPT-5+)
|
|
224
|
-
llm_max_tokens: 1000 # Default max tokens for LLM responses
|
|
225
|
-
llm_temperature: 0.0 # Default temperature (0.0 = deterministic, 1.0 = creative)
|
|
226
|
-
|
|
227
190
|
# Secret Detection & Redaction:
|
|
228
191
|
# ReAlign can use detect-secrets to automatically scan for and redact:
|
|
229
192
|
# - API keys, tokens, passwords
|
realign/dashboard/app.py
CHANGED
|
@@ -242,10 +242,7 @@ class AlineDashboard(App):
|
|
|
242
242
|
self.query_one(WatcherPanel).action_next_page()
|
|
243
243
|
elif active_tab_id == "worker":
|
|
244
244
|
self.query_one(WorkerPanel).action_next_page()
|
|
245
|
-
|
|
246
|
-
self.query_one(SessionsTable).action_next_page()
|
|
247
|
-
elif active_tab_id == "events":
|
|
248
|
-
self.query_one(EventsTable).action_next_page()
|
|
245
|
+
# sessions and events tabs use scrolling instead of pagination
|
|
249
246
|
|
|
250
247
|
def action_page_prev(self) -> None:
|
|
251
248
|
"""Go to previous page in current panel."""
|
|
@@ -256,10 +253,7 @@ class AlineDashboard(App):
|
|
|
256
253
|
self.query_one(WatcherPanel).action_prev_page()
|
|
257
254
|
elif active_tab_id == "worker":
|
|
258
255
|
self.query_one(WorkerPanel).action_prev_page()
|
|
259
|
-
|
|
260
|
-
self.query_one(SessionsTable).action_prev_page()
|
|
261
|
-
elif active_tab_id == "events":
|
|
262
|
-
self.query_one(EventsTable).action_prev_page()
|
|
256
|
+
# sessions and events tabs use scrolling instead of pagination
|
|
263
257
|
|
|
264
258
|
def action_switch_view(self) -> None:
|
|
265
259
|
"""Switch view in current panel (if supported)."""
|
|
@@ -329,20 +323,22 @@ class AlineDashboard(App):
|
|
|
329
323
|
self.push_screen(ShareImportScreen())
|
|
330
324
|
|
|
331
325
|
async def action_load_context(self) -> None:
|
|
332
|
-
"""Load selected sessions/events into the active
|
|
326
|
+
"""Load selected sessions/events into the active terminal context (Claude/Codex)."""
|
|
333
327
|
tabbed_content = self.query_one(TabbedContent)
|
|
334
328
|
active_tab_id = tabbed_content.active
|
|
335
329
|
|
|
336
330
|
try:
|
|
337
331
|
from . import tmux_manager
|
|
338
332
|
|
|
339
|
-
context_id = tmux_manager.
|
|
333
|
+
context_id = tmux_manager.get_active_context_id(
|
|
334
|
+
allowed_providers={"claude", "codex"}
|
|
335
|
+
)
|
|
340
336
|
except Exception:
|
|
341
337
|
context_id = None
|
|
342
338
|
|
|
343
339
|
if not context_id:
|
|
344
340
|
self.notify(
|
|
345
|
-
"No active
|
|
341
|
+
"No active context found. Use the Terminal tab and select a Claude ('cc') or Codex terminal.",
|
|
346
342
|
title="Load Context",
|
|
347
343
|
severity="warning",
|
|
348
344
|
timeout=4,
|
|
@@ -364,7 +364,6 @@ class EventDetailScreen(ModalScreen):
|
|
|
364
364
|
"claude": "Claude",
|
|
365
365
|
"codex": "Codex",
|
|
366
366
|
"gemini": "Gemini",
|
|
367
|
-
"antigravity": "Antigravity",
|
|
368
367
|
}
|
|
369
368
|
source = source_map.get(session_type, session_type)
|
|
370
369
|
project = workspace.split("/")[-1] if workspace else "-"
|
|
@@ -398,7 +397,6 @@ class EventDetailScreen(ModalScreen):
|
|
|
398
397
|
"claude": "Claude",
|
|
399
398
|
"codex": "Codex",
|
|
400
399
|
"gemini": "Gemini",
|
|
401
|
-
"antigravity": "Antigravity",
|
|
402
400
|
}
|
|
403
401
|
source = source_map.get(session_type, session_type)
|
|
404
402
|
project = str(workspace).split("/")[-1] if workspace else "-"
|
|
@@ -533,7 +531,6 @@ class EventDetailScreen(ModalScreen):
|
|
|
533
531
|
"claude": "Claude",
|
|
534
532
|
"codex": "Codex",
|
|
535
533
|
"gemini": "Gemini",
|
|
536
|
-
"antigravity": "Antigravity",
|
|
537
534
|
}
|
|
538
535
|
source = source_map.get(
|
|
539
536
|
record_type or "", record_type or session.get("source") or "unknown"
|
|
@@ -516,9 +516,21 @@ def ensure_inner_session() -> bool:
|
|
|
516
516
|
return False
|
|
517
517
|
|
|
518
518
|
if _run_inner_tmux(["has-session", "-t", INNER_SESSION]).returncode != 0:
|
|
519
|
-
|
|
519
|
+
# Create a stable "home" window so user-created terminals can use names like "zsh"
|
|
520
|
+
# without always becoming "zsh-2".
|
|
521
|
+
if (
|
|
522
|
+
_run_inner_tmux(["new-session", "-d", "-s", INNER_SESSION, "-n", "home"]).returncode
|
|
523
|
+
!= 0
|
|
524
|
+
):
|
|
520
525
|
return False
|
|
521
526
|
|
|
527
|
+
# Ensure the default/home window stays named "home" (tmux auto-rename would otherwise
|
|
528
|
+
# change it to "zsh"/"opencode" depending on the last foreground command).
|
|
529
|
+
try:
|
|
530
|
+
_ensure_inner_home_window()
|
|
531
|
+
except Exception:
|
|
532
|
+
pass
|
|
533
|
+
|
|
522
534
|
# Dedicated inner server; safe to enable mouse globally there.
|
|
523
535
|
_run_inner_tmux(["set-option", "-g", "mouse", "on"])
|
|
524
536
|
|
|
@@ -537,6 +549,101 @@ def ensure_inner_session() -> bool:
|
|
|
537
549
|
return True
|
|
538
550
|
|
|
539
551
|
|
|
552
|
+
def _ensure_inner_home_window() -> None:
|
|
553
|
+
"""Ensure the inner session has a reserved, non-renaming 'home' window (best-effort)."""
|
|
554
|
+
if _run_inner_tmux(["has-session", "-t", INNER_SESSION]).returncode != 0:
|
|
555
|
+
return
|
|
556
|
+
|
|
557
|
+
out = (
|
|
558
|
+
_run_inner_tmux(
|
|
559
|
+
[
|
|
560
|
+
"list-windows",
|
|
561
|
+
"-t",
|
|
562
|
+
INNER_SESSION,
|
|
563
|
+
"-F",
|
|
564
|
+
"#{window_id}\t#{window_index}\t#{window_name}\t#{"
|
|
565
|
+
+ OPT_TERMINAL_ID
|
|
566
|
+
+ "}\t#{"
|
|
567
|
+
+ OPT_PROVIDER
|
|
568
|
+
+ "}\t#{"
|
|
569
|
+
+ OPT_SESSION_TYPE
|
|
570
|
+
+ "}\t#{"
|
|
571
|
+
+ OPT_CONTEXT_ID
|
|
572
|
+
+ "}\t#{"
|
|
573
|
+
+ OPT_CREATED_AT
|
|
574
|
+
+ "}\t#{"
|
|
575
|
+
+ OPT_NO_TRACK
|
|
576
|
+
+ "}",
|
|
577
|
+
],
|
|
578
|
+
capture=True,
|
|
579
|
+
).stdout
|
|
580
|
+
or ""
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
candidates: list[tuple[str, int, str, str, str, str, str, str, str]] = []
|
|
584
|
+
for line in _parse_lines(out):
|
|
585
|
+
parts = (line.split("\t", 8) + [""] * 9)[:9]
|
|
586
|
+
window_id = parts[0]
|
|
587
|
+
try:
|
|
588
|
+
window_index = int(parts[1])
|
|
589
|
+
except Exception:
|
|
590
|
+
window_index = 9999
|
|
591
|
+
window_name = parts[2]
|
|
592
|
+
terminal_id = parts[3]
|
|
593
|
+
provider = parts[4]
|
|
594
|
+
session_type = parts[5]
|
|
595
|
+
context_id = parts[6]
|
|
596
|
+
created_at = parts[7]
|
|
597
|
+
no_track = parts[8]
|
|
598
|
+
|
|
599
|
+
# Pick an unmanaged window (the default one created by `new-session`) as "home".
|
|
600
|
+
unmanaged = (
|
|
601
|
+
not (terminal_id or "").strip()
|
|
602
|
+
and not (provider or "").strip()
|
|
603
|
+
and not (session_type or "").strip()
|
|
604
|
+
and not (context_id or "").strip()
|
|
605
|
+
and not (created_at or "").strip()
|
|
606
|
+
)
|
|
607
|
+
if unmanaged:
|
|
608
|
+
candidates.append(
|
|
609
|
+
(
|
|
610
|
+
window_id,
|
|
611
|
+
window_index,
|
|
612
|
+
window_name,
|
|
613
|
+
terminal_id,
|
|
614
|
+
provider,
|
|
615
|
+
session_type,
|
|
616
|
+
context_id,
|
|
617
|
+
created_at,
|
|
618
|
+
no_track,
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
if not candidates:
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
# Prefer the first window (index 0) if present.
|
|
626
|
+
candidates.sort(key=lambda t: t[1])
|
|
627
|
+
window_id = candidates[0][0]
|
|
628
|
+
|
|
629
|
+
# Rename to "home" and prevent tmux auto-renaming it based on foreground command.
|
|
630
|
+
_run_inner_tmux(["rename-window", "-t", window_id, "home"])
|
|
631
|
+
_run_inner_tmux(["set-option", "-w", "-t", window_id, "automatic-rename", "off"])
|
|
632
|
+
_run_inner_tmux(["set-option", "-w", "-t", window_id, "allow-rename", "off"])
|
|
633
|
+
|
|
634
|
+
# Mark as internal/no-track so UI can hide it.
|
|
635
|
+
try:
|
|
636
|
+
set_inner_window_options(
|
|
637
|
+
window_id,
|
|
638
|
+
{
|
|
639
|
+
OPT_NO_TRACK: "1",
|
|
640
|
+
OPT_CREATED_AT: str(time.time()),
|
|
641
|
+
},
|
|
642
|
+
)
|
|
643
|
+
except Exception:
|
|
644
|
+
pass
|
|
645
|
+
|
|
646
|
+
|
|
540
647
|
def ensure_right_pane(width_percent: int = 50) -> bool:
|
|
541
648
|
"""Create the right-side pane (terminal area) if it doesn't exist.
|
|
542
649
|
|
|
@@ -701,6 +808,7 @@ def create_inner_window(
|
|
|
701
808
|
terminal_id: str | None = None,
|
|
702
809
|
provider: str | None = None,
|
|
703
810
|
context_id: str | None = None,
|
|
811
|
+
no_track: bool = False,
|
|
704
812
|
) -> InnerWindow | None:
|
|
705
813
|
if not ensure_right_pane():
|
|
706
814
|
return None
|
|
@@ -744,6 +852,10 @@ def create_inner_window(
|
|
|
744
852
|
opts.setdefault(OPT_SESSION_TYPE, "")
|
|
745
853
|
opts.setdefault(OPT_SESSION_ID, "")
|
|
746
854
|
opts.setdefault(OPT_TRANSCRIPT_PATH, "")
|
|
855
|
+
if no_track:
|
|
856
|
+
opts[OPT_NO_TRACK] = "1"
|
|
857
|
+
else:
|
|
858
|
+
opts.setdefault(OPT_NO_TRACK, "")
|
|
747
859
|
set_inner_window_options(window_id, opts)
|
|
748
860
|
|
|
749
861
|
_run_inner_tmux(["select-window", "-t", window_id])
|
|
@@ -784,6 +896,16 @@ def clear_attention(window_id: str) -> bool:
|
|
|
784
896
|
|
|
785
897
|
def get_active_claude_context_id() -> str | None:
|
|
786
898
|
"""Return the active inner tmux window's Claude ALINE_CONTEXT_ID (if any)."""
|
|
899
|
+
return get_active_context_id(allowed_providers={"claude"})
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def get_active_codex_context_id() -> str | None:
|
|
903
|
+
"""Return the active inner tmux window's Codex ALINE_CONTEXT_ID (if any)."""
|
|
904
|
+
return get_active_context_id(allowed_providers={"codex"})
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def get_active_context_id(*, allowed_providers: set[str] | None = None) -> str | None:
|
|
908
|
+
"""Return the active inner tmux window's ALINE_CONTEXT_ID (optionally filtered by provider)."""
|
|
787
909
|
try:
|
|
788
910
|
windows = list_inner_windows()
|
|
789
911
|
except Exception:
|
|
@@ -793,9 +915,12 @@ def get_active_claude_context_id() -> str | None:
|
|
|
793
915
|
if active is None:
|
|
794
916
|
return None
|
|
795
917
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
918
|
+
if allowed_providers is not None:
|
|
919
|
+
allowed = {str(p).strip() for p in allowed_providers if str(p).strip()}
|
|
920
|
+
provider = (active.provider or "").strip()
|
|
921
|
+
session_type = (active.session_type or "").strip()
|
|
922
|
+
if provider not in allowed and session_type not in allowed:
|
|
923
|
+
return None
|
|
799
924
|
|
|
800
925
|
context_id = (active.context_id or "").strip()
|
|
801
926
|
return context_id or None
|