tactus 0.34.1__py3-none-any.whl → 0.35.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.
Files changed (81) hide show
  1. tactus/__init__.py +1 -1
  2. tactus/adapters/broker_log.py +17 -14
  3. tactus/adapters/channels/__init__.py +17 -15
  4. tactus/adapters/channels/base.py +16 -7
  5. tactus/adapters/channels/broker.py +43 -13
  6. tactus/adapters/channels/cli.py +19 -15
  7. tactus/adapters/channels/host.py +15 -6
  8. tactus/adapters/channels/ipc.py +82 -31
  9. tactus/adapters/channels/sse.py +41 -23
  10. tactus/adapters/cli_hitl.py +19 -19
  11. tactus/adapters/cli_log.py +4 -4
  12. tactus/adapters/control_loop.py +138 -99
  13. tactus/adapters/cost_collector_log.py +9 -9
  14. tactus/adapters/file_storage.py +56 -52
  15. tactus/adapters/http_callback_log.py +23 -13
  16. tactus/adapters/ide_log.py +17 -9
  17. tactus/adapters/lua_tools.py +4 -5
  18. tactus/adapters/mcp.py +16 -19
  19. tactus/adapters/mcp_manager.py +46 -30
  20. tactus/adapters/memory.py +9 -9
  21. tactus/adapters/plugins.py +42 -42
  22. tactus/broker/client.py +75 -78
  23. tactus/broker/protocol.py +57 -57
  24. tactus/broker/server.py +252 -197
  25. tactus/cli/app.py +3 -1
  26. tactus/cli/control.py +2 -2
  27. tactus/core/config_manager.py +181 -135
  28. tactus/core/dependencies/registry.py +66 -48
  29. tactus/core/dsl_stubs.py +222 -163
  30. tactus/core/exceptions.py +10 -1
  31. tactus/core/execution_context.py +152 -112
  32. tactus/core/lua_sandbox.py +72 -64
  33. tactus/core/message_history_manager.py +138 -43
  34. tactus/core/mocking.py +41 -27
  35. tactus/core/output_validator.py +49 -44
  36. tactus/core/registry.py +94 -80
  37. tactus/core/runtime.py +211 -176
  38. tactus/core/template_resolver.py +16 -16
  39. tactus/core/yaml_parser.py +55 -45
  40. tactus/docs/extractor.py +7 -6
  41. tactus/ide/server.py +119 -78
  42. tactus/primitives/control.py +10 -6
  43. tactus/primitives/file.py +48 -46
  44. tactus/primitives/handles.py +47 -35
  45. tactus/primitives/host.py +29 -27
  46. tactus/primitives/human.py +154 -137
  47. tactus/primitives/json.py +22 -23
  48. tactus/primitives/log.py +26 -26
  49. tactus/primitives/message_history.py +285 -31
  50. tactus/primitives/model.py +15 -9
  51. tactus/primitives/procedure.py +86 -64
  52. tactus/primitives/procedure_callable.py +58 -51
  53. tactus/primitives/retry.py +31 -29
  54. tactus/primitives/session.py +42 -29
  55. tactus/primitives/state.py +54 -43
  56. tactus/primitives/step.py +9 -13
  57. tactus/primitives/system.py +34 -21
  58. tactus/primitives/tool.py +44 -31
  59. tactus/primitives/tool_handle.py +76 -54
  60. tactus/primitives/toolset.py +25 -22
  61. tactus/sandbox/config.py +4 -4
  62. tactus/sandbox/container_runner.py +161 -107
  63. tactus/sandbox/docker_manager.py +20 -20
  64. tactus/sandbox/entrypoint.py +16 -14
  65. tactus/sandbox/protocol.py +15 -15
  66. tactus/stdlib/classify/llm.py +1 -3
  67. tactus/stdlib/core/validation.py +0 -3
  68. tactus/testing/pydantic_eval_runner.py +1 -1
  69. tactus/utils/asyncio_helpers.py +27 -0
  70. tactus/utils/cost_calculator.py +7 -7
  71. tactus/utils/model_pricing.py +11 -12
  72. tactus/utils/safe_file_library.py +156 -132
  73. tactus/utils/safe_libraries.py +27 -27
  74. tactus/validation/error_listener.py +18 -5
  75. tactus/validation/semantic_visitor.py +392 -333
  76. tactus/validation/validator.py +89 -49
  77. {tactus-0.34.1.dist-info → tactus-0.35.0.dist-info}/METADATA +12 -3
  78. {tactus-0.34.1.dist-info → tactus-0.35.0.dist-info}/RECORD +81 -80
  79. {tactus-0.34.1.dist-info → tactus-0.35.0.dist-info}/WHEEL +0 -0
  80. {tactus-0.34.1.dist-info → tactus-0.35.0.dist-info}/entry_points.txt +0 -0
  81. {tactus-0.34.1.dist-info → tactus-0.35.0.dist-info}/licenses/LICENSE +0 -0
