power-loop 3.8.0__tar.gz → 3.9.0__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-3.8.0 → power_loop-3.9.0}/PKG-INFO +1 -1
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/__init__.py +1 -1
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/hook_contexts.py +9 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/core/pipeline.py +10 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/tools/default_tools.py +15 -2
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop.egg-info/PKG-INFO +1 -1
- {power_loop-3.8.0 → power_loop-3.9.0}/LICENSE +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/README.md +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/interface.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/agent/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/agent/follow_up.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/agent/sink.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/agent/stateful_loop.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/agent/system_prompt.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/agent/types.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/errors.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/event_payloads.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/events.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/handlers.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/hooks.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/messages.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/protocols.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contracts/tools.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contrib/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contrib/_redact.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contrib/jsonl_sink.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contrib/logging_sink.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contrib/mcp.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contrib/metrics_sink.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/contrib/otel_sink.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/core/agent_context.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/core/events.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/core/hooks.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/core/phase.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/core/runner.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/core/state.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/py.typed +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/blackboard.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/budget.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/cancellation.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/compact.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/env.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/exec_backend.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/fold.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/fold_adapter.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/history_projector.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/history_sanitize.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/human_input.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/memory.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/notes.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/provider.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/representation.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/retry.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/runtime_state.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/session_store.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/skills.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/spec.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/backends/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/backends/mysql.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/backends/postgres.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/capabilities.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/db.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/dialect.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/factory.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/schema.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/store.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/store/types.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/structured.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/stub_provider.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/runtime/timers.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/tools/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/tools/blackboard.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/tools/default_manifest.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/tools/registry.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/tools/spawn_agent.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/__init__.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/api.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/engine.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/introspect.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/journal.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/result.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/resume.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/runner.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/spec.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/subprocess_executor.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/tool.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop/workflow/worker.py +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop.egg-info/SOURCES.txt +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop.egg-info/dependency_links.txt +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop.egg-info/requires.txt +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/power_loop.egg-info/top_level.txt +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/pyproject.toml +0 -0
- {power_loop-3.8.0 → power_loop-3.9.0}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ Stability tiers
|
|
|
15
15
|
无版本承诺,可随时变更或删除。
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "3.
|
|
18
|
+
__version__ = "3.9.0"
|
|
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).
|
|
@@ -125,6 +125,13 @@ class LlmBeforeCtx(BaseHookCtx):
|
|
|
125
125
|
Handler may modify any input field. ``messages`` is the fresh per-call list
|
|
126
126
|
actually sent to the LLM — mutating it (e.g. appending an ephemeral memory
|
|
127
127
|
block) never touches the loop's persisted history.
|
|
128
|
+
|
|
129
|
+
``persist_messages`` is the durable counterpart: any message a handler appends
|
|
130
|
+
here becomes a REAL history/store row (stamped with the round's send_index, via
|
|
131
|
+
the same append path as the loop's own turns) AND is added to this round's
|
|
132
|
+
request. Use it for injected turns that must survive the send — e.g. a periodic
|
|
133
|
+
"you haven't called X in N rounds" reminder — as opposed to the ephemeral,
|
|
134
|
+
request-only edits to ``messages``. Appended in order, at the tail of the request.
|
|
128
135
|
"""
|
|
129
136
|
|
|
130
137
|
messages: list[LoopMessage] = field(default_factory=list)
|
|
@@ -133,6 +140,8 @@ class LlmBeforeCtx(BaseHookCtx):
|
|
|
133
140
|
max_tokens: int = 8000
|
|
134
141
|
temperature: float = 0.0
|
|
135
142
|
session_id: str | None = None
|
|
143
|
+
# Durable injections: persisted as real turns after LLM_BEFORE, then added to the request.
|
|
144
|
+
persist_messages: list[LoopMessage] = field(default_factory=list)
|
|
136
145
|
# Handler output (for SHORT_CIRCUIT)
|
|
137
146
|
output: LLMResponse | None = None
|
|
138
147
|
|
|
@@ -944,6 +944,16 @@ class AgentPipeline:
|
|
|
944
944
|
llm_before.messages, _pre_hook_ids, _hook_audit_mode
|
|
945
945
|
)
|
|
946
946
|
|
|
947
|
+
# Durable LLM_BEFORE injections (persist_messages): unlike the ephemeral, request-only
|
|
948
|
+
# edits to `messages` (captured by hook_audit above), each of these becomes a REAL turn —
|
|
949
|
+
# persisted to history + store with the round's send_index via the loop's own append path
|
|
950
|
+
# — and is added to this round's request tail. Used for injected turns that must survive
|
|
951
|
+
# the send (e.g. a periodic "you haven't called X in N rounds" reminder). Computed AFTER
|
|
952
|
+
# hook_audit so these durable rows don't get counted as ephemeral injections.
|
|
953
|
+
for _pm in llm_before.persist_messages:
|
|
954
|
+
await self._append_message(_pm, round_index=round_idx)
|
|
955
|
+
llm_before.messages.append(dict(_pm))
|
|
956
|
+
|
|
947
957
|
if llm_before.directive == HookDirective.SHORT_CIRCUIT:
|
|
948
958
|
response = llm_before.output
|
|
949
959
|
if not isinstance(response, LLMResponse):
|
|
@@ -124,8 +124,21 @@ def _looks_binary(data: bytes) -> bool:
|
|
|
124
124
|
if not data:
|
|
125
125
|
return False
|
|
126
126
|
sample = data[:4096]
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
# Fast path: mostly-ASCII text (incl. common whitespace) is clearly text.
|
|
128
|
+
ascii_ish = sum(byte in b"\n\r\t\b\f" or 32 <= byte <= 126 for byte in sample)
|
|
129
|
+
if ascii_ish / len(sample) >= 0.70:
|
|
130
|
+
return False
|
|
131
|
+
# Otherwise it may be NON-Latin UTF-8 text (CJK / emoji, whose bytes are mostly >126) —
|
|
132
|
+
# NOT binary. Decode leniently (a multibyte char truncated at the 4KB sample boundary is
|
|
133
|
+
# negligible) and flag as binary only if the text is littered with undecodable bytes
|
|
134
|
+
# (U+FFFD) or C0/C1 control chars (excluding normal whitespace), which real text lacks.
|
|
135
|
+
text = sample.decode("utf-8", errors="replace")
|
|
136
|
+
bad = sum(
|
|
137
|
+
1
|
|
138
|
+
for ch in text
|
|
139
|
+
if ch == "�" or ord(ch) == 0x7f or (ord(ch) < 32 and ch not in "\n\r\t\f\b")
|
|
140
|
+
)
|
|
141
|
+
return bad / len(text) > 0.15
|
|
129
142
|
|
|
130
143
|
|
|
131
144
|
def _read_text(fp: Path, *, max_bytes: int | None = TEXT_FILE_MAX_BYTES) -> str:
|
|
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
|