glaip-sdk 0.1.4__py3-none-any.whl → 0.2.0__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.
- glaip_sdk/__init__.py +1 -1
- glaip_sdk/_version.py +1 -0
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/agents.py +22 -37
- glaip_sdk/cli/commands/configure.py +67 -6
- glaip_sdk/cli/commands/mcps.py +19 -23
- glaip_sdk/cli/commands/tools.py +17 -41
- glaip_sdk/cli/commands/transcripts.py +747 -0
- glaip_sdk/cli/config.py +6 -1
- glaip_sdk/cli/display.py +1 -0
- glaip_sdk/cli/main.py +12 -31
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/prompt.py +2 -0
- glaip_sdk/cli/slash/session.py +251 -88
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +32 -0
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +6 -2
- glaip_sdk/cli/utils.py +170 -0
- glaip_sdk/client/_agent_payloads.py +5 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/utils/__init__.py +12 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +9 -1
- glaip_sdk/utils/rendering/renderer/__init__.py +0 -20
- glaip_sdk/utils/rendering/renderer/debug.py +3 -20
- glaip_sdk/utils/rendering/steps.py +5 -6
- glaip_sdk/utils/resource_refs.py +2 -1
- glaip_sdk/utils/serialization.py +2 -0
- {glaip_sdk-0.1.4.dist-info → glaip_sdk-0.2.0.dist-info}/METADATA +1 -1
- {glaip_sdk-0.1.4.dist-info → glaip_sdk-0.2.0.dist-info}/RECORD +36 -33
- {glaip_sdk-0.1.4.dist-info → glaip_sdk-0.2.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.1.4.dist-info → glaip_sdk-0.2.0.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/config.py
CHANGED
|
@@ -12,7 +12,12 @@ import yaml
|
|
|
12
12
|
|
|
13
13
|
CONFIG_DIR = Path.home() / ".aip"
|
|
14
14
|
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
15
|
-
_ALLOWED_KEYS = {
|
|
15
|
+
_ALLOWED_KEYS = {
|
|
16
|
+
"api_url",
|
|
17
|
+
"api_key",
|
|
18
|
+
"timeout",
|
|
19
|
+
"history_default_limit",
|
|
20
|
+
}
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
def _sanitize_config(data: dict[str, Any] | None) -> dict[str, Any]:
|
glaip_sdk/cli/display.py
CHANGED
|
@@ -208,6 +208,7 @@ def build_resource_result_data(resource: Any, fields: list[str]) -> dict[str, An
|
|
|
208
208
|
|
|
209
209
|
|
|
210
210
|
def _normalise_field_value(field: str, value: Any) -> Any:
|
|
211
|
+
"""Convert special sentinel values into display-friendly text."""
|
|
211
212
|
if value is _MISSING:
|
|
212
213
|
return "N/A"
|
|
213
214
|
if hasattr(value, "_mock_name"):
|
glaip_sdk/cli/main.py
CHANGED
|
@@ -33,11 +33,12 @@ from glaip_sdk.cli.commands.configure import (
|
|
|
33
33
|
from glaip_sdk.cli.commands.mcps import mcps_group
|
|
34
34
|
from glaip_sdk.cli.commands.models import models_group
|
|
35
35
|
from glaip_sdk.cli.commands.tools import tools_group
|
|
36
|
+
from glaip_sdk.cli.commands.transcripts import transcripts_group
|
|
36
37
|
from glaip_sdk.cli.commands.update import update_command
|
|
37
38
|
from glaip_sdk.cli.config import load_config
|
|
38
39
|
from glaip_sdk.cli.transcript import get_transcript_cache_stats
|
|
39
40
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
40
|
-
from glaip_sdk.cli.utils import in_slash_mode, sdk_version, spinner_context, update_spinner
|
|
41
|
+
from glaip_sdk.cli.utils import format_size, in_slash_mode, sdk_version, spinner_context, update_spinner
|
|
41
42
|
from glaip_sdk.config.constants import (
|
|
42
43
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
43
44
|
)
|
|
@@ -55,24 +56,6 @@ except ImportError: # pragma: no cover - optional slash dependencies
|
|
|
55
56
|
AVAILABLE_STATUS = "✅ Available"
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
def _format_size(num: int) -> str:
|
|
59
|
-
"""Return a human-readable byte size."""
|
|
60
|
-
if num <= 0:
|
|
61
|
-
return "0B"
|
|
62
|
-
|
|
63
|
-
units = ["B", "KB", "MB", "GB", "TB"]
|
|
64
|
-
value = float(num)
|
|
65
|
-
for unit in units:
|
|
66
|
-
if value < 1024 or unit == units[-1]:
|
|
67
|
-
if value >= 100 or unit == "B":
|
|
68
|
-
return f"{value:.0f}{unit}"
|
|
69
|
-
if value >= 10:
|
|
70
|
-
return f"{value:.1f}{unit}"
|
|
71
|
-
return f"{value:.2f}{unit}"
|
|
72
|
-
value /= 1024
|
|
73
|
-
return f"{value:.1f}TB" # pragma: no cover - defensive fallback
|
|
74
|
-
|
|
75
|
-
|
|
76
59
|
@click.group(invoke_without_command=True)
|
|
77
60
|
@click.version_option(package_name="glaip-sdk", prog_name="aip")
|
|
78
61
|
@click.option(
|
|
@@ -157,6 +140,7 @@ main.add_command(config_group)
|
|
|
157
140
|
main.add_command(tools_group)
|
|
158
141
|
main.add_command(mcps_group)
|
|
159
142
|
main.add_command(models_group)
|
|
143
|
+
main.add_command(transcripts_group)
|
|
160
144
|
|
|
161
145
|
# Add top-level commands
|
|
162
146
|
main.add_command(configure_command)
|
|
@@ -240,15 +224,15 @@ def _collect_cache_summary() -> tuple[str | None, str | None]:
|
|
|
240
224
|
try:
|
|
241
225
|
cache_stats = get_transcript_cache_stats()
|
|
242
226
|
except Exception:
|
|
243
|
-
return "[dim]Saved
|
|
227
|
+
return "[dim]Saved transcripts[/dim]: unavailable", None
|
|
244
228
|
|
|
245
229
|
runs_text = f"{cache_stats.entry_count} runs saved"
|
|
246
230
|
if cache_stats.total_bytes:
|
|
247
|
-
size_part = f" · {
|
|
231
|
+
size_part = f" · {format_size(cache_stats.total_bytes)} used"
|
|
248
232
|
else:
|
|
249
233
|
size_part = ""
|
|
250
234
|
|
|
251
|
-
cache_line = f"[dim]Saved
|
|
235
|
+
cache_line = f"[dim]Saved transcripts[/dim]: {runs_text}{size_part} · {cache_stats.cache_dir}"
|
|
252
236
|
return cache_line, None
|
|
253
237
|
|
|
254
238
|
|
|
@@ -429,14 +413,11 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
429
413
|
|
|
430
414
|
# Update using pip
|
|
431
415
|
try:
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
"--upgrade",
|
|
438
|
-
"glaip-sdk",
|
|
439
|
-
]
|
|
416
|
+
from glaip_sdk.cli.commands.update import _build_upgrade_command
|
|
417
|
+
|
|
418
|
+
cmd = list(_build_upgrade_command(include_prerelease=False))
|
|
419
|
+
# Replace package name with "glaip-sdk" (main.py uses different name)
|
|
420
|
+
cmd[-1] = "glaip-sdk"
|
|
440
421
|
if force:
|
|
441
422
|
cmd.insert(5, "--force-reinstall")
|
|
442
423
|
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
@@ -491,4 +472,4 @@ def update(check_only: bool, force: bool) -> None:
|
|
|
491
472
|
|
|
492
473
|
|
|
493
474
|
if __name__ == "__main__":
|
|
494
|
-
main()
|
|
475
|
+
main() # pylint: disable=no-value-for-parameter
|
glaip_sdk/cli/slash/__init__.py
CHANGED
|
@@ -6,19 +6,10 @@ Authors:
|
|
|
6
6
|
|
|
7
7
|
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
8
8
|
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
9
|
-
from glaip_sdk.cli.commands.configure import configure_command, load_config
|
|
10
|
-
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
11
|
-
from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT
|
|
12
9
|
from glaip_sdk.cli.slash.session import SlashSession
|
|
13
|
-
from glaip_sdk.cli.utils import get_client
|
|
14
10
|
|
|
15
11
|
__all__ = [
|
|
16
|
-
"AgentRunSession",
|
|
17
12
|
"SlashSession",
|
|
18
|
-
"_HAS_PROMPT_TOOLKIT",
|
|
19
13
|
"agents_get_command",
|
|
20
14
|
"agents_run_command",
|
|
21
|
-
"configure_command",
|
|
22
|
-
"get_client",
|
|
23
|
-
"load_config",
|
|
24
15
|
]
|
glaip_sdk/cli/slash/prompt.py
CHANGED
|
@@ -163,6 +163,7 @@ def _create_key_bindings(_session: SlashSession) -> Any:
|
|
|
163
163
|
def _iter_command_completions(
|
|
164
164
|
session: SlashSession, text: str
|
|
165
165
|
) -> Iterable[Completion]: # pragma: no cover - thin wrapper
|
|
166
|
+
"""Yield completions for global slash commands."""
|
|
166
167
|
prefix = text[1:]
|
|
167
168
|
seen: set[str] = set()
|
|
168
169
|
|
|
@@ -203,6 +204,7 @@ def _generate_command_completions(cmd: Any, prefix: str, text: str, seen: set[st
|
|
|
203
204
|
def _iter_contextual_completions(
|
|
204
205
|
session: SlashSession, text: str
|
|
205
206
|
) -> Iterable[Completion]: # pragma: no cover - thin wrapper
|
|
207
|
+
"""Yield completions for context-specific slash commands."""
|
|
206
208
|
prefix = text[1:]
|
|
207
209
|
seen: set[str] = set()
|
|
208
210
|
|
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -33,6 +33,7 @@ from glaip_sdk.branding import (
|
|
|
33
33
|
WARNING_STYLE,
|
|
34
34
|
AIPBranding,
|
|
35
35
|
)
|
|
36
|
+
from glaip_sdk.cli.commands import transcripts as transcripts_cmd
|
|
36
37
|
from glaip_sdk.cli.commands.configure import configure_command, load_config
|
|
37
38
|
from glaip_sdk.cli.commands.update import update_command
|
|
38
39
|
from glaip_sdk.cli.slash.agent_session import AgentRunSession
|
|
@@ -46,9 +47,7 @@ from glaip_sdk.cli.slash.prompt import (
|
|
|
46
47
|
)
|
|
47
48
|
from glaip_sdk.cli.transcript import (
|
|
48
49
|
export_cached_transcript,
|
|
49
|
-
|
|
50
|
-
resolve_manifest_for_export,
|
|
51
|
-
suggest_filename,
|
|
50
|
+
load_history_snapshot,
|
|
52
51
|
)
|
|
53
52
|
from glaip_sdk.cli.transcript.viewer import ViewerContext, run_viewer_session
|
|
54
53
|
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
@@ -56,6 +55,7 @@ from glaip_sdk.cli.utils import (
|
|
|
56
55
|
_fuzzy_pick_for_resources,
|
|
57
56
|
command_hint,
|
|
58
57
|
format_command_hint,
|
|
58
|
+
format_size,
|
|
59
59
|
get_client,
|
|
60
60
|
)
|
|
61
61
|
from glaip_sdk.rich_components import AIPGrid, AIPPanel, AIPTable
|
|
@@ -73,6 +73,39 @@ class SlashCommand:
|
|
|
73
73
|
aliases: tuple[str, ...] = ()
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
NEW_QUICK_ACTIONS: tuple[dict[str, Any], ...] = (
|
|
77
|
+
{
|
|
78
|
+
"cli": "transcripts",
|
|
79
|
+
"slash": "transcripts",
|
|
80
|
+
"description": "Review transcript cache",
|
|
81
|
+
"tag": "NEW",
|
|
82
|
+
"priority": 10,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
DEFAULT_QUICK_ACTIONS: tuple[dict[str, Any], ...] = (
|
|
88
|
+
{
|
|
89
|
+
"cli": "status",
|
|
90
|
+
"slash": "status",
|
|
91
|
+
"description": "Connection check",
|
|
92
|
+
"priority": 0,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"cli": "agents list",
|
|
96
|
+
"slash": "agents",
|
|
97
|
+
"description": "Browse agents",
|
|
98
|
+
"priority": 0,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"cli": "help",
|
|
102
|
+
"slash": "help",
|
|
103
|
+
"description": "Show all commands",
|
|
104
|
+
"priority": 0,
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
76
109
|
class SlashSession:
|
|
77
110
|
"""Interactive command palette controller."""
|
|
78
111
|
|
|
@@ -99,7 +132,7 @@ class SlashSession:
|
|
|
99
132
|
self._active_renderer: Any | None = None
|
|
100
133
|
self._current_agent: Any | None = None
|
|
101
134
|
|
|
102
|
-
self._home_placeholder = "
|
|
135
|
+
self._home_placeholder = "Hint: type / to explore commands · Ctrl+D exits"
|
|
103
136
|
|
|
104
137
|
# Command string constants to avoid duplication
|
|
105
138
|
self.STATUS_COMMAND = "/status"
|
|
@@ -263,6 +296,10 @@ class SlashSession:
|
|
|
263
296
|
return False
|
|
264
297
|
return True
|
|
265
298
|
|
|
299
|
+
def _continue_session(self) -> bool:
|
|
300
|
+
"""Signal that the slash session should remain active."""
|
|
301
|
+
return not self._should_exit
|
|
302
|
+
|
|
266
303
|
# ------------------------------------------------------------------
|
|
267
304
|
# Command handlers
|
|
268
305
|
# ------------------------------------------------------------------
|
|
@@ -345,7 +382,7 @@ class SlashSession:
|
|
|
345
382
|
self._show_default_quick_actions()
|
|
346
383
|
except click.ClickException as exc:
|
|
347
384
|
self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
|
|
348
|
-
return
|
|
385
|
+
return self._continue_session()
|
|
349
386
|
|
|
350
387
|
def _cmd_status(self, _args: list[str], _invoked_from_agent: bool) -> bool:
|
|
351
388
|
ctx_obj = self.ctx.obj if isinstance(self.ctx.obj, dict) else None
|
|
@@ -374,7 +411,116 @@ class SlashSession:
|
|
|
374
411
|
ctx_obj.pop("_slash_console", None)
|
|
375
412
|
else:
|
|
376
413
|
ctx_obj["_slash_console"] = previous_console
|
|
377
|
-
return
|
|
414
|
+
return self._continue_session()
|
|
415
|
+
|
|
416
|
+
def _cmd_transcripts(self, args: list[str], _invoked_from_agent: bool) -> bool:
|
|
417
|
+
if args and args[0].lower() in {"detail", "show"}:
|
|
418
|
+
if len(args) < 2:
|
|
419
|
+
self.console.print(f"[{WARNING_STYLE}]Usage: /transcripts detail <run_id>[/]")
|
|
420
|
+
return self._continue_session()
|
|
421
|
+
self._show_transcript_detail(args[1])
|
|
422
|
+
return self._continue_session()
|
|
423
|
+
|
|
424
|
+
limit, ok = self._parse_transcripts_limit(args)
|
|
425
|
+
if not ok:
|
|
426
|
+
return self._continue_session()
|
|
427
|
+
|
|
428
|
+
snapshot = load_history_snapshot(limit=limit, ctx=self.ctx)
|
|
429
|
+
|
|
430
|
+
if self._handle_transcripts_empty(snapshot, limit):
|
|
431
|
+
return self._continue_session()
|
|
432
|
+
|
|
433
|
+
self._render_transcripts_snapshot(snapshot)
|
|
434
|
+
return self._continue_session()
|
|
435
|
+
|
|
436
|
+
def _parse_transcripts_limit(self, args: list[str]) -> tuple[int | None, bool]:
|
|
437
|
+
if not args:
|
|
438
|
+
return None, True
|
|
439
|
+
try:
|
|
440
|
+
limit = int(args[0])
|
|
441
|
+
except ValueError:
|
|
442
|
+
self.console.print(f"[{WARNING_STYLE}]Usage: /transcripts [limit][/]")
|
|
443
|
+
return None, False
|
|
444
|
+
if limit < 0:
|
|
445
|
+
self.console.print(f"[{WARNING_STYLE}]Usage: /transcripts [limit][/]")
|
|
446
|
+
return None, False
|
|
447
|
+
return limit, True
|
|
448
|
+
|
|
449
|
+
def _handle_transcripts_empty(self, snapshot: Any, limit: int | None) -> bool:
|
|
450
|
+
if snapshot.cached_entries == 0:
|
|
451
|
+
self.console.print(f"[{WARNING_STYLE}]No cached transcripts yet. Run an agent first.[/]")
|
|
452
|
+
for warning in snapshot.warnings:
|
|
453
|
+
self.console.print(f"[{WARNING_STYLE}]{warning}[/]")
|
|
454
|
+
return True
|
|
455
|
+
if limit == 0 and snapshot.cached_entries:
|
|
456
|
+
self.console.print(f"[{WARNING_STYLE}]Limit is 0; nothing to display.[/]")
|
|
457
|
+
return True
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
def _render_transcripts_snapshot(self, snapshot: Any) -> None:
|
|
461
|
+
size_text = format_size(snapshot.total_size_bytes)
|
|
462
|
+
header = f"[dim]Manifest: {snapshot.manifest_path} · {snapshot.total_entries} runs · {size_text} used[/]"
|
|
463
|
+
self.console.print(header)
|
|
464
|
+
|
|
465
|
+
if snapshot.limit_clamped:
|
|
466
|
+
self.console.print(
|
|
467
|
+
f"[{WARNING_STYLE}]Requested limit exceeded maximum; showing first {snapshot.limit_applied} runs.[/]"
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
if snapshot.total_entries > len(snapshot.entries):
|
|
471
|
+
subset_message = (
|
|
472
|
+
f"[dim]Showing {len(snapshot.entries)} of {snapshot.total_entries} "
|
|
473
|
+
f"runs (limit={snapshot.limit_applied}).[/]"
|
|
474
|
+
)
|
|
475
|
+
self.console.print(subset_message)
|
|
476
|
+
self.console.print("[dim]Hint: run `/transcripts <limit>` to change how many rows are displayed.[/]")
|
|
477
|
+
|
|
478
|
+
if snapshot.migration_summary:
|
|
479
|
+
self.console.print(f"[{INFO_STYLE}]{snapshot.migration_summary}[/]")
|
|
480
|
+
|
|
481
|
+
for warning in snapshot.warnings:
|
|
482
|
+
self.console.print(f"[{WARNING_STYLE}]{warning}[/]")
|
|
483
|
+
|
|
484
|
+
table = transcripts_cmd._build_table(snapshot.entries)
|
|
485
|
+
self.console.print(table)
|
|
486
|
+
self.console.print("[dim]! Missing transcript[/]")
|
|
487
|
+
|
|
488
|
+
def _show_transcript_detail(self, run_id: str) -> None:
|
|
489
|
+
"""Render the cached transcript log for a single run."""
|
|
490
|
+
snapshot = load_history_snapshot(ctx=self.ctx)
|
|
491
|
+
entry = snapshot.index.get(run_id)
|
|
492
|
+
if entry is None:
|
|
493
|
+
self.console.print(f"[{WARNING_STYLE}]Run id {run_id} was not found in the cache manifest.[/]")
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
transcript_path, transcript_text = transcripts_cmd._load_transcript_text(entry)
|
|
498
|
+
except click.ClickException as exc:
|
|
499
|
+
self.console.print(f"[{WARNING_STYLE}]{exc}[/]")
|
|
500
|
+
return
|
|
501
|
+
|
|
502
|
+
meta, events = transcripts_cmd._decode_transcript(transcript_text)
|
|
503
|
+
if transcripts_cmd._maybe_launch_transcript_viewer(
|
|
504
|
+
self.ctx,
|
|
505
|
+
entry,
|
|
506
|
+
meta,
|
|
507
|
+
events,
|
|
508
|
+
console_override=self.console,
|
|
509
|
+
force=True,
|
|
510
|
+
initial_view="transcript",
|
|
511
|
+
):
|
|
512
|
+
if snapshot.migration_summary:
|
|
513
|
+
self.console.print(f"[{INFO_STYLE}]{snapshot.migration_summary}[/]")
|
|
514
|
+
for warning in snapshot.warnings:
|
|
515
|
+
self.console.print(f"[{WARNING_STYLE}]{warning}[/]")
|
|
516
|
+
return
|
|
517
|
+
|
|
518
|
+
if snapshot.migration_summary:
|
|
519
|
+
self.console.print(f"[{INFO_STYLE}]{snapshot.migration_summary}[/]")
|
|
520
|
+
for warning in snapshot.warnings:
|
|
521
|
+
self.console.print(f"[{WARNING_STYLE}]{warning}[/]")
|
|
522
|
+
view = transcripts_cmd._render_transcript_display(entry, snapshot.manifest_path, transcript_path, meta, events)
|
|
523
|
+
self.console.print(view, markup=False, highlight=False, soft_wrap=True, end="")
|
|
378
524
|
|
|
379
525
|
def _cmd_agents(self, args: list[str], _invoked_from_agent: bool) -> bool:
|
|
380
526
|
client = self._get_client_or_fail()
|
|
@@ -442,7 +588,7 @@ class SlashSession:
|
|
|
442
588
|
self._render_header()
|
|
443
589
|
|
|
444
590
|
self._show_agent_followup_actions(picked_agent)
|
|
445
|
-
return
|
|
591
|
+
return self._continue_session()
|
|
446
592
|
|
|
447
593
|
def _show_agent_followup_actions(self, picked_agent: Any) -> None:
|
|
448
594
|
"""Show follow-up action hints after agent session."""
|
|
@@ -498,6 +644,13 @@ class SlashSession:
|
|
|
498
644
|
handler=SlashSession._cmd_status,
|
|
499
645
|
)
|
|
500
646
|
)
|
|
647
|
+
self._register(
|
|
648
|
+
SlashCommand(
|
|
649
|
+
name="transcripts",
|
|
650
|
+
help="Review cached transcript history. Add a number (e.g. `/transcripts 5`) to change the row limit.",
|
|
651
|
+
handler=SlashSession._cmd_transcripts,
|
|
652
|
+
)
|
|
653
|
+
)
|
|
501
654
|
self._register(
|
|
502
655
|
SlashCommand(
|
|
503
656
|
name="agents",
|
|
@@ -574,55 +727,13 @@ class SlashSession:
|
|
|
574
727
|
manifest = ctx_obj.get("_last_transcript_manifest")
|
|
575
728
|
return payload, manifest
|
|
576
729
|
|
|
577
|
-
def _cmd_export(self,
|
|
730
|
+
def _cmd_export(self, _args: list[str], _invoked_from_agent: bool) -> bool:
|
|
578
731
|
"""Slash handler for `/export` command."""
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if run_id:
|
|
585
|
-
self.console.print(
|
|
586
|
-
f"[{WARNING_STYLE}]No cached transcript found with run id {run_id!r}. "
|
|
587
|
-
"Omit the run id to export the most recent run.[/]"
|
|
588
|
-
)
|
|
589
|
-
else:
|
|
590
|
-
self.console.print(f"[{WARNING_STYLE}]No cached transcripts available yet. Run an agent first.[/]")
|
|
591
|
-
return False
|
|
592
|
-
|
|
593
|
-
destination = self._resolve_export_destination(path_arg, manifest_entry)
|
|
594
|
-
if destination is None:
|
|
595
|
-
return False
|
|
596
|
-
|
|
597
|
-
try:
|
|
598
|
-
exported = export_cached_transcript(
|
|
599
|
-
destination=destination,
|
|
600
|
-
run_id=manifest_entry.get("run_id"),
|
|
601
|
-
)
|
|
602
|
-
except FileNotFoundError as exc:
|
|
603
|
-
self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
|
|
604
|
-
return False
|
|
605
|
-
except Exception as exc: # pragma: no cover - unexpected IO failures
|
|
606
|
-
self.console.print(f"[{ERROR_STYLE}]Failed to export transcript: {exc}[/]")
|
|
607
|
-
return False
|
|
608
|
-
else:
|
|
609
|
-
self.console.print(f"[{SUCCESS_STYLE}]Transcript exported to[/] {exported}")
|
|
610
|
-
return True
|
|
611
|
-
|
|
612
|
-
def _resolve_export_destination(self, path_arg: str | None, manifest_entry: dict[str, Any]) -> Path | None:
|
|
613
|
-
if path_arg:
|
|
614
|
-
return normalise_export_destination(Path(path_arg))
|
|
615
|
-
|
|
616
|
-
default_name = suggest_filename(manifest_entry)
|
|
617
|
-
prompt = f"Save transcript to [{default_name}]: "
|
|
618
|
-
try:
|
|
619
|
-
response = self.console.input(prompt)
|
|
620
|
-
except EOFError:
|
|
621
|
-
self.console.print("[dim]Export cancelled.[/dim]")
|
|
622
|
-
return None
|
|
623
|
-
|
|
624
|
-
chosen = response.strip() or default_name
|
|
625
|
-
return normalise_export_destination(Path(chosen))
|
|
732
|
+
self.console.print(
|
|
733
|
+
f"[{WARNING_STYLE}]`/export` is deprecated. Use `/transcripts`, select a run, "
|
|
734
|
+
"and open the transcript viewer to export.[/]"
|
|
735
|
+
)
|
|
736
|
+
return True
|
|
626
737
|
|
|
627
738
|
def _cmd_update(self, args: list[str], _invoked_from_agent: bool) -> bool:
|
|
628
739
|
"""Slash handler for `/update` command."""
|
|
@@ -981,35 +1092,73 @@ class SlashSession:
|
|
|
981
1092
|
return None
|
|
982
1093
|
|
|
983
1094
|
def _show_default_quick_actions(self) -> None:
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
command_hint("agents list", slash_command="agents", ctx=self.ctx),
|
|
991
|
-
"Browse agents",
|
|
992
|
-
),
|
|
993
|
-
(
|
|
994
|
-
command_hint("help", slash_command="help", ctx=self.ctx),
|
|
995
|
-
"Show all commands",
|
|
996
|
-
),
|
|
997
|
-
]
|
|
998
|
-
filtered = [(cmd, desc) for cmd, desc in hints if cmd]
|
|
999
|
-
if filtered:
|
|
1000
|
-
self._show_quick_actions(filtered, title="Quick actions")
|
|
1095
|
+
new_hints = self._collect_quick_action_hints(NEW_QUICK_ACTIONS, highlight_new=True)
|
|
1096
|
+
evergreen_hints = self._collect_quick_action_hints(DEFAULT_QUICK_ACTIONS)
|
|
1097
|
+
if new_hints or evergreen_hints:
|
|
1098
|
+
self.console.print(f"[dim]{'─' * 40}[/]")
|
|
1099
|
+
self._render_quick_action_group(new_hints, "New commands")
|
|
1100
|
+
self._render_quick_action_group(evergreen_hints, "Quick actions")
|
|
1001
1101
|
self._default_actions_shown = True
|
|
1002
1102
|
|
|
1103
|
+
def _collect_quick_action_hints(
|
|
1104
|
+
self,
|
|
1105
|
+
actions: Iterable[dict[str, Any]],
|
|
1106
|
+
*,
|
|
1107
|
+
highlight_new: bool = False,
|
|
1108
|
+
) -> list[tuple[str, str]]:
|
|
1109
|
+
collected: list[tuple[str, str]] = []
|
|
1110
|
+
for action in sorted(actions, key=lambda payload: payload.get("priority", 0), reverse=True):
|
|
1111
|
+
hint = self._build_quick_action_hint(action, highlight_new=highlight_new)
|
|
1112
|
+
if hint:
|
|
1113
|
+
collected.append(hint)
|
|
1114
|
+
return collected
|
|
1115
|
+
|
|
1116
|
+
def _build_quick_action_hint(
|
|
1117
|
+
self,
|
|
1118
|
+
action: dict[str, Any],
|
|
1119
|
+
*,
|
|
1120
|
+
highlight_new: bool = False,
|
|
1121
|
+
) -> tuple[str, str] | None:
|
|
1122
|
+
command = command_hint(action.get("cli"), slash_command=action.get("slash"), ctx=self.ctx)
|
|
1123
|
+
if not command:
|
|
1124
|
+
return None
|
|
1125
|
+
description = action.get("description", "")
|
|
1126
|
+
tag = action.get("tag")
|
|
1127
|
+
if tag:
|
|
1128
|
+
description = f"[{ACCENT_STYLE}]{tag}[/] · {description}"
|
|
1129
|
+
if highlight_new:
|
|
1130
|
+
description = f"✨ {description}"
|
|
1131
|
+
return command, description
|
|
1132
|
+
|
|
1133
|
+
def _render_quick_action_group(self, hints: list[tuple[str, str]], title: str) -> None:
|
|
1134
|
+
if not hints:
|
|
1135
|
+
return
|
|
1136
|
+
formatted_tokens: list[str] = []
|
|
1137
|
+
for command, description in hints:
|
|
1138
|
+
formatted = format_command_hint(command, description)
|
|
1139
|
+
if formatted:
|
|
1140
|
+
formatted_tokens.append(f"• {formatted}")
|
|
1141
|
+
if not formatted_tokens:
|
|
1142
|
+
return
|
|
1143
|
+
header = f"[dim]{title}[/dim] · {formatted_tokens[0]}"
|
|
1144
|
+
self.console.print(header)
|
|
1145
|
+
remaining = formatted_tokens[1:]
|
|
1146
|
+
for chunk in self._chunk_tokens(remaining, size=3):
|
|
1147
|
+
self.console.print(" " + " ".join(chunk))
|
|
1148
|
+
|
|
1149
|
+
def _chunk_tokens(self, tokens: list[str], *, size: int) -> Iterable[list[str]]:
|
|
1150
|
+
for index in range(0, len(tokens), size):
|
|
1151
|
+
yield tokens[index : index + size]
|
|
1152
|
+
|
|
1003
1153
|
def _render_home_hint(self) -> None:
|
|
1004
1154
|
if self._home_hint_shown:
|
|
1005
1155
|
return
|
|
1006
|
-
|
|
1007
|
-
f"[{HINT_PREFIX_STYLE}]Hint:[/]"
|
|
1008
|
-
f"
|
|
1009
|
-
"
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
self.console.print("\n".join(hint_lines))
|
|
1156
|
+
hint_text = (
|
|
1157
|
+
f"[{HINT_PREFIX_STYLE}]Hint:[/] "
|
|
1158
|
+
f"Type {format_command_hint('/') or '/'} to explore commands · "
|
|
1159
|
+
"Press [dim]Ctrl+D[/] to quit"
|
|
1160
|
+
)
|
|
1161
|
+
self.console.print(hint_text)
|
|
1013
1162
|
self._home_hint_shown = True
|
|
1014
1163
|
|
|
1015
1164
|
def _show_quick_actions(
|
|
@@ -1019,26 +1168,40 @@ class SlashSession:
|
|
|
1019
1168
|
title: str = "Quick actions",
|
|
1020
1169
|
inline: bool = False,
|
|
1021
1170
|
) -> None:
|
|
1022
|
-
hint_list =
|
|
1171
|
+
hint_list = self._normalize_quick_action_hints(hints)
|
|
1023
1172
|
if not hint_list:
|
|
1024
1173
|
return
|
|
1025
1174
|
|
|
1026
1175
|
if inline:
|
|
1027
|
-
|
|
1028
|
-
for command, description in hint_list:
|
|
1029
|
-
formatted = format_command_hint(command, description)
|
|
1030
|
-
if formatted:
|
|
1031
|
-
lines.append(formatted)
|
|
1032
|
-
if lines:
|
|
1033
|
-
self.console.print("\n".join(lines))
|
|
1176
|
+
self._render_inline_quick_actions(hint_list, title)
|
|
1034
1177
|
return
|
|
1035
1178
|
|
|
1179
|
+
self._render_panel_quick_actions(hint_list, title)
|
|
1180
|
+
|
|
1181
|
+
def _normalize_quick_action_hints(self, hints: Iterable[tuple[str, str]]) -> list[tuple[str, str]]:
|
|
1182
|
+
return [(command, description) for command, description in hints if command]
|
|
1183
|
+
|
|
1184
|
+
def _render_inline_quick_actions(self, hint_list: list[tuple[str, str]], title: str) -> None:
|
|
1185
|
+
tokens: list[str] = []
|
|
1186
|
+
for command, description in hint_list:
|
|
1187
|
+
formatted = format_command_hint(command, description)
|
|
1188
|
+
if formatted:
|
|
1189
|
+
tokens.append(formatted)
|
|
1190
|
+
if not tokens:
|
|
1191
|
+
return
|
|
1192
|
+
prefix = f"[dim]{title}:[/]" if title else ""
|
|
1193
|
+
body = " ".join(tokens)
|
|
1194
|
+
text = f"{prefix} {body}" if prefix else body
|
|
1195
|
+
self.console.print(text.strip())
|
|
1196
|
+
|
|
1197
|
+
def _render_panel_quick_actions(self, hint_list: list[tuple[str, str]], title: str) -> None:
|
|
1036
1198
|
body_lines: list[Text] = []
|
|
1037
1199
|
for command, description in hint_list:
|
|
1038
1200
|
formatted = format_command_hint(command, description)
|
|
1039
1201
|
if formatted:
|
|
1040
1202
|
body_lines.append(Text.from_markup(formatted))
|
|
1041
|
-
|
|
1203
|
+
if not body_lines:
|
|
1204
|
+
return
|
|
1042
1205
|
panel_content = Group(*body_lines)
|
|
1043
1206
|
self.console.print(AIPPanel(panel_content, title=title, border_style=SECONDARY_LIGHT, expand=False))
|
|
1044
1207
|
|
|
@@ -4,68 +4,28 @@ Authors:
|
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from glaip_sdk.cli.transcript.cache import (
|
|
8
|
-
TranscriptCacheStats,
|
|
9
|
-
TranscriptPayload,
|
|
10
|
-
TranscriptStoreResult,
|
|
11
|
-
ensure_cache_dir,
|
|
12
|
-
get_transcript_cache_stats,
|
|
13
|
-
latest_manifest_entry,
|
|
14
|
-
manifest_path,
|
|
15
|
-
resolve_manifest_entry,
|
|
16
|
-
store_transcript,
|
|
17
|
-
suggest_filename,
|
|
18
|
-
)
|
|
19
7
|
from glaip_sdk.cli.transcript.cache import (
|
|
20
8
|
export_transcript as export_cached_transcript,
|
|
21
9
|
)
|
|
22
|
-
from glaip_sdk.cli.transcript.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
coerce_result_text,
|
|
26
|
-
compute_finished_at,
|
|
27
|
-
extract_server_run_id,
|
|
28
|
-
register_last_transcript,
|
|
29
|
-
store_transcript_for_session,
|
|
10
|
+
from glaip_sdk.cli.transcript.cache import (
|
|
11
|
+
get_transcript_cache_stats,
|
|
12
|
+
suggest_filename,
|
|
30
13
|
)
|
|
14
|
+
from glaip_sdk.cli.transcript.capture import store_transcript_for_session
|
|
31
15
|
from glaip_sdk.cli.transcript.export import (
|
|
32
16
|
normalise_export_destination,
|
|
33
17
|
resolve_manifest_for_export,
|
|
34
18
|
)
|
|
35
|
-
from glaip_sdk.cli.transcript.
|
|
36
|
-
|
|
37
|
-
should_launch_post_run_viewer,
|
|
38
|
-
)
|
|
39
|
-
from glaip_sdk.cli.transcript.viewer import (
|
|
40
|
-
PostRunViewer,
|
|
41
|
-
ViewerContext,
|
|
42
|
-
run_viewer_session,
|
|
43
|
-
)
|
|
19
|
+
from glaip_sdk.cli.transcript.history import load_history_snapshot
|
|
20
|
+
from glaip_sdk.cli.transcript.launcher import maybe_launch_post_run_viewer
|
|
44
21
|
|
|
45
22
|
__all__ = [
|
|
46
|
-
"TranscriptCacheStats",
|
|
47
|
-
"TranscriptPayload",
|
|
48
|
-
"TranscriptStoreResult",
|
|
49
|
-
"ensure_cache_dir",
|
|
50
|
-
"get_transcript_cache_stats",
|
|
51
|
-
"manifest_path",
|
|
52
|
-
"store_transcript",
|
|
53
|
-
"suggest_filename",
|
|
54
|
-
"latest_manifest_entry",
|
|
55
|
-
"resolve_manifest_entry",
|
|
56
23
|
"export_cached_transcript",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"coerce_result_text",
|
|
60
|
-
"compute_finished_at",
|
|
61
|
-
"extract_server_run_id",
|
|
62
|
-
"register_last_transcript",
|
|
63
|
-
"store_transcript_for_session",
|
|
64
|
-
"resolve_manifest_for_export",
|
|
65
|
-
"normalise_export_destination",
|
|
24
|
+
"get_transcript_cache_stats",
|
|
25
|
+
"load_history_snapshot",
|
|
66
26
|
"maybe_launch_post_run_viewer",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
27
|
+
"normalise_export_destination",
|
|
28
|
+
"resolve_manifest_for_export",
|
|
29
|
+
"store_transcript_for_session",
|
|
30
|
+
"suggest_filename",
|
|
71
31
|
]
|