langchain-agentx-python 0.9.2__py3-none-any.whl → 0.9.3__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.
- langchain_agentx/__init__.py +1 -1
- langchain_agentx/observability/events/finish_event_payload_composer.py +30 -4
- langchain_agentx/session/__init__.py +10 -0
- langchain_agentx/session/agent_session.py +81 -9
- langchain_agentx/session/recovery.py +111 -0
- langchain_agentx/tool_runtime/path_safety.py +7 -2
- {langchain_agentx_python-0.9.2.dist-info → langchain_agentx_python-0.9.3.dist-info}/METADATA +1 -1
- {langchain_agentx_python-0.9.2.dist-info → langchain_agentx_python-0.9.3.dist-info}/RECORD +11 -10
- {langchain_agentx_python-0.9.2.dist-info → langchain_agentx_python-0.9.3.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-0.9.2.dist-info → langchain_agentx_python-0.9.3.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-0.9.2.dist-info → langchain_agentx_python-0.9.3.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -22,8 +22,10 @@ from langchain_core.messages import AIMessage
|
|
|
22
22
|
|
|
23
23
|
from langchain_agentx.loop.exit.reason_codes import (
|
|
24
24
|
ERROR_TERMINAL_REASONS,
|
|
25
|
+
TERMINAL_REASON_COMPLETED,
|
|
25
26
|
TERMINAL_REASON_ERROR,
|
|
26
27
|
TERMINAL_REASON_MAX_TOKENS,
|
|
28
|
+
TRANSITION_API_ERROR_TERMINAL,
|
|
27
29
|
TRANSITION_CONTEXT_WINDOW_RECOVERY_EXHAUSTED,
|
|
28
30
|
TRANSITION_MAX_OUTPUT_TOKENS_RECOVERY_EXHAUSTED,
|
|
29
31
|
TRANSITION_WITHHOLD_RECOVERY_EXHAUSTED,
|
|
@@ -117,7 +119,9 @@ class FinishEventPayloadComposer:
|
|
|
117
119
|
|
|
118
120
|
from langchain_agentx.loop.exit.exit_logic import RECOVERABLE_TERMINAL_REASONS
|
|
119
121
|
|
|
120
|
-
is_error = self._resolve_is_error(
|
|
122
|
+
is_error = self._resolve_is_error(
|
|
123
|
+
terminal_reason, withheld_error, snap, messages=messages
|
|
124
|
+
)
|
|
121
125
|
recoverable = (
|
|
122
126
|
terminal_reason in RECOVERABLE_TERMINAL_REASONS if terminal_reason else False
|
|
123
127
|
)
|
|
@@ -125,6 +129,14 @@ class FinishEventPayloadComposer:
|
|
|
125
129
|
recovery_exhausted = (
|
|
126
130
|
transition_reason in _RECOVERY_EXHAUSTED_TRANSITIONS if is_error else False
|
|
127
131
|
)
|
|
132
|
+
if (
|
|
133
|
+
not recovery_exhausted
|
|
134
|
+
and is_error
|
|
135
|
+
and terminal_reason == TERMINAL_REASON_MAX_TOKENS
|
|
136
|
+
and resolve_overflow_kind(withheld_error) == "context_window"
|
|
137
|
+
and bool(snap.get("has_attempted_reactive_compact"))
|
|
138
|
+
):
|
|
139
|
+
recovery_exhausted = True
|
|
128
140
|
error_kind = self._infer_error_kind(
|
|
129
141
|
terminal_reason=terminal_reason,
|
|
130
142
|
is_error=is_error,
|
|
@@ -166,7 +178,14 @@ class FinishEventPayloadComposer:
|
|
|
166
178
|
terminal_reason: str | None,
|
|
167
179
|
withheld_error: dict[str, Any] | None,
|
|
168
180
|
snap: dict[str, Any],
|
|
181
|
+
*,
|
|
182
|
+
messages: list[Any] | None = None,
|
|
169
183
|
) -> bool:
|
|
184
|
+
transition_reason = _transition_reason(snap)
|
|
185
|
+
if transition_reason == TRANSITION_API_ERROR_TERMINAL:
|
|
186
|
+
return True
|
|
187
|
+
if _is_api_error_message(_last_ai_message(messages)):
|
|
188
|
+
return True
|
|
170
189
|
if terminal_reason in ERROR_TERMINAL_REASONS:
|
|
171
190
|
return True
|
|
172
191
|
if isinstance(withheld_error, dict) and withheld_error and snap.get("should_end"):
|
|
@@ -185,6 +204,13 @@ class FinishEventPayloadComposer:
|
|
|
185
204
|
if not is_error:
|
|
186
205
|
return None
|
|
187
206
|
|
|
207
|
+
last_ai = _last_ai_message(messages)
|
|
208
|
+
if (
|
|
209
|
+
transition_reason == TRANSITION_API_ERROR_TERMINAL
|
|
210
|
+
or _is_api_error_message(last_ai)
|
|
211
|
+
):
|
|
212
|
+
return "api_error"
|
|
213
|
+
|
|
188
214
|
overflow_kind = resolve_overflow_kind(withheld_error)
|
|
189
215
|
if terminal_reason == TERMINAL_REASON_MAX_TOKENS:
|
|
190
216
|
if overflow_kind == "context_window" or (
|
|
@@ -198,9 +224,9 @@ class FinishEventPayloadComposer:
|
|
|
198
224
|
return "unknown"
|
|
199
225
|
|
|
200
226
|
if terminal_reason == TERMINAL_REASON_ERROR:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
227
|
+
return "unknown"
|
|
228
|
+
|
|
229
|
+
if terminal_reason == TERMINAL_REASON_COMPLETED:
|
|
204
230
|
return "unknown"
|
|
205
231
|
|
|
206
232
|
return "unknown"
|
|
@@ -6,12 +6,22 @@ from .conversation_factory import create_conversation_session
|
|
|
6
6
|
from .conversation_session import ConversationSession
|
|
7
7
|
from .factory import create_agent_session
|
|
8
8
|
from .protocol import LoopContainer
|
|
9
|
+
from .recovery import (
|
|
10
|
+
SessionRecoveryRunResult,
|
|
11
|
+
finish_outcome_from_graph_output,
|
|
12
|
+
parse_result_finish_outcome,
|
|
13
|
+
run_with_recovery_loop,
|
|
14
|
+
)
|
|
9
15
|
|
|
10
16
|
__all__ = [
|
|
11
17
|
"AgentSession",
|
|
12
18
|
"ConversationSession",
|
|
13
19
|
"LoopContainer",
|
|
20
|
+
"SessionRecoveryRunResult",
|
|
14
21
|
"create_agent_session",
|
|
15
22
|
"create_conversation_session",
|
|
23
|
+
"finish_outcome_from_graph_output",
|
|
16
24
|
"load_session_messages",
|
|
25
|
+
"parse_result_finish_outcome",
|
|
26
|
+
"run_with_recovery_loop",
|
|
17
27
|
]
|
|
@@ -29,8 +29,13 @@ from ..command.builtin import (
|
|
|
29
29
|
make_memory_command,
|
|
30
30
|
make_reload_plugins_command,
|
|
31
31
|
)
|
|
32
|
-
from ..observability.events.finish_outcome import enrich_run_result
|
|
32
|
+
from ..observability.events.finish_outcome import RecoveryPolicyMode, enrich_run_result
|
|
33
33
|
from ..loop.config import merge_run_config
|
|
34
|
+
from .recovery import (
|
|
35
|
+
SessionRecoveryRunResult,
|
|
36
|
+
finish_outcome_from_graph_output,
|
|
37
|
+
run_with_recovery_loop,
|
|
38
|
+
)
|
|
34
39
|
from ..loop.context.compaction_service import default_loop_context_compaction
|
|
35
40
|
from ..loop.context.message_utils import total_estimated_tokens_for_messages
|
|
36
41
|
from ..loop.hook.types import HookContext, HookEvent
|
|
@@ -74,6 +79,7 @@ class AgentSession:
|
|
|
74
79
|
self._enable_trace = enable_trace
|
|
75
80
|
self._transcript_writer = transcript_writer
|
|
76
81
|
self._last_transcript_uuid: str | None = None
|
|
82
|
+
self._last_finish_outcome: dict[str, Any] | None = None
|
|
77
83
|
self._session_memory_manager: Any | None = None
|
|
78
84
|
self._capabilities = capabilities or RuntimeCapabilities.build(
|
|
79
85
|
resource_root=workspace_cfg.workspace_root,
|
|
@@ -119,10 +125,48 @@ class AgentSession:
|
|
|
119
125
|
await self._plugin_loader.unload_all()
|
|
120
126
|
|
|
121
127
|
async def run_loop(self, user_input: str) -> dict[str, Any]:
|
|
128
|
+
result = await self._run_loop_turn(user_input=user_input)
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
async def resume_after_compact(self) -> dict[str, Any]:
|
|
132
|
+
"""compact 后重跑当前轮:不再 append 新的 HumanMessage。
|
|
133
|
+
|
|
134
|
+
适用于 CLI `/compact` 或 headless auto 恢复的第二跳;与 ``run_loop(same_query)``
|
|
135
|
+
不同,不会把同一用户问题重复写入 messages / transcript。
|
|
136
|
+
"""
|
|
137
|
+
return await self._run_loop_turn(user_input=None)
|
|
138
|
+
|
|
139
|
+
async def run_with_recovery(
|
|
140
|
+
self,
|
|
141
|
+
user_input: str,
|
|
142
|
+
*,
|
|
143
|
+
mode: RecoveryPolicyMode = "auto",
|
|
144
|
+
max_outer_retries: int = 1,
|
|
145
|
+
progress_callback: Any = None,
|
|
146
|
+
) -> SessionRecoveryRunResult:
|
|
147
|
+
"""B1 外层自动恢复:recoverable 时 compact + resume,受 ``max_outer_retries`` 约束。
|
|
148
|
+
|
|
149
|
+
- ``mode="auto"``:可恢复且未 exhausted 时自动 compact 并 resume。
|
|
150
|
+
- ``mode="interactive"``:只跑一轮,把 ``RecoveryAction`` 留给 CLI 决策。
|
|
151
|
+
"""
|
|
152
|
+
return await run_with_recovery_loop(
|
|
153
|
+
user_input=user_input,
|
|
154
|
+
mode=mode,
|
|
155
|
+
max_outer_retries=max_outer_retries,
|
|
156
|
+
run_new_turn=lambda inp: self._run_loop_turn(user_input=inp),
|
|
157
|
+
resume_current_turn=lambda: self._run_loop_turn(user_input=None),
|
|
158
|
+
compact=self.compact_context,
|
|
159
|
+
progress_callback=progress_callback,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def _run_loop_turn(self, *, user_input: str | None) -> dict[str, Any]:
|
|
122
163
|
if self._graph is None:
|
|
123
164
|
raise RuntimeError("AgentSession graph is not initialized")
|
|
124
|
-
|
|
125
|
-
|
|
165
|
+
|
|
166
|
+
appended_message: HumanMessage | None = None
|
|
167
|
+
if user_input is not None:
|
|
168
|
+
appended_message = HumanMessage(content=user_input)
|
|
169
|
+
self._messages.append(appended_message)
|
|
126
170
|
baseline_count = len(self._messages)
|
|
127
171
|
run_config = merge_run_config(self._graph, None)
|
|
128
172
|
try:
|
|
@@ -134,20 +178,26 @@ class AgentSession:
|
|
|
134
178
|
config=run_config,
|
|
135
179
|
)
|
|
136
180
|
except Exception:
|
|
137
|
-
|
|
138
|
-
if self._messages and self._messages[-1] is appended_message:
|
|
181
|
+
if appended_message is not None and self._messages and self._messages[-1] is appended_message:
|
|
139
182
|
self._messages.pop()
|
|
140
183
|
raise
|
|
184
|
+
|
|
141
185
|
if isinstance(result, dict) and isinstance(result.get("messages"), list):
|
|
142
186
|
new_messages = list(result["messages"])
|
|
143
187
|
self._messages = new_messages
|
|
144
|
-
|
|
188
|
+
if appended_message is not None:
|
|
189
|
+
self._append_transcript_message(appended_message)
|
|
145
190
|
for message in new_messages[baseline_count:]:
|
|
146
191
|
self._append_transcript_message(message)
|
|
147
192
|
self._active_files.extend(self._extract_accessed_files(result))
|
|
193
|
+
|
|
194
|
+
enriched: dict[str, Any]
|
|
148
195
|
if isinstance(result, dict):
|
|
149
|
-
|
|
150
|
-
|
|
196
|
+
enriched = enrich_run_result(dict(result))
|
|
197
|
+
else:
|
|
198
|
+
enriched = {"result": result}
|
|
199
|
+
self._last_finish_outcome = enriched.get("finish_outcome")
|
|
200
|
+
return enriched
|
|
151
201
|
|
|
152
202
|
async def stream_loop_events(
|
|
153
203
|
self,
|
|
@@ -161,6 +211,10 @@ class AgentSession:
|
|
|
161
211
|
说明:
|
|
162
212
|
该方法用于需要 astream_events 的场景(如 SSE),执行完成后会尝试
|
|
163
213
|
从 on_chain_end(LangGraph) 事件回填消息列表,保持与 run_loop 一致的上下文累积语义。
|
|
214
|
+
|
|
215
|
+
契约:
|
|
216
|
+
``last_finish_outcome`` 仅在事件流完整消费到 ``on_chain_end(name=LangGraph)``
|
|
217
|
+
后可靠;中途 break / 异常时可能仍为 None 或上一轮快照。
|
|
164
218
|
"""
|
|
165
219
|
if self._graph is None:
|
|
166
220
|
raise RuntimeError("AgentSession graph is not initialized")
|
|
@@ -168,6 +222,7 @@ class AgentSession:
|
|
|
168
222
|
self._messages.append(appended_message)
|
|
169
223
|
baseline_count = len(self._messages)
|
|
170
224
|
output_messages: list[Any] | None = None
|
|
225
|
+
graph_output: dict[str, Any] | None = None
|
|
171
226
|
run_config = merge_run_config(self._graph, config)
|
|
172
227
|
try:
|
|
173
228
|
async for event in self._graph.astream_events(
|
|
@@ -181,6 +236,8 @@ class AgentSession:
|
|
|
181
236
|
if event.get("event") == "on_chain_end" and event.get("name") == "LangGraph":
|
|
182
237
|
data = event.get("data", {}) or {}
|
|
183
238
|
output = data.get("output")
|
|
239
|
+
if isinstance(output, dict):
|
|
240
|
+
graph_output = dict(output)
|
|
184
241
|
if isinstance(output, dict) and isinstance(output.get("messages"), list):
|
|
185
242
|
output_messages = list(output["messages"])
|
|
186
243
|
yield event
|
|
@@ -193,6 +250,21 @@ class AgentSession:
|
|
|
193
250
|
self._append_transcript_message(appended_message)
|
|
194
251
|
for message in output_messages[baseline_count:]:
|
|
195
252
|
self._append_transcript_message(message)
|
|
253
|
+
if graph_output is not None:
|
|
254
|
+
self._last_finish_outcome = finish_outcome_from_graph_output(graph_output)
|
|
255
|
+
if isinstance(graph_output.get("_accessed_files"), list):
|
|
256
|
+
self._active_files.extend(
|
|
257
|
+
[item for item in graph_output["_accessed_files"] if isinstance(item, str)]
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def last_finish_outcome(self) -> dict[str, Any] | None:
|
|
262
|
+
"""最近一次 run_loop / resume / stream 结束后的 finish_outcome 快照。
|
|
263
|
+
|
|
264
|
+
流式路径:仅当 ``stream_loop_events`` 完整消费到 LangGraph ``on_chain_end`` 后更新;
|
|
265
|
+
中途中断时可能为 None 或 stale,勿当作本轮终态。
|
|
266
|
+
"""
|
|
267
|
+
return self._last_finish_outcome
|
|
196
268
|
|
|
197
269
|
@property
|
|
198
270
|
def active_files(self) -> list[str]:
|
|
@@ -347,4 +419,4 @@ class AgentSession:
|
|
|
347
419
|
self._runtime_command_registry.register(make_reload_plugins_command())
|
|
348
420
|
|
|
349
421
|
|
|
350
|
-
__all__ = ["AgentSession"]
|
|
422
|
+
__all__ = ["AgentSession", "SessionRecoveryRunResult"]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
session/recovery.py — AgentSession 外层恢复编排(B1 auto / interactive 分流)
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
在 run_loop + compact_context 之上封装「compact 后继续同一问题」与 auto 模式重试预算,
|
|
6
|
+
避免消费方重复 append 同一条 HumanMessage。
|
|
7
|
+
|
|
8
|
+
链路位置:
|
|
9
|
+
AgentSession.run_with_recovery / resume_after_compact → 本模块
|
|
10
|
+
|
|
11
|
+
当前裁剪范围:
|
|
12
|
+
- 不做图内 reactive_compact 拓扑修复(见 release 文档缺口 2)
|
|
13
|
+
- 不做 B2 透明自恢复(单次 invoke 内无感知恢复)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any, Awaitable, Callable, Protocol
|
|
20
|
+
|
|
21
|
+
from langchain_agentx.observability.events.finish_outcome import (
|
|
22
|
+
FinishOutcome,
|
|
23
|
+
FinishOutcomeParser,
|
|
24
|
+
RecoveryAction,
|
|
25
|
+
RecoveryActionResolver,
|
|
26
|
+
RecoveryPolicyMode,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _CompactCallable(Protocol):
|
|
31
|
+
async def __call__(self, *, progress_callback: Any = None) -> tuple[int, int]: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _RunOnceCallable(Protocol):
|
|
35
|
+
async def __call__(self) -> dict[str, Any]: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class SessionRecoveryRunResult:
|
|
40
|
+
"""run_with_recovery 返回:最后一轮 graph 结果 + 终态语义 + 外层 compact 次数。"""
|
|
41
|
+
|
|
42
|
+
result: dict[str, Any]
|
|
43
|
+
outcome: FinishOutcome
|
|
44
|
+
action: RecoveryAction
|
|
45
|
+
outer_recovery_attempts: int
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_result_finish_outcome(result: dict[str, Any]) -> FinishOutcome:
|
|
49
|
+
raw = result.get("finish_outcome")
|
|
50
|
+
if isinstance(raw, dict):
|
|
51
|
+
return FinishOutcomeParser().parse_finish_data(raw)
|
|
52
|
+
return FinishOutcomeParser().from_loop_state(result)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def finish_outcome_from_graph_output(output: dict[str, Any] | None) -> dict[str, Any]:
|
|
56
|
+
"""从 LangGraph on_chain_end output 构建 finish_outcome 字典(流式路径)。"""
|
|
57
|
+
if not isinstance(output, dict):
|
|
58
|
+
return FinishOutcomeParser().from_loop_state({}).to_dict()
|
|
59
|
+
outcome = FinishOutcomeParser().from_loop_state(output)
|
|
60
|
+
return outcome.to_dict()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def run_with_recovery_loop(
|
|
64
|
+
*,
|
|
65
|
+
user_input: str,
|
|
66
|
+
mode: RecoveryPolicyMode,
|
|
67
|
+
max_outer_retries: int,
|
|
68
|
+
run_new_turn: Callable[[str], Awaitable[dict[str, Any]]],
|
|
69
|
+
resume_current_turn: Callable[[], Awaitable[dict[str, Any]]],
|
|
70
|
+
compact: _CompactCallable,
|
|
71
|
+
progress_callback: Any = None,
|
|
72
|
+
) -> SessionRecoveryRunResult:
|
|
73
|
+
"""B1 外层编排:首轮 run_new_turn;recoverable 时 compact + resume(不重复 append)。"""
|
|
74
|
+
if max_outer_retries < 0:
|
|
75
|
+
raise ValueError("max_outer_retries must be >= 0")
|
|
76
|
+
|
|
77
|
+
result = await run_new_turn(user_input)
|
|
78
|
+
outcome = parse_result_finish_outcome(result)
|
|
79
|
+
action = RecoveryActionResolver.resolve(outcome, mode=mode)
|
|
80
|
+
attempts = 0
|
|
81
|
+
|
|
82
|
+
while attempts < max_outer_retries:
|
|
83
|
+
if outcome.succeeded:
|
|
84
|
+
break
|
|
85
|
+
if RecoveryActionResolver.blocks_auto_compact(outcome):
|
|
86
|
+
break
|
|
87
|
+
if mode == "interactive":
|
|
88
|
+
break
|
|
89
|
+
resolved = RecoveryActionResolver.resolve(outcome, mode="auto")
|
|
90
|
+
if resolved != RecoveryAction.AUTO_COMPACT:
|
|
91
|
+
break
|
|
92
|
+
await compact(progress_callback=progress_callback)
|
|
93
|
+
attempts += 1
|
|
94
|
+
result = await resume_current_turn()
|
|
95
|
+
outcome = parse_result_finish_outcome(result)
|
|
96
|
+
action = RecoveryActionResolver.resolve(outcome, mode=mode)
|
|
97
|
+
|
|
98
|
+
return SessionRecoveryRunResult(
|
|
99
|
+
result=result,
|
|
100
|
+
outcome=outcome,
|
|
101
|
+
action=action,
|
|
102
|
+
outer_recovery_attempts=attempts,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
__all__ = [
|
|
107
|
+
"SessionRecoveryRunResult",
|
|
108
|
+
"finish_outcome_from_graph_output",
|
|
109
|
+
"parse_result_finish_outcome",
|
|
110
|
+
"run_with_recovery_loop",
|
|
111
|
+
]
|
|
@@ -52,6 +52,12 @@ _WORKTREES_SEGMENT = "worktrees"
|
|
|
52
52
|
# agent_home_segment 下需 ask 的子目录(对齐 isClaudeConfigFilePath 的 commands/agents/skills)
|
|
53
53
|
_AGENT_CONFIG_SUBDIRS: tuple[str, ...] = ("commands", "agents", "skills")
|
|
54
54
|
|
|
55
|
+
# agent_home 下允许静默写入的子目录(对齐 CC checkEditableInternalPath)。
|
|
56
|
+
# 与 agent_home_segment 绑定:.langchain_agentx / .claude / .cursor 均适用 memory/、sessions/。
|
|
57
|
+
_AGENT_INTERNAL_WRITABLE_SUBDIRS: frozenset[str] = frozenset(
|
|
58
|
+
{"memory", "sessions", "worktrees"}
|
|
59
|
+
)
|
|
60
|
+
|
|
55
61
|
_IDE_SETTINGS_PARENTS: tuple[str, ...] = (".vscode", ".cursor")
|
|
56
62
|
|
|
57
63
|
|
|
@@ -104,7 +110,6 @@ class PathSafetyEvaluator:
|
|
|
104
110
|
s.lower() for s in (*config.dangerous_directory_segments, config.agent_home_segment)
|
|
105
111
|
)
|
|
106
112
|
self._agent_home_lower = home
|
|
107
|
-
self._worktrees_lower = config.worktrees_segment.lower()
|
|
108
113
|
|
|
109
114
|
def evaluate_unc_path(self, path: str) -> AuthorizationDecision | None:
|
|
110
115
|
"""UNC / extended-length 前缀:Read 与 Write 均先拦截(CC 对齐)。"""
|
|
@@ -162,7 +167,7 @@ class PathSafetyEvaluator:
|
|
|
162
167
|
continue
|
|
163
168
|
if seg_lower == self._agent_home_lower:
|
|
164
169
|
next_part = parts[i + 1] if i + 1 < len(parts) else None
|
|
165
|
-
if next_part and next_part.lower()
|
|
170
|
+
if next_part and next_part.lower() in _AGENT_INTERNAL_WRITABLE_SUBDIRS:
|
|
166
171
|
continue
|
|
167
172
|
return True
|
|
168
173
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
langchain_agentx/__init__.py,sha256=
|
|
1
|
+
langchain_agentx/__init__.py,sha256=Xm0Q5GvkbAI2yNpAGB84U70PzzdX-ai12e_4gFGtVto,1678
|
|
2
2
|
langchain_agentx/command/__init__.py,sha256=Ej260S5uFQcmEtGZQG_NH7BYjHoStrpBLtGUsBTxyZk,631
|
|
3
3
|
langchain_agentx/command/context.py,sha256=DIGOGPBw5UGDBm0WNNJlKx1h_9rCRmcdROv4DT61Qug,731
|
|
4
4
|
langchain_agentx/command/dispatcher.py,sha256=72gRi20Gqv3b96MZygKfJX635AnPcFNuZOOh01n3Wm0,7309
|
|
@@ -137,7 +137,7 @@ langchain_agentx/observability/evaluation/checkers/exit_quality.py,sha256=kcV2VY
|
|
|
137
137
|
langchain_agentx/observability/evaluation/checkers/session_memory.py,sha256=jYDdIDUAhYktggsymkBADBunrSbxdC66veM-tZt3a3s,1579
|
|
138
138
|
langchain_agentx/observability/evaluation/checkers/tool_behavior.py,sha256=o1nH8SeE9145H7ej4naahRcmgw_9st21dY1W3IQa2sg,2097
|
|
139
139
|
langchain_agentx/observability/events/__init__.py,sha256=4z4AIMSU8QrtsW_siEN6QkfMGwcxqbOcWYdqh8wYxy0,794
|
|
140
|
-
langchain_agentx/observability/events/finish_event_payload_composer.py,sha256=
|
|
140
|
+
langchain_agentx/observability/events/finish_event_payload_composer.py,sha256=b7RKImz5eWtqx0cezk2m3epT4pljPmqW7mJlgZhYC3o,9781
|
|
141
141
|
langchain_agentx/observability/events/finish_outcome.py,sha256=VIzDUfvK9htoi9UOJN7TWzRszUHL2Nle5H6dxmYWnKo,7049
|
|
142
142
|
langchain_agentx/observability/events/langchain_agentx_event_adapter.py,sha256=w9BuAJ6dvduCDUdH93OSzeQJAjRZId7mJRQJmoAM6XQ,55020
|
|
143
143
|
langchain_agentx/observability/logging/__init__.py,sha256=mDtPOxYZZFx8ezQCSwnmnfrdVi305DeJFzThwHBtnrY,508
|
|
@@ -198,13 +198,14 @@ langchain_agentx/provider/semantics/families/openai_codec.py,sha256=RmzLq_LR1n3o
|
|
|
198
198
|
langchain_agentx/provider/semantics/families/openai_finish_reason.py,sha256=t5ebGN2JMxUYPm8Jw8uebOgdAPc20GPgUiKJVja5r7U,1993
|
|
199
199
|
langchain_agentx/provider/semantics/families/openai_interpreter.py,sha256=zT7EFfHJ5fmYZ_F59vDfyrYdEwcMJANxkoW1Pt3iocI,1915
|
|
200
200
|
langchain_agentx/provider/semantics/families/openai_normalizer.py,sha256=oaf2Riz8EY6VQpc6SNKtsb-naMELw3gYV3IsnZoRBYU,2155
|
|
201
|
-
langchain_agentx/session/__init__.py,sha256=
|
|
202
|
-
langchain_agentx/session/agent_session.py,sha256=
|
|
201
|
+
langchain_agentx/session/__init__.py,sha256=x8RCR4gBO5vC5QPfPxPVB-aXJNzOykWvPlWP0NhZ9eU,789
|
|
202
|
+
langchain_agentx/session/agent_session.py,sha256=TfTXRnWtK5cGFt-ZptGx274FxjlvR-d2ZEFF80IJRtw,17619
|
|
203
203
|
langchain_agentx/session/conversation_factory.py,sha256=nZYRAwmQDgD7ll99KO929Z_Zc4i2dpZ0bePCZuKgyq8,6683
|
|
204
204
|
langchain_agentx/session/conversation_recovery.py,sha256=U0M3p4NKaSz6heEvNzlzehC8oTX9EB2Vz-QdYhhyk0U,5813
|
|
205
205
|
langchain_agentx/session/conversation_session.py,sha256=4iVaEdGll5RO4WHepvVicT1_nDVSBzHg_igNQg2FJ-o,8203
|
|
206
206
|
langchain_agentx/session/factory.py,sha256=IYoON1w-7NQ7RJTW43_0tg9bONOGYtbo56jZngB37A4,7260
|
|
207
207
|
langchain_agentx/session/protocol.py,sha256=uNkNiQ2MD9ReJgPaYe5bF0x6JzVqv2TL-hAG8ME1Obs,622
|
|
208
|
+
langchain_agentx/session/recovery.py,sha256=p-h1KRS3Qblng4REcyfCkQlrb-sta5Oc_mSk9meHFHo,3625
|
|
208
209
|
langchain_agentx/task_runtime/__init__.py,sha256=B9OoHXkyJRURhupkwEtUbEUN1BLOH0rWjkhvc6qdCEo,3513
|
|
209
210
|
langchain_agentx/task_runtime/core/__init__.py,sha256=D5Y5TDp4oh7ml4O-sVdm8r-3-kEDJ7SJmPK7M6vy8_Y,1094
|
|
210
211
|
langchain_agentx/task_runtime/core/ids.py,sha256=g19BWz6IK6YYyiuW3AU0HaaQGFjBzDue_ZCT8n_lip0,876
|
|
@@ -299,7 +300,7 @@ langchain_agentx/tool_runtime/loop_runtime_context.py,sha256=HBZMdtKD7MpU8o7JmUR
|
|
|
299
300
|
langchain_agentx/tool_runtime/messages.py,sha256=Fp-ZiLAbOQAQa8uIv-owE8PXtWyhpYSXxtzy3PVbKSw,3631
|
|
300
301
|
langchain_agentx/tool_runtime/models.py,sha256=Xc1kEi4n6bqgWBKriNWQyBy2_g4nJkziBu-IKW5HlT0,17721
|
|
301
302
|
langchain_agentx/tool_runtime/override.py,sha256=GE_i0J3O7RmfmeSdnQ95FGdTZ4Fu8TdfqXlVjfn0J3w,10422
|
|
302
|
-
langchain_agentx/tool_runtime/path_safety.py,sha256=
|
|
303
|
+
langchain_agentx/tool_runtime/path_safety.py,sha256=kTTKlhPr1wxDDXUikd3vf-0IYa8Tt3Yi3NcYH6V-a7s,6607
|
|
303
304
|
langchain_agentx/tool_runtime/permission_context.py,sha256=R5JxV-6XSmjUjIOJ9YgERoidYcCGyQDPCvuedfIPq1c,4803
|
|
304
305
|
langchain_agentx/tool_runtime/permission_decision.py,sha256=u1ju2Y0QkjyvquGG1FBBU2wdwbJPz3oJVhcpj4t-vs8,4828
|
|
305
306
|
langchain_agentx/tool_runtime/pipeline.py,sha256=zvEyhVseD0h5LeZpZiref7VFNwV4qcMkGoG3tcP7q6M,47345
|
|
@@ -556,8 +557,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
|
|
|
556
557
|
langchain_agentx/workspace/tool_boundary.py,sha256=3b4KwMrGhsS_HZaoCjytJJk39pN0lN3nvYQIj1vLD1k,3327
|
|
557
558
|
langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
|
|
558
559
|
langchain_agentx/workspace/view.py,sha256=Ip02CZNI-ZjF5k0OYT3q7RUR_auMEw83VJ6rQsaBcYU,4588
|
|
559
|
-
langchain_agentx_python-0.9.
|
|
560
|
-
langchain_agentx_python-0.9.
|
|
561
|
-
langchain_agentx_python-0.9.
|
|
562
|
-
langchain_agentx_python-0.9.
|
|
563
|
-
langchain_agentx_python-0.9.
|
|
560
|
+
langchain_agentx_python-0.9.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
561
|
+
langchain_agentx_python-0.9.3.dist-info/METADATA,sha256=5z9zWacixOCy3OxF66ONnRM_H-HDppjwU-ksxsIxqMA,21468
|
|
562
|
+
langchain_agentx_python-0.9.3.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
|
|
563
|
+
langchain_agentx_python-0.9.3.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
|
|
564
|
+
langchain_agentx_python-0.9.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-0.9.2.dist-info → langchain_agentx_python-0.9.3.dist-info}/top_level.txt
RENAMED
|
File without changes
|