zjcode 0.0.1__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 (163) hide show
  1. deepagents_code/__init__.py +42 -0
  2. deepagents_code/__main__.py +6 -0
  3. deepagents_code/_ask_user_types.py +90 -0
  4. deepagents_code/_cli_context.py +96 -0
  5. deepagents_code/_constants.py +41 -0
  6. deepagents_code/_debug.py +148 -0
  7. deepagents_code/_debug_buffer.py +204 -0
  8. deepagents_code/_env_vars.py +411 -0
  9. deepagents_code/_fake_models.py +66 -0
  10. deepagents_code/_git.py +521 -0
  11. deepagents_code/_paths.py +69 -0
  12. deepagents_code/_server_config.py +576 -0
  13. deepagents_code/_session_stats.py +235 -0
  14. deepagents_code/_startup_error.py +45 -0
  15. deepagents_code/_testing_models.py +305 -0
  16. deepagents_code/_textual_patches.py +420 -0
  17. deepagents_code/_tool_stream.py +694 -0
  18. deepagents_code/_version.py +46 -0
  19. deepagents_code/agent.py +1976 -0
  20. deepagents_code/app.py +19239 -0
  21. deepagents_code/app.tcss +438 -0
  22. deepagents_code/approval_mode.py +131 -0
  23. deepagents_code/ask_user.py +306 -0
  24. deepagents_code/auth_display.py +185 -0
  25. deepagents_code/auth_store.py +545 -0
  26. deepagents_code/built_in_skills/__init__.py +5 -0
  27. deepagents_code/built_in_skills/remember/SKILL.md +118 -0
  28. deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
  29. deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
  30. deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
  31. deepagents_code/client/__init__.py +1 -0
  32. deepagents_code/client/commands/__init__.py +1 -0
  33. deepagents_code/client/commands/auth.py +541 -0
  34. deepagents_code/client/commands/config.py +714 -0
  35. deepagents_code/client/commands/mcp.py +250 -0
  36. deepagents_code/client/commands/tools.py +416 -0
  37. deepagents_code/client/launch/__init__.py +1 -0
  38. deepagents_code/client/launch/server.py +978 -0
  39. deepagents_code/client/launch/server_manager.py +540 -0
  40. deepagents_code/client/non_interactive.py +1758 -0
  41. deepagents_code/client/remote_client.py +794 -0
  42. deepagents_code/clipboard.py +217 -0
  43. deepagents_code/command_registry.py +450 -0
  44. deepagents_code/config.py +4829 -0
  45. deepagents_code/config_manifest.py +1451 -0
  46. deepagents_code/configurable_model.py +577 -0
  47. deepagents_code/default_agent_prompt.md +12 -0
  48. deepagents_code/doctor.py +559 -0
  49. deepagents_code/editor.py +142 -0
  50. deepagents_code/event_bus.py +411 -0
  51. deepagents_code/extras_info.py +661 -0
  52. deepagents_code/file_ops.py +576 -0
  53. deepagents_code/formatting.py +135 -0
  54. deepagents_code/goal_rubric.py +497 -0
  55. deepagents_code/goal_tools.py +459 -0
  56. deepagents_code/hooks.py +348 -0
  57. deepagents_code/input.py +1041 -0
  58. deepagents_code/integrations/__init__.py +1 -0
  59. deepagents_code/integrations/openai_codex.py +551 -0
  60. deepagents_code/integrations/sandbox_config.py +198 -0
  61. deepagents_code/integrations/sandbox_factory.py +1124 -0
  62. deepagents_code/integrations/sandbox_provider.py +137 -0
  63. deepagents_code/integrations/sandbox_registry.py +350 -0
  64. deepagents_code/iterm_cursor_guide.py +176 -0
  65. deepagents_code/local_context.py +926 -0
  66. deepagents_code/main.py +3985 -0
  67. deepagents_code/managed_tools.py +642 -0
  68. deepagents_code/mcp_auth.py +1950 -0
  69. deepagents_code/mcp_config.py +176 -0
  70. deepagents_code/mcp_disabled.py +212 -0
  71. deepagents_code/mcp_login_service.py +281 -0
  72. deepagents_code/mcp_oauth_ui.py +199 -0
  73. deepagents_code/mcp_providers/__init__.py +23 -0
  74. deepagents_code/mcp_providers/_registry.py +39 -0
  75. deepagents_code/mcp_providers/base.py +133 -0
  76. deepagents_code/mcp_providers/github.py +102 -0
  77. deepagents_code/mcp_providers/slack.py +175 -0
  78. deepagents_code/mcp_tools.py +2427 -0
  79. deepagents_code/mcp_trust.py +207 -0
  80. deepagents_code/media_utils.py +826 -0
  81. deepagents_code/memory_guard.py +474 -0
  82. deepagents_code/model_config.py +4156 -0
  83. deepagents_code/notifications.py +247 -0
  84. deepagents_code/offload.py +402 -0
  85. deepagents_code/onboarding.py +223 -0
  86. deepagents_code/output.py +69 -0
  87. deepagents_code/paste_collapse.py +103 -0
  88. deepagents_code/project_utils.py +231 -0
  89. deepagents_code/py.typed +0 -0
  90. deepagents_code/reasoning_effort.py +641 -0
  91. deepagents_code/reliable_rubric.py +97 -0
  92. deepagents_code/resume_state.py +237 -0
  93. deepagents_code/server_graph.py +310 -0
  94. deepagents_code/session_end_summary.py +329 -0
  95. deepagents_code/sessions.py +1716 -0
  96. deepagents_code/skills/__init__.py +18 -0
  97. deepagents_code/skills/commands.py +1252 -0
  98. deepagents_code/skills/invocation.py +112 -0
  99. deepagents_code/skills/load.py +196 -0
  100. deepagents_code/skills/trust.py +547 -0
  101. deepagents_code/state_migration.py +136 -0
  102. deepagents_code/subagents.py +278 -0
  103. deepagents_code/system_prompt.md +204 -0
  104. deepagents_code/terminal_capabilities.py +115 -0
  105. deepagents_code/terminal_escape.py +287 -0
  106. deepagents_code/theme.py +891 -0
  107. deepagents_code/todo_list_prompt.md +12 -0
  108. deepagents_code/tool_catalog.py +509 -0
  109. deepagents_code/tool_display.py +367 -0
  110. deepagents_code/tools.py +516 -0
  111. deepagents_code/tui/__init__.py +1 -0
  112. deepagents_code/tui/textual_adapter.py +2553 -0
  113. deepagents_code/tui/widgets/__init__.py +9 -0
  114. deepagents_code/tui/widgets/_js_eval_display.py +139 -0
  115. deepagents_code/tui/widgets/_links.py +261 -0
  116. deepagents_code/tui/widgets/agent_selector.py +420 -0
  117. deepagents_code/tui/widgets/approval.py +602 -0
  118. deepagents_code/tui/widgets/ask_user.py +515 -0
  119. deepagents_code/tui/widgets/auth.py +1997 -0
  120. deepagents_code/tui/widgets/autocomplete.py +890 -0
  121. deepagents_code/tui/widgets/chat_input.py +3181 -0
  122. deepagents_code/tui/widgets/codex_auth.py +452 -0
  123. deepagents_code/tui/widgets/cwd_switch.py +242 -0
  124. deepagents_code/tui/widgets/debug_console.py +868 -0
  125. deepagents_code/tui/widgets/diff.py +252 -0
  126. deepagents_code/tui/widgets/effort_selector.py +189 -0
  127. deepagents_code/tui/widgets/goal_review.py +390 -0
  128. deepagents_code/tui/widgets/goal_status.py +50 -0
  129. deepagents_code/tui/widgets/history.py +194 -0
  130. deepagents_code/tui/widgets/install_confirm.py +248 -0
  131. deepagents_code/tui/widgets/launch_init.py +482 -0
  132. deepagents_code/tui/widgets/loading.py +227 -0
  133. deepagents_code/tui/widgets/mcp_login.py +539 -0
  134. deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
  135. deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
  136. deepagents_code/tui/widgets/message_store.py +999 -0
  137. deepagents_code/tui/widgets/messages.py +3846 -0
  138. deepagents_code/tui/widgets/model_selector.py +2343 -0
  139. deepagents_code/tui/widgets/notification_center.py +456 -0
  140. deepagents_code/tui/widgets/notification_detail.py +257 -0
  141. deepagents_code/tui/widgets/notification_settings.py +170 -0
  142. deepagents_code/tui/widgets/restart_prompt.py +158 -0
  143. deepagents_code/tui/widgets/skill_trust.py +131 -0
  144. deepagents_code/tui/widgets/startup_tip.py +91 -0
  145. deepagents_code/tui/widgets/status.py +781 -0
  146. deepagents_code/tui/widgets/subagent_panel.py +969 -0
  147. deepagents_code/tui/widgets/theme_selector.py +401 -0
  148. deepagents_code/tui/widgets/thread_selector.py +2564 -0
  149. deepagents_code/tui/widgets/tool_renderers.py +190 -0
  150. deepagents_code/tui/widgets/tool_widgets.py +304 -0
  151. deepagents_code/tui/widgets/update_available.py +311 -0
  152. deepagents_code/tui/widgets/update_confirm.py +184 -0
  153. deepagents_code/tui/widgets/update_progress.py +327 -0
  154. deepagents_code/tui/widgets/welcome.py +508 -0
  155. deepagents_code/turn_end_summary.py +522 -0
  156. deepagents_code/ui.py +858 -0
  157. deepagents_code/unicode_security.py +563 -0
  158. deepagents_code/update_check.py +3124 -0
  159. zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
  160. zjcode-0.0.1.dist-info/METADATA +220 -0
  161. zjcode-0.0.1.dist-info/RECORD +163 -0
  162. zjcode-0.0.1.dist-info/WHEEL +4 -0
  163. zjcode-0.0.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,459 @@
