langchain-agentx-python 2.1.0__py3-none-any.whl → 2.1.2__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.
@@ -1,64 +1,64 @@
1
- """
2
- LangChain-based agent utilities for CodeBaseX.
3
-
4
- This package provides:
5
- - LangChain-AgentX LangGraph agent factory (`loop`)
6
-
7
- Typical usage:
8
-
9
- ```python
10
- from langchain_agentx import create_loop_agent
11
- ```
12
- """
13
-
14
- __version__ = "2.1.0"
15
-
16
- from .loop import ( # noqa: F401
17
- create_loop_agent,
18
- )
19
-
20
- from .loop.hook import ( # noqa: F401
21
- ATTACHMENT_HOOK_BLOCKING_ERROR,
22
- ATTACHMENT_HOOK_CANCELLED,
23
- ATTACHMENT_HOOK_NON_BLOCKING_ERROR,
24
- ATTACHMENT_HOOK_STOPPED_CONTINUATION,
25
- ATTACHMENT_HOOK_SUCCESS,
26
- )
27
- from .observability import ( # noqa: F401
28
- EventAdapterState,
29
- LangchainAgentEvent,
30
- LangchainAgentEventType,
31
- LangGraphToLangchainAgentEventAdapter,
32
- TraceCollector,
33
- TraceSession,
34
- TraceSpan,
35
- )
36
- from .provider import CompatibleChatOpenAI # noqa: F401
37
- from .session import AgentSession, create_agent_session # noqa: F401
38
- from .health import HealthCheck, HealthReport, PreflightResult, preflight, sdk_health # noqa: F401
39
- from .workflow import BaseWorkflow # noqa: F401
40
-
41
- __all__ = [
42
- "ATTACHMENT_HOOK_BLOCKING_ERROR",
43
- "ATTACHMENT_HOOK_CANCELLED",
44
- "ATTACHMENT_HOOK_NON_BLOCKING_ERROR",
45
- "ATTACHMENT_HOOK_STOPPED_CONTINUATION",
46
- "ATTACHMENT_HOOK_SUCCESS",
47
- "create_loop_agent",
48
- "create_agent_session",
49
- "AgentSession",
50
- "BaseWorkflow",
51
- "EventAdapterState",
52
- "LangchainAgentEvent",
53
- "LangchainAgentEventType",
54
- "LangGraphToLangchainAgentEventAdapter",
55
- "TraceCollector",
56
- "TraceSession",
57
- "TraceSpan",
58
- "CompatibleChatOpenAI",
59
- "HealthCheck",
60
- "HealthReport",
61
- "PreflightResult",
62
- "preflight",
63
- "sdk_health",
64
- ]
1
+ """
2
+ LangChain-based agent utilities for CodeBaseX.
3
+
4
+ This package provides:
5
+ - LangChain-AgentX LangGraph agent factory (`loop`)
6
+
7
+ Typical usage:
8
+
9
+ ```python
10
+ from langchain_agentx import create_loop_agent
11
+ ```
12
+ """
13
+
14
+ __version__ = "2.1.2"
15
+
16
+ from .loop import ( # noqa: F401
17
+ create_loop_agent,
18
+ )
19
+
20
+ from .loop.hook import ( # noqa: F401
21
+ ATTACHMENT_HOOK_BLOCKING_ERROR,
22
+ ATTACHMENT_HOOK_CANCELLED,
23
+ ATTACHMENT_HOOK_NON_BLOCKING_ERROR,
24
+ ATTACHMENT_HOOK_STOPPED_CONTINUATION,
25
+ ATTACHMENT_HOOK_SUCCESS,
26
+ )
27
+ from .observability import ( # noqa: F401
28
+ EventAdapterState,
29
+ LangchainAgentEvent,
30
+ LangchainAgentEventType,
31
+ LangGraphToLangchainAgentEventAdapter,
32
+ TraceCollector,
33
+ TraceSession,
34
+ TraceSpan,
35
+ )
36
+ from .provider import CompatibleChatOpenAI # noqa: F401
37
+ from .session import AgentSession, create_agent_session # noqa: F401
38
+ from .health import HealthCheck, HealthReport, PreflightResult, preflight, sdk_health # noqa: F401
39
+ from .workflow import BaseWorkflow # noqa: F401
40
+
41
+ __all__ = [
42
+ "ATTACHMENT_HOOK_BLOCKING_ERROR",
43
+ "ATTACHMENT_HOOK_CANCELLED",
44
+ "ATTACHMENT_HOOK_NON_BLOCKING_ERROR",
45
+ "ATTACHMENT_HOOK_STOPPED_CONTINUATION",
46
+ "ATTACHMENT_HOOK_SUCCESS",
47
+ "create_loop_agent",
48
+ "create_agent_session",
49
+ "AgentSession",
50
+ "BaseWorkflow",
51
+ "EventAdapterState",
52
+ "LangchainAgentEvent",
53
+ "LangchainAgentEventType",
54
+ "LangGraphToLangchainAgentEventAdapter",
55
+ "TraceCollector",
56
+ "TraceSession",
57
+ "TraceSpan",
58
+ "CompatibleChatOpenAI",
59
+ "HealthCheck",
60
+ "HealthReport",
61
+ "PreflightResult",
62
+ "preflight",
63
+ "sdk_health",
64
+ ]
@@ -1,123 +1,123 @@
1
- """
2
- command/prompt_shell.py — Prompt 内嵌 shell 命令执行(CC executeShellCommandsInPrompt 对齐)。
3
-
4
- 职责:
5
- 解析并执行 prompt 中的 !`command` 占位,将输出内联替换;执行前走路由权限检查。
6
-
7
- 链路位置:
8
- 由 command/builtin/commit* 在展开 prompt 前调用。
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import re
14
- from collections.abc import Callable, Sequence
15
- from pathlib import Path
16
- from typing import Protocol
17
-
18
- from langchain_agentx.tool_runtime.models import ToolExecutionContext
19
- from langchain_agentx.tool_runtime.permission_context import set_command_bash_allow_rules
20
- from langchain_agentx.tool_runtime.session_store import AgentSessionStore
21
- from langchain_agentx.utils.subprocess_text import TextSubprocessRunner, default_text_subprocess_runner
22
-
23
- _INLINE_PATTERN = re.compile(r"!`([^`]+)`")
24
-
25
-
26
- class PromptShellPermissionError(RuntimeError):
27
- """prompt 内嵌 shell 命令未通过 Bash 权限检查。"""
28
-
29
-
30
- class _ShellRunner(Protocol):
31
- def run_text(
32
- self,
33
- argv: list[str],
34
- *,
35
- cwd: str | Path | None = None,
36
- timeout_sec: float | None = None,
37
- ) -> tuple[int, str, str]: ...
38
-
39
-
40
- def _default_runner_call(
41
- runner: TextSubprocessRunner,
42
- command: str,
43
- *,
44
- cwd: str | Path | None,
45
- ) -> tuple[int, str, str]:
46
- completed = runner.run(
47
- ["bash", "-lc", command],
48
- cwd=cwd,
49
- timeout=30.0,
50
- capture_output=True,
51
- )
52
- stdout = completed.stdout or ""
53
- stderr = completed.stderr or ""
54
- if completed.returncode != 0 and not stdout.strip():
55
- return completed.returncode, "", stderr.strip() or f"exit {completed.returncode}"
56
- return completed.returncode, stdout, stderr
57
-
58
-
59
- def _assert_prompt_shell_permission(
60
- command: str,
61
- *,
62
- bash_allow_rules: Sequence[str] | None,
63
- cwd: str | Path | None,
64
- ) -> None:
65
- from langchain_agentx.tools.bash.tool import BashRuntimeTool
66
-
67
- store = AgentSessionStore("prompt-shell")
68
- if bash_allow_rules:
69
- set_command_bash_allow_rules(store, bash_allow_rules)
70
- workspace_root = str(cwd) if cwd is not None else None
71
- ctx = ToolExecutionContext(
72
- tool_name="Bash",
73
- tool_call_id="prompt-shell",
74
- input_args={"command": command},
75
- state={},
76
- session_store=store,
77
- workspace_root=workspace_root,
78
- cwd=workspace_root,
79
- )
80
- l1 = BashRuntimeTool().run_l1_permission_check({"command": command}, ctx)
81
- if l1.outcome == "deny":
82
- message = (l1.decision.message if l1.decision else None) or "Permission denied"
83
- raise PromptShellPermissionError(
84
- f'Shell command permission check failed for "!`{command}`": {message}',
85
- )
86
-
87
-
88
- async def execute_shell_commands_in_prompt(
89
- text: str,
90
- *,
91
- cwd: str | Path | None = None,
92
- runner: TextSubprocessRunner | None = None,
93
- shell_runner: Callable[[str], tuple[int, str, str]] | None = None,
94
- bash_allow_rules: Sequence[str] | None = None,
95
- check_permissions: bool = True,
96
- ) -> str:
97
- """将 text 中所有 !`command` 替换为命令 stdout(失败时内联 stderr/exit)。"""
98
- if "!`" not in text:
99
- return text
100
-
101
- text_runner = runner or default_text_subprocess_runner()
102
- result = text
103
-
104
- for match in list(_INLINE_PATTERN.finditer(text)):
105
- command = match.group(1).strip()
106
- if not command:
107
- continue
108
- if check_permissions and shell_runner is None:
109
- _assert_prompt_shell_permission(
110
- command,
111
- bash_allow_rules=bash_allow_rules,
112
- cwd=cwd,
113
- )
114
- if shell_runner is not None:
115
- _code, stdout, stderr = shell_runner(command)
116
- else:
117
- _code, stdout, stderr = _default_runner_call(text_runner, command, cwd=cwd)
118
- replacement = stdout.strip() if stdout.strip() else stderr.strip()
119
- if not replacement:
120
- replacement = "(no output)"
121
- result = result.replace(match.group(0), replacement, 1)
122
-
123
- return result
1
+ """
2
+ command/prompt_shell.py — Prompt 内嵌 shell 命令执行(CC executeShellCommandsInPrompt 对齐)。
3
+
4
+ 职责:
5
+ 解析并执行 prompt 中的 !`command` 占位,将输出内联替换;执行前走路由权限检查。
6
+
7
+ 链路位置:
8
+ 由 command/builtin/commit* 在展开 prompt 前调用。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from collections.abc import Callable, Sequence
15
+ from pathlib import Path
16
+ from typing import Protocol
17
+
18
+ from langchain_agentx.tool_runtime.models import ToolExecutionContext
19
+ from langchain_agentx.tool_runtime.permission_context import set_command_bash_allow_rules
20
+ from langchain_agentx.tool_runtime.session_store import AgentSessionStore
21
+ from langchain_agentx.utils.subprocess_text import TextSubprocessRunner, default_text_subprocess_runner
22
+
23
+ _INLINE_PATTERN = re.compile(r"!`([^`]+)`")
24
+
25
+
26
+ class PromptShellPermissionError(RuntimeError):
27
+ """prompt 内嵌 shell 命令未通过 Bash 权限检查。"""
28
+
29
+
30
+ class _ShellRunner(Protocol):
31
+ def run_text(
32
+ self,
33
+ argv: list[str],
34
+ *,
35
+ cwd: str | Path | None = None,
36
+ timeout_sec: float | None = None,
37
+ ) -> tuple[int, str, str]: ...
38
+
39
+
40
+ def _default_runner_call(
41
+ runner: TextSubprocessRunner,
42
+ command: str,
43
+ *,
44
+ cwd: str | Path | None,
45
+ ) -> tuple[int, str, str]:
46
+ completed = runner.run(
47
+ ["bash", "-lc", command],
48
+ cwd=cwd,
49
+ timeout=30.0,
50
+ capture_output=True,
51
+ )
52
+ stdout = completed.stdout or ""
53
+ stderr = completed.stderr or ""
54
+ if completed.returncode != 0 and not stdout.strip():
55
+ return completed.returncode, "", stderr.strip() or f"exit {completed.returncode}"
56
+ return completed.returncode, stdout, stderr
57
+
58
+
59
+ def _assert_prompt_shell_permission(
60
+ command: str,
61
+ *,
62
+ bash_allow_rules: Sequence[str] | None,
63
+ cwd: str | Path | None,
64
+ ) -> None:
65
+ from langchain_agentx.tools.bash.tool import BashRuntimeTool
66
+
67
+ store = AgentSessionStore("prompt-shell")
68
+ if bash_allow_rules:
69
+ set_command_bash_allow_rules(store, bash_allow_rules)
70
+ workspace_root = str(cwd) if cwd is not None else None
71
+ ctx = ToolExecutionContext(
72
+ tool_name="Bash",
73
+ tool_call_id="prompt-shell",
74
+ input_args={"command": command},
75
+ state={},
76
+ session_store=store,
77
+ workspace_root=workspace_root,
78
+ cwd=workspace_root,
79
+ )
80
+ l1 = BashRuntimeTool().run_l1_permission_check({"command": command}, ctx)
81
+ if l1.outcome == "deny":
82
+ message = (l1.decision.message if l1.decision else None) or "Permission denied"
83
+ raise PromptShellPermissionError(
84
+ f'Shell command permission check failed for "!`{command}`": {message}',
85
+ )
86
+
87
+
88
+ async def execute_shell_commands_in_prompt(
89
+ text: str,
90
+ *,
91
+ cwd: str | Path | None = None,
92
+ runner: TextSubprocessRunner | None = None,
93
+ shell_runner: Callable[[str], tuple[int, str, str]] | None = None,
94
+ bash_allow_rules: Sequence[str] | None = None,
95
+ check_permissions: bool = True,
96
+ ) -> str:
97
+ """将 text 中所有 !`command` 替换为命令 stdout(失败时内联 stderr/exit)。"""
98
+ if "!`" not in text:
99
+ return text
100
+
101
+ text_runner = runner or default_text_subprocess_runner()
102
+ result = text
103
+
104
+ for match in list(_INLINE_PATTERN.finditer(text)):
105
+ command = match.group(1).strip()
106
+ if not command:
107
+ continue
108
+ if check_permissions and shell_runner is None:
109
+ _assert_prompt_shell_permission(
110
+ command,
111
+ bash_allow_rules=bash_allow_rules,
112
+ cwd=cwd,
113
+ )
114
+ if shell_runner is not None:
115
+ _code, stdout, stderr = shell_runner(command)
116
+ else:
117
+ _code, stdout, stderr = _default_runner_call(text_runner, command, cwd=cwd)
118
+ replacement = stdout.strip() if stdout.strip() else stderr.strip()
119
+ if not replacement:
120
+ replacement = "(no output)"
121
+ result = result.replace(match.group(0), replacement, 1)
122
+
123
+ return result
@@ -1,109 +1,109 @@
1
- """
2
- 主循环上下文治理的 **策略与提示词**(单一配置对象,便于替换与单测)。
3
-
4
- 与 Claude Code 对齐:
5
- - 数值与 `OPENCODE_*` 环境变量覆盖语义、会话摘要模板与 CC `messagesForQuery` 后续各阶段读取的阈值/提示一致方向;
6
- - 各 `CompactionStage` 实现应通过 `get_active_compaction_settings()` 读取,避免散落魔法数。
7
-
8
- 设计要点:
9
- - `CompactionSettings` 为 `frozen` 值对象;可变绑定为模块级 `active_compaction_settings`,单测可 `set_active_compaction_settings(...)` 整体替换。
10
-
11
- 注意:
12
- - 不依赖 LangGraph;与 `tool_runtime` 单条截断分工见设计文档。
13
- - 紧凑化提示词函数(如 `get_compact_prompt`)在 **`loop/prompt/compact.py`**,不在本模块再导出。
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- from dataclasses import dataclass, field
19
- from typing import FrozenSet
20
-
21
- from ..prompt.compact import AutocompactLlmPromptKind
22
- from .query_source import QuerySourcePolicy
23
-
24
-
25
- @dataclass(frozen=True)
26
- class CompactionSettings:
27
- """CC 对齐上下文治理的默认策略(提示词 + 数值阈值)。"""
28
-
29
- num_chars_per_token: int = 4
30
- prune_protect_tokens: int = 40_000
31
- prune_minimum_tokens: int = 20_000
32
- prune_protected_tools: FrozenSet[str] = field(default_factory=lambda: frozenset({"Skill"}))
33
- default_max_context_tokens: int = 120_000
34
- default_compress_trigger_fraction: float = 0.8
35
- default_compress_keep_recent_messages: int = 8
36
- # --- autocompact LLM 提示词(对齐 `compact.ts` + `prompt.ts`)---
37
- # 摘要插在保留 tail 之前 → 默认 `partial_up_to`;全量折叠无尾窗时用 `full`;与 CC partial pivot「from」一致时用 `partial_from`。
38
- autocompact_llm_prompt_kind: AutocompactLlmPromptKind = "partial_up_to"
39
- # 追加到 CC 模板末尾的 Additional Instructions(对齐 hook merge 的 customInstructions)。
40
- compact_custom_instructions: str | None = None
41
- # 非 None 时跳过 builder,整段作为 compact 调用的指令(单测 / 完全自定义)。
42
- autocompact_llm_instructions_override: str | None = None
43
- # 兼容旧字段:未设置 override 时若此字段非空则优先于 builder(避免破坏只传 compaction_system_prompt 的调用方)。
44
- compaction_system_prompt: str | None = None
45
- # --- tool_result_budget(对齐 applyToolResultBudget:单条结果软上限)---
46
- tool_result_max_chars_per_message: int = 48_000
47
- tool_result_budget_suffix: str = field(
48
- default="\n\n[... truncated by tool_result_budget ...]"
49
- )
50
- # --- snip_compact(对齐 snip:尾部保留 + 历史大块截断)---
51
- snip_preserve_tail_messages: int = 10
52
- snip_max_tool_content_chars: int = 12_288
53
- snip_suffix: str = field(default="\n\n[... snipped by snip_compact ...]")
54
- # --- microcompact(轻量规范化;递归压缩时跳过)---
55
- microcompact_skip_query_sources: FrozenSet[str] = field(
56
- default_factory=QuerySourcePolicy.microcompact_skip_sources
57
- )
58
- # --- context_collapse(重复 tool 结果治理)---
59
- collapse_dedupe_consecutive_same_tool_call_id: bool = True
60
- # --- autocompact(阈值触发;产品与 CC 对齐默认走 LLM 摘要,无 model 时回退 prune)---
61
- autocompact_skip_query_sources: FrozenSet[str] = field(
62
- default_factory=QuerySourcePolicy.autocompact_skip_sources
63
- )
64
- autocompact_use_llm_when_available: bool = True
65
- autocompact_llm_excerpt_chars_per_message: int = 20_000
66
- # --- snip carry → autocompact 阈值放宽(对齐 CC snipTokensFreed 计入阈值)---
67
- # True:将本流水线已累计的 `snip_tokens_freed_carry` 加到触发阈值上,避免 snip 已释放后仍立刻 autocompact。
68
- autocompact_snip_carry_threshold_slack: bool = True
69
- # --- blocking / token 告警(对齐 CC calculateTokenWarningState 分档)---
70
- compaction_token_warn_fraction: float = 0.85
71
- compaction_block_compact_llm_fraction: float = 0.98
72
- # 达到或超过视为「主上下文已顶满」,建议由调用方拦主模型(本守卫 `should_block`)。
73
- compaction_block_main_model_fraction: float = 1.0
74
- # True:`model` 节点在 `invoke` 前若 `should_block` 命中则不再调主模型,写入合成 AIMessage 并结束。
75
- enable_main_model_context_token_block: bool = True
76
- # --- tool transcript guard(对齐 CC `ensureToolResultPairing`:发往 LLM 前修复 tool 配对)---
77
- # True:在绑定模型 `invoke/ainvoke` 前对 `messages` 做确定性修复(跨 assistant 去重 tool_call_id、
78
- # 去重连续 ToolMessage、补 synthetic 等),降低提供商 400 与会话卡死风险;不依赖提示词。
79
- enable_tool_transcript_repair_before_llm: bool = True
80
- # --- SystemMessage 单一来源兜底(对齐 CC normalizeMessagesForAPI)---
81
- # True:在 LLM 调用前剥离 state["messages"] 中所有 SystemMessage,避免 OpenAI 兼容 API 400 错误。
82
- enable_system_message_sanitizer: bool = True
83
- # True:剥离时输出内容预览(最多 3 条),便于排查违规来源。
84
- enable_system_message_content_preview: bool = True
85
- # --- 观测:写入 state.compaction_pipeline_meta(需 LoopAgentStateFields 含该键)---
86
- emit_compaction_pipeline_meta: bool = True
87
-
88
-
89
- active_compaction_settings: CompactionSettings = CompactionSettings()
90
-
91
-
92
- def get_active_compaction_settings() -> CompactionSettings:
93
- """返回当前生效的策略(默认即模块级 `active_compaction_settings`)。"""
94
- return active_compaction_settings
95
-
96
-
97
- def set_active_compaction_settings(settings: CompactionSettings) -> None:
98
- """替换全局策略(单测或进程级配置用)。"""
99
- global active_compaction_settings
100
- active_compaction_settings = settings
101
-
102
-
103
- __all__ = [
104
- "AutocompactLlmPromptKind",
105
- "CompactionSettings",
106
- "active_compaction_settings",
107
- "get_active_compaction_settings",
108
- "set_active_compaction_settings",
109
- ]
1
+ """
2
+ 主循环上下文治理的 **策略与提示词**(单一配置对象,便于替换与单测)。
3
+
4
+ 与 Claude Code 对齐:
5
+ - 数值与 `OPENCODE_*` 环境变量覆盖语义、会话摘要模板与 CC `messagesForQuery` 后续各阶段读取的阈值/提示一致方向;
6
+ - 各 `CompactionStage` 实现应通过 `get_active_compaction_settings()` 读取,避免散落魔法数。
7
+
8
+ 设计要点:
9
+ - `CompactionSettings` 为 `frozen` 值对象;可变绑定为模块级 `active_compaction_settings`,单测可 `set_active_compaction_settings(...)` 整体替换。
10
+
11
+ 注意:
12
+ - 不依赖 LangGraph;与 `tool_runtime` 单条截断分工见设计文档。
13
+ - 紧凑化提示词函数(如 `get_compact_prompt`)在 **`loop/prompt/compact.py`**,不在本模块再导出。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, field
19
+ from typing import FrozenSet
20
+
21
+ from ..prompt.compact import AutocompactLlmPromptKind
22
+ from .query_source import QuerySourcePolicy
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class CompactionSettings:
27
+ """CC 对齐上下文治理的默认策略(提示词 + 数值阈值)。"""
28
+
29
+ num_chars_per_token: int = 4
30
+ prune_protect_tokens: int = 40_000
31
+ prune_minimum_tokens: int = 20_000
32
+ prune_protected_tools: FrozenSet[str] = field(default_factory=lambda: frozenset({"Skill"}))
33
+ default_max_context_tokens: int = 120_000
34
+ default_compress_trigger_fraction: float = 0.8
35
+ default_compress_keep_recent_messages: int = 8
36
+ # --- autocompact LLM 提示词(对齐 `compact.ts` + `prompt.ts`)---
37
+ # 摘要插在保留 tail 之前 → 默认 `partial_up_to`;全量折叠无尾窗时用 `full`;与 CC partial pivot「from」一致时用 `partial_from`。
38
+ autocompact_llm_prompt_kind: AutocompactLlmPromptKind = "partial_up_to"
39
+ # 追加到 CC 模板末尾的 Additional Instructions(对齐 hook merge 的 customInstructions)。
40
+ compact_custom_instructions: str | None = None
41
+ # 非 None 时跳过 builder,整段作为 compact 调用的指令(单测 / 完全自定义)。
42
+ autocompact_llm_instructions_override: str | None = None
43
+ # 兼容旧字段:未设置 override 时若此字段非空则优先于 builder(避免破坏只传 compaction_system_prompt 的调用方)。
44
+ compaction_system_prompt: str | None = None
45
+ # --- tool_result_budget(对齐 applyToolResultBudget:单条结果软上限)---
46
+ tool_result_max_chars_per_message: int = 48_000
47
+ tool_result_budget_suffix: str = field(
48
+ default="\n\n[... truncated by tool_result_budget ...]"
49
+ )
50
+ # --- snip_compact(对齐 snip:尾部保留 + 历史大块截断)---
51
+ snip_preserve_tail_messages: int = 10
52
+ snip_max_tool_content_chars: int = 12_288
53
+ snip_suffix: str = field(default="\n\n[... snipped by snip_compact ...]")
54
+ # --- microcompact(轻量规范化;递归压缩时跳过)---
55
+ microcompact_skip_query_sources: FrozenSet[str] = field(
56
+ default_factory=QuerySourcePolicy.microcompact_skip_sources
57
+ )
58
+ # --- context_collapse(重复 tool 结果治理)---
59
+ collapse_dedupe_consecutive_same_tool_call_id: bool = True
60
+ # --- autocompact(阈值触发;产品与 CC 对齐默认走 LLM 摘要,无 model 时回退 prune)---
61
+ autocompact_skip_query_sources: FrozenSet[str] = field(
62
+ default_factory=QuerySourcePolicy.autocompact_skip_sources
63
+ )
64
+ autocompact_use_llm_when_available: bool = True
65
+ autocompact_llm_excerpt_chars_per_message: int = 20_000
66
+ # --- snip carry → autocompact 阈值放宽(对齐 CC snipTokensFreed 计入阈值)---
67
+ # True:将本流水线已累计的 `snip_tokens_freed_carry` 加到触发阈值上,避免 snip 已释放后仍立刻 autocompact。
68
+ autocompact_snip_carry_threshold_slack: bool = True
69
+ # --- blocking / token 告警(对齐 CC calculateTokenWarningState 分档)---
70
+ compaction_token_warn_fraction: float = 0.85
71
+ compaction_block_compact_llm_fraction: float = 0.98
72
+ # 达到或超过视为「主上下文已顶满」,建议由调用方拦主模型(本守卫 `should_block`)。
73
+ compaction_block_main_model_fraction: float = 1.0
74
+ # True:`model` 节点在 `invoke` 前若 `should_block` 命中则不再调主模型,写入合成 AIMessage 并结束。
75
+ enable_main_model_context_token_block: bool = True
76
+ # --- tool transcript guard(对齐 CC `ensureToolResultPairing`:发往 LLM 前修复 tool 配对)---
77
+ # True:在绑定模型 `invoke/ainvoke` 前对 `messages` 做确定性修复(跨 assistant 去重 tool_call_id、
78
+ # 去重连续 ToolMessage、补 synthetic 等),降低提供商 400 与会话卡死风险;不依赖提示词。
79
+ enable_tool_transcript_repair_before_llm: bool = True
80
+ # --- SystemMessage 单一来源兜底(对齐 CC normalizeMessagesForAPI)---
81
+ # True:在 LLM 调用前剥离 state["messages"] 中所有 SystemMessage,避免 OpenAI 兼容 API 400 错误。
82
+ enable_system_message_sanitizer: bool = True
83
+ # True:剥离时输出内容预览(最多 3 条),便于排查违规来源。
84
+ enable_system_message_content_preview: bool = True
85
+ # --- 观测:写入 state.compaction_pipeline_meta(需 LoopAgentStateFields 含该键)---
86
+ emit_compaction_pipeline_meta: bool = True
87
+
88
+
89
+ active_compaction_settings: CompactionSettings = CompactionSettings()
90
+
91
+
92
+ def get_active_compaction_settings() -> CompactionSettings:
93
+ """返回当前生效的策略(默认即模块级 `active_compaction_settings`)。"""
94
+ return active_compaction_settings
95
+
96
+
97
+ def set_active_compaction_settings(settings: CompactionSettings) -> None:
98
+ """替换全局策略(单测或进程级配置用)。"""
99
+ global active_compaction_settings
100
+ active_compaction_settings = settings
101
+
102
+
103
+ __all__ = [
104
+ "AutocompactLlmPromptKind",
105
+ "CompactionSettings",
106
+ "active_compaction_settings",
107
+ "get_active_compaction_settings",
108
+ "set_active_compaction_settings",
109
+ ]
@@ -91,7 +91,7 @@ class SubagentContext:
91
91
  parent_tool_call_id: str | None = None # 触发本子 Agent 的 tool_call_id
92
92
  parent_conversation_session_id: str | None = None
93
93
  enable_trace: bool = True
94
- runnable_config: Any = None # 父 graph runnable_config,用于子 graph 事件自动冒泡到父 graph
94
+ runnable_config: Any = None # 父 runnable_config:仅供 tool_progress custom event 回调链;非子 raw 合并
95
95
  isolation: str = "none"
96
96
  worktree_dir: str | None = None
97
97
  workspace_root: str = ""
@@ -307,7 +307,8 @@ class SubagentOrchestrator:
307
307
  ) -> "AgentToolOutput":
308
308
  """异步执行子代理。
309
309
 
310
- 子 graph 事件通过 config 自动冒泡到父 graph,无需手动回调。
310
+ 子 graph 在独立 LoopStreamDriver 消费(Phase-4);progress 经 custom event 冒泡,
311
+ 裸子 on_tool_* 不并进父 astream。
311
312
  """
312
313
  from langchain_agentx.tools.agent.models import AgentToolOutput
313
314
 
@@ -595,7 +596,7 @@ class SubagentOrchestrator:
595
596
  ) -> Any:
596
597
  """异步启动子 agent。
597
598
 
598
- 子 graph 事件通过 config 自动冒泡到父 graph。
599
+ 子 graph Phase-4 独立 Driver 消费;progress 经 custom event 冒泡到父流。
599
600
  """
600
601
  owner_run_ctx = get_run_context()
601
602
  handle = self._register_background_agent(
@@ -226,7 +226,7 @@ async def _emit_progress(
226
226
  """发射子 Agent teaser lane 到父 graph(CC 对齐:仅 tool_use / tool_result)。
227
227
 
228
228
  同步 dispatch 优先:并行线程池 / _run_coroutine_sync 子线程内 adispatch 无法触达父回调链。
229
- graph 的 config 通过 ctx.runnable_config 传递(在 _collect_subagent_events 中合并)。
229
+ 父 config ctx.runnable_config 仅服务 custom event 派发(Phase-4 不合并父 callbacks)。
230
230
  """
231
231
  if event.type not in ("tool_use", "tool_result"):
232
232
  return