power-loop 7.2.0__tar.gz → 7.3.1__tar.gz
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.
- {power_loop-7.2.0 → power_loop-7.3.1}/PKG-INFO +1 -1
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/__init__.py +5 -1
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/agent/stateful_loop.py +13 -1
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/agent/types.py +6 -0
- power_loop-7.3.1/power_loop/core/pass_turn.py +101 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/core/pipeline.py +39 -1
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop.egg-info/PKG-INFO +1 -1
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop.egg-info/SOURCES.txt +1 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/LICENSE +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/README.md +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/attempt_scope.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/capabilities.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/interface.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/multimodal.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/_vendor/llm_client/proxy_fallback.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/agent/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/agent/follow_up.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/agent/sink.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/agent/system_prompt.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/context_policy.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/errors.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/event_payloads.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/events.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/handlers.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/hook_contexts.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/hooks.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/messages.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/protocols.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contracts/tools.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contrib/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contrib/_redact.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contrib/jsonl_sink.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contrib/logging_sink.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contrib/mcp.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contrib/metrics_sink.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/contrib/otel_sink.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/core/agent_context.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/core/events.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/core/hooks.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/core/phase.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/core/runner.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/core/state.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/py.typed +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/background.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/bash_capture.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/blackboard.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/budget.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/cancellation.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/capability_probe.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/compact.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/context_worker.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/env.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/exec_backend.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/fold.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/fold_adapter.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/history_projector.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/history_sanitize.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/human_input.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/image_recall.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/lineage.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/memory.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/notes.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/provider.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/representation.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/retry.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/runtime_state.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/session_store.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/skills.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/spec.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/steer.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/stop_policy.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/backends/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/backends/mysql.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/backends/postgres.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/backends/sqlite.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/capabilities.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/context_cache.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/db.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/dialect.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/factory.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/schema.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/store.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/store/types.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/structured.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/stub_provider.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/timers.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/todos.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/tool_archive.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/tool_context.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/tool_truncation.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/runtime/usage_estimate.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/blackboard.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/command_policy.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/command_rules.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/default_manifest.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/default_tools.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/registry.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/tools/spawn_agent.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/__init__.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/admission.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/api.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/engine.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/introspect.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/journal.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/result.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/resume.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/runner.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/spec.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/subprocess_executor.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/tool.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop/workflow/worker.py +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop.egg-info/dependency_links.txt +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop.egg-info/requires.txt +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/power_loop.egg-info/top_level.txt +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/pyproject.toml +0 -0
- {power_loop-7.2.0 → power_loop-7.3.1}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ Stability tiers
|
|
|
15
15
|
无版本承诺,可随时变更或删除。
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "7.
|
|
18
|
+
__version__ = "7.3.1"
|
|
19
19
|
|
|
20
20
|
# Public LLM contract (SDK-free) re-exported so callers (e.g. writing llm.* hooks or
|
|
21
21
|
# a custom LLMService) don't reach into the internal vendored transport package (H3.4).
|
|
@@ -140,6 +140,7 @@ from power_loop.contracts.tools import ToolDefinition, validate_tool_args
|
|
|
140
140
|
from power_loop.core.agent_context import get_effective_tools
|
|
141
141
|
from power_loop.core.events import AgentEventBus
|
|
142
142
|
from power_loop.core.hooks import AgentHooks
|
|
143
|
+
from power_loop.core.pass_turn import PASS_TURN_TOOL, pass_progress, request_pass
|
|
143
144
|
from power_loop.core.phase import PhaseContext, PhaseResult, phase
|
|
144
145
|
from power_loop.core.pipeline import AgentPipeline
|
|
145
146
|
from power_loop.core.runner import AgentRunner
|
|
@@ -303,6 +304,9 @@ STABLE_API = (
|
|
|
303
304
|
)
|
|
304
305
|
|
|
305
306
|
__all__ = [
|
|
307
|
+
"PASS_TURN_TOOL",
|
|
308
|
+
"request_pass",
|
|
309
|
+
"pass_progress",
|
|
306
310
|
"ToolContextPolicy",
|
|
307
311
|
"ToolContextConfig",
|
|
308
312
|
"__version__",
|
|
@@ -64,6 +64,7 @@ from power_loop.core.agent_context import (
|
|
|
64
64
|
)
|
|
65
65
|
from power_loop.core.events import AgentEventBus
|
|
66
66
|
from power_loop.core.hooks import AgentHooks
|
|
67
|
+
from power_loop.core.pass_turn import ensure_pass_turn
|
|
67
68
|
from power_loop.core.pipeline import (
|
|
68
69
|
AgentPipeline,
|
|
69
70
|
_tool_call_args,
|
|
@@ -132,6 +133,12 @@ class StatefulResult:
|
|
|
132
133
|
usage: dict[str, int] = field(default_factory=dict)
|
|
133
134
|
#: Tool invocations executed during this send.
|
|
134
135
|
tool_calls: int = 0
|
|
136
|
+
#: design/188:调 pass_turn 明确结束的、理由;停的那一刻有没有刚折进来的新输入。
|
|
137
|
+
passed: bool = False
|
|
138
|
+
pass_reason: str | None = None
|
|
139
|
+
new_input_after_pass: bool = False
|
|
140
|
+
#: 每一步写的文字:[{step, text, tools}],按顺序(不只是最后一段)。
|
|
141
|
+
texts: list[dict[str, Any]] = field(default_factory=list)
|
|
135
142
|
|
|
136
143
|
|
|
137
144
|
class _SyncLoopRunner:
|
|
@@ -1415,7 +1422,8 @@ class StatefulAgentLoop:
|
|
|
1415
1422
|
base,
|
|
1416
1423
|
inject_tool_descriptions=self.config.inject_tool_descriptions,
|
|
1417
1424
|
tool_catalog_header=self.config.tool_catalog_header,
|
|
1418
|
-
|
|
1425
|
+
# pipeline 会补上永远在的 pass_turn(design/188):预览也补,才和模型看到的一样
|
|
1426
|
+
tool_registry=ensure_pass_turn(self.tool_registry) if self.tool_registry is not None else None,
|
|
1419
1427
|
skills_dir=self.config.skills_dir,
|
|
1420
1428
|
)
|
|
1421
1429
|
|
|
@@ -2232,6 +2240,10 @@ class StatefulAgentLoop:
|
|
|
2232
2240
|
pending_interactions=result.pending_interactions,
|
|
2233
2241
|
usage=result.usage,
|
|
2234
2242
|
tool_calls=result.tool_calls,
|
|
2243
|
+
passed=result.passed,
|
|
2244
|
+
pass_reason=result.pass_reason,
|
|
2245
|
+
new_input_after_pass=result.new_input_after_pass,
|
|
2246
|
+
texts=result.texts,
|
|
2235
2247
|
)
|
|
2236
2248
|
|
|
2237
2249
|
async def _migrate_prior_history_to_projection(
|
|
@@ -464,3 +464,9 @@ class AgentLoopResult:
|
|
|
464
464
|
usage: dict[str, int] = field(default_factory=dict)
|
|
465
465
|
#: Tool invocations executed during this run.
|
|
466
466
|
tool_calls: int = 0
|
|
467
|
+
#: design/188:这一轮是不是调 pass_turn 明确结束的、理由;停的那一刻是否有刚折进来的新输入。
|
|
468
|
+
passed: bool = False
|
|
469
|
+
pass_reason: str | None = None
|
|
470
|
+
new_input_after_pass: bool = False
|
|
471
|
+
#: 每一步写的文字(不只是最后一段):[{step, text, tools}],按顺序。
|
|
472
|
+
texts: list[dict[str, Any]] = field(default_factory=list)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""pass_turn —— 内置的「这一步做完了,结束这一轮」(design/188)。
|
|
2
|
+
|
|
3
|
+
一个 send 有两种正常的结束:这一轮只写字、不调工具(自然结束);或者调 ``pass_turn``(明确结束)。
|
|
4
|
+
不强制以 pass_turn 结尾。
|
|
5
|
+
|
|
6
|
+
- 工具**永远在**:pipeline 建立时 ``ensure_pass_turn`` 把它补进工具表(宿主去不掉)。
|
|
7
|
+
- 宿主可以用同名工具覆盖说明和处理(例如先核对再决定停不停);处理里调 ``request_pass(理由)`` 才算数。
|
|
8
|
+
- 硬停:标记之后,pipeline 在**下一轮开始前**停(不再调模型),结果上 ``passed=True``、``pass_reason``;
|
|
9
|
+
停的时候若有刚折进来的新输入(转向消息),记 ``new_input_after_pass``,由宿主决定要不要另起一轮。
|
|
10
|
+
- 状态按 send 隔离(ContextVar,每个 pipeline.run 开始时装一份、结束时还原):子 agent 在同一个任务里
|
|
11
|
+
内联跑,它的 pass 不会翻父 send 的标记。
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from contextvars import ContextVar, Token
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from power_loop.contracts.tools import ToolDefinition
|
|
19
|
+
|
|
20
|
+
NAME = "pass_turn"
|
|
21
|
+
REASON_MAX = 300
|
|
22
|
+
|
|
23
|
+
DESCRIPTION = (
|
|
24
|
+
"这一步的事做完了,结束这一轮(不再继续调用模型)。"
|
|
25
|
+
"什么时候用:要做的已经做完、结果已经交出去(写出来了或用工具交了),没有别的要做;"
|
|
26
|
+
"或者在等后台的结果(它好了会叫醒你)。"
|
|
27
|
+
"只写字、不调工具也会结束这一轮——用它是明确地说「做完了」。reason 写一句为什么结束。"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
PASS_TURN_TOOL = ToolDefinition(
|
|
31
|
+
name=NAME,
|
|
32
|
+
description=DESCRIPTION,
|
|
33
|
+
input_schema={
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {"reason": {"type": "string", "description": "一句话:为什么结束这一轮(记日志,不展示)。"}},
|
|
36
|
+
"required": ["reason"],
|
|
37
|
+
},
|
|
38
|
+
required_params=("reason",),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_state: ContextVar[dict[str, Any] | None] = ContextVar("power_loop_pass_turn", default=None)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def begin() -> Token:
|
|
45
|
+
"""一个 send 开始:装一份新的状态。返回还原用的 token。"""
|
|
46
|
+
return _state.set({"passed": False, "reason": None, "new_input_after_pass": False, "texts": 0, "work_tools": 0})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def end(token: Token) -> None:
|
|
50
|
+
_state.reset(token)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def current() -> dict[str, Any] | None:
|
|
54
|
+
return _state.get()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def request_pass(reason: str = "") -> bool:
|
|
58
|
+
"""标记「这一轮结束」:下一轮开始前停。不在 send 里(没有状态)返回 False。"""
|
|
59
|
+
st = _state.get()
|
|
60
|
+
if st is None:
|
|
61
|
+
return False
|
|
62
|
+
st["passed"] = True
|
|
63
|
+
st["reason"] = (reason or "").strip()[:REASON_MAX]
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def note_step(wrote_text: bool, tool_names: list[str]) -> None:
|
|
68
|
+
"""pipeline 每一轮调一次:记下这次 send 写过几段字、调过几次别的工具(pass_turn 不算)。"""
|
|
69
|
+
st = _state.get()
|
|
70
|
+
if st is None:
|
|
71
|
+
return
|
|
72
|
+
if wrote_text:
|
|
73
|
+
st["texts"] = st.get("texts", 0) + 1
|
|
74
|
+
st["work_tools"] = st.get("work_tools", 0) + sum(1 for n in tool_names if n != PASS_TURN_TOOL.name)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def pass_progress() -> dict[str, int] | None:
|
|
78
|
+
"""这次 send 到现在:写过几段字(texts)、调过几次别的工具(work_tools)。宿主覆盖 pass_turn 时用来判断
|
|
79
|
+
「还什么都没交就要结束」。不在 send 里返回 None。"""
|
|
80
|
+
st = _state.get()
|
|
81
|
+
if st is None:
|
|
82
|
+
return None
|
|
83
|
+
return {"texts": int(st.get("texts", 0)), "work_tools": int(st.get("work_tools", 0))}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def pass_requested() -> bool:
|
|
87
|
+
st = _state.get()
|
|
88
|
+
return bool(st and st.get("passed"))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def default_handler(reason: str = "") -> str:
|
|
92
|
+
if not request_pass(reason):
|
|
93
|
+
return "pass_turn 记下了(当前没有在跑的一轮)。"
|
|
94
|
+
return "这一轮结束了。不用再写别的。"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def ensure_pass_turn(registry: Any) -> Any:
|
|
98
|
+
"""工具表里没有 pass_turn 就补上(永远在)。"""
|
|
99
|
+
if not registry.has(NAME):
|
|
100
|
+
registry.register(PASS_TURN_TOOL, default_handler)
|
|
101
|
+
return registry
|
|
@@ -353,7 +353,10 @@ class AgentPipeline:
|
|
|
353
353
|
) -> None:
|
|
354
354
|
self.llm = llm
|
|
355
355
|
self.config = config
|
|
356
|
-
|
|
356
|
+
# design/188:pass_turn 永远在(宿主可以用同名工具覆盖说明和处理)。
|
|
357
|
+
# 没有工具表 = 工具全由宿主执行(调用原样交回,pending_tools):那种模式不补,宿主自己处理
|
|
358
|
+
from power_loop.core.pass_turn import ensure_pass_turn
|
|
359
|
+
self.tool_registry = ensure_pass_turn(tool_registry) if tool_registry is not None else None
|
|
357
360
|
self.hooks = hooks
|
|
358
361
|
self.bus = bus
|
|
359
362
|
self.ctx = ctx
|
|
@@ -845,6 +848,11 @@ class AgentPipeline:
|
|
|
845
848
|
pending_interactions: list | None = None) -> AgentLoopResult:
|
|
846
849
|
self._completed_rounds = rounds # for MemorySnapshot
|
|
847
850
|
self._log_send_summary(status, rounds)
|
|
851
|
+
st = getattr(self, "_pass_state", None) or {}
|
|
852
|
+
texts = list(getattr(self, "_step_texts", []) or [])
|
|
853
|
+
# 明确结束(pass_turn)时最后一轮往往没有文字:答案取最后一段写过的文字
|
|
854
|
+
if st.get("passed") and not final_text and texts:
|
|
855
|
+
final_text = texts[-1]["text"]
|
|
848
856
|
return AgentLoopResult(
|
|
849
857
|
status=status, # type: ignore[arg-type]
|
|
850
858
|
final_text=final_text,
|
|
@@ -854,6 +862,10 @@ class AgentPipeline:
|
|
|
854
862
|
messages=self.history,
|
|
855
863
|
usage=dict(self.ctx.usage_totals),
|
|
856
864
|
tool_calls=int(self.ctx.tool_calls),
|
|
865
|
+
passed=bool(st.get("passed")),
|
|
866
|
+
pass_reason=st.get("reason"),
|
|
867
|
+
new_input_after_pass=bool(st.get("new_input_after_pass")),
|
|
868
|
+
texts=texts,
|
|
857
869
|
)
|
|
858
870
|
|
|
859
871
|
async def _persist_pending_interaction(
|
|
@@ -1516,6 +1528,17 @@ class AgentPipeline:
|
|
|
1516
1528
|
|
|
1517
1529
|
async def run(self, messages: list[LoopMessage]) -> AgentLoopResult:
|
|
1518
1530
|
"""Run the full agent loop. Returns when done, cancelled, or hit round limit."""
|
|
1531
|
+
from power_loop.core import pass_turn as _pass
|
|
1532
|
+
|
|
1533
|
+
token = _pass.begin() # pass_turn 的状态按 send 隔离(子 agent 内联跑也不串)
|
|
1534
|
+
self._pass_state = _pass.current()
|
|
1535
|
+
self._step_texts: list[dict[str, Any]] = []
|
|
1536
|
+
try:
|
|
1537
|
+
return await self._run(messages)
|
|
1538
|
+
finally:
|
|
1539
|
+
_pass.end(token)
|
|
1540
|
+
|
|
1541
|
+
async def _run(self, messages: list[LoopMessage]) -> AgentLoopResult:
|
|
1519
1542
|
self.history = [dict(m) for m in messages]
|
|
1520
1543
|
self._image_rounds = {} # 6.12.0:本 send 新入的图片行 id(row) → round_index
|
|
1521
1544
|
self._tok_len = -1 # SCALE-4: wholesale (re)assignment invalidates the estimate
|
|
@@ -1663,6 +1686,14 @@ class AgentPipeline:
|
|
|
1663
1686
|
for msg in drained:
|
|
1664
1687
|
await self._append_message(msg, round_index=round_idx)
|
|
1665
1688
|
|
|
1689
|
+
# ── pass_turn:上一轮调了 → 这一轮开始前硬停(design/188,不再调模型)──
|
|
1690
|
+
if self._pass_state and self._pass_state.get("passed"):
|
|
1691
|
+
if drained_count:
|
|
1692
|
+
# 停的这一刻有刚折进来的新输入:不撤销停,留痕交给宿主(另起一轮处理)
|
|
1693
|
+
self._pass_state["new_input_after_pass"] = True
|
|
1694
|
+
await self._finalize("pass_turn")
|
|
1695
|
+
return self._make_result("completed", rounds=round_idx)
|
|
1696
|
+
|
|
1666
1697
|
# ── Hook: ROUND_START ──
|
|
1667
1698
|
round_ctx = RoundStartCtx(
|
|
1668
1699
|
round_index=round_idx, messages=self.history,
|
|
@@ -1871,6 +1902,13 @@ class AgentPipeline:
|
|
|
1871
1902
|
tool_calls = response.get_tool_calls()
|
|
1872
1903
|
self._emit(AgentEventType.ROUND_TOOLS_PRESENT, RoundToolsPresentPayload(has_tools=bool(tool_calls)), round_index=round_idx)
|
|
1873
1904
|
|
|
1905
|
+
# 每一步的文字(design/188):模型每一轮都可能写字,全部留着交给宿主(不只是最后一段)
|
|
1906
|
+
if assistant_text:
|
|
1907
|
+
self._step_texts.append({"step": round_idx + 1, "text": assistant_text,
|
|
1908
|
+
"tools": [_tool_call_name(tc) for tc in (tool_calls or [])]})
|
|
1909
|
+
from power_loop.core import pass_turn as _pass_state
|
|
1910
|
+
_pass_state.note_step(bool(assistant_text), [_tool_call_name(tc) for tc in (tool_calls or [])])
|
|
1911
|
+
|
|
1874
1912
|
# Any productive round (said something OR called a tool) clears the empty streak.
|
|
1875
1913
|
if assistant_text or tool_calls:
|
|
1876
1914
|
self._empty_response_streak = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|