glaip-sdk 0.2.0__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 +214 -4
- glaip_sdk/cli/transcript/viewer.py +226 -0
- glaip_sdk/cli/update_notifier.py +7 -4
- 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.0.dist-info → glaip_sdk-0.2.2.dist-info}/METADATA +1 -1
- {glaip_sdk-0.2.0.dist-info → glaip_sdk-0.2.2.dist-info}/RECORD +28 -27
- {glaip_sdk-0.2.0.dist-info → glaip_sdk-0.2.2.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.2.0.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
|
@@ -153,9 +153,15 @@ class SlashSession:
|
|
|
153
153
|
# ------------------------------------------------------------------
|
|
154
154
|
# Session orchestration
|
|
155
155
|
# ------------------------------------------------------------------
|
|
156
|
-
def refresh_branding(
|
|
156
|
+
def refresh_branding(
|
|
157
|
+
self,
|
|
158
|
+
sdk_version: str | None = None,
|
|
159
|
+
*,
|
|
160
|
+
branding_cls: type[AIPBranding] | None = None,
|
|
161
|
+
) -> None:
|
|
157
162
|
"""Refresh branding assets after an in-session SDK upgrade."""
|
|
158
|
-
|
|
163
|
+
branding_type = branding_cls or AIPBranding
|
|
164
|
+
self._branding = branding_type.create_from_sdk(
|
|
159
165
|
sdk_version=sdk_version,
|
|
160
166
|
package_name="glaip-sdk",
|
|
161
167
|
)
|
|
@@ -165,6 +171,7 @@ class SlashSession:
|
|
|
165
171
|
self._render_header(initial=True)
|
|
166
172
|
|
|
167
173
|
def _setup_prompt_toolkit(self) -> None:
|
|
174
|
+
"""Initialize prompt_toolkit session and style."""
|
|
168
175
|
session, style = setup_prompt_toolkit(self, interactive=self._interactive)
|
|
169
176
|
self._ptk_session = session
|
|
170
177
|
self._ptk_style = style
|
|
@@ -304,6 +311,15 @@ class SlashSession:
|
|
|
304
311
|
# Command handlers
|
|
305
312
|
# ------------------------------------------------------------------
|
|
306
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
|
+
"""
|
|
307
323
|
try:
|
|
308
324
|
if invoked_from_agent:
|
|
309
325
|
self._render_agent_help()
|
|
@@ -316,11 +332,12 @@ class SlashSession:
|
|
|
316
332
|
return True
|
|
317
333
|
|
|
318
334
|
def _render_agent_help(self) -> None:
|
|
335
|
+
"""Render help text for agent context commands."""
|
|
319
336
|
table = AIPTable()
|
|
320
337
|
table.add_column("Input", style=HINT_COMMAND_STYLE, no_wrap=True)
|
|
321
338
|
table.add_column("What happens", style=HINT_DESCRIPTION_COLOR)
|
|
322
339
|
table.add_row("<message>", "Run the active agent once with that prompt.")
|
|
323
|
-
table.add_row("/details", "Show the
|
|
340
|
+
table.add_row("/details", "Show the agent export (prompts to expand instructions).")
|
|
324
341
|
table.add_row(self.STATUS_COMMAND, "Display connection status without leaving.")
|
|
325
342
|
table.add_row("/export [path]", "Export the latest agent transcript as JSONL.")
|
|
326
343
|
table.add_row("/exit (/back)", "Return to the slash home screen.")
|
|
@@ -344,6 +361,7 @@ class SlashSession:
|
|
|
344
361
|
)
|
|
345
362
|
|
|
346
363
|
def _render_global_help(self) -> None:
|
|
364
|
+
"""Render help text for global slash commands."""
|
|
347
365
|
table = AIPTable()
|
|
348
366
|
table.add_column("Command", style=HINT_COMMAND_STYLE, no_wrap=True)
|
|
349
367
|
table.add_column("Description", style=HINT_DESCRIPTION_COLOR)
|
|
@@ -370,6 +388,15 @@ class SlashSession:
|
|
|
370
388
|
)
|
|
371
389
|
|
|
372
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
|
+
"""
|
|
373
400
|
self.console.print(f"[{ACCENT_STYLE}]Launching configuration wizard...[/]")
|
|
374
401
|
try:
|
|
375
402
|
self.ctx.invoke(configure_command)
|
|
@@ -385,6 +412,15 @@ class SlashSession:
|
|
|
385
412
|
return self._continue_session()
|
|
386
413
|
|
|
387
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
|
+
"""
|
|
388
424
|
ctx_obj = self.ctx.obj if isinstance(self.ctx.obj, dict) else None
|
|
389
425
|
previous_console = None
|
|
390
426
|
try:
|
|
@@ -414,6 +450,15 @@ class SlashSession:
|
|
|
414
450
|
return self._continue_session()
|
|
415
451
|
|
|
416
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
|
+
"""
|
|
417
462
|
if args and args[0].lower() in {"detail", "show"}:
|
|
418
463
|
if len(args) < 2:
|
|
419
464
|
self.console.print(f"[{WARNING_STYLE}]Usage: /transcripts detail <run_id>[/]")
|
|
@@ -434,6 +479,14 @@ class SlashSession:
|
|
|
434
479
|
return self._continue_session()
|
|
435
480
|
|
|
436
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
|
+
"""
|
|
437
490
|
if not args:
|
|
438
491
|
return None, True
|
|
439
492
|
try:
|
|
@@ -447,6 +500,15 @@ class SlashSession:
|
|
|
447
500
|
return limit, True
|
|
448
501
|
|
|
449
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
|
+
"""
|
|
450
512
|
if snapshot.cached_entries == 0:
|
|
451
513
|
self.console.print(f"[{WARNING_STYLE}]No cached transcripts yet. Run an agent first.[/]")
|
|
452
514
|
for warning in snapshot.warnings:
|
|
@@ -458,6 +520,11 @@ class SlashSession:
|
|
|
458
520
|
return False
|
|
459
521
|
|
|
460
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
|
+
"""
|
|
461
528
|
size_text = format_size(snapshot.total_size_bytes)
|
|
462
529
|
header = f"[dim]Manifest: {snapshot.manifest_path} · {snapshot.total_entries} runs · {size_text} used[/]"
|
|
463
530
|
self.console.print(header)
|
|
@@ -523,6 +590,15 @@ class SlashSession:
|
|
|
523
590
|
self.console.print(view, markup=False, highlight=False, soft_wrap=True, end="")
|
|
524
591
|
|
|
525
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
|
+
"""
|
|
526
602
|
client = self._get_client_or_fail()
|
|
527
603
|
if not client:
|
|
528
604
|
return True
|
|
@@ -608,6 +684,15 @@ class SlashSession:
|
|
|
608
684
|
self._show_quick_actions(hints, title="Next actions")
|
|
609
685
|
|
|
610
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
|
+
"""
|
|
611
696
|
if invoked_from_agent:
|
|
612
697
|
# Returning False would stop the full session; we only want to exit
|
|
613
698
|
# the agent context. Raising a custom flag keeps the outer loop
|
|
@@ -621,6 +706,7 @@ class SlashSession:
|
|
|
621
706
|
# Utilities
|
|
622
707
|
# ------------------------------------------------------------------
|
|
623
708
|
def _register_defaults(self) -> None:
|
|
709
|
+
"""Register default slash commands."""
|
|
624
710
|
self._register(
|
|
625
711
|
SlashCommand(
|
|
626
712
|
name="help",
|
|
@@ -682,6 +768,11 @@ class SlashSession:
|
|
|
682
768
|
)
|
|
683
769
|
|
|
684
770
|
def _register(self, command: SlashCommand) -> None:
|
|
771
|
+
"""Register a slash command.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
command: SlashCommand to register.
|
|
775
|
+
"""
|
|
685
776
|
self._unique_commands[command.name] = command
|
|
686
777
|
for key in (command.name, *command.aliases):
|
|
687
778
|
self._commands[key] = command
|
|
@@ -710,6 +801,14 @@ class SlashSession:
|
|
|
710
801
|
)
|
|
711
802
|
|
|
712
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
|
+
"""
|
|
713
812
|
return export_cached_transcript(destination=destination, run_id=run_id)
|
|
714
813
|
|
|
715
814
|
try:
|
|
@@ -806,6 +905,14 @@ class SlashSession:
|
|
|
806
905
|
pass
|
|
807
906
|
|
|
808
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
|
+
"""
|
|
809
916
|
try:
|
|
810
917
|
tokens = shlex.split(raw)
|
|
811
918
|
except ValueError:
|
|
@@ -821,6 +928,14 @@ class SlashSession:
|
|
|
821
928
|
return head, tokens[1:]
|
|
822
929
|
|
|
823
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
|
+
"""
|
|
824
939
|
keys = [cmd.name for cmd in self._unique_commands.values()]
|
|
825
940
|
match = get_close_matches(verb, keys, n=1)
|
|
826
941
|
return match[0] if match else None
|
|
@@ -849,6 +964,7 @@ class SlashSession:
|
|
|
849
964
|
if callable(message):
|
|
850
965
|
|
|
851
966
|
def prompt_text() -> Any:
|
|
967
|
+
"""Get formatted prompt text from callable message."""
|
|
852
968
|
return self._convert_message(message())
|
|
853
969
|
else:
|
|
854
970
|
prompt_text = self._convert_message(message)
|
|
@@ -894,6 +1010,11 @@ class SlashSession:
|
|
|
894
1010
|
return self._prompt_with_basic_input(message, placeholder)
|
|
895
1011
|
|
|
896
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
|
+
"""
|
|
897
1018
|
if self._client is None:
|
|
898
1019
|
self._client = get_client(self.ctx)
|
|
899
1020
|
return self._client
|
|
@@ -912,6 +1033,11 @@ class SlashSession:
|
|
|
912
1033
|
return self._contextual_include_global
|
|
913
1034
|
|
|
914
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
|
+
"""
|
|
915
1041
|
agent_data = {
|
|
916
1042
|
"id": str(getattr(agent, "id", "")),
|
|
917
1043
|
"name": getattr(agent, "name", "") or "",
|
|
@@ -929,6 +1055,13 @@ class SlashSession:
|
|
|
929
1055
|
focus_agent: bool = False,
|
|
930
1056
|
initial: bool = False,
|
|
931
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
|
+
"""
|
|
932
1065
|
if focus_agent and active_agent is not None:
|
|
933
1066
|
self._render_focused_agent_header(active_agent)
|
|
934
1067
|
return
|
|
@@ -1033,7 +1166,7 @@ class SlashSession:
|
|
|
1033
1166
|
|
|
1034
1167
|
keybar.add_row(
|
|
1035
1168
|
format_command_hint("/help", "Show commands") or "",
|
|
1036
|
-
format_command_hint("/details", "Agent config") or "",
|
|
1169
|
+
format_command_hint("/details", "Agent config (expand prompt)") or "",
|
|
1037
1170
|
format_command_hint("/exit", "Back") or "",
|
|
1038
1171
|
)
|
|
1039
1172
|
|
|
@@ -1092,6 +1225,7 @@ class SlashSession:
|
|
|
1092
1225
|
return None
|
|
1093
1226
|
|
|
1094
1227
|
def _show_default_quick_actions(self) -> None:
|
|
1228
|
+
"""Show default quick action hints for new and evergreen commands."""
|
|
1095
1229
|
new_hints = self._collect_quick_action_hints(NEW_QUICK_ACTIONS, highlight_new=True)
|
|
1096
1230
|
evergreen_hints = self._collect_quick_action_hints(DEFAULT_QUICK_ACTIONS)
|
|
1097
1231
|
if new_hints or evergreen_hints:
|
|
@@ -1106,6 +1240,15 @@ class SlashSession:
|
|
|
1106
1240
|
*,
|
|
1107
1241
|
highlight_new: bool = False,
|
|
1108
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
|
+
"""
|
|
1109
1252
|
collected: list[tuple[str, str]] = []
|
|
1110
1253
|
for action in sorted(actions, key=lambda payload: payload.get("priority", 0), reverse=True):
|
|
1111
1254
|
hint = self._build_quick_action_hint(action, highlight_new=highlight_new)
|
|
@@ -1119,6 +1262,15 @@ class SlashSession:
|
|
|
1119
1262
|
*,
|
|
1120
1263
|
highlight_new: bool = False,
|
|
1121
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
|
+
"""
|
|
1122
1274
|
command = command_hint(action.get("cli"), slash_command=action.get("slash"), ctx=self.ctx)
|
|
1123
1275
|
if not command:
|
|
1124
1276
|
return None
|
|
@@ -1131,6 +1283,12 @@ class SlashSession:
|
|
|
1131
1283
|
return command, description
|
|
1132
1284
|
|
|
1133
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
|
+
"""
|
|
1134
1292
|
if not hints:
|
|
1135
1293
|
return
|
|
1136
1294
|
formatted_tokens: list[str] = []
|
|
@@ -1147,10 +1305,20 @@ class SlashSession:
|
|
|
1147
1305
|
self.console.print(" " + " ".join(chunk))
|
|
1148
1306
|
|
|
1149
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
|
+
"""
|
|
1150
1317
|
for index in range(0, len(tokens), size):
|
|
1151
1318
|
yield tokens[index : index + size]
|
|
1152
1319
|
|
|
1153
1320
|
def _render_home_hint(self) -> None:
|
|
1321
|
+
"""Render hint text for home screen."""
|
|
1154
1322
|
if self._home_hint_shown:
|
|
1155
1323
|
return
|
|
1156
1324
|
hint_text = (
|
|
@@ -1168,6 +1336,13 @@ class SlashSession:
|
|
|
1168
1336
|
title: str = "Quick actions",
|
|
1169
1337
|
inline: bool = False,
|
|
1170
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
|
+
"""
|
|
1171
1346
|
hint_list = self._normalize_quick_action_hints(hints)
|
|
1172
1347
|
if not hint_list:
|
|
1173
1348
|
return
|
|
@@ -1179,9 +1354,23 @@ class SlashSession:
|
|
|
1179
1354
|
self._render_panel_quick_actions(hint_list, title)
|
|
1180
1355
|
|
|
1181
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
|
+
"""
|
|
1182
1365
|
return [(command, description) for command, description in hints if command]
|
|
1183
1366
|
|
|
1184
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
|
+
"""
|
|
1185
1374
|
tokens: list[str] = []
|
|
1186
1375
|
for command, description in hint_list:
|
|
1187
1376
|
formatted = format_command_hint(command, description)
|
|
@@ -1195,6 +1384,12 @@ class SlashSession:
|
|
|
1195
1384
|
self.console.print(text.strip())
|
|
1196
1385
|
|
|
1197
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
|
+
"""
|
|
1198
1393
|
body_lines: list[Text] = []
|
|
1199
1394
|
for command, description in hint_list:
|
|
1200
1395
|
formatted = format_command_hint(command, description)
|
|
@@ -1206,6 +1401,11 @@ class SlashSession:
|
|
|
1206
1401
|
self.console.print(AIPPanel(panel_content, title=title, border_style=SECONDARY_LIGHT, expand=False))
|
|
1207
1402
|
|
|
1208
1403
|
def _load_config(self) -> dict[str, Any]:
|
|
1404
|
+
"""Load configuration with caching.
|
|
1405
|
+
|
|
1406
|
+
Returns:
|
|
1407
|
+
Configuration dictionary.
|
|
1408
|
+
"""
|
|
1209
1409
|
if self._config_cache is None:
|
|
1210
1410
|
try:
|
|
1211
1411
|
self._config_cache = load_config() or {}
|
|
@@ -1214,6 +1414,16 @@ class SlashSession:
|
|
|
1214
1414
|
return self._config_cache
|
|
1215
1415
|
|
|
1216
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
|
+
"""
|
|
1217
1427
|
ref = ref.strip()
|
|
1218
1428
|
if not ref:
|
|
1219
1429
|
return None
|