glaip-sdk 0.2.1__py3-none-any.whl → 0.2.2__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/_version.py +8 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/commands/agents.py +131 -5
- glaip_sdk/cli/commands/mcps.py +16 -0
- glaip_sdk/cli/commands/models.py +8 -0
- glaip_sdk/cli/commands/tools.py +8 -0
- glaip_sdk/cli/commands/transcripts.py +8 -0
- glaip_sdk/cli/constants.py +35 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/display.py +34 -19
- glaip_sdk/cli/main.py +13 -4
- glaip_sdk/cli/masking.py +8 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/slash/agent_session.py +54 -7
- glaip_sdk/cli/slash/prompt.py +5 -0
- glaip_sdk/cli/slash/session.py +206 -2
- glaip_sdk/cli/transcript/viewer.py +226 -0
- glaip_sdk/cli/update_notifier.py +2 -2
- glaip_sdk/cli/utils.py +84 -27
- glaip_sdk/client/_agent_payloads.py +30 -0
- glaip_sdk/client/agents.py +144 -0
- glaip_sdk/client/main.py +5 -0
- glaip_sdk/client/run_rendering.py +66 -0
- glaip_sdk/utils/serialization.py +16 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/METADATA +1 -1
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/RECORD +28 -27
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.2.1.dist-info → glaip_sdk-0.2.2.dist-info}/entry_points.txt +0 -0
|
@@ -14,6 +14,7 @@ import click
|
|
|
14
14
|
from glaip_sdk.branding import ERROR_STYLE, HINT_PREFIX_STYLE
|
|
15
15
|
from glaip_sdk.cli.commands.agents import get as agents_get_command
|
|
16
16
|
from glaip_sdk.cli.commands.agents import run as agents_run_command
|
|
17
|
+
from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
|
|
17
18
|
from glaip_sdk.cli.slash.prompt import _HAS_PROMPT_TOOLKIT, FormattedText
|
|
18
19
|
from glaip_sdk.cli.utils import format_command_hint
|
|
19
20
|
|
|
@@ -38,11 +39,12 @@ class AgentRunSession:
|
|
|
38
39
|
self._agent_name = getattr(agent, "name", "") or self._agent_id
|
|
39
40
|
self._prompt_placeholder: str = "Chat with this agent here; use / for shortcuts. Alt+Enter inserts a newline."
|
|
40
41
|
self._contextual_completion_help: dict[str, str] = {
|
|
41
|
-
"details": "Show this agent's
|
|
42
|
+
"details": "Show this agent's configuration (+ expands prompt).",
|
|
42
43
|
"help": "Display this context-aware menu.",
|
|
43
44
|
"exit": "Return to the command palette.",
|
|
44
45
|
"q": "Return to the command palette.",
|
|
45
46
|
}
|
|
47
|
+
self._instruction_preview_limit = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
|
|
46
48
|
|
|
47
49
|
def run(self) -> None:
|
|
48
50
|
"""Run the interactive agent session loop."""
|
|
@@ -86,6 +88,7 @@ class AgentRunSession:
|
|
|
86
88
|
try:
|
|
87
89
|
|
|
88
90
|
def _prompt_message() -> Any:
|
|
91
|
+
"""Get formatted prompt message for agent session."""
|
|
89
92
|
prompt_prefix = f"{self._agent_name} ({self._agent_id}) "
|
|
90
93
|
|
|
91
94
|
# Use FormattedText if prompt_toolkit is available, otherwise use simple string
|
|
@@ -122,7 +125,7 @@ class AgentRunSession:
|
|
|
122
125
|
if raw in {"/exit", "/back", "/q"}:
|
|
123
126
|
return self._handle_exit_command()
|
|
124
127
|
|
|
125
|
-
if raw
|
|
128
|
+
if raw == "/details":
|
|
126
129
|
return self._handle_details_command(agent_id)
|
|
127
130
|
|
|
128
131
|
if raw in {"/help", "/?"}:
|
|
@@ -151,16 +154,59 @@ class AgentRunSession:
|
|
|
151
154
|
self.session.handle_command(raw, invoked_from_agent=True)
|
|
152
155
|
return not self.session._should_exit
|
|
153
156
|
|
|
154
|
-
def _show_details(self, agent_id: str) -> None:
|
|
157
|
+
def _show_details(self, agent_id: str, *, enable_prompt: bool = True) -> None:
|
|
158
|
+
"""Render the agent's configuration export inside the command palette."""
|
|
155
159
|
try:
|
|
156
|
-
self.session.ctx.invoke(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
self.session.ctx.invoke(
|
|
161
|
+
agents_get_command,
|
|
162
|
+
agent_ref=agent_id,
|
|
163
|
+
instruction_preview=self._instruction_preview_limit,
|
|
160
164
|
)
|
|
165
|
+
if enable_prompt:
|
|
166
|
+
self._prompt_instruction_view_toggle(agent_id)
|
|
167
|
+
self.console.print(
|
|
168
|
+
f"[{HINT_PREFIX_STYLE}]Tip:[/] Continue the conversation in this prompt, or use "
|
|
169
|
+
f"{format_command_hint('/help') or '/help'} for shortcuts."
|
|
170
|
+
)
|
|
161
171
|
except click.ClickException as exc:
|
|
162
172
|
self.console.print(f"[{ERROR_STYLE}]{exc}[/]")
|
|
163
173
|
|
|
174
|
+
def _prompt_instruction_view_toggle(self, agent_id: str) -> None:
|
|
175
|
+
"""Offer a prompt to expand or collapse the instruction preview after details."""
|
|
176
|
+
if not getattr(self.console, "is_terminal", False):
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
while True:
|
|
180
|
+
mode = "expanded" if self._instruction_preview_limit == 0 else "trimmed"
|
|
181
|
+
self.console.print(f"[dim]Instruction view is {mode}. Press Ctrl+T to toggle, Enter to continue.[/dim]")
|
|
182
|
+
try:
|
|
183
|
+
ch = click.getchar()
|
|
184
|
+
except (EOFError, KeyboardInterrupt): # pragma: no cover - defensive guard
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
if not self._handle_instruction_toggle_input(agent_id, ch):
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
if self._instruction_preview_limit == 0:
|
|
191
|
+
self._instruction_preview_limit = DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
|
|
192
|
+
self.console.print("")
|
|
193
|
+
|
|
194
|
+
def _handle_instruction_toggle_input(self, agent_id: str, ch: str) -> bool:
|
|
195
|
+
"""Process a single toggle keypress; return False when the loop should exit."""
|
|
196
|
+
if ch in {"\r", "\n"}:
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
lowered = ch.lower()
|
|
200
|
+
if lowered == "t" or ch == "\x14": # support literal 't' or Ctrl+T
|
|
201
|
+
self._instruction_preview_limit = (
|
|
202
|
+
DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT if self._instruction_preview_limit == 0 else 0
|
|
203
|
+
)
|
|
204
|
+
self._show_details(agent_id, enable_prompt=False)
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
# Ignore other keys and continue prompting.
|
|
208
|
+
return True
|
|
209
|
+
|
|
164
210
|
def _after_agent_run(self) -> None:
|
|
165
211
|
"""Handle transcript viewer behaviour after a successful run."""
|
|
166
212
|
payload, manifest = self.session._get_last_transcript()
|
|
@@ -221,6 +267,7 @@ class AgentRunSession:
|
|
|
221
267
|
ctx_obj["_slash_session"] = previous_session
|
|
222
268
|
|
|
223
269
|
def _run_agent(self, agent_id: str, message: str) -> None:
|
|
270
|
+
"""Execute the agents run command for the active agent."""
|
|
224
271
|
if not message:
|
|
225
272
|
return
|
|
226
273
|
|
glaip_sdk/cli/slash/prompt.py
CHANGED
|
@@ -123,6 +123,11 @@ def _create_key_bindings(_session: SlashSession) -> Any:
|
|
|
123
123
|
bindings = KeyBindings()
|
|
124
124
|
|
|
125
125
|
def _refresh_completions(buffer: Any) -> None: # type: ignore[no-any-return]
|
|
126
|
+
"""Refresh completions when slash command is typed.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
buffer: Prompt buffer instance.
|
|
130
|
+
"""
|
|
126
131
|
text = buffer.document.text_before_cursor or ""
|
|
127
132
|
if text.startswith("/") and " " not in text:
|
|
128
133
|
buffer.start_completion(select_first=False)
|
glaip_sdk/cli/slash/session.py
CHANGED
|
@@ -171,6 +171,7 @@ class SlashSession:
|
|
|
171
171
|
self._render_header(initial=True)
|
|
172
172
|
|
|
173
173
|
def _setup_prompt_toolkit(self) -> None:
|
|
174
|
+
"""Initialize prompt_toolkit session and style."""
|
|
174
175
|
session, style = setup_prompt_toolkit(self, interactive=self._interactive)
|
|
175
176
|
self._ptk_session = session
|
|
176
177
|
self._ptk_style = style
|
|
@@ -310,6 +311,15 @@ class SlashSession:
|
|
|
310
311
|
# Command handlers
|
|
311
312
|
# ------------------------------------------------------------------
|
|
312
313
|
def _cmd_help(self, _args: list[str], invoked_from_agent: bool) -> bool:
|
|
314
|
+
"""Handle the /help command.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
_args: Command arguments (unused).
|
|
318
|
+
invoked_from_agent: Whether invoked from agent context.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
True to continue session.
|
|
322
|
+
"""
|
|
313
323
|
try:
|
|
314
324
|
if invoked_from_agent:
|
|
315
325
|
self._render_agent_help()
|
|
@@ -322,11 +332,12 @@ class SlashSession:
|
|
|
322
332
|
return True
|
|
323
333
|
|
|
324
334
|
def _render_agent_help(self) -> None:
|
|
335
|
+
"""Render help text for agent context commands."""
|
|
325
336
|
table = AIPTable()
|
|
326
337
|
table.add_column("Input", style=HINT_COMMAND_STYLE, no_wrap=True)
|
|
327
338
|
table.add_column("What happens", style=HINT_DESCRIPTION_COLOR)
|
|
328
339
|
table.add_row("<message>", "Run the active agent once with that prompt.")
|
|
329
|
-
table.add_row("/details", "Show the
|
|
340
|
+
table.add_row("/details", "Show the agent export (prompts to expand instructions).")
|
|
330
341
|
table.add_row(self.STATUS_COMMAND, "Display connection status without leaving.")
|
|
331
342
|
table.add_row("/export [path]", "Export the latest agent transcript as JSONL.")
|
|
332
343
|
table.add_row("/exit (/back)", "Return to the slash home screen.")
|
|
@@ -350,6 +361,7 @@ class SlashSession:
|
|
|
350
361
|
)
|
|
351
362
|
|
|
352
363
|
def _render_global_help(self) -> None:
|
|
364
|
+
"""Render help text for global slash commands."""
|
|
353
365
|
table = AIPTable()
|
|
354
366
|
table.add_column("Command", style=HINT_COMMAND_STYLE, no_wrap=True)
|
|
355
367
|
table.add_column("Description", style=HINT_DESCRIPTION_COLOR)
|
|
@@ -376,6 +388,15 @@ class SlashSession:
|
|
|
376
388
|
)
|
|
377
389
|
|
|
378
390
|
def _cmd_login(self, _args: list[str], _invoked_from_agent: bool) -> bool:
|
|
391
|
+
"""Handle the /login command.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
_args: Command arguments (unused).
|
|
395
|
+
_invoked_from_agent: Whether invoked from agent context (unused).
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
True to continue session.
|
|
399
|
+
"""
|
|
379
400
|
self.console.print(f"[{ACCENT_STYLE}]Launching configuration wizard...[/]")
|
|
380
401
|
try:
|
|
381
402
|
self.ctx.invoke(configure_command)
|
|
@@ -391,6 +412,15 @@ class SlashSession:
|
|
|
391
412
|
return self._continue_session()
|
|
392
413
|
|
|
393
414
|
def _cmd_status(self, _args: list[str], _invoked_from_agent: bool) -> bool:
|
|
415
|
+
"""Handle the /status command.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
_args: Command arguments (unused).
|
|
419
|
+
_invoked_from_agent: Whether invoked from agent context (unused).
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
True to continue session.
|
|
423
|
+
"""
|
|
394
424
|
ctx_obj = self.ctx.obj if isinstance(self.ctx.obj, dict) else None
|
|
395
425
|
previous_console = None
|
|
396
426
|
try:
|
|
@@ -420,6 +450,15 @@ class SlashSession:
|
|
|
420
450
|
return self._continue_session()
|
|
421
451
|
|
|
422
452
|
def _cmd_transcripts(self, args: list[str], _invoked_from_agent: bool) -> bool:
|
|
453
|
+
"""Handle the /transcripts command.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
args: Command arguments (limit or detail/show with run_id).
|
|
457
|
+
_invoked_from_agent: Whether invoked from agent context (unused).
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
True to continue session.
|
|
461
|
+
"""
|
|
423
462
|
if args and args[0].lower() in {"detail", "show"}:
|
|
424
463
|
if len(args) < 2:
|
|
425
464
|
self.console.print(f"[{WARNING_STYLE}]Usage: /transcripts detail <run_id>[/]")
|
|
@@ -440,6 +479,14 @@ class SlashSession:
|
|
|
440
479
|
return self._continue_session()
|
|
441
480
|
|
|
442
481
|
def _parse_transcripts_limit(self, args: list[str]) -> tuple[int | None, bool]:
|
|
482
|
+
"""Parse limit argument from transcripts command.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
args: Command arguments.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
Tuple of (limit value or None, success boolean).
|
|
489
|
+
"""
|
|
443
490
|
if not args:
|
|
444
491
|
return None, True
|
|
445
492
|
try:
|
|
@@ -453,6 +500,15 @@ class SlashSession:
|
|
|
453
500
|
return limit, True
|
|
454
501
|
|
|
455
502
|
def _handle_transcripts_empty(self, snapshot: Any, limit: int | None) -> bool:
|
|
503
|
+
"""Handle empty transcript snapshot cases.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
snapshot: Transcript snapshot object.
|
|
507
|
+
limit: Limit value or None.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
True if empty case was handled, False otherwise.
|
|
511
|
+
"""
|
|
456
512
|
if snapshot.cached_entries == 0:
|
|
457
513
|
self.console.print(f"[{WARNING_STYLE}]No cached transcripts yet. Run an agent first.[/]")
|
|
458
514
|
for warning in snapshot.warnings:
|
|
@@ -464,6 +520,11 @@ class SlashSession:
|
|
|
464
520
|
return False
|
|
465
521
|
|
|
466
522
|
def _render_transcripts_snapshot(self, snapshot: Any) -> None:
|
|
523
|
+
"""Render transcript snapshot table and metadata.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
snapshot: Transcript snapshot object to render.
|
|
527
|
+
"""
|
|
467
528
|
size_text = format_size(snapshot.total_size_bytes)
|
|
468
529
|
header = f"[dim]Manifest: {snapshot.manifest_path} · {snapshot.total_entries} runs · {size_text} used[/]"
|
|
469
530
|
self.console.print(header)
|
|
@@ -529,6 +590,15 @@ class SlashSession:
|
|
|
529
590
|
self.console.print(view, markup=False, highlight=False, soft_wrap=True, end="")
|
|
530
591
|
|
|
531
592
|
def _cmd_agents(self, args: list[str], _invoked_from_agent: bool) -> bool:
|
|
593
|
+
"""Handle the /agents command.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
args: Command arguments (optional agent reference).
|
|
597
|
+
_invoked_from_agent: Whether invoked from agent context (unused).
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
True to continue session.
|
|
601
|
+
"""
|
|
532
602
|
client = self._get_client_or_fail()
|
|
533
603
|
if not client:
|
|
534
604
|
return True
|
|
@@ -614,6 +684,15 @@ class SlashSession:
|
|
|
614
684
|
self._show_quick_actions(hints, title="Next actions")
|
|
615
685
|
|
|
616
686
|
def _cmd_exit(self, _args: list[str], invoked_from_agent: bool) -> bool:
|
|
687
|
+
"""Handle the /exit command.
|
|
688
|
+
|
|
689
|
+
Args:
|
|
690
|
+
_args: Command arguments (unused).
|
|
691
|
+
invoked_from_agent: Whether invoked from agent context.
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
False to exit session, True to continue.
|
|
695
|
+
"""
|
|
617
696
|
if invoked_from_agent:
|
|
618
697
|
# Returning False would stop the full session; we only want to exit
|
|
619
698
|
# the agent context. Raising a custom flag keeps the outer loop
|
|
@@ -627,6 +706,7 @@ class SlashSession:
|
|
|
627
706
|
# Utilities
|
|
628
707
|
# ------------------------------------------------------------------
|
|
629
708
|
def _register_defaults(self) -> None:
|
|
709
|
+
"""Register default slash commands."""
|
|
630
710
|
self._register(
|
|
631
711
|
SlashCommand(
|
|
632
712
|
name="help",
|
|
@@ -688,6 +768,11 @@ class SlashSession:
|
|
|
688
768
|
)
|
|
689
769
|
|
|
690
770
|
def _register(self, command: SlashCommand) -> None:
|
|
771
|
+
"""Register a slash command.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
command: SlashCommand to register.
|
|
775
|
+
"""
|
|
691
776
|
self._unique_commands[command.name] = command
|
|
692
777
|
for key in (command.name, *command.aliases):
|
|
693
778
|
self._commands[key] = command
|
|
@@ -716,6 +801,14 @@ class SlashSession:
|
|
|
716
801
|
)
|
|
717
802
|
|
|
718
803
|
def _export(destination: Path) -> Path:
|
|
804
|
+
"""Export cached transcript to destination.
|
|
805
|
+
|
|
806
|
+
Args:
|
|
807
|
+
destination: Path to export transcript to.
|
|
808
|
+
|
|
809
|
+
Returns:
|
|
810
|
+
Path to exported transcript file.
|
|
811
|
+
"""
|
|
719
812
|
return export_cached_transcript(destination=destination, run_id=run_id)
|
|
720
813
|
|
|
721
814
|
try:
|
|
@@ -812,6 +905,14 @@ class SlashSession:
|
|
|
812
905
|
pass
|
|
813
906
|
|
|
814
907
|
def _parse(self, raw: str) -> tuple[str, list[str]]:
|
|
908
|
+
"""Parse a raw command string into verb and arguments.
|
|
909
|
+
|
|
910
|
+
Args:
|
|
911
|
+
raw: Raw command string.
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
Tuple of (verb, args).
|
|
915
|
+
"""
|
|
815
916
|
try:
|
|
816
917
|
tokens = shlex.split(raw)
|
|
817
918
|
except ValueError:
|
|
@@ -827,6 +928,14 @@ class SlashSession:
|
|
|
827
928
|
return head, tokens[1:]
|
|
828
929
|
|
|
829
930
|
def _suggest(self, verb: str) -> str | None:
|
|
931
|
+
"""Suggest a similar command name for an unknown verb.
|
|
932
|
+
|
|
933
|
+
Args:
|
|
934
|
+
verb: Unknown command verb.
|
|
935
|
+
|
|
936
|
+
Returns:
|
|
937
|
+
Suggested command name or None.
|
|
938
|
+
"""
|
|
830
939
|
keys = [cmd.name for cmd in self._unique_commands.values()]
|
|
831
940
|
match = get_close_matches(verb, keys, n=1)
|
|
832
941
|
return match[0] if match else None
|
|
@@ -855,6 +964,7 @@ class SlashSession:
|
|
|
855
964
|
if callable(message):
|
|
856
965
|
|
|
857
966
|
def prompt_text() -> Any:
|
|
967
|
+
"""Get formatted prompt text from callable message."""
|
|
858
968
|
return self._convert_message(message())
|
|
859
969
|
else:
|
|
860
970
|
prompt_text = self._convert_message(message)
|
|
@@ -900,6 +1010,11 @@ class SlashSession:
|
|
|
900
1010
|
return self._prompt_with_basic_input(message, placeholder)
|
|
901
1011
|
|
|
902
1012
|
def _get_client(self) -> Any: # type: ignore[no-any-return]
|
|
1013
|
+
"""Get or create the API client instance.
|
|
1014
|
+
|
|
1015
|
+
Returns:
|
|
1016
|
+
API client instance.
|
|
1017
|
+
"""
|
|
903
1018
|
if self._client is None:
|
|
904
1019
|
self._client = get_client(self.ctx)
|
|
905
1020
|
return self._client
|
|
@@ -918,6 +1033,11 @@ class SlashSession:
|
|
|
918
1033
|
return self._contextual_include_global
|
|
919
1034
|
|
|
920
1035
|
def _remember_agent(self, agent: Any) -> None: # type: ignore[no-any-return]
|
|
1036
|
+
"""Remember an agent in recent agents list.
|
|
1037
|
+
|
|
1038
|
+
Args:
|
|
1039
|
+
agent: Agent object to remember.
|
|
1040
|
+
"""
|
|
921
1041
|
agent_data = {
|
|
922
1042
|
"id": str(getattr(agent, "id", "")),
|
|
923
1043
|
"name": getattr(agent, "name", "") or "",
|
|
@@ -935,6 +1055,13 @@ class SlashSession:
|
|
|
935
1055
|
focus_agent: bool = False,
|
|
936
1056
|
initial: bool = False,
|
|
937
1057
|
) -> None:
|
|
1058
|
+
"""Render the session header with branding and status.
|
|
1059
|
+
|
|
1060
|
+
Args:
|
|
1061
|
+
active_agent: Optional active agent to display.
|
|
1062
|
+
focus_agent: Whether to focus on agent display.
|
|
1063
|
+
initial: Whether this is the initial render.
|
|
1064
|
+
"""
|
|
938
1065
|
if focus_agent and active_agent is not None:
|
|
939
1066
|
self._render_focused_agent_header(active_agent)
|
|
940
1067
|
return
|
|
@@ -1039,7 +1166,7 @@ class SlashSession:
|
|
|
1039
1166
|
|
|
1040
1167
|
keybar.add_row(
|
|
1041
1168
|
format_command_hint("/help", "Show commands") or "",
|
|
1042
|
-
format_command_hint("/details", "Agent config") or "",
|
|
1169
|
+
format_command_hint("/details", "Agent config (expand prompt)") or "",
|
|
1043
1170
|
format_command_hint("/exit", "Back") or "",
|
|
1044
1171
|
)
|
|
1045
1172
|
|
|
@@ -1098,6 +1225,7 @@ class SlashSession:
|
|
|
1098
1225
|
return None
|
|
1099
1226
|
|
|
1100
1227
|
def _show_default_quick_actions(self) -> None:
|
|
1228
|
+
"""Show default quick action hints for new and evergreen commands."""
|
|
1101
1229
|
new_hints = self._collect_quick_action_hints(NEW_QUICK_ACTIONS, highlight_new=True)
|
|
1102
1230
|
evergreen_hints = self._collect_quick_action_hints(DEFAULT_QUICK_ACTIONS)
|
|
1103
1231
|
if new_hints or evergreen_hints:
|
|
@@ -1112,6 +1240,15 @@ class SlashSession:
|
|
|
1112
1240
|
*,
|
|
1113
1241
|
highlight_new: bool = False,
|
|
1114
1242
|
) -> list[tuple[str, str]]:
|
|
1243
|
+
"""Collect quick action hints from action definitions.
|
|
1244
|
+
|
|
1245
|
+
Args:
|
|
1246
|
+
actions: Iterable of action dictionaries.
|
|
1247
|
+
highlight_new: Whether to highlight new actions.
|
|
1248
|
+
|
|
1249
|
+
Returns:
|
|
1250
|
+
List of (command, description) tuples.
|
|
1251
|
+
"""
|
|
1115
1252
|
collected: list[tuple[str, str]] = []
|
|
1116
1253
|
for action in sorted(actions, key=lambda payload: payload.get("priority", 0), reverse=True):
|
|
1117
1254
|
hint = self._build_quick_action_hint(action, highlight_new=highlight_new)
|
|
@@ -1125,6 +1262,15 @@ class SlashSession:
|
|
|
1125
1262
|
*,
|
|
1126
1263
|
highlight_new: bool = False,
|
|
1127
1264
|
) -> tuple[str, str] | None:
|
|
1265
|
+
"""Build a quick action hint from an action definition.
|
|
1266
|
+
|
|
1267
|
+
Args:
|
|
1268
|
+
action: Action dictionary.
|
|
1269
|
+
highlight_new: Whether to highlight as new.
|
|
1270
|
+
|
|
1271
|
+
Returns:
|
|
1272
|
+
Tuple of (command, description) or None.
|
|
1273
|
+
"""
|
|
1128
1274
|
command = command_hint(action.get("cli"), slash_command=action.get("slash"), ctx=self.ctx)
|
|
1129
1275
|
if not command:
|
|
1130
1276
|
return None
|
|
@@ -1137,6 +1283,12 @@ class SlashSession:
|
|
|
1137
1283
|
return command, description
|
|
1138
1284
|
|
|
1139
1285
|
def _render_quick_action_group(self, hints: list[tuple[str, str]], title: str) -> None:
|
|
1286
|
+
"""Render a group of quick action hints.
|
|
1287
|
+
|
|
1288
|
+
Args:
|
|
1289
|
+
hints: List of (command, description) tuples.
|
|
1290
|
+
title: Group title.
|
|
1291
|
+
"""
|
|
1140
1292
|
if not hints:
|
|
1141
1293
|
return
|
|
1142
1294
|
formatted_tokens: list[str] = []
|
|
@@ -1153,10 +1305,20 @@ class SlashSession:
|
|
|
1153
1305
|
self.console.print(" " + " ".join(chunk))
|
|
1154
1306
|
|
|
1155
1307
|
def _chunk_tokens(self, tokens: list[str], *, size: int) -> Iterable[list[str]]:
|
|
1308
|
+
"""Chunk tokens into groups of specified size.
|
|
1309
|
+
|
|
1310
|
+
Args:
|
|
1311
|
+
tokens: List of tokens to chunk.
|
|
1312
|
+
size: Size of each chunk.
|
|
1313
|
+
|
|
1314
|
+
Yields:
|
|
1315
|
+
Lists of tokens.
|
|
1316
|
+
"""
|
|
1156
1317
|
for index in range(0, len(tokens), size):
|
|
1157
1318
|
yield tokens[index : index + size]
|
|
1158
1319
|
|
|
1159
1320
|
def _render_home_hint(self) -> None:
|
|
1321
|
+
"""Render hint text for home screen."""
|
|
1160
1322
|
if self._home_hint_shown:
|
|
1161
1323
|
return
|
|
1162
1324
|
hint_text = (
|
|
@@ -1174,6 +1336,13 @@ class SlashSession:
|
|
|
1174
1336
|
title: str = "Quick actions",
|
|
1175
1337
|
inline: bool = False,
|
|
1176
1338
|
) -> None:
|
|
1339
|
+
"""Show quick action hints.
|
|
1340
|
+
|
|
1341
|
+
Args:
|
|
1342
|
+
hints: Iterable of (command, description) tuples.
|
|
1343
|
+
title: Title for the hints.
|
|
1344
|
+
inline: Whether to render inline or in a panel.
|
|
1345
|
+
"""
|
|
1177
1346
|
hint_list = self._normalize_quick_action_hints(hints)
|
|
1178
1347
|
if not hint_list:
|
|
1179
1348
|
return
|
|
@@ -1185,9 +1354,23 @@ class SlashSession:
|
|
|
1185
1354
|
self._render_panel_quick_actions(hint_list, title)
|
|
1186
1355
|
|
|
1187
1356
|
def _normalize_quick_action_hints(self, hints: Iterable[tuple[str, str]]) -> list[tuple[str, str]]:
|
|
1357
|
+
"""Normalize quick action hints by filtering out empty commands.
|
|
1358
|
+
|
|
1359
|
+
Args:
|
|
1360
|
+
hints: Iterable of (command, description) tuples.
|
|
1361
|
+
|
|
1362
|
+
Returns:
|
|
1363
|
+
List of normalized hints.
|
|
1364
|
+
"""
|
|
1188
1365
|
return [(command, description) for command, description in hints if command]
|
|
1189
1366
|
|
|
1190
1367
|
def _render_inline_quick_actions(self, hint_list: list[tuple[str, str]], title: str) -> None:
|
|
1368
|
+
"""Render quick actions inline.
|
|
1369
|
+
|
|
1370
|
+
Args:
|
|
1371
|
+
hint_list: List of (command, description) tuples.
|
|
1372
|
+
title: Title for the hints.
|
|
1373
|
+
"""
|
|
1191
1374
|
tokens: list[str] = []
|
|
1192
1375
|
for command, description in hint_list:
|
|
1193
1376
|
formatted = format_command_hint(command, description)
|
|
@@ -1201,6 +1384,12 @@ class SlashSession:
|
|
|
1201
1384
|
self.console.print(text.strip())
|
|
1202
1385
|
|
|
1203
1386
|
def _render_panel_quick_actions(self, hint_list: list[tuple[str, str]], title: str) -> None:
|
|
1387
|
+
"""Render quick actions in a panel.
|
|
1388
|
+
|
|
1389
|
+
Args:
|
|
1390
|
+
hint_list: List of (command, description) tuples.
|
|
1391
|
+
title: Panel title.
|
|
1392
|
+
"""
|
|
1204
1393
|
body_lines: list[Text] = []
|
|
1205
1394
|
for command, description in hint_list:
|
|
1206
1395
|
formatted = format_command_hint(command, description)
|
|
@@ -1212,6 +1401,11 @@ class SlashSession:
|
|
|
1212
1401
|
self.console.print(AIPPanel(panel_content, title=title, border_style=SECONDARY_LIGHT, expand=False))
|
|
1213
1402
|
|
|
1214
1403
|
def _load_config(self) -> dict[str, Any]:
|
|
1404
|
+
"""Load configuration with caching.
|
|
1405
|
+
|
|
1406
|
+
Returns:
|
|
1407
|
+
Configuration dictionary.
|
|
1408
|
+
"""
|
|
1215
1409
|
if self._config_cache is None:
|
|
1216
1410
|
try:
|
|
1217
1411
|
self._config_cache = load_config() or {}
|
|
@@ -1220,6 +1414,16 @@ class SlashSession:
|
|
|
1220
1414
|
return self._config_cache
|
|
1221
1415
|
|
|
1222
1416
|
def _resolve_agent_from_ref(self, client: Any, available_agents: list[Any], ref: str) -> Any | None:
|
|
1417
|
+
"""Resolve an agent from a reference string.
|
|
1418
|
+
|
|
1419
|
+
Args:
|
|
1420
|
+
client: API client instance.
|
|
1421
|
+
available_agents: List of available agents.
|
|
1422
|
+
ref: Reference string (ID or name).
|
|
1423
|
+
|
|
1424
|
+
Returns:
|
|
1425
|
+
Resolved agent or None.
|
|
1426
|
+
"""
|
|
1223
1427
|
ref = ref.strip()
|
|
1224
1428
|
if not ref:
|
|
1225
1429
|
return None
|