@@ -10,7 +10,7 @@ import logging
10
10
  import os
11
11
  import uuid
12
12
  from datetime import datetime
13
- from typing import Dict, Optional
13
+ from typing import Optional
14
14
 
15
15
  from tactus.broker.protocol import read_message, write_message
16
16
  from tactus.protocols.control import (
@@ -45,9 +45,9 @@ class IPCControlChannel:
45
45
  self.channel_id = "ipc"
46
46
 
47
47
  self._server: Optional[asyncio.Server] = None
48
- self._clients: Dict[str, asyncio.StreamWriter] = {} # client_id -> writer
48
+ self._clients: dict[str, asyncio.StreamWriter] = {} # client_id -> writer
49
49
  self._response_queue: asyncio.Queue[ControlResponse] = asyncio.Queue()
50
- self._pending_requests: Dict[str, ControlRequest] = {} # request_id -> request
50
+ self._pending_requests: dict[str, ControlRequest] = {} # request_id -> request
51
51
  self._initialized = False
52
52
 
53
53
  @property
@@ -67,7 +67,7 @@ class IPCControlChannel:
67
67
  if self._initialized:
68
68
  return
69
69
 
70
- logger.info(f"{self.channel_id}: initializing...")
70
+ logger.info("%s: initializing...", self.channel_id)
71
71
 
72
72
  # Remove old socket file if it exists
73
73
  if os.path.exists(self.socket_path):
@@ -84,7 +84,11 @@ class IPCControlChannel:
84
84
  os.chmod(self.socket_path, 0o600)
85
85
 
86
86
  self._initialized = True
87
- logger.info(f"{self.channel_id}: ready (listening on {self.socket_path})")
87
+ logger.info(
88
+ "%s: ready (listening on %s)",
89
+ self.channel_id,
90
+ self.socket_path,
91
+ )
88
92
 
89
93
  async def send(self, request: ControlRequest) -> DeliveryResult:
90
94
  """
@@ -96,10 +100,14 @@ class IPCControlChannel:
96
100
  Returns:
97
101
  DeliveryResult with success/failure info
98
102
  """
99
- logger.info(f"{self.channel_id}: sending notification for {request.request_id}")
103
+ logger.info(
104
+ "%s: sending notification for %s",
105
+ self.channel_id,
106
+ request.request_id,
107
+ )
100
108
 
101
109
  # Create control request message from ControlRequest object
102
- request_data = {
110
+ request_payload = {
103
111
  "type": "control.request",
104
112
  "request_id": request.request_id,
105
113
  "procedure_id": request.procedure_id,
@@ -120,7 +128,7 @@ class IPCControlChannel:
120
128
  }
121
129
 
122
130
  # Store pending request
123
- self._pending_requests[request.request_id] = request_data
131
+ self._pending_requests[request.request_id] = request_payload
124
132
 
125
133
  # Send to all connected clients
126
134
  successful = 0
@@ -128,16 +136,21 @@ class IPCControlChannel:
128
136
 
129
137
  for client_id, writer in list(self._clients.items()):
130
138
  try:
131
- await write_message(writer, request_data)
139
+ await write_message(writer, request_payload)
132
140
  successful += 1
133
- except Exception as e:
134
- logger.error(f"{self.channel_id}: failed to send to client {client_id}: {e}")
141
+ except Exception as error:
142
+ logger.error(
143
+ "%s: failed to send to client %s: %s",
144
+ self.channel_id,
145
+ client_id,
146
+ error,
147
+ )
135
148
  failed += 1
136
149
  # Remove dead client
137
150
  self._clients.pop(client_id, None)
138
151
 
139
152
  if successful == 0 and len(self._clients) == 0:
140
- logger.warning(f"{self.channel_id}: no clients connected")
153
+ logger.warning("%s: no clients connected", self.channel_id)
141
154
 
142
155
  # Return DeliveryResult
143
156
  return DeliveryResult(
@@ -157,7 +170,11 @@ class IPCControlChannel:
157
170
  """
158
171
  while True:
159
172
  response = await self._response_queue.get()
160
- logger.info(f"{self.channel_id}: received response for {response.request_id}")
173
+ logger.info(
174
+ "%s: received response for %s",
175
+ self.channel_id,
176
+ response.request_id,
177
+ )
161
178
  yield response
162
179
 
163
180
  async def cancel(self, request_id: str, reason: str) -> None:
@@ -168,7 +185,12 @@ class IPCControlChannel:
168
185
  request_id: Request to cancel
169
186
  reason: Cancellation reason
170
187
  """
171
- logger.debug(f"{self.channel_id}: cancelling {request_id} ({reason})")
188
+ logger.debug(
189
+ "%s: cancelling %s (%s)",
190
+ self.channel_id,
191
+ request_id,
192
+ reason,
193
+ )
172
194
 
173
195
  # Remove from pending
174
196
  self._pending_requests.pop(request_id, None)
@@ -179,20 +201,30 @@ class IPCControlChannel:
179
201
  for client_id, writer in list(self._clients.items()):
180
202
  try:
181
203
  await write_message(writer, cancel_message)
182
- except Exception as e:
183
- logger.error(f"{self.channel_id}: failed to send cancellation to {client_id}: {e}")
204
+ except Exception as error:
205
+ logger.error(
206
+ "%s: failed to send cancellation to %s: %s",
207
+ self.channel_id,
208
+ client_id,
209
+ error,
210
+ )
184
211
 
185
212
  async def shutdown(self) -> None:
186
213
  """Clean up and close server."""
187
- logger.info(f"{self.channel_id}: shutting down")
214
+ logger.info("%s: shutting down", self.channel_id)
188
215
 
189
216
  # Close all client connections
190
217
  for client_id, writer in list(self._clients.items()):
191
218
  try:
192
219
  writer.close()
193
220
  await writer.wait_closed()
194
- except Exception as e:
195
- logger.error(f"{self.channel_id}: error closing client {client_id}: {e}")
221
+ except Exception as error:
222
+ logger.error(
223
+ "%s: error closing client %s: %s",
224
+ self.channel_id,
225
+ client_id,
226
+ error,
227
+ )
196
228
 
197
229
  self._clients.clear()
198
230
 
@@ -205,8 +237,12 @@ class IPCControlChannel:
205
237
  if os.path.exists(self.socket_path):
206
238
  try:
207
239
  os.unlink(self.socket_path)
208
- except Exception as e:
209
- logger.error(f"{self.channel_id}: failed to remove socket file: {e}")
240
+ except Exception as error:
241
+ logger.error(
242
+ "%s: failed to remove socket file: %s",
243
+ self.channel_id,
244
+ error,
245
+ )
210
246
 
211
247
  self._initialized = False
212
248
 
@@ -222,7 +258,7 @@ class IPCControlChannel:
222
258
  """
223
259
  client_id = str(uuid.uuid4())[:8]
224
260
 
225
- logger.info(f"{self.channel_id}: client connected ({client_id})")
261
+ logger.info("%s: client connected (%s)", self.channel_id, client_id)
226
262
 
227
263
  # Register client
228
264
  self._clients[client_id] = writer
@@ -232,19 +268,22 @@ class IPCControlChannel:
232
268
  for request_id, request_data in self._pending_requests.items():
233
269
  try:
234
270
  await write_message(writer, request_data)
235
- except Exception as e:
271
+ except Exception as error:
236
272
  logger.error(
237
- f"{self.channel_id}: failed to send pending request to {client_id}: {e}"
273
+ "%s: failed to send pending request to %s: %s",
274
+ self.channel_id,
275
+ client_id,
276
+ error,
238
277
  )
239
278
 
240
279
  # Read messages from client
241
280
  while True:
242
281
  try:
243
282
  message = await read_message(reader)
244
- except EOFError:
245
- break
246
283
  except asyncio.IncompleteReadError:
247
284
  break
285
+ except EOFError:
286
+ break
248
287
 
249
288
  # Handle message
250
289
  msg_type = message.get("type")
@@ -264,7 +303,11 @@ class IPCControlChannel:
264
303
  channel_id=self.channel_id,
265
304
  )
266
305
  await self._response_queue.put(response)
267
- logger.info(f"{self.channel_id}: received response for {response.request_id}")
306
+ logger.info(
307
+ "%s: received response for %s",
308
+ self.channel_id,
309
+ response.request_id,
310
+ )
268
311
 
269
312
  # Remove from pending
270
313
  self._pending_requests.pop(response.request_id, None)
@@ -279,16 +322,24 @@ class IPCControlChannel:
279
322
 
280
323
  else:
281
324
  logger.warning(
282
- f"{self.channel_id}: unknown message type from {client_id}: {msg_type}"
325
+ "%s: unknown message type from %s: %s",
326
+ self.channel_id,
327
+ client_id,
328
+ msg_type,
283
329
  )
284
330
 
285
- except Exception as e:
286
- logger.error(f"{self.channel_id}: error handling client {client_id}: {e}")
331
+ except Exception as error:
332
+ logger.error(
333
+ "%s: error handling client %s: %s",
334
+ self.channel_id,
335
+ client_id,
336
+ error,
337
+ )
287
338
 
288
339
  finally:
289
340
  # Clean up
290
341
  self._clients.pop(client_id, None)
291
- logger.info(f"{self.channel_id}: client disconnected ({client_id})")
342
+ logger.info("%s: client disconnected (%s)", self.channel_id, client_id)
292
343
 
293
344
  try:
294
345
  writer.close()
@@ -8,7 +8,7 @@ and receives responses via HTTP POST callbacks.
8
8
  import asyncio
9
9
  import logging
10
10
  import queue
11
- from typing import Optional, Any
11
+ from typing import Any, Optional
12
12
  from datetime import datetime, timezone
13
13
 
14
14
  from tactus.adapters.channels.base import InProcessChannel
@@ -46,7 +46,7 @@ class SSEControlChannel(InProcessChannel):
46
46
  super().__init__()
47
47
  self._event_emitter = event_emitter
48
48
  # Use thread-safe queue.Queue for sync access from Flask SSE stream
49
- self._event_queue: queue.Queue[dict] = queue.Queue()
49
+ self._event_queue: queue.Queue[dict[str, Any]] = queue.Queue()
50
50
 
51
51
  @property
52
52
  def channel_id(self) -> str:
@@ -68,9 +68,9 @@ class SSEControlChannel(InProcessChannel):
68
68
 
69
69
  async def initialize(self) -> None:
70
70
  """Initialize SSE channel (no-op, Flask SSE already running)."""
71
- logger.info(f"{self.channel_id}: initializing...")
71
+ logger.info("%s: initializing...", self.channel_id)
72
72
  # No auth or connection needed - Flask SSE already set up
73
- logger.info(f"{self.channel_id}: ready")
73
+ logger.info("%s: ready", self.channel_id)
74
74
 
75
75
  async def send(self, request: ControlRequest) -> DeliveryResult:
76
76
  """
@@ -78,18 +78,22 @@ class SSEControlChannel(InProcessChannel):
78
78
 
79
79
  Creates a hitl.request event with rich context and pushes to SSE stream.
80
80
  """
81
- logger.info(f"{self.channel_id}: sending notification for {request.request_id}")
81
+ logger.info(
82
+ "%s: sending notification for %s",
83
+ self.channel_id,
84
+ request.request_id,
85
+ )
82
86
 
83
87
  try:
84
88
  # Build SSE event payload
85
- event = self._build_hitl_event(request)
89
+ event_payload = self._build_hitl_event(request)
86
90
 
87
91
  # Emit event to SSE stream
88
92
  if self._event_emitter:
89
- await self._event_emitter(event)
93
+ await self._event_emitter(event_payload)
90
94
  else:
91
95
  # Queue for external consumption if no emitter (thread-safe)
92
- self._event_queue.put(event)
96
+ self._event_queue.put(event_payload)
93
97
 
94
98
  return DeliveryResult(
95
99
  channel_id=self.channel_id,
@@ -98,14 +102,18 @@ class SSEControlChannel(InProcessChannel):
98
102
  success=True,
99
103
  )
100
104
 
101
- except Exception as e:
102
- logger.error(f"{self.channel_id}: failed to send notification: {e}")
105
+ except Exception as error:
106
+ logger.error(
107
+ "%s: failed to send notification: %s",
108
+ self.channel_id,
109
+ error,
110
+ )
103
111
  return DeliveryResult(
104
112
  channel_id=self.channel_id,
105
113
  external_message_id=request.request_id,
106
114
  delivered_at=datetime.now(timezone.utc),
107
115
  success=False,
108
- error_message=str(e),
116
+ error_message=str(error),
109
117
  )
110
118
 
111
119
  def _build_hitl_event(self, request: ControlRequest) -> dict:
@@ -114,7 +122,7 @@ class SSEControlChannel(InProcessChannel):
114
122
 
115
123
  Returns dict that will be serialized to JSON and sent as SSE event.
116
124
  """
117
- event = {
125
+ event_payload = {
118
126
  "event_type": "hitl.request", # Frontend expects event_type, not type
119
127
  "request_id": request.request_id,
120
128
  # Identity
@@ -195,7 +203,7 @@ class SSEControlChannel(InProcessChannel):
195
203
  "metadata": request.metadata,
196
204
  }
197
205
 
198
- return event
206
+ return event_payload
199
207
 
200
208
  def _serialize_runtime_context(self, runtime_context) -> Optional[dict]:
201
209
  """Serialize RuntimeContext to dict for SSE payload."""
@@ -238,7 +246,7 @@ class SSEControlChannel(InProcessChannel):
238
246
  request_id: The request being responded to
239
247
  value: The response value from the IDE
240
248
  """
241
- logger.info(f"{self.channel_id}: received response for {request_id}")
249
+ logger.info("%s: received response for %s", self.channel_id, request_id)
242
250
 
243
251
  response = ControlResponse(
244
252
  request_id=request_id,
@@ -251,15 +259,20 @@ class SSEControlChannel(InProcessChannel):
251
259
  # Push to queue from sync context (Flask thread)
252
260
  # Get the running event loop and schedule the put operation
253
261
  try:
254
- loop = asyncio.get_event_loop()
255
- if loop.is_running():
262
+ event_loop = asyncio.get_event_loop()
263
+ if event_loop.is_running():
256
264
  # Schedule the coroutine in the running loop
257
- asyncio.run_coroutine_threadsafe(self._response_queue.put(response), loop)
265
+ asyncio.run_coroutine_threadsafe(self._response_queue.put(response), event_loop)
258
266
  else:
259
267
  # If no loop is running, use put_nowait (shouldn't happen)
260
268
  self._response_queue.put_nowait(response)
261
- except Exception as e:
262
- logger.error(f"{self.channel_id}: failed to queue response for {request_id}: {e}")
269
+ except Exception as error:
270
+ logger.error(
271
+ "%s: failed to queue response for %s: %s",
272
+ self.channel_id,
273
+ request_id,
274
+ error,
275
+ )
263
276
 
264
277
  def get_next_event(self, timeout: float = 0.001) -> Optional[dict]:
265
278
  """
@@ -275,8 +288,8 @@ class SSEControlChannel(InProcessChannel):
275
288
  Event dict or None if queue is empty
276
289
  """
277
290
  try:
278
- event = self._event_queue.get(timeout=timeout)
279
- return event
291
+ event_payload = self._event_queue.get(timeout=timeout)
292
+ return event_payload
280
293
  except queue.Empty:
281
294
  return None
282
295
 
@@ -286,7 +299,12 @@ class SSEControlChannel(InProcessChannel):
286
299
 
287
300
  Sends a hitl.cancel event to dismiss the prompt.
288
301
  """
289
- logger.debug(f"{self.channel_id}: cancelling {external_message_id}: {reason}")
302
+ logger.debug(
303
+ "%s: cancelling %s: %s",
304
+ self.channel_id,
305
+ external_message_id,
306
+ reason,
307
+ )
290
308
 
291
309
  cancel_event = {
292
310
  "event_type": "hitl.cancel", # Frontend expects event_type, not type
@@ -301,5 +319,5 @@ class SSEControlChannel(InProcessChannel):
301
319
 
302
320
  async def shutdown(self) -> None:
303
321
  """Shutdown SSE channel."""
304
- logger.info(f"{self.channel_id}: shutting down")
322
+ logger.info("%s: shutting down", self.channel_id)
305
323
  self._shutdown_event.set()
@@ -55,7 +55,7 @@ class CLIHITLHandler:
55
55
  Returns:
56
56
  HITLResponse with user's response
57
57
  """
58
- logger.debug(f"HITL request: {request.request_type} - {request.message}")
58
+ logger.debug("HITL request: %s - %s", request.request_type, request.message)
59
59
 
60
60
  # Display the request in a panel
61
61
  self.console.print()
@@ -101,10 +101,10 @@ class CLIHITLHandler:
101
101
  if request.options:
102
102
  # Display options
103
103
  self.console.print("\n[bold]Options:[/bold]")
104
- for i, option in enumerate(request.options, 1):
105
- label = option.get("label", f"Option {i}")
104
+ for index, option in enumerate(request.options, 1):
105
+ label = option.get("label", f"Option {index}")
106
106
  description = option.get("description", "")
107
- self.console.print(f" {i}. [cyan]{label}[/cyan]")
107
+ self.console.print(f" {index}. [cyan]{label}[/cyan]")
108
108
  if description:
109
109
  self.console.print(f" [dim]{description}[/dim]")
110
110
 
@@ -195,19 +195,19 @@ class CLIHITLHandler:
195
195
 
196
196
  # Display summary
197
197
  self.console.print(f"\n[bold cyan]Collecting {len(items)} inputs:[/bold cyan]")
198
- for idx, item in enumerate(items, 1):
199
- label = item.get("label", f"Item {idx}")
198
+ for index, item in enumerate(items, 1):
199
+ label = item.get("label", f"Item {index}")
200
200
  required = item.get("required", True)
201
201
  req_marker = "*" if required else ""
202
- self.console.print(f" {idx}. [cyan]{label}[/cyan]{req_marker}")
202
+ self.console.print(f" {index}. [cyan]{label}[/cyan]{req_marker}")
203
203
  self.console.print()
204
204
 
205
205
  # Collect responses for each item
206
206
  responses = {}
207
207
 
208
- for idx, item in enumerate(items, 1):
208
+ for index, item in enumerate(items, 1):
209
209
  item_id = item.get("item_id")
210
- label = item.get("label", f"Item {idx}")
210
+ label = item.get("label", f"Item {index}")
211
211
  request_type = item.get("request_type", "input")
212
212
  message = item.get("message", "")
213
213
  required = item.get("required", True)
@@ -219,7 +219,7 @@ class CLIHITLHandler:
219
219
  self.console.print(
220
220
  Panel(
221
221
  message,
222
- title=f"[bold]{idx}/{len(items)}: {label}[/bold]",
222
+ title=f"[bold]{index}/{len(items)}: {label}[/bold]",
223
223
  style="cyan" if required else "blue",
224
224
  )
225
225
  )
@@ -237,13 +237,13 @@ class CLIHITLHandler:
237
237
  self.console.print(
238
238
  "\n[bold]Select multiple options (comma-separated numbers):[/bold]"
239
239
  )
240
- for i, option in enumerate(options, 1):
240
+ for index, option in enumerate(options, 1):
241
241
  label_text = (
242
- option.get("label", f"Option {i}")
242
+ option.get("label", f"Option {index}")
243
243
  if isinstance(option, dict)
244
244
  else option
245
245
  )
246
- self.console.print(f" {i}. [cyan]{label_text}[/cyan]")
246
+ self.console.print(f" {index}. [cyan]{label_text}[/cyan]")
247
247
 
248
248
  min_selections = metadata.get("min", 0)
249
249
  max_selections = metadata.get("max", len(options))
@@ -288,15 +288,15 @@ class CLIHITLHandler:
288
288
  else:
289
289
  # Single selection
290
290
  self.console.print("\n[bold]Options:[/bold]")
291
- for i, option in enumerate(options, 1):
291
+ for index, option in enumerate(options, 1):
292
292
  if isinstance(option, dict):
293
- label_text = option.get("label", f"Option {i}")
293
+ label_text = option.get("label", f"Option {index}")
294
294
  description = option.get("description", "")
295
- self.console.print(f" {i}. [cyan]{label_text}[/cyan]")
295
+ self.console.print(f" {index}. [cyan]{label_text}[/cyan]")
296
296
  if description:
297
297
  self.console.print(f" [dim]{description}[/dim]")
298
298
  else:
299
- self.console.print(f" {i}. [cyan]{option}[/cyan]")
299
+ self.console.print(f" {index}. [cyan]{option}[/cyan]")
300
300
 
301
301
  while True:
302
302
  choice_str = Prompt.ask("Select option (number)", console=self.console)
@@ -394,7 +394,7 @@ class CLIHITLHandler:
394
394
  value=responses, responded_at=datetime.now(timezone.utc), timed_out=False
395
395
  )
396
396
 
397
- def check_pending_response(self, procedure_id: str, message_id: str) -> Optional[HITLResponse]:
397
+ def check_pending_response(self, procedure_id: str, request_id: str) -> Optional[HITLResponse]:
398
398
  """
399
399
  Check for pending response (not used in CLI mode).
400
400
 
@@ -402,7 +402,7 @@ class CLIHITLHandler:
402
402
  """
403
403
  return None
404
404
 
405
- def cancel_pending_request(self, procedure_id: str, message_id: str) -> None:
405
+ def cancel_pending_request(self, procedure_id: str, request_id: str) -> None:
406
406
  """
407
407
  Cancel pending request (not used in CLI mode).
408
408
 
@@ -126,12 +126,12 @@ class CLILogHandler:
126
126
  # Format result if available
127
127
  result_str = ""
128
128
  if event.tool_result is not None:
129
- result_value = str(event.tool_result)
130
- if len(result_value) < 60:
131
- result_str = f"\n Result: {result_value}"
129
+ result_text = str(event.tool_result)
130
+ if len(result_text) < 60:
131
+ result_str = f"\n Result: {result_text}"
132
132
  else:
133
133
  # Truncate long results
134
- result_str = f"\n Result: {result_value[:57]}..."
134
+ result_str = f"\n Result: {result_text[:57]}..."
135
135
 
136
136
  duration_str = f" ({event.duration_ms:.0f}ms)" if event.duration_ms else ""
137
137