1
+ """Goal tools exposed to the agent for persisted TUI goals."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Annotated,
8
+ Any,
9
+ Literal,
10
+ NotRequired,
11
+ TypedDict,
12
+ TypeVar,
13
+ cast,
14
+ )
15
+
16
+ from langchain.agents.middleware.types import (
17
+ AgentMiddleware,
18
+ ContextT,
19
+ ModelRequest,
20
+ ModelResponse,
21
+ )
22
+ from langchain_core.messages import SystemMessage, ToolMessage
23
+ from langchain_core.tools import InjectedToolCallId, tool
24
+ from langgraph.prebuilt import InjectedState
25
+ from langgraph.types import Command
26
+ from typing_extensions import override
27
+
28
+ # Runtime (not TYPE_CHECKING) imports. `GoalRubricChannels` supplies the shared
29
+ # `PrivateStateAttr`-marked goal/rubric channels that `GoalToolState` extends, so
30
+ # the markers are declared once (see that class). `coerce_goal_status` is used at
31
+ # runtime by `_goal_snapshot`; `GoalStatus` types its result and snapshot fields.
32
+ from deepagents_code.resume_state import (
33
+ GoalRubricChannels,
34
+ GoalStatus,
35
+ coerce_goal_status,
36
+ )
37
+
38
+ if TYPE_CHECKING:
39
+ from collections.abc import Awaitable, Callable
40
+
41
+ RubricSource = Literal["goal", "sticky", "invocation"]
42
+ """Where the active rubric criteria came from, as reported to the model."""
43
+
44
+ GOAL_TOOLS_SYSTEM_PROMPT = """## Goal and Rubric Tools
45
+
46
+ Use `get_rubric` to inspect active acceptance criteria before deciding whether work is
47
+ complete.
48
+ When a goal is active, use `get_goal` to inspect the objective and current status.
49
+ A paused goal is persisted for later but must not drive work until the user resumes it.
50
+ Use `update_goal` only when you have evidence that the goal is complete or blocked."""
51
+ """Model-visible guidance injected before each request by `GoalToolsMiddleware`."""
52
+
53
+ ResponseT = TypeVar("ResponseT")
54
+
55
+
56
+ def _runtime_blocked_goal_retry_context(ctx: object) -> str | None:
57
+ """Return blocked-goal retry context from LangGraph runtime context."""
58
+ if isinstance(ctx, dict):
59
+ value = ctx.get("blocked_goal_retry_context")
60
+ else:
61
+ value = getattr(ctx, "blocked_goal_retry_context", None)
62
+ return value if isinstance(value, str) and value else None
63
+
64
+
65
+ class RubricSnapshot(TypedDict):
66
+ """Read-only rubric view returned by the `get_rubric` tool to the model."""
67
+
68
+ active: bool
69
+ """Whether acceptance criteria are currently available."""
70
+
71
+ criteria: str | None
72
+ """Current acceptance criteria, or `None` when no rubric is set."""
73
+
74
+ source: RubricSource | None
75
+ """Where the criteria came from: `goal`, `sticky`, `invocation`, or `None`."""
76
+
77
+ grading_status: str | None
78
+ """Latest `RubricMiddleware` grading status for the in-progress or
79
+ just-completed graded turn, or `None`.
80
+
81
+ The middleware clears this at the start of the next graded turn, so
82
+ a `None` does not imply grading never ran.
83
+ """
84
+
85
+
86
+ class GoalSnapshot(TypedDict):
87
+ """Read-only goal view returned by the `get_goal` tool to the model.
88
+
89
+ A fixed-shape projection of goal state. Both construction branches in
90
+ `_goal_snapshot` must populate every key, so the type checker catches a
91
+ drift between them.
92
+ """
93
+
94
+ active: bool
95
+ """Whether the goal is actionable (should drive work).
96
+
97
+ Derived from `status`: `active` and `blocked` goals are actionable, while
98
+ `paused` and `complete` goals are not. Note a `paused` goal is unfinished
99
+ yet reports `active=False`. `False` when no goal is set (the
100
+ `objective is None` branch), where `status` is also `None`.
101
+ """
102
+
103
+ objective: str | None
104
+ """Active goal objective, or `None` when no goal is set."""
105
+
106
+ status: GoalStatus | None
107
+ """Lifecycle status, or `None` when no goal is set.
108
+
109
+ A set-but-unlabeled or unrecognized persisted value is normalized to
110
+ `"active"` by `coerce_goal_status`, so this is always a known `GoalStatus`
111
+ when a goal is set.
112
+ """
113
+
114
+ criteria: str | None
115
+ """Persisted goal criteria, or shared rubric criteria when no goal rubric exists."""
116
+
117
+ note: str | None
118
+ """Latest evidence or blocker note recorded by `update_goal`."""
119
+
120
+
121
+ class GoalToolState(GoalRubricChannels):
122
+ """State fields used by goal tools.
123
+
124
+ Inherits the shared `_goal_*`/`_sticky_rubric` channels (with their
125
+ `PrivateStateAttr` markers) from `GoalRubricChannels`, so the goal tools and
126
+ `ResumeState` cannot drift apart. Adds only the public `rubric` graph input,
127
+ which is intentionally non-private — it is the `RubricMiddleware` input.
128
+ """
129
+
130
+ rubric: NotRequired[str | None]
131
+ """Public `RubricMiddleware` graph input (intentionally non-private).
132
+
133
+ Distinct from the TUI-owned `_sticky_rubric`: this is the per-invocation
134
+ rubric passed in via the graph schema, not checkpointed TUI state.
135
+ """
136
+
137
+
138
+ def _clean_state_text(state: dict[str, Any], key: str) -> str | None:
139
+ """Return a non-empty string from state, or `None`."""
140
+ value = state.get(key)
141
+ if not isinstance(value, str):
142
+ return None
143
+ value = value.strip()
144
+ return value or None
145
+
146
+
147
+ def _rubric_snapshot(state: dict[str, Any]) -> RubricSnapshot:
148
+ """Build the `get_rubric` response from graph state.
149
+
150
+ Args:
151
+ state: Current graph state injected by LangGraph.
152
+
153
+ Returns:
154
+ Rubric snapshot visible to the model.
155
+ """
156
+ criteria = _clean_state_text(state, "rubric")
157
+ goal_rubric = _clean_state_text(state, "_goal_rubric")
158
+ sticky_rubric = _clean_state_text(state, "_sticky_rubric")
159
+ objective = _clean_state_text(state, "_goal_objective")
160
+ status = coerce_goal_status(state.get("_goal_status")) or "active"
161
+ goal_is_actionable = objective is not None and status in {"active", "blocked"}
162
+ sticky_is_goal_rubric = objective is not None and sticky_rubric == goal_rubric
163
+
164
+ source: RubricSource | None = None
165
+ if criteria is not None:
166
+ if goal_is_actionable and goal_rubric == criteria:
167
+ source = "goal"
168
+ elif sticky_rubric == criteria and not sticky_is_goal_rubric:
169
+ source = "sticky"
170
+ else:
171
+ source = "invocation"
172
+ # Fallback branches below run only when there is no public `rubric` input,
173
+ # so `invocation` is unreachable here by construction — the criteria can
174
+ # only be attributed to an actionable `goal` or a standalone `sticky` rubric.
175
+ elif goal_is_actionable and goal_rubric is not None:
176
+ criteria = goal_rubric
177
+ source = "goal"
178
+ elif sticky_rubric is not None and not sticky_is_goal_rubric:
179
+ criteria = sticky_rubric
180
+ source = "sticky"
181
+
182
+ # `_rubric_status` is owned by the SDK's `RubricMiddleware`, co-composed into
183
+ # this agent's graph; see the `grading_status` field docstring above.
184
+ grading_status = _clean_state_text(state, "_rubric_status")
185
+ return {
186
+ "active": criteria is not None,
187
+ "criteria": criteria,
188
+ "source": source,
189
+ "grading_status": grading_status,
190
+ }
191
+
192
+
193
+ def _goal_snapshot(state: dict[str, Any]) -> GoalSnapshot:
194
+ """Build the `get_goal` response from graph state.
195
+
196
+ Args:
197
+ state: Current graph state injected by LangGraph.
198
+
199
+ Returns:
200
+ Goal snapshot visible to the model.
201
+ """
202
+ objective = _clean_state_text(state, "_goal_objective")
203
+ rubric = _rubric_snapshot(state)
204
+ if objective is None:
205
+ return {
206
+ "active": False,
207
+ "objective": None,
208
+ "status": None,
209
+ "criteria": rubric["criteria"],
210
+ "note": None,
211
+ }
212
+ # A set-but-unlabeled or unrecognized status defaults to "active"; an
213
+ # unknown persisted value never leaks to the model as a bogus status.
214
+ status: GoalStatus = coerce_goal_status(state.get("_goal_status")) or "active"
215
+ criteria = _clean_state_text(state, "_goal_rubric") or rubric["criteria"]
216
+ note = _clean_state_text(state, "_goal_status_note")
217
+ return {
218
+ # Blocked goals remain actionable, while paused and complete goals do not
219
+ # drive work until the user changes their state.
220
+ "active": status in {"active", "blocked"},
221
+ "objective": objective,
222
+ "status": status,
223
+ "criteria": criteria,
224
+ "note": note,
225
+ }
226
+
227
+
228
+ def _update_goal_command(
229
+ *,
230
+ status: Literal["complete", "blocked"],
231
+ note: str,
232
+ tool_call_id: str,
233
+ state: dict[str, Any],
234
+ ) -> Command[Any]:
235
+ """Build the constrained `update_goal` command.
236
+
237
+ Args:
238
+ status: Goal status the model is reporting (`complete` or `blocked`).
239
+ note: Evidence the goal is complete, or the specific blocker. Required;
240
+ the status is not committed without it.
241
+ tool_call_id: Tool call ID for the returned `ToolMessage`.
242
+ state: Current graph state injected by LangGraph.
243
+
244
+ Returns:
245
+ Command updating goal metadata and returning a tool response.
246
+ A `complete` request stages `_pending_goal_completion_note` for
247
+ the TUI to resolve once the rubric verdict lands, rather than
248
+ committing the status directly; `blocked` commits immediately.
249
+
250
+ When no goal is set or `note` is empty, nothing is committed
251
+ and the `ToolMessage` explains what the model must do instead.
252
+ """
253
+ # Enforced preconditions here are only: an active goal exists and `note` is
254
+ # non-empty. Completion is staged because `RubricMiddleware` records its
255
+ # final verdict after the model stops making tool calls; the TUI resolves
256
+ # the staged request during post-turn checkpoint sync.
257
+ objective = state.get("_goal_objective")
258
+ if not isinstance(objective, str) or not objective:
259
+ return Command(
260
+ update={
261
+ "messages": [
262
+ ToolMessage(
263
+ content="No active goal is set.",
264
+ tool_call_id=tool_call_id,
265
+ )
266
+ ]
267
+ }
268
+ )
269
+ goal_status = coerce_goal_status(state.get("_goal_status")) or "active"
270
+ if goal_status in {"paused", "complete"}:
271
+ if goal_status == "paused":
272
+ message = (
273
+ "The goal is paused. The user must run `/goal resume` before its "
274
+ "status can be updated."
275
+ )
276
+ else:
277
+ message = "The goal is already complete and cannot be updated."
278
+ return Command(
279
+ update={
280
+ "messages": [ToolMessage(content=message, tool_call_id=tool_call_id)]
281
+ }
282
+ )
283
+ clean_note = note.strip()
284
+ if not clean_note:
285
+ # Evidence is required: refuse to commit a status with no justification
286
+ # rather than silently storing an empty note.
287
+ return Command(
288
+ update={
289
+ "messages": [
290
+ ToolMessage(
291
+ content=(
292
+ f"Provide a note with evidence before marking the "
293
+ f"goal {status}."
294
+ ),
295
+ tool_call_id=tool_call_id,
296
+ )
297
+ ]
298
+ }
299
+ )
300
+ if status == "complete":
301
+ return Command(
302
+ update={
303
+ "_pending_goal_completion_note": clean_note,
304
+ "messages": [
305
+ ToolMessage(
306
+ content=(
307
+ "Goal completion requested. It will be recorded if "
308
+ "the accepted rubric is satisfied."
309
+ ),
310
+ tool_call_id=tool_call_id,
311
+ )
312
+ ],
313
+ }
314
+ )
315
+ return Command(
316
+ update={
317
+ "_goal_status": status,
318
+ "_goal_status_note": clean_note,
319
+ "_pending_goal_completion_note": None,
320
+ "messages": [
321
+ ToolMessage(
322
+ content=f"Goal marked {status}. {clean_note}",
323
+ tool_call_id=tool_call_id,
324
+ )
325
+ ],
326
+ }
327
+ )
328
+
329
+
330
+ class GoalToolsMiddleware(AgentMiddleware[GoalToolState, ContextT]):
331
+ """Expose constrained goal tools to the main agent."""
332
+
333
+ state_schema = GoalToolState
334
+
335
+ def __init__(self) -> None:
336
+ """Initialize goal tools."""
337
+ super().__init__()
338
+
339
+ @tool
340
+ def get_rubric(
341
+ state: Annotated[dict[str, Any], InjectedState],
342
+ ) -> RubricSnapshot:
343
+ """Read the current acceptance criteria used to evaluate completion.
344
+
345
+ Call this to inspect the active rubric, whether it came from a goal,
346
+ a sticky rubric, or the current invocation, and the latest grading
347
+ status if a graded turn has already run.
348
+
349
+ Returns:
350
+ Rubric snapshot with `active`, `criteria`, `source`, and
351
+ `grading_status` keys.
352
+ """
353
+ return _rubric_snapshot(state)
354
+
355
+ @tool
356
+ def get_goal(
357
+ state: Annotated[dict[str, Any], InjectedState],
358
+ ) -> GoalSnapshot:
359
+ """Read the current persistent goal and acceptance criteria.
360
+
361
+ Call this before deciding whether work is done to see the objective,
362
+ the current acceptance criteria (which may come from the goal or a
363
+ standalone rubric), the current status, and any prior note.
364
+
365
+ Returns:
366
+ Goal snapshot with `active`, `objective`, `status`, `criteria`,
367
+ and `note` keys.
368
+ """
369
+ return _goal_snapshot(state)
370
+
371
+ @tool
372
+ def update_goal(
373
+ status: Literal["complete", "blocked"],
374
+ note: str,
375
+ tool_call_id: Annotated[str, InjectedToolCallId],
376
+ state: Annotated[dict[str, Any], InjectedState],
377
+ ) -> Command[Any]:
378
+ """Mark the current goal complete or blocked with evidence.
379
+
380
+ Use `complete` only when the accepted criteria are satisfied; use
381
+ `blocked` when you cannot proceed without user input. Completion is
382
+ rejected unless the latest rubric result is satisfied. Do not create,
383
+ pause, resume, clear, or replace goals — those are user-controlled.
384
+
385
+ Args:
386
+ status: `complete` when the criteria are met, `blocked` when you
387
+ are stuck and need the user.
388
+ note: Evidence the criteria are satisfied, or the specific
389
+ blocker. Required — the status is not recorded without it.
390
+ tool_call_id: Injected tool call ID for the tool response.
391
+ state: Injected graph state holding the current goal.
392
+
393
+ Returns:
394
+ Command that updates goal status and returns a tool message.
395
+ """
396
+ return _update_goal_command(
397
+ status=status,
398
+ note=note,
399
+ tool_call_id=tool_call_id,
400
+ state=state,
401
+ )
402
+
403
+ self.tools = [get_rubric, get_goal, update_goal]
404
+
405
+ @staticmethod
406
+ def _request_with_goal_system_context(
407
+ request: ModelRequest[ContextT],
408
+ ) -> ModelRequest[ContextT]:
409
+ """Inject goal guidance and any one-turn retry context.
410
+
411
+ Returns:
412
+ Model request with goal context appended to the system prompt.
413
+ """
414
+ retry_context = _runtime_blocked_goal_retry_context(request.runtime.context)
415
+ prompt_parts = [GOAL_TOOLS_SYSTEM_PROMPT]
416
+ if retry_context is not None:
417
+ prompt_parts.append(retry_context)
418
+ prompt = "\n\n".join(prompt_parts)
419
+
420
+ if request.system_message is not None:
421
+ content = [
422
+ *request.system_message.content_blocks,
423
+ {"type": "text", "text": f"\n\n{prompt}"},
424
+ ]
425
+ else:
426
+ content = [{"type": "text", "text": prompt}]
427
+ return request.override(
428
+ system_message=SystemMessage(
429
+ content=cast("list[str | dict[str, str]]", content)
430
+ )
431
+ )
432
+
433
+ @override
434
+ def wrap_model_call(
435
+ self,
436
+ request: ModelRequest[ContextT],
437
+ handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
438
+ ) -> ModelResponse[ResponseT]:
439
+ """Inject goal-tool guidance into each model request.
440
+
441
+ Returns:
442
+ Model response from the wrapped handler.
443
+ """
444
+ return handler(self._request_with_goal_system_context(request))
445
+
446
+ @override
447
+ async def awrap_model_call(
448
+ self,
449
+ request: ModelRequest[ContextT],
450
+ handler: Callable[
451
+ [ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]
452
+ ],
453
+ ) -> ModelResponse[ResponseT]:
454
+ """Inject goal-tool guidance into each async model request.
455
+
456
+ Returns:
457
+ Model response from the wrapped handler.
458
+ """
459
+ return await handler(self._request_with_goal_system_context(request))