power-loop 3.3.0__tar.gz → 3.4.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.
Files changed (104) hide show
  1. {power_loop-3.3.0 → power_loop-3.4.0}/PKG-INFO +1 -1
  2. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/__init__.py +3 -1
  3. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/representation.py +102 -34
  4. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop.egg-info/PKG-INFO +1 -1
  5. {power_loop-3.3.0 → power_loop-3.4.0}/LICENSE +0 -0
  6. {power_loop-3.3.0 → power_loop-3.4.0}/README.md +0 -0
  7. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/__init__.py +0 -0
  8. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
  9. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
  10. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
  11. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/interface.py +0 -0
  12. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
  13. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
  14. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
  15. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
  16. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/agent/__init__.py +0 -0
  17. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/agent/follow_up.py +0 -0
  18. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/agent/sink.py +0 -0
  19. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/agent/stateful_loop.py +0 -0
  20. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/agent/system_prompt.py +0 -0
  21. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/agent/types.py +0 -0
  22. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/__init__.py +0 -0
  23. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/errors.py +0 -0
  24. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/event_payloads.py +0 -0
  25. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/events.py +0 -0
  26. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/handlers.py +0 -0
  27. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/hook_contexts.py +0 -0
  28. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/hooks.py +0 -0
  29. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/messages.py +0 -0
  30. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/protocols.py +0 -0
  31. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contracts/tools.py +0 -0
  32. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contrib/__init__.py +0 -0
  33. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contrib/_redact.py +0 -0
  34. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contrib/jsonl_sink.py +0 -0
  35. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contrib/logging_sink.py +0 -0
  36. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contrib/mcp.py +0 -0
  37. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contrib/metrics_sink.py +0 -0
  38. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/contrib/otel_sink.py +0 -0
  39. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/core/agent_context.py +0 -0
  40. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/core/events.py +0 -0
  41. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/core/hooks.py +0 -0
  42. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/core/phase.py +0 -0
  43. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/core/pipeline.py +0 -0
  44. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/core/runner.py +0 -0
  45. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/core/state.py +0 -0
  46. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/py.typed +0 -0
  47. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/blackboard.py +0 -0
  48. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/budget.py +0 -0
  49. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/cancellation.py +0 -0
  50. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/compact.py +0 -0
  51. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/env.py +0 -0
  52. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/exec_backend.py +0 -0
  53. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/fold.py +0 -0
  54. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/fold_adapter.py +0 -0
  55. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/history_projector.py +0 -0
  56. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/history_sanitize.py +0 -0
  57. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/human_input.py +0 -0
  58. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/memory.py +0 -0
  59. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/notes.py +0 -0
  60. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/provider.py +0 -0
  61. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/retry.py +0 -0
  62. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/runtime_state.py +0 -0
  63. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/session_store.py +0 -0
  64. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/skills.py +0 -0
  65. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/spec.py +0 -0
  66. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/__init__.py +0 -0
  67. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/__init__.py +0 -0
  68. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/mysql.py +0 -0
  69. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/postgres.py +0 -0
  70. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
  71. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/capabilities.py +0 -0
  72. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/db.py +0 -0
  73. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/dialect.py +0 -0
  74. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/factory.py +0 -0
  75. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/schema.py +0 -0
  76. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/store.py +0 -0
  77. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/store/types.py +0 -0
  78. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/structured.py +0 -0
  79. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/stub_provider.py +0 -0
  80. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/runtime/timers.py +0 -0
  81. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/tools/__init__.py +0 -0
  82. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/tools/blackboard.py +0 -0
  83. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/tools/default_manifest.py +0 -0
  84. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/tools/default_tools.py +0 -0
  85. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/tools/registry.py +0 -0
  86. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/tools/spawn_agent.py +0 -0
  87. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/__init__.py +0 -0
  88. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/api.py +0 -0
  89. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/engine.py +0 -0
  90. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/introspect.py +0 -0
  91. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/journal.py +0 -0
  92. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/result.py +0 -0
  93. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/resume.py +0 -0
  94. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/runner.py +0 -0
  95. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/spec.py +0 -0
  96. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/subprocess_executor.py +0 -0
  97. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/tool.py +0 -0
  98. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop/workflow/worker.py +0 -0
  99. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop.egg-info/SOURCES.txt +0 -0
  100. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop.egg-info/dependency_links.txt +0 -0
  101. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop.egg-info/requires.txt +0 -0
  102. {power_loop-3.3.0 → power_loop-3.4.0}/power_loop.egg-info/top_level.txt +0 -0
  103. {power_loop-3.3.0 → power_loop-3.4.0}/pyproject.toml +0 -0
  104. {power_loop-3.3.0 → power_loop-3.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 3.3.0
3
+ Version: 3.4.0
4
4
  Summary: Embeddable agent execution kernel — LLM loop, hooks, events, tools, dynamic sub-agents.
5
5
  Author-email: zhangran <zhangran24@126.com>
6
6
  License: MIT
@@ -15,7 +15,7 @@ Stability tiers
15
15
  无版本承诺,可随时变更或删除。
16
16
  """
17
17
 
18
- __version__ = "3.3.0"
18
+ __version__ = "3.4.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).
@@ -177,6 +177,7 @@ from power_loop.runtime.representation import (
177
177
  ProjectedRepresentation,
178
178
  ProjectedRow,
179
179
  ProjectedSend,
180
+ ProjectionRenderConfig,
180
181
  Representation,
181
182
  VerbatimRepresentation,
182
183
  )
@@ -338,6 +339,7 @@ __all__ = [
338
339
  "Representation",
339
340
  "VerbatimRepresentation",
340
341
  "ProjectedRepresentation",
342
+ "ProjectionRenderConfig",
341
343
  "FoldStrategy",
342
344
  "FoldContext",
343
345
  "FoldResult",
@@ -23,6 +23,7 @@ from __future__ import annotations
23
23
  import json
24
24
  from collections import deque
25
25
  from dataclasses import dataclass, field
26
+ from dataclasses import fields as dataclass_fields
26
27
  from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
27
28
 
28
29
  from power_loop.runtime.store.types import MessageRow, ProjectMessageRow
@@ -40,6 +41,7 @@ __all__ = [
40
41
  "Representation",
41
42
  "VerbatimRepresentation",
42
43
  "ProjectedRepresentation",
44
+ "ProjectionRenderConfig",
43
45
  "ProjectedRow",
44
46
  "ProjectedSend",
45
47
  ]
@@ -175,6 +177,34 @@ class VerbatimRepresentation:
175
177
  # ── Projection (generic structured summary; no LLM) ─────────────────────────────
176
178
 
177
179
 
180
+ @dataclass
181
+ class ProjectionRenderConfig:
182
+ """Tunable format knobs for :meth:`ProjectedRepresentation.render` — they change the SHAPE of the
183
+ rendered text only, never what is stored. Every field is a plain scalar so the whole config
184
+ round-trips through JSON (a host can surface it in an admin UI and retune the rendered context
185
+ live, then pass it back via ``ProjectedRepresentation(render_config=…)``). Placeholders: ``{n}`` in
186
+ a tag → the send_index (empty tag or ``None`` index → no tag); ``{range}`` in ``fold_note`` → the
187
+ folded ``#lo–#hi`` span. The defaults reproduce the built-in rendering exactly."""
188
+
189
+ user_tag: str = "[#{n}] " # prefix on a user/input row; {n}=send_index
190
+ project_tag: str = "#{n} " # prefix on a project/assistant row
191
+ tools_header: str = "[tools] " # before the tool list in a project row
192
+ tool_sep: str = "; " # between tools
193
+ tool_arg_sep: str = ", " # between a tool's k=v fields
194
+ include_tools: bool = True # render the tool list at all
195
+ include_final_text: bool = True # render the project row's trailing final_text
196
+ empty_project: str = "(no output)" # a project row that renders to nothing
197
+ fold_note: str = "[older sends {range} folded — recall_send(send_index=N) to expand]"
198
+
199
+ @classmethod
200
+ def from_dict(cls, data: dict[str, Any] | None) -> ProjectionRenderConfig:
201
+ """Build from a plain dict (e.g. JSON config), silently ignoring unknown keys."""
202
+ if not data:
203
+ return cls()
204
+ names = {f.name for f in dataclass_fields(cls)}
205
+ return cls(**{k: v for k, v in data.items() if k in names})
206
+
207
+
178
208
  @dataclass
179
209
  class ProjectedRepresentation:
180
210
  """Generic, deterministic, no-LLM per-send projection. Each send →
@@ -183,14 +213,24 @@ class ProjectedRepresentation:
183
213
  ``project`` row: ``{"tools": [...], "final_text": ...}``. Each tool call is summarized via its
184
214
  ``ToolDefinition.project`` hook when present, else a truncating fallback. Rendered to terse
185
215
  plain text with NO tool-protocol structure. (This is the old ``DefaultDeterministicProjector``
186
- MINUS its fold knobs/``compact()`` — folding now lives on :class:`FoldStrategy`.)"""
216
+ MINUS its fold knobs/``compact()`` — folding now lives on :class:`FoldStrategy`.)
217
+
218
+ The render is customizable two ways without copy-pasting it: pass a :class:`ProjectionRenderConfig`
219
+ (``render_config=`` — JSON-friendly format knobs, retunable from config/an admin UI) and/or
220
+ override one of the small per-row methods (``render_row`` → ``render_user_row`` /
221
+ ``render_project_row`` / ``render_compact_row``). Defaults reproduce the prior output exactly."""
187
222
 
188
223
  kind: str = "projection"
189
224
  version: int = 1
190
225
  max_chars: int = 300 # per-field truncation budget
226
+ #: Format knobs for render() — see :class:`ProjectionRenderConfig`. A plain dict is coerced (so a
227
+ #: host can pass JSON config straight through). Subclasses can ALSO override the render_* methods.
228
+ render_config: ProjectionRenderConfig = field(default_factory=ProjectionRenderConfig)
191
229
 
192
230
  def __post_init__(self) -> None:
193
231
  _validate_representation_params(version=self.version, max_chars=self.max_chars)
232
+ if isinstance(self.render_config, dict):
233
+ self.render_config = ProjectionRenderConfig.from_dict(self.render_config)
194
234
 
195
235
  def project_send(
196
236
  self, send_rows: list[MessageRow], *, send_index: int, tool_registry: ToolRegistry | None
@@ -264,55 +304,83 @@ class ProjectedRepresentation:
264
304
  return {"name": name, "result": _truncate(result_str, self.max_chars)}
265
305
 
266
306
  # rendering ----------------------------------------------------------------
307
+ # Orchestration only; the per-row shapes live in small overridable methods (override exactly the
308
+ # one you want instead of copy-pasting render) and the format lives in render_config (retune from
309
+ # config, no code). Each rendered send is tagged with its ``#N`` send_index so the model can call
310
+ # recall_send(send_index=N) on a folded earlier turn — the tool docstring + the host's
311
+ # RECALL_SEND_NOTE tell it to use "the #N the summary shows", so the tags MUST be emitted (else
312
+ # recall_send is undiscoverable); changing user_tag/project_tag away from a ``{n}`` form is at the
313
+ # host's own risk.
267
314
  def render(self, rows: list[ProjectMessageRow]) -> list[LoopMessage]:
268
- # Each rendered send is tagged with its ``#N`` send_index so the model can call
269
- # recall_send(send_index=N) on a folded/compacted earlier turn — the tool docstring and the
270
- # host's RECALL_SEND_NOTE both tell it to use "the #N the summary shows", so render MUST
271
- # actually emit them (else recall_send is undiscoverable). The folded compact carries its
272
- # covered range.
273
315
  out: list[LoopMessage] = []
274
316
  for r in rows:
275
- si = r.send_index
276
- if r.kind == "user":
277
- content = r.content or {}
278
- # ``input`` since 3.3; ``human`` is the pre-3.3 key — read both so old projection
279
- # rows still render correctly after upgrade.
280
- inputs = content.get("input")
281
- if inputs is None:
282
- inputs = content.get("human") or []
283
- tag = f"[#{si}] " if si is not None else ""
284
- out.append({"role": "user", "content": tag + "\n".join(str(h) for h in inputs)})
285
- elif r.kind == "project":
286
- tag = f"#{si} " if si is not None else ""
287
- out.append({"role": "assistant", "content": tag + self._render_project(r.content)})
288
- elif r.kind == "compact":
289
- msg = _render_compact_row(r)
290
- lo, hi = r.compact_from_send, r.compact_to_send
291
- if lo is not None and hi is not None and hi >= lo > 0:
292
- rng = f"#{lo}" if lo == hi else f"#{lo}–#{hi}"
293
- msg = {
294
- "role": "user",
295
- "content": f"[older sends {rng} folded — recall_send(send_index=N) to "
296
- f"expand]\n{msg['content']}",
297
- }
317
+ msg = self.render_row(r)
318
+ if msg is not None:
298
319
  out.append(msg)
299
320
  return out
300
321
 
322
+ def render_row(self, r: ProjectMessageRow) -> LoopMessage | None:
323
+ """Dispatch one stored row to its per-kind renderer. Override to add a kind / change routing;
324
+ an unhandled kind returns None (skipped)."""
325
+ if r.kind == "user":
326
+ return self.render_user_row(r)
327
+ if r.kind == "project":
328
+ return self.render_project_row(r)
329
+ if r.kind == "compact":
330
+ return self.render_compact_row(r)
331
+ return None
332
+
333
+ def render_user_row(self, r: ProjectMessageRow) -> LoopMessage:
334
+ content = r.content or {}
335
+ # ``input`` since 3.3; ``human`` is the pre-3.3 key — read both so old rows still render.
336
+ inputs = content.get("input")
337
+ if inputs is None:
338
+ inputs = content.get("human") or []
339
+ return {
340
+ "role": "user",
341
+ "content": self._send_tag(self.render_config.user_tag, r.send_index)
342
+ + "\n".join(str(h) for h in inputs),
343
+ }
344
+
345
+ def render_project_row(self, r: ProjectMessageRow) -> LoopMessage:
346
+ return {
347
+ "role": "assistant",
348
+ "content": self._send_tag(self.render_config.project_tag, r.send_index)
349
+ + self._render_project(r.content),
350
+ }
351
+
352
+ def render_compact_row(self, r: ProjectMessageRow) -> LoopMessage:
353
+ msg = _render_compact_row(r)
354
+ lo, hi = r.compact_from_send, r.compact_to_send
355
+ if lo is not None and hi is not None and hi >= lo > 0:
356
+ rng = f"#{lo}" if lo == hi else f"#{lo}–#{hi}"
357
+ note = self.render_config.fold_note.replace("{range}", rng)
358
+ return {"role": "user", "content": f"{note}\n{msg['content']}"}
359
+ return msg
360
+
361
+ @staticmethod
362
+ def _send_tag(template: str, send_index: int | None) -> str:
363
+ """A send_index tag from a ``{n}`` template; empty template or ``None`` index → no tag."""
364
+ if send_index is None or not template:
365
+ return ""
366
+ return template.replace("{n}", str(send_index))
367
+
301
368
  def _render_tool(self, t: dict[str, Any]) -> str:
302
369
  name = t.get("name", "?")
303
370
  rest = {k: v for k, v in (t or {}).items() if k != "name"}
304
371
  if not rest:
305
372
  return str(name)
306
- body = ", ".join(f"{k}={v}" for k, v in rest.items())
373
+ body = self.render_config.tool_arg_sep.join(f"{k}={v}" for k, v in rest.items())
307
374
  return f"{name}({body})"
308
375
 
309
376
  def _render_project(self, content: dict[str, Any] | None) -> str:
310
377
  content = content or {}
378
+ cfg = self.render_config
311
379
  parts: list[str] = []
312
380
  tools = content.get("tools") or []
313
- if tools:
314
- parts.append("[tools] " + "; ".join(self._render_tool(t) for t in tools))
381
+ if tools and cfg.include_tools:
382
+ parts.append(cfg.tools_header + cfg.tool_sep.join(self._render_tool(t) for t in tools))
315
383
  ft = content.get("final_text")
316
- if ft:
384
+ if ft and cfg.include_final_text:
317
385
  parts.append(str(ft))
318
- return "\n".join(parts) if parts else "(no output)"
386
+ return "\n".join(parts) if parts else cfg.empty_project
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: power-loop
3
- Version: 3.3.0
3
+ Version: 3.4.0
4
4
  Summary: Embeddable agent execution kernel — LLM loop, hooks, events, tools, dynamic sub-agents.
5
5
  Author-email: zhangran <zhangran24@126.com>
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes