sidekick-agent-cli 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (19) hide show
  1. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/PKG-INFO +1 -1
  2. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/pyproject.toml +1 -1
  3. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/cli.py +13 -6
  4. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/runtime.py +71 -59
  5. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/.gitignore +0 -0
  6. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/LICENSE +0 -0
  7. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/README.md +0 -0
  8. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/SECURITY.md +0 -0
  9. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/__init__.py +0 -0
  10. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/__main__.py +0 -0
  11. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/api_client.py +0 -0
  12. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/auth.py +0 -0
  13. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/chat.py +0 -0
  14. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/chat_display.py +0 -0
  15. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/cli_common.py +0 -0
  16. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/display.py +0 -0
  17. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/local_tools.py +0 -0
  18. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/run.py +0 -0
  19. {sidekick_agent_cli-0.2.0 → sidekick_agent_cli-0.2.2}/src/agent_runtime/runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sidekick-agent-cli
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: CLI for the Sidekick Agent Orchestrator
5
5
  Project-URL: Homepage, https://github.com/datex-labs/sidekick
6
6
  Project-URL: Repository, https://github.com/datex-labs/sidekick
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sidekick-agent-cli"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "CLI for the Sidekick Agent Orchestrator"
9
9
  requires-python = ">=3.11"
10
10
  license = "MIT"
@@ -489,11 +489,22 @@ def _run_agent_self(args: argparse.Namespace) -> None:
489
489
  api = RuntimeAPIClient(base_url=ctx.api_url, token=ctx.token)
490
490
  try:
491
491
  agent = None
492
+ source = "User default"
492
493
 
493
- # Check SIDEKICK_AGENT_ID env var first
494
+ # Check SIDEKICK_AGENT_ID env var first (set when running as a tool inside an agent turn)
494
495
  env_agent_id = os.environ.get("SIDEKICK_AGENT_ID")
495
496
  if env_agent_id:
496
497
  agent = await api.get_agent(env_agent_id)
498
+ if agent:
499
+ source = "SIDEKICK_AGENT_ID env var (current agent turn)"
500
+
501
+ # Fall back to SIDEKICK_CONVERSATION_ID → look up agent from conversation
502
+ if not agent:
503
+ env_conv_id = os.environ.get("SIDEKICK_CONVERSATION_ID")
504
+ if env_conv_id:
505
+ # The conversation's agent is the one invoking us
506
+ # For now, fall through to default — future: add conversation→agent lookup
507
+ pass
497
508
 
498
509
  # Fall back to default agent
499
510
  if not agent:
@@ -513,11 +524,7 @@ def _run_agent_self(args: argparse.Namespace) -> None:
513
524
  table.add_row("Description", agent.get("description") or "")
514
525
  table.add_row("Model", agent.get("model") or "(default)")
515
526
  table.add_row("Default", "Yes" if agent.get("is_default") else "No")
516
-
517
- if env_agent_id:
518
- table.add_row("Source", "SIDEKICK_AGENT_ID env var")
519
- else:
520
- table.add_row("Source", "User default")
527
+ table.add_row("Source", source)
521
528
 
522
529
  console.print(table)
523
530
  finally:
@@ -13,6 +13,7 @@ It never touches databases, Redis, or LLM providers directly.
13
13
  import asyncio
14
14
  import json
15
15
  import logging
16
+ import os
16
17
  from typing import Dict, List, Optional
17
18
 
18
19
  from .api_client import (
@@ -63,6 +64,12 @@ class AgentRuntime:
63
64
  """
64
65
  turn = await self.api.start_turn(conversation_id, node_id, ref_name, user_id=user_id)
65
66
 
67
+ # Expose agent/conversation context as env vars so local tools
68
+ # (RunCommand, etc.) inherit them in their subprocess environment.
69
+ if turn.agent_id:
70
+ os.environ["SIDEKICK_AGENT_ID"] = turn.agent_id
71
+ os.environ["SIDEKICK_CONVERSATION_ID"] = conversation_id
72
+
66
73
  logger.info(
67
74
  f"Turn started: {turn.turn_id} | "
68
75
  f"conversation={conversation_id} | "
@@ -75,75 +82,80 @@ class AgentRuntime:
75
82
  iteration = 0
76
83
  max_iterations = 25 # Safety limit
77
84
 
78
- while iteration < max_iterations:
79
- iteration += 1
85
+ try:
86
+ while iteration < max_iterations:
87
+ iteration += 1
88
+
89
+ # Check cancellation
90
+ if await self.api.is_cancelled(turn.turn_id):
91
+ logger.info(f"Turn {turn.turn_id} cancelled")
92
+ if self._display:
93
+ self._display.on_turn_cancelled(turn.turn_id)
94
+ break
80
95
 
81
- # Check cancellation
82
- if await self.api.is_cancelled(turn.turn_id):
83
- logger.info(f"Turn {turn.turn_id} cancelled")
96
+ # Call LLM (Sidekick handles frontend streaming as a side effect)
97
+ logger.info(f"LLM call #{iteration} for turn {turn.turn_id}")
84
98
  if self._display:
85
- self._display.on_turn_cancelled(turn.turn_id)
86
- break
87
-
88
- # Call LLM (Sidekick handles frontend streaming as a side effect)
89
- logger.info(f"LLM call #{iteration} for turn {turn.turn_id}")
90
- if self._display:
91
- self._display.on_llm_call(iteration, turn.turn_id)
92
- response = await self.api.stream_llm(turn.turn_id, turn, on_delta=on_delta)
93
-
94
- if not response.tool_calls:
95
- # No tool calls — finalize the turn
96
- await self.api.complete_turn(turn.turn_id, response.parts)
99
+ self._display.on_llm_call(iteration, turn.turn_id)
100
+ response = await self.api.stream_llm(turn.turn_id, turn, on_delta=on_delta)
101
+
102
+ if not response.tool_calls:
103
+ # No tool calls finalize the turn
104
+ await self.api.complete_turn(turn.turn_id, response.parts)
105
+ logger.info(
106
+ f"Turn {turn.turn_id} completed (no tools) after {iteration} iteration(s)"
107
+ )
108
+ if self._display:
109
+ self._display.on_turn_completed(turn.turn_id, iteration)
110
+ return
111
+
112
+ # Register tool calls with Sidekick (creates pending nodes)
113
+ tool_nodes = await self.api.register_tool_calls(
114
+ turn.turn_id,
115
+ response.parts,
116
+ response.tool_calls,
117
+ turn.tool_definitions,
118
+ )
119
+
120
+ tool_names = [tn.tool_name for tn in tool_nodes]
97
121
  logger.info(
98
- f"Turn {turn.turn_id} completed (no tools) after {iteration} iteration(s)"
122
+ f"Registered {len(tool_nodes)} tool calls: "
123
+ f"{', '.join(tool_names)}"
99
124
  )
100
125
  if self._display:
101
- self._display.on_turn_completed(turn.turn_id, iteration)
102
- return
103
-
104
- # Register tool calls with Sidekick (creates pending nodes)
105
- tool_nodes = await self.api.register_tool_calls(
106
- turn.turn_id,
107
- response.parts,
108
- response.tool_calls,
109
- turn.tool_definitions,
110
- )
126
+ self._display.on_tools_registered(tool_names)
111
127
 
112
- tool_names = [tn.tool_name for tn in tool_nodes]
113
- logger.info(
114
- f"Registered {len(tool_nodes)} tool calls: "
115
- f"{', '.join(tool_names)}"
116
- )
117
- if self._display:
118
- self._display.on_tools_registered(tool_names)
119
-
120
- # Execute tools (local or remote)
121
- results = await self._execute_tools(
122
- tool_nodes, conversation_id, node_id, ref_name,
123
- on_tool_start=on_tool_start, on_tool_complete=on_tool_complete,
124
- )
128
+ # Execute tools (local or remote)
129
+ results = await self._execute_tools(
130
+ tool_nodes, conversation_id, node_id, ref_name,
131
+ on_tool_start=on_tool_start, on_tool_complete=on_tool_complete,
132
+ )
125
133
 
126
- # Submit results to Sidekick
127
- await self.api.submit_tool_results(turn.turn_id, results)
134
+ # Submit results to Sidekick
135
+ await self.api.submit_tool_results(turn.turn_id, results)
128
136
 
129
- # Evaluate oversized tool results via ephemeral branches
130
- eval_config = (turn.agent_config or {}).get("context_evaluation", {})
131
- if eval_config.get("enabled"):
132
- await self._evaluate_tool_results(
133
- turn, results, eval_config, tool_nodes
134
- )
137
+ # Evaluate oversized tool results via ephemeral branches
138
+ eval_config = (turn.agent_config or {}).get("context_evaluation", {})
139
+ if eval_config.get("enabled"):
140
+ await self._evaluate_tool_results(
141
+ turn, results, eval_config, tool_nodes
142
+ )
135
143
 
136
- # Get updated context for next LLM call
137
- continuation = await self.api.continue_turn(turn.turn_id)
138
- turn.messages = continuation.messages
139
- turn.tool_definitions = continuation.tool_definitions
144
+ # Get updated context for next LLM call
145
+ continuation = await self.api.continue_turn(turn.turn_id)
146
+ turn.messages = continuation.messages
147
+ turn.tool_definitions = continuation.tool_definitions
140
148
 
141
- if iteration >= max_iterations:
142
- logger.warning(
143
- f"Turn {turn.turn_id} hit max iterations ({max_iterations})"
144
- )
145
- # Still complete the turn with whatever we have
146
- await self.api.complete_turn(turn.turn_id, [])
149
+ if iteration >= max_iterations:
150
+ logger.warning(
151
+ f"Turn {turn.turn_id} hit max iterations ({max_iterations})"
152
+ )
153
+ # Still complete the turn with whatever we have
154
+ await self.api.complete_turn(turn.turn_id, [])
155
+ finally:
156
+ # Clean up turn-scoped env vars
157
+ os.environ.pop("SIDEKICK_AGENT_ID", None)
158
+ os.environ.pop("SIDEKICK_CONVERSATION_ID", None)
147
159
 
148
160
  async def _execute_single_tool(
149
161
  self,