glaip-sdk 0.1.0__py3-none-any.whl → 0.6.10__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 +5 -2
- glaip_sdk/_version.py +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1191 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +265 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +251 -173
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +735 -143
- glaip_sdk/cli/commands/mcps.py +266 -134
- glaip_sdk/cli/commands/models.py +13 -9
- glaip_sdk/cli/commands/tools.py +67 -88
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +49 -7
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +846 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +45 -32
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +232 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +12 -19
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +807 -225
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -499
- glaip_sdk/cli/update_notifier.py +177 -24
- glaip_sdk/cli/utils.py +242 -1308
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +53 -37
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +320 -92
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +123 -15
- glaip_sdk/client/run_rendering.py +136 -101
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +163 -34
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +232 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +706 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +58 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +7 -35
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +3 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -49
- glaip_sdk/utils/rendering/renderer/base.py +258 -1577
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +10 -51
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +1 -3
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +76 -517
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +20 -28
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
- glaip_sdk-0.6.10.dist-info/RECORD +159 -0
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""SSE event processing mixin for StepManager.
|
|
2
2
|
|
|
3
3
|
Authors:
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
@@ -7,385 +7,27 @@ Authors:
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
|
-
from collections.abc import
|
|
10
|
+
from collections.abc import Mapping
|
|
11
11
|
from copy import deepcopy
|
|
12
12
|
from time import monotonic
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
|
-
from glaip_sdk.icons import ICON_AGENT_STEP, ICON_DELEGATE, ICON_TOOL_STEP
|
|
16
15
|
from glaip_sdk.utils.rendering.models import Step
|
|
17
|
-
from glaip_sdk.utils.rendering.
|
|
16
|
+
from glaip_sdk.utils.rendering.timing import coerce_server_time
|
|
18
17
|
|
|
19
18
|
logger = logging.getLogger(__name__)
|
|
20
|
-
UNKNOWN_STEP_DETAIL = "Unknown step detail"
|
|
21
19
|
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
"""Manages the lifecycle and organization of execution steps.
|
|
21
|
+
COERCION_FAILED_KEY = "_meta_coercion_failed_"
|
|
25
22
|
|
|
26
|
-
Tracks step creation, parent-child relationships, and execution state
|
|
27
|
-
with automatic pruning of old steps when limits are reached.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
def __init__(self, max_steps: int = 200) -> None:
|
|
31
|
-
"""Initialize the step manager.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
max_steps: Maximum number of steps to retain before pruning
|
|
35
|
-
"""
|
|
36
|
-
normalised_max = int(max_steps) if isinstance(max_steps, (int, float)) else 0
|
|
37
|
-
self.state = StepTreeState(max_steps=normalised_max)
|
|
38
|
-
self.by_id: dict[str, Step] = self.state.step_index
|
|
39
|
-
self.key_index: dict[tuple, str] = {}
|
|
40
|
-
self.slot_counter: dict[tuple, int] = {}
|
|
41
|
-
self.max_steps = normalised_max
|
|
42
|
-
self._last_running: dict[tuple, str] = {}
|
|
43
|
-
self._step_aliases: dict[str, str] = {}
|
|
44
|
-
self.root_agent_id: str | None = None
|
|
45
|
-
self._scope_anchors: dict[str, list[str]] = {}
|
|
46
|
-
self._step_scope_map: dict[str, str] = {}
|
|
47
|
-
|
|
48
|
-
def set_root_agent(self, agent_id: str | None) -> None:
|
|
49
|
-
"""Record the root agent identifier for scope-aware parenting."""
|
|
50
|
-
if isinstance(agent_id, str) and agent_id.strip():
|
|
51
|
-
self.root_agent_id = agent_id.strip()
|
|
52
|
-
|
|
53
|
-
def _alloc_slot(
|
|
54
|
-
self,
|
|
55
|
-
task_id: str | None,
|
|
56
|
-
context_id: str | None,
|
|
57
|
-
kind: str,
|
|
58
|
-
name: str,
|
|
59
|
-
) -> int:
|
|
60
|
-
k = (task_id, context_id, kind, name)
|
|
61
|
-
self.slot_counter[k] = self.slot_counter.get(k, 0) + 1
|
|
62
|
-
return self.slot_counter[k]
|
|
63
|
-
|
|
64
|
-
def _key(
|
|
65
|
-
self,
|
|
66
|
-
task_id: str | None,
|
|
67
|
-
context_id: str | None,
|
|
68
|
-
kind: str,
|
|
69
|
-
name: str,
|
|
70
|
-
slot: int,
|
|
71
|
-
) -> tuple[str | None, str | None, str, str, int]:
|
|
72
|
-
return (task_id, context_id, kind, name, slot)
|
|
73
|
-
|
|
74
|
-
def _make_id(
|
|
75
|
-
self,
|
|
76
|
-
task_id: str | None,
|
|
77
|
-
context_id: str | None,
|
|
78
|
-
kind: str,
|
|
79
|
-
name: str,
|
|
80
|
-
slot: int,
|
|
81
|
-
) -> str:
|
|
82
|
-
return f"{task_id or 't'}::{context_id or 'c'}::{kind}::{name}::{slot}"
|
|
83
|
-
|
|
84
|
-
def start_or_get(
|
|
85
|
-
self,
|
|
86
|
-
*,
|
|
87
|
-
task_id: str | None,
|
|
88
|
-
context_id: str | None,
|
|
89
|
-
kind: str,
|
|
90
|
-
name: str,
|
|
91
|
-
parent_id: str | None = None,
|
|
92
|
-
args: dict[str, object] | None = None,
|
|
93
|
-
) -> Step:
|
|
94
|
-
"""Start a new step or return existing running step with same parameters.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
task_id: Task identifier
|
|
98
|
-
context_id: Context identifier
|
|
99
|
-
kind: Step kind (tool, delegate, agent)
|
|
100
|
-
name: Step name
|
|
101
|
-
parent_id: Parent step ID if this is a child step
|
|
102
|
-
args: Step arguments
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
The Step instance (new or existing)
|
|
106
|
-
"""
|
|
107
|
-
existing = self.find_running(
|
|
108
|
-
task_id=task_id, context_id=context_id, kind=kind, name=name
|
|
109
|
-
)
|
|
110
|
-
if existing:
|
|
111
|
-
if args and existing.args != args:
|
|
112
|
-
existing.args = args
|
|
113
|
-
return existing
|
|
114
|
-
slot = self._alloc_slot(task_id, context_id, kind, name)
|
|
115
|
-
key = self._key(task_id, context_id, kind, name, slot)
|
|
116
|
-
step_id = self._make_id(task_id, context_id, kind, name, slot)
|
|
117
|
-
st = Step(
|
|
118
|
-
step_id=step_id,
|
|
119
|
-
kind=kind,
|
|
120
|
-
name=name,
|
|
121
|
-
parent_id=parent_id,
|
|
122
|
-
task_id=task_id,
|
|
123
|
-
context_id=context_id,
|
|
124
|
-
args=args or {},
|
|
125
|
-
)
|
|
126
|
-
self.by_id[step_id] = st
|
|
127
|
-
if parent_id:
|
|
128
|
-
self.children.setdefault(parent_id, []).append(step_id)
|
|
129
|
-
else:
|
|
130
|
-
self.order.append(step_id)
|
|
131
|
-
self.key_index[key] = step_id
|
|
132
|
-
self.state.retained_ids.add(step_id)
|
|
133
|
-
self._prune_steps()
|
|
134
|
-
self._last_running[(task_id, context_id, kind, name)] = step_id
|
|
135
|
-
return st
|
|
136
|
-
|
|
137
|
-
def _calculate_total_steps(self) -> int:
|
|
138
|
-
"""Calculate total number of steps."""
|
|
139
|
-
return len(self.order) + sum(len(v) for v in self.children.values())
|
|
140
|
-
|
|
141
|
-
def _get_subtree_size(self, root_id: str) -> int:
|
|
142
|
-
"""Get the size of a subtree (including root)."""
|
|
143
|
-
subtree = [root_id]
|
|
144
|
-
stack = list(self.children.get(root_id, []))
|
|
145
|
-
while stack:
|
|
146
|
-
x = stack.pop()
|
|
147
|
-
subtree.append(x)
|
|
148
|
-
stack.extend(self.children.get(x, []))
|
|
149
|
-
return len(subtree)
|
|
150
|
-
|
|
151
|
-
def _remove_subtree(self, root_id: str) -> None:
|
|
152
|
-
"""Remove a complete subtree from all data structures."""
|
|
153
|
-
for step_id in self._collect_subtree_ids(root_id):
|
|
154
|
-
self._purge_step_references(step_id)
|
|
155
|
-
|
|
156
|
-
def _collect_subtree_ids(self, root_id: str) -> list[str]:
|
|
157
|
-
"""Return a flat list of step ids contained within a subtree."""
|
|
158
|
-
stack = [root_id]
|
|
159
|
-
collected: list[str] = []
|
|
160
|
-
while stack:
|
|
161
|
-
sid = stack.pop()
|
|
162
|
-
collected.append(sid)
|
|
163
|
-
stack.extend(self.children.pop(sid, []))
|
|
164
|
-
return collected
|
|
165
|
-
|
|
166
|
-
def _purge_step_references(self, step_id: str) -> None:
|
|
167
|
-
"""Remove a single step id from all indexes and helper structures."""
|
|
168
|
-
st = self.by_id.pop(step_id, None)
|
|
169
|
-
if st:
|
|
170
|
-
key = (st.task_id, st.context_id, st.kind, st.name)
|
|
171
|
-
self._last_running.pop(key, None)
|
|
172
|
-
self.state.retained_ids.discard(step_id)
|
|
173
|
-
self.state.discard_running(step_id)
|
|
174
|
-
self._remove_parent_links(step_id)
|
|
175
|
-
if step_id in self.order:
|
|
176
|
-
self.order.remove(step_id)
|
|
177
|
-
self.state.buffered_children.pop(step_id, None)
|
|
178
|
-
self.state.pending_branch_failures.discard(step_id)
|
|
179
|
-
|
|
180
|
-
def _remove_parent_links(self, child_id: str) -> None:
|
|
181
|
-
"""Detach a child id from any parent lists."""
|
|
182
|
-
for parent, kids in self.children.copy().items():
|
|
183
|
-
if child_id not in kids:
|
|
184
|
-
continue
|
|
185
|
-
kids.remove(child_id)
|
|
186
|
-
if not kids:
|
|
187
|
-
self.children.pop(parent, None)
|
|
188
|
-
|
|
189
|
-
def _should_prune_steps(self, total: int) -> bool:
|
|
190
|
-
"""Check if steps should be pruned."""
|
|
191
|
-
if self.max_steps <= 0:
|
|
192
|
-
return False
|
|
193
|
-
return total > self.max_steps
|
|
194
|
-
|
|
195
|
-
def _get_oldest_step_id(self) -> str | None:
|
|
196
|
-
"""Get the oldest step ID for pruning."""
|
|
197
|
-
return self.order[0] if self.order else None
|
|
198
|
-
|
|
199
|
-
def _prune_steps(self) -> None:
|
|
200
|
-
"""Prune steps when total exceeds maximum."""
|
|
201
|
-
total = self._calculate_total_steps()
|
|
202
|
-
if not self._should_prune_steps(total):
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
while self._should_prune_steps(total) and self.order:
|
|
206
|
-
sid = self._get_oldest_step_id()
|
|
207
|
-
if not sid:
|
|
208
|
-
break
|
|
209
|
-
|
|
210
|
-
subtree_size = self._get_subtree_size(sid)
|
|
211
|
-
self._remove_subtree(sid)
|
|
212
|
-
total -= subtree_size
|
|
213
|
-
|
|
214
|
-
def remove_step(self, step_id: str) -> None:
|
|
215
|
-
"""Remove a single step from the tree and cached indexes."""
|
|
216
|
-
step = self.by_id.pop(step_id, None)
|
|
217
|
-
if not step:
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
if step.parent_id:
|
|
221
|
-
self.state.unlink_child(step.parent_id, step_id)
|
|
222
|
-
else:
|
|
223
|
-
self.state.unlink_root(step_id)
|
|
224
|
-
|
|
225
|
-
self.children.pop(step_id, None)
|
|
226
|
-
self.state.buffered_children.pop(step_id, None)
|
|
227
|
-
self.state.retained_ids.discard(step_id)
|
|
228
|
-
self.state.pending_branch_failures.discard(step_id)
|
|
229
|
-
self.state.discard_running(step_id)
|
|
230
|
-
|
|
231
|
-
self.key_index = {
|
|
232
|
-
key: sid for key, sid in self.key_index.items() if sid != step_id
|
|
233
|
-
}
|
|
234
|
-
for key, last_sid in self._last_running.copy().items():
|
|
235
|
-
if last_sid == step_id:
|
|
236
|
-
self._last_running.pop(key, None)
|
|
237
|
-
|
|
238
|
-
aliases = [
|
|
239
|
-
alias
|
|
240
|
-
for alias, target in self._step_aliases.items()
|
|
241
|
-
if alias == step_id or target == step_id
|
|
242
|
-
]
|
|
243
|
-
for alias in aliases:
|
|
244
|
-
self._step_aliases.pop(alias, None)
|
|
245
|
-
|
|
246
|
-
def get_child_count(self, step_id: str) -> int:
|
|
247
|
-
"""Get the number of child steps for a given step.
|
|
248
|
-
|
|
249
|
-
Args:
|
|
250
|
-
step_id: The parent step ID
|
|
251
23
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
"""
|
|
255
|
-
return len(self.children.get(step_id, []))
|
|
24
|
+
class StepEventMixin:
|
|
25
|
+
"""Mixin providing SSE event processing capabilities for StepManager.
|
|
256
26
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
context_id: str | None,
|
|
262
|
-
kind: str,
|
|
263
|
-
name: str,
|
|
264
|
-
) -> Step | None:
|
|
265
|
-
"""Find a currently running step with the given parameters.
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
task_id: Task identifier
|
|
269
|
-
context_id: Context identifier
|
|
270
|
-
kind: Step kind (tool, delegate, agent)
|
|
271
|
-
name: Step name
|
|
272
|
-
|
|
273
|
-
Returns:
|
|
274
|
-
The running Step if found, None otherwise
|
|
275
|
-
"""
|
|
276
|
-
key = (task_id, context_id, kind, name)
|
|
277
|
-
step_id = self._last_running.get(key)
|
|
278
|
-
if step_id:
|
|
279
|
-
st = self.by_id.get(step_id)
|
|
280
|
-
if st and st.status != "finished":
|
|
281
|
-
return st
|
|
282
|
-
for sid in reversed(list(self._iter_all_steps())):
|
|
283
|
-
st = self.by_id.get(sid)
|
|
284
|
-
if (
|
|
285
|
-
st
|
|
286
|
-
and (st.task_id, st.context_id, st.kind, st.name)
|
|
287
|
-
== (
|
|
288
|
-
task_id,
|
|
289
|
-
context_id,
|
|
290
|
-
kind,
|
|
291
|
-
name,
|
|
292
|
-
)
|
|
293
|
-
and st.status != "finished"
|
|
294
|
-
):
|
|
295
|
-
return st
|
|
296
|
-
return None
|
|
297
|
-
|
|
298
|
-
def finish(
|
|
299
|
-
self,
|
|
300
|
-
*,
|
|
301
|
-
task_id: str | None,
|
|
302
|
-
context_id: str | None,
|
|
303
|
-
kind: str,
|
|
304
|
-
name: str,
|
|
305
|
-
output: object | None = None,
|
|
306
|
-
duration_raw: float | None = None,
|
|
307
|
-
) -> Step:
|
|
308
|
-
"""Finish a step with the given parameters.
|
|
309
|
-
|
|
310
|
-
Args:
|
|
311
|
-
task_id: Task identifier
|
|
312
|
-
context_id: Context identifier
|
|
313
|
-
kind: Step kind (tool, delegate, agent)
|
|
314
|
-
name: Step name
|
|
315
|
-
output: Step output data
|
|
316
|
-
duration_raw: Raw duration in seconds
|
|
317
|
-
|
|
318
|
-
Returns:
|
|
319
|
-
The finished Step instance
|
|
320
|
-
|
|
321
|
-
Raises:
|
|
322
|
-
RuntimeError: If no matching step is found
|
|
323
|
-
"""
|
|
324
|
-
st = self.find_running(
|
|
325
|
-
task_id=task_id, context_id=context_id, kind=kind, name=name
|
|
326
|
-
)
|
|
327
|
-
if not st:
|
|
328
|
-
# Try to find any existing step with matching parameters, even if not running
|
|
329
|
-
for sid in reversed(list(self._iter_all_steps())):
|
|
330
|
-
st_check = self.by_id.get(sid)
|
|
331
|
-
if (
|
|
332
|
-
st_check
|
|
333
|
-
and st_check.task_id == task_id
|
|
334
|
-
and st_check.context_id == context_id
|
|
335
|
-
and st_check.kind == kind
|
|
336
|
-
and st_check.name == name
|
|
337
|
-
):
|
|
338
|
-
st = st_check
|
|
339
|
-
break
|
|
340
|
-
|
|
341
|
-
# If still no step found, create a new one
|
|
342
|
-
if not st:
|
|
343
|
-
st = self.start_or_get(
|
|
344
|
-
task_id=task_id, context_id=context_id, kind=kind, name=name
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
if output:
|
|
348
|
-
st.output = output
|
|
349
|
-
st.finish(duration_raw)
|
|
350
|
-
key = (task_id, context_id, kind, name)
|
|
351
|
-
if self._last_running.get(key) == st.step_id:
|
|
352
|
-
self._last_running.pop(key, None)
|
|
353
|
-
return st
|
|
354
|
-
|
|
355
|
-
def _iter_all_steps(self) -> Iterator[str]:
|
|
356
|
-
for root in self.order:
|
|
357
|
-
yield root
|
|
358
|
-
stack = list(self.children.get(root, []))
|
|
359
|
-
while stack:
|
|
360
|
-
sid = stack.pop()
|
|
361
|
-
yield sid
|
|
362
|
-
stack.extend(self.children.get(sid, []))
|
|
363
|
-
|
|
364
|
-
def iter_tree(self) -> Iterator[tuple[str, tuple[bool, ...]]]:
|
|
365
|
-
"""Expose depth-first traversal info for rendering."""
|
|
366
|
-
yield from self.state.iter_visible_tree()
|
|
367
|
-
|
|
368
|
-
@property
|
|
369
|
-
def order(self) -> list[str]:
|
|
370
|
-
"""Root step ordering accessor backed by StepTreeState."""
|
|
371
|
-
return self.state.root_order
|
|
372
|
-
|
|
373
|
-
@order.setter
|
|
374
|
-
def order(self, value: list[str]) -> None:
|
|
375
|
-
self.state.root_order = list(value)
|
|
376
|
-
|
|
377
|
-
@property
|
|
378
|
-
def children(self) -> dict[str, list[str]]:
|
|
379
|
-
"""Child mapping accessor backed by StepTreeState."""
|
|
380
|
-
return self.state.child_map
|
|
381
|
-
|
|
382
|
-
@children.setter
|
|
383
|
-
def children(self, value: dict[str, list[str]]) -> None:
|
|
384
|
-
self.state.child_map = value
|
|
385
|
-
|
|
386
|
-
# ------------------------------------------------------------------
|
|
387
|
-
# SSE-aware helpers
|
|
388
|
-
# ------------------------------------------------------------------
|
|
27
|
+
This mixin adds methods to process server-sent events (SSE) and update
|
|
28
|
+
step state accordingly. It handles event parsing, step creation/updates,
|
|
29
|
+
parent-child relationships, and duration tracking.
|
|
30
|
+
"""
|
|
389
31
|
|
|
390
32
|
def apply_event(self, event: dict[str, Any]) -> Step:
|
|
391
33
|
"""Apply an SSE step event and return the updated step."""
|
|
@@ -457,18 +99,14 @@ class StepManager:
|
|
|
457
99
|
return cloned
|
|
458
100
|
|
|
459
101
|
@staticmethod
|
|
460
|
-
def _copy_tool_call_field(
|
|
461
|
-
call: dict[str, Any], target: dict[str, Any], field: str
|
|
462
|
-
) -> None:
|
|
102
|
+
def _copy_tool_call_field(call: dict[str, Any], target: dict[str, Any], field: str) -> None:
|
|
463
103
|
"""Copy a field from the tool call when it exists."""
|
|
464
104
|
value = call.get(field)
|
|
465
105
|
if value:
|
|
466
106
|
target[field] = value
|
|
467
107
|
|
|
468
108
|
@staticmethod
|
|
469
|
-
def _derive_call_step_id(
|
|
470
|
-
call: dict[str, Any], base_step_id: str, index: int
|
|
471
|
-
) -> str:
|
|
109
|
+
def _derive_call_step_id(call: dict[str, Any], base_step_id: str, index: int) -> str:
|
|
472
110
|
"""Determine the per-call step identifier."""
|
|
473
111
|
call_id = call.get("id")
|
|
474
112
|
if isinstance(call_id, str):
|
|
@@ -479,6 +117,7 @@ class StepManager:
|
|
|
479
117
|
|
|
480
118
|
def _apply_single_event(self, event: dict[str, Any]) -> Step:
|
|
481
119
|
metadata, step_id, tool_info, args = self._parse_event_payload(event)
|
|
120
|
+
metadata_failed = bool(metadata.pop(COERCION_FAILED_KEY, False))
|
|
482
121
|
tool_name = self._resolve_tool_name(tool_info, metadata, step_id)
|
|
483
122
|
kind = self._derive_step_kind(tool_name, metadata)
|
|
484
123
|
parent_hint = self._coerce_parent_id(metadata.get("previous_step_ids"))
|
|
@@ -495,18 +134,17 @@ class StepManager:
|
|
|
495
134
|
self._link_step(step, parent_id)
|
|
496
135
|
|
|
497
136
|
self.state.retained_ids.add(step.step_id)
|
|
498
|
-
|
|
499
|
-
step.
|
|
500
|
-
|
|
137
|
+
if metadata_failed:
|
|
138
|
+
step.metadata = {}
|
|
139
|
+
else:
|
|
140
|
+
step.metadata = dict(metadata)
|
|
501
141
|
self._flush_buffered_children(step.step_id)
|
|
502
142
|
self._apply_pending_branch_flags(step.step_id)
|
|
503
143
|
|
|
504
|
-
status = self._normalise_status(
|
|
505
|
-
metadata.get("status"), event.get("status"), event.get("task_state")
|
|
506
|
-
)
|
|
144
|
+
status = self._normalise_status(metadata.get("status"), event.get("status"), event.get("task_state"))
|
|
507
145
|
status = self._apply_failure_state(step, status, event)
|
|
508
146
|
|
|
509
|
-
server_time =
|
|
147
|
+
server_time = coerce_server_time(metadata.get("time"))
|
|
510
148
|
self._update_server_timestamps(step, server_time, status)
|
|
511
149
|
|
|
512
150
|
self._apply_duration(
|
|
@@ -524,18 +162,17 @@ class StepManager:
|
|
|
524
162
|
status=status,
|
|
525
163
|
)
|
|
526
164
|
|
|
527
|
-
step.status_icon = self._status_icon_for_step(step)
|
|
528
165
|
self._update_parallel_tracking(step)
|
|
529
166
|
self._update_running_index(step)
|
|
530
167
|
self._prune_steps()
|
|
531
168
|
return step
|
|
532
169
|
|
|
533
|
-
def _parse_event_payload(
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if not isinstance(metadata, dict):
|
|
170
|
+
def _parse_event_payload(self, event: dict[str, Any]) -> tuple[dict[str, Any], str, dict[str, Any], dict[str, Any]]:
|
|
171
|
+
metadata_raw = event.get("metadata") or {}
|
|
172
|
+
metadata, metadata_reliable = self._coerce_event_metadata(metadata_raw)
|
|
173
|
+
if not metadata:
|
|
538
174
|
raise ValueError("Step event missing metadata payload")
|
|
175
|
+
metadata[COERCION_FAILED_KEY] = not metadata_reliable
|
|
539
176
|
|
|
540
177
|
step_id = metadata.get("step_id")
|
|
541
178
|
if not isinstance(step_id, str) or not step_id:
|
|
@@ -553,9 +190,40 @@ class StepManager:
|
|
|
553
190
|
|
|
554
191
|
return metadata, step_id, tool_info, args
|
|
555
192
|
|
|
556
|
-
def
|
|
557
|
-
|
|
558
|
-
|
|
193
|
+
def _coerce_event_metadata(self, metadata: Any) -> tuple[dict[str, Any], bool]:
|
|
194
|
+
"""Return a dict copy of event metadata with graceful fallbacks."""
|
|
195
|
+
if isinstance(metadata, dict):
|
|
196
|
+
return metadata, True
|
|
197
|
+
if isinstance(metadata, Mapping):
|
|
198
|
+
try:
|
|
199
|
+
return dict(metadata), True
|
|
200
|
+
except Exception:
|
|
201
|
+
logger.debug("Failed to coerce mapping metadata; falling back to key-by-key copy", exc_info=True)
|
|
202
|
+
return self._copy_mapping_fields(metadata), False
|
|
203
|
+
# All other payloads are treated as empty
|
|
204
|
+
return {}, False
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def _copy_mapping_fields(metadata: Mapping[str, Any]) -> dict[str, Any]:
|
|
208
|
+
"""Copy known fields from a mapping without iteration."""
|
|
209
|
+
copied: dict[str, Any] = {}
|
|
210
|
+
for key in (
|
|
211
|
+
"step_id",
|
|
212
|
+
"tool_info",
|
|
213
|
+
"status",
|
|
214
|
+
"kind",
|
|
215
|
+
"previous_step_ids",
|
|
216
|
+
"time",
|
|
217
|
+
"agent_name",
|
|
218
|
+
"task_id",
|
|
219
|
+
"context_id",
|
|
220
|
+
):
|
|
221
|
+
value = metadata.get(key) # type: ignore[attr-defined]
|
|
222
|
+
if value is not None:
|
|
223
|
+
copied[key] = value
|
|
224
|
+
return copied
|
|
225
|
+
|
|
226
|
+
def _resolve_tool_name(self, tool_info: dict[str, Any], metadata: dict[str, Any], step_id: str) -> str:
|
|
559
227
|
name = tool_info.get("name")
|
|
560
228
|
if not name:
|
|
561
229
|
call = self._first_tool_call(tool_info)
|
|
@@ -601,12 +269,8 @@ class StepManager:
|
|
|
601
269
|
) -> Step:
|
|
602
270
|
existing = self.by_id.get(step_id)
|
|
603
271
|
if existing:
|
|
604
|
-
return self._update_existing_step(
|
|
605
|
-
|
|
606
|
-
)
|
|
607
|
-
return self._create_step_from_event(
|
|
608
|
-
step_id, kind, tool_name, event, metadata, args
|
|
609
|
-
)
|
|
272
|
+
return self._update_existing_step(existing, kind, tool_name, event, metadata, args)
|
|
273
|
+
return self._create_step_from_event(step_id, kind, tool_name, event, metadata, args)
|
|
610
274
|
|
|
611
275
|
def _create_step_from_event(
|
|
612
276
|
self,
|
|
@@ -621,12 +285,8 @@ class StepManager:
|
|
|
621
285
|
step_id=step_id,
|
|
622
286
|
kind=kind,
|
|
623
287
|
name=tool_name or step_id,
|
|
624
|
-
task_id=self._coalesce_metadata_value(
|
|
625
|
-
|
|
626
|
-
),
|
|
627
|
-
context_id=self._coalesce_metadata_value(
|
|
628
|
-
"context_id", event, metadata, fallback=None
|
|
629
|
-
),
|
|
288
|
+
task_id=self._coalesce_metadata_value("task_id", event, metadata, fallback=None),
|
|
289
|
+
context_id=self._coalesce_metadata_value("context_id", event, metadata, fallback=None),
|
|
630
290
|
args=args or {},
|
|
631
291
|
)
|
|
632
292
|
self.by_id[step_id] = step
|
|
@@ -646,20 +306,12 @@ class StepManager:
|
|
|
646
306
|
step.name = tool_name or step.name
|
|
647
307
|
if args:
|
|
648
308
|
step.args = args
|
|
649
|
-
step.task_id = self._coalesce_metadata_value(
|
|
650
|
-
|
|
651
|
-
)
|
|
652
|
-
step.context_id = self._coalesce_metadata_value(
|
|
653
|
-
"context_id", event, metadata, fallback=step.context_id
|
|
654
|
-
)
|
|
309
|
+
step.task_id = self._coalesce_metadata_value("task_id", event, metadata, fallback=step.task_id)
|
|
310
|
+
step.context_id = self._coalesce_metadata_value("context_id", event, metadata, fallback=step.context_id)
|
|
655
311
|
return step
|
|
656
312
|
|
|
657
|
-
def _apply_failure_state(
|
|
658
|
-
|
|
659
|
-
) -> str:
|
|
660
|
-
failure_reason = self._extract_failure_reason(
|
|
661
|
-
status, event.get("task_state"), event.get("content")
|
|
662
|
-
)
|
|
313
|
+
def _apply_failure_state(self, step: Step, status: str, event: dict[str, Any]) -> str:
|
|
314
|
+
failure_reason = self._extract_failure_reason(status, event.get("task_state"), event.get("content"))
|
|
663
315
|
if not failure_reason:
|
|
664
316
|
step.status = status
|
|
665
317
|
return status
|
|
@@ -679,9 +331,7 @@ class StepManager:
|
|
|
679
331
|
args: dict[str, Any],
|
|
680
332
|
server_time: float | None,
|
|
681
333
|
) -> None:
|
|
682
|
-
duration_ms, duration_source = self._resolve_duration_from_event(
|
|
683
|
-
tool_info, args
|
|
684
|
-
)
|
|
334
|
+
duration_ms, duration_source = self._resolve_duration_from_event(tool_info, args)
|
|
685
335
|
if duration_ms is not None:
|
|
686
336
|
step.duration_ms = duration_ms
|
|
687
337
|
step.duration_source = duration_source
|
|
@@ -750,6 +400,8 @@ class StepManager:
|
|
|
750
400
|
return "delegate"
|
|
751
401
|
if tool.startswith("agent_"):
|
|
752
402
|
return "agent"
|
|
403
|
+
if kind == "agent_step":
|
|
404
|
+
return "tool" if tool else "agent_step"
|
|
753
405
|
return kind or "tool"
|
|
754
406
|
|
|
755
407
|
def _clean_kind(self, metadata_kind: Any) -> str:
|
|
@@ -763,9 +415,7 @@ class StepManager:
|
|
|
763
415
|
def _is_delegate_tool(self, tool: str) -> bool:
|
|
764
416
|
return tool.startswith(("delegate_to_", "delegate-", "delegate ", "delegate_"))
|
|
765
417
|
|
|
766
|
-
def _is_top_level_agent(
|
|
767
|
-
self, tool_name: str | None, metadata: dict[str, Any], kind: str
|
|
768
|
-
) -> bool:
|
|
418
|
+
def _is_top_level_agent(self, tool_name: str | None, metadata: dict[str, Any], kind: str) -> bool:
|
|
769
419
|
if kind != "agent_step":
|
|
770
420
|
return False
|
|
771
421
|
agent_name = metadata.get("agent_name")
|
|
@@ -780,65 +430,6 @@ class StepManager:
|
|
|
780
430
|
return False
|
|
781
431
|
return all(ch in "0123456789abcdefABCDEF" for ch in stripped)
|
|
782
432
|
|
|
783
|
-
def _step_icon_for_kind(self, step_kind: str) -> str:
|
|
784
|
-
if step_kind == "agent":
|
|
785
|
-
return ICON_AGENT_STEP
|
|
786
|
-
if step_kind == "delegate":
|
|
787
|
-
return ICON_DELEGATE
|
|
788
|
-
if step_kind == "thinking":
|
|
789
|
-
return "💭"
|
|
790
|
-
return ICON_TOOL_STEP
|
|
791
|
-
|
|
792
|
-
def _humanize_tool_name(self, raw_name: str | None) -> str:
|
|
793
|
-
if not raw_name:
|
|
794
|
-
return UNKNOWN_STEP_DETAIL
|
|
795
|
-
name = raw_name
|
|
796
|
-
if name.startswith("delegate_to_"):
|
|
797
|
-
name = name.removeprefix("delegate_to_")
|
|
798
|
-
elif name.startswith("delegate_"):
|
|
799
|
-
name = name.removeprefix("delegate_")
|
|
800
|
-
cleaned = name.replace("_", " ").replace("-", " ").strip()
|
|
801
|
-
if not cleaned:
|
|
802
|
-
return UNKNOWN_STEP_DETAIL
|
|
803
|
-
return cleaned[:1].upper() + cleaned[1:]
|
|
804
|
-
|
|
805
|
-
def _compose_display_label(
|
|
806
|
-
self,
|
|
807
|
-
step_kind: str,
|
|
808
|
-
tool_name: str | None,
|
|
809
|
-
args: dict[str, Any],
|
|
810
|
-
metadata: dict[str, Any],
|
|
811
|
-
) -> str:
|
|
812
|
-
icon = self._step_icon_for_kind(step_kind)
|
|
813
|
-
body = self._resolve_label_body(step_kind, tool_name, metadata)
|
|
814
|
-
label = f"{icon} {body}".strip()
|
|
815
|
-
if isinstance(args, dict) and args:
|
|
816
|
-
label = f"{label} —"
|
|
817
|
-
return label or UNKNOWN_STEP_DETAIL
|
|
818
|
-
|
|
819
|
-
def _resolve_label_body(
|
|
820
|
-
self,
|
|
821
|
-
step_kind: str,
|
|
822
|
-
tool_name: str | None,
|
|
823
|
-
metadata: dict[str, Any],
|
|
824
|
-
) -> str:
|
|
825
|
-
if step_kind == "thinking":
|
|
826
|
-
thinking_text = metadata.get("thinking_and_activity_info")
|
|
827
|
-
if isinstance(thinking_text, str) and thinking_text.strip():
|
|
828
|
-
return thinking_text.strip()
|
|
829
|
-
return "Thinking…"
|
|
830
|
-
|
|
831
|
-
if step_kind == "delegate":
|
|
832
|
-
return self._humanize_tool_name(tool_name)
|
|
833
|
-
|
|
834
|
-
if step_kind == "agent":
|
|
835
|
-
agent_name = metadata.get("agent_name")
|
|
836
|
-
if isinstance(agent_name, str) and agent_name.strip():
|
|
837
|
-
return agent_name.strip()
|
|
838
|
-
|
|
839
|
-
friendly = self._humanize_tool_name(tool_name)
|
|
840
|
-
return friendly
|
|
841
|
-
|
|
842
433
|
def _normalise_status(
|
|
843
434
|
self,
|
|
844
435
|
metadata_status: Any,
|
|
@@ -864,9 +455,7 @@ class StepManager:
|
|
|
864
455
|
content: Any,
|
|
865
456
|
) -> str | None:
|
|
866
457
|
failure_states = {"failed", "stopped", "error"}
|
|
867
|
-
task_state_str = (
|
|
868
|
-
(task_state or "").lower() if isinstance(task_state, str) else ""
|
|
869
|
-
)
|
|
458
|
+
task_state_str = (task_state or "").lower() if isinstance(task_state, str) else ""
|
|
870
459
|
if status in failure_states or task_state_str in failure_states:
|
|
871
460
|
if isinstance(content, str) and content.strip():
|
|
872
461
|
return content.strip()
|
|
@@ -893,15 +482,11 @@ class StepManager:
|
|
|
893
482
|
|
|
894
483
|
return None, None
|
|
895
484
|
|
|
896
|
-
def _determine_parent_id(
|
|
897
|
-
self, step: Step, metadata: dict[str, Any], parent_hint: str | None
|
|
898
|
-
) -> str | None:
|
|
485
|
+
def _determine_parent_id(self, step: Step, metadata: dict[str, Any], parent_hint: str | None) -> str | None:
|
|
899
486
|
scope_parent = self._lookup_scope_parent(metadata, step)
|
|
900
487
|
candidate = scope_parent or parent_hint
|
|
901
488
|
if candidate == step.step_id:
|
|
902
|
-
logger.debug(
|
|
903
|
-
"Step %s cannot parent itself; dropping parent hint", candidate
|
|
904
|
-
)
|
|
489
|
+
logger.debug("Step %s cannot parent itself; dropping parent hint", candidate)
|
|
905
490
|
return None
|
|
906
491
|
return candidate
|
|
907
492
|
|
|
@@ -926,9 +511,7 @@ class StepManager:
|
|
|
926
511
|
self._detach_from_current_parent(step)
|
|
927
512
|
self._attach_to_parent(step, parent_id)
|
|
928
513
|
|
|
929
|
-
def _sanitize_parent_reference(
|
|
930
|
-
self, step: Step, parent_id: str | None
|
|
931
|
-
) -> str | None:
|
|
514
|
+
def _sanitize_parent_reference(self, step: Step, parent_id: str | None) -> str | None:
|
|
932
515
|
"""Guard against self-referential parent assignments."""
|
|
933
516
|
if parent_id != step.step_id:
|
|
934
517
|
return parent_id
|
|
@@ -1044,18 +627,7 @@ class StepManager:
|
|
|
1044
627
|
slug = slug.replace("-", "_").strip()
|
|
1045
628
|
return slug or None
|
|
1046
629
|
|
|
1047
|
-
|
|
1048
|
-
def _coerce_server_time(value: Any) -> float | None:
|
|
1049
|
-
if isinstance(value, (int, float)):
|
|
1050
|
-
return float(value)
|
|
1051
|
-
try:
|
|
1052
|
-
return float(value)
|
|
1053
|
-
except (TypeError, ValueError):
|
|
1054
|
-
return None
|
|
1055
|
-
|
|
1056
|
-
def _update_server_timestamps(
|
|
1057
|
-
self, step: Step, server_time: float | None, status: str
|
|
1058
|
-
) -> None:
|
|
630
|
+
def _update_server_timestamps(self, step: Step, server_time: float | None, status: str) -> None:
|
|
1059
631
|
if server_time is None:
|
|
1060
632
|
return
|
|
1061
633
|
if status == "running" and step.server_started_at is None:
|
|
@@ -1065,9 +637,7 @@ class StepManager:
|
|
|
1065
637
|
if step.server_started_at is None:
|
|
1066
638
|
step.server_started_at = server_time
|
|
1067
639
|
|
|
1068
|
-
def _calculate_server_duration(
|
|
1069
|
-
self, step: Step, server_time: float | None
|
|
1070
|
-
) -> int | None:
|
|
640
|
+
def _calculate_server_duration(self, step: Step, server_time: float | None) -> int | None:
|
|
1071
641
|
start = step.server_started_at
|
|
1072
642
|
end = server_time if server_time is not None else step.server_finished_at
|
|
1073
643
|
if start is None or end is None:
|
|
@@ -1092,7 +662,6 @@ class StepManager:
|
|
|
1092
662
|
step = self.by_id.get(step_id)
|
|
1093
663
|
if step:
|
|
1094
664
|
step.branch_failed = True
|
|
1095
|
-
step.status_icon = "warning"
|
|
1096
665
|
self.state.pending_branch_failures.discard(step_id)
|
|
1097
666
|
|
|
1098
667
|
def _set_branch_warning(self, parent_id: str | None) -> None:
|
|
@@ -1101,7 +670,6 @@ class StepManager:
|
|
|
1101
670
|
parent = self.by_id.get(parent_id)
|
|
1102
671
|
if parent:
|
|
1103
672
|
parent.branch_failed = True
|
|
1104
|
-
parent.status_icon = "warning"
|
|
1105
673
|
else:
|
|
1106
674
|
self.state.pending_branch_failures.add(parent_id)
|
|
1107
675
|
|
|
@@ -1133,15 +701,6 @@ class StepManager:
|
|
|
1133
701
|
if current:
|
|
1134
702
|
current.is_parallel = is_parallel
|
|
1135
703
|
|
|
1136
|
-
def _status_icon_for_step(self, step: Step) -> str:
|
|
1137
|
-
if step.status == "finished":
|
|
1138
|
-
return "warning" if step.branch_failed else "success"
|
|
1139
|
-
if step.status == "failed":
|
|
1140
|
-
return "failed"
|
|
1141
|
-
if step.status == "stopped":
|
|
1142
|
-
return "warning"
|
|
1143
|
-
return "spinner"
|
|
1144
|
-
|
|
1145
704
|
def _canonicalize_step_id(self, step_id: str, tool_info: dict[str, Any]) -> str:
|
|
1146
705
|
alias = self._lookup_alias(step_id)
|
|
1147
706
|
if alias:
|