handler-ai 0.1.0__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.
- handler_ai-0.1.0.dist-info/METADATA +60 -0
- handler_ai-0.1.0.dist-info/RECORD +234 -0
- handler_ai-0.1.0.dist-info/WHEEL +4 -0
- handler_ai-0.1.0.dist-info/entry_points.txt +6 -0
- handler_cli/__init__.py +1 -0
- handler_cli/account_commands.py +314 -0
- handler_cli/addon_commands.py +128 -0
- handler_cli/agent_commands.py +139 -0
- handler_cli/app_commands.py +175 -0
- handler_cli/app_window.py +85 -0
- handler_cli/automation_commands.py +309 -0
- handler_cli/bridge.py +42 -0
- handler_cli/cli.py +326 -0
- handler_cli/client.py +81 -0
- handler_cli/daemon_commands.py +108 -0
- handler_cli/image_commands.py +102 -0
- handler_cli/inbox_commands.py +33 -0
- handler_cli/output.py +12 -0
- handler_cli/project_commands.py +63 -0
- handler_cli/provider_commands.py +113 -0
- handler_cli/secret_commands.py +64 -0
- handler_cli/session_bridge.py +93 -0
- handler_cli/session_commands.py +485 -0
- handler_cli/setup.py +120 -0
- handler_cli/terminal.py +280 -0
- handler_cli/terminal_client.py +37 -0
- handler_contracts/__init__.py +642 -0
- handler_contracts/account.py +36 -0
- handler_contracts/acp_endpoint.py +69 -0
- handler_contracts/addon.py +147 -0
- handler_contracts/agent_spec.py +75 -0
- handler_contracts/automation.py +285 -0
- handler_contracts/common.py +229 -0
- handler_contracts/control.py +1208 -0
- handler_contracts/harness.py +300 -0
- handler_contracts/miniapp.py +141 -0
- handler_contracts/provider.py +82 -0
- handler_contracts/remote.py +167 -0
- handler_contracts/sandbox.py +199 -0
- handler_contracts/schemas.py +313 -0
- handler_contracts/terminal.py +32 -0
- handler_control/__init__.py +7 -0
- handler_control/access.py +62 -0
- handler_control/accounts.py +279 -0
- handler_control/addon_install.py +268 -0
- handler_control/addons.py +118 -0
- handler_control/agent_state.py +46 -0
- handler_control/agents.py +257 -0
- handler_control/api/__init__.py +137 -0
- handler_control/api/accounts.py +231 -0
- handler_control/api/acp.py +116 -0
- handler_control/api/addons.py +95 -0
- handler_control/api/agents.py +150 -0
- handler_control/api/apps.py +104 -0
- handler_control/api/automations.py +128 -0
- handler_control/api/bindings.py +105 -0
- handler_control/api/builtin_agents.py +22 -0
- handler_control/api/caches.py +79 -0
- handler_control/api/catalog.py +197 -0
- handler_control/api/common.py +205 -0
- handler_control/api/contexts.py +88 -0
- handler_control/api/folders.py +51 -0
- handler_control/api/images.py +164 -0
- handler_control/api/inbox.py +21 -0
- handler_control/api/projects.py +170 -0
- handler_control/api/providers.py +107 -0
- handler_control/api/remote.py +45 -0
- handler_control/api/secrets.py +177 -0
- handler_control/api/sessions.py +192 -0
- handler_control/api/skills.py +270 -0
- handler_control/app_api.py +84 -0
- handler_control/app_http.py +214 -0
- handler_control/app_remote.py +206 -0
- handler_control/apps.py +867 -0
- handler_control/auth.py +48 -0
- handler_control/automation_config.py +255 -0
- handler_control/automations.py +1134 -0
- handler_control/bindings.py +149 -0
- handler_control/builds.py +341 -0
- handler_control/builtin_agents.py +82 -0
- handler_control/caches.py +113 -0
- handler_control/checks.py +287 -0
- handler_control/compose.py +317 -0
- handler_control/config_files.py +41 -0
- handler_control/contexts.py +75 -0
- handler_control/daemon.py +64 -0
- handler_control/data/automation/bin/handler-llm +85 -0
- handler_control/data/automations/env-issues/automation.toml +17 -0
- handler_control/data/automations/env-issues/extract.py +73 -0
- handler_control/data/automations/env-issues/findings.schema.json +28 -0
- handler_control/data/automations/env-issues/prompt.md +19 -0
- handler_control/data/builtin-agents/skill-creator/agent.toml +10 -0
- handler_control/data/builtin-agents/skill-creator/instructions.md +5 -0
- handler_control/data/builtin-agents/skill-creator/skills/handler-skills/SKILL.md +44 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/LICENSE.txt +202 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/SKILL.md +485 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/analyzer.md +274 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/comparator.md +202 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/agents/grader.md +223 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/assets/eval_review.html +146 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/references/schemas.md +430 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/__init__.py +0 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/generate_report.py +326 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/improve_description.py +247 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/package_skill.py +136 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/quick_validate.py +103 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/run_eval.py +310 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/run_loop.py +328 -0
- handler_control/data/builtin-agents/skill-creator/skills/skill-creator/scripts/utils.py +47 -0
- handler_control/data/builtin-agents/skill-review/agent.toml +13 -0
- handler_control/data/builtin-agents/skill-review/instructions.md +30 -0
- handler_control/data/builtin-agents/skill-review/interface.json +83 -0
- handler_control/data/interfaces/llm/v1/interface.toml +17 -0
- handler_control/data/interfaces/llm/v1/tools.json +138 -0
- handler_control/data/templates/README.md +10 -0
- handler_control/data/templates/providers/anthropic-api.toml +18 -0
- handler_control/data/templates/providers/llamacpp.toml +210 -0
- handler_control/data/templates/providers/mistral-api.toml +14 -0
- handler_control/data/templates/providers/ollama.toml +10 -0
- handler_control/data/templates/providers/openai-api.toml +13 -0
- handler_control/data/templates/providers/openrouter.toml +10 -0
- handler_control/desktop.py +68 -0
- handler_control/display.py +62 -0
- handler_control/files.py +23 -0
- handler_control/folders.py +68 -0
- handler_control/git_read.py +39 -0
- handler_control/image_check.py +322 -0
- handler_control/image_updates.py +174 -0
- handler_control/inbox.py +70 -0
- handler_control/interfaces.py +151 -0
- handler_control/knowledge.py +237 -0
- handler_control/launcher.py +356 -0
- handler_control/llm.py +194 -0
- handler_control/migrations/__init__.py +62 -0
- handler_control/policy.py +305 -0
- handler_control/projects.py +266 -0
- handler_control/providers.py +180 -0
- handler_control/relay.py +133 -0
- handler_control/remote.py +888 -0
- handler_control/remote_crypto.py +97 -0
- handler_control/runtime.py +140 -0
- handler_control/sandbox_process.py +224 -0
- handler_control/schedules.py +58 -0
- handler_control/secrets.py +242 -0
- handler_control/service_router.py +180 -0
- handler_control/services.py +92 -0
- handler_control/session_files.py +429 -0
- handler_control/sessions.py +1116 -0
- handler_control/settings.py +129 -0
- handler_control/shortcuts.py +80 -0
- handler_control/skill_scan.py +88 -0
- handler_control/skill_sources.py +61 -0
- handler_control/skills.py +378 -0
- handler_control/terminal.py +79 -0
- handler_control/terminals.py +108 -0
- handler_control/transcript.py +174 -0
- handler_control/web-ui/assets/index-1w5RPsfA.js +190 -0
- handler_control/web-ui/assets/index-dWnzCLzQ.css +1 -0
- handler_control/web-ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- handler_control/web-ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- handler_control/web-ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- handler_control/web-ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- handler_control/web-ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- handler_control/web-ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- handler_control/web-ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- handler_control/web-ui/favicon.svg +1 -0
- handler_control/web-ui/index.html +15 -0
- handler_control/webpush.py +90 -0
- handler_control/webui.py +105 -0
- handler_harness/__init__.py +13 -0
- handler_harness/addons.py +129 -0
- handler_harness/catalog.py +116 -0
- handler_harness/cli.py +109 -0
- handler_harness/data/addons/README.md +44 -0
- handler_harness/data/addons/miniapp/Containerfile +20 -0
- handler_harness/data/addons/miniapp/PROVISIONING.md +16 -0
- handler_harness/data/addons/miniapp/addon.toml +16 -0
- handler_harness/data/addons/miniapp/skills/miniapp/SKILL.md +140 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/.gitignore +2 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/app.toml +15 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/db.ts +30 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/deno.json +7 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/handler.ts +26 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/main.ts +18 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/main_test.ts +32 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/migrations/001_notes.sql +6 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/server/routes.ts +32 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/deno.json +12 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/index.html +12 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/App.svelte +44 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/api.ts +16 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/app.css +24 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/src/main.ts +5 -0
- handler_harness/data/addons/miniapp/skills/miniapp/template/ui/vite.config.ts +17 -0
- handler_harness/data/catalog/README.md +12 -0
- handler_harness/data/catalog/playwright/Containerfile +28 -0
- handler_harness/data/catalog/playwright/PROVISIONING.md +53 -0
- handler_harness/data/catalog/playwright/addon.toml +25 -0
- handler_harness/data/catalog/playwright/handler/playwright-mcp-check.mjs +23 -0
- handler_harness/data/catalog/playwright/handler/playwright-mcp.mjs +61 -0
- handler_harness/data/catalog/playwright/skills/browser/SKILL.md +41 -0
- handler_harness/data/catalog/shopify/Containerfile +22 -0
- handler_harness/data/catalog/shopify/PROVISIONING.md +49 -0
- handler_harness/data/catalog/shopify/addon.toml +39 -0
- handler_harness/data/catalog/shopify/skills/shopify/SKILL.md +41 -0
- handler_harness/data/handler-sandbox-env +30 -0
- handler_harness/data/harnesses/README.md +47 -0
- handler_harness/data/harnesses/claude-code/Containerfile +27 -0
- handler_harness/data/harnesses/claude-code/harness.toml +95 -0
- handler_harness/data/harnesses/codex/Containerfile +26 -0
- handler_harness/data/harnesses/codex/harness.toml +103 -0
- handler_harness/data/harnesses/pi/Containerfile +27 -0
- handler_harness/data/harnesses/pi/harness.toml +65 -0
- handler_harness/data/harnesses/vibe/Containerfile +19 -0
- handler_harness/data/harnesses/vibe/harness.toml +96 -0
- handler_harness/data/toolbox/Containerfile +56 -0
- handler_harness/data/toolbox/README.md +24 -0
- handler_harness/data/toolbox/toolbox.toml +41 -0
- handler_harness/images.py +62 -0
- handler_harness/providers.py +300 -0
- handler_harness/render.py +472 -0
- handler_sandbox_podman/__init__.py +1 -0
- handler_sandbox_podman/caches.py +97 -0
- handler_sandbox_podman/check.py +121 -0
- handler_sandbox_podman/cli.py +239 -0
- handler_sandbox_podman/egress.py +118 -0
- handler_sandbox_podman/images.py +199 -0
- handler_sandbox_podman/launch.py +474 -0
- handler_sandbox_podman/podman.py +108 -0
- handler_sandbox_podman/proxy/Containerfile +6 -0
- handler_sandbox_podman/proxy/addon.py +255 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""A session's event log (F.5), or a slice of it, as markdown for reading (I.4): what an
|
|
2
|
+
automation's script, and the model it calls, read of a conversation.
|
|
3
|
+
|
|
4
|
+
read the events of a log from one `seq` to another
|
|
5
|
+
render those events as a transcript: a header, then each event in order with its `seq`
|
|
6
|
+
|
|
7
|
+
Pure: no redaction here. The log is already redacted with the session's own secrets (F.5); the
|
|
8
|
+
caller redacts the result again with every value of the secret store before it leaves.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
HEAD = 2000 # characters kept at the start of a tool call's input or output
|
|
19
|
+
TAIL = 1000 # and at its end
|
|
20
|
+
CHUNKS = {
|
|
21
|
+
"user_message_chunk": "Person",
|
|
22
|
+
"agent_message_chunk": "Agent",
|
|
23
|
+
"agent_thought_chunk": "Agent's thought",
|
|
24
|
+
}
|
|
25
|
+
TOOL_CALLS = ("tool_call", "tool_call_update")
|
|
26
|
+
|
|
27
|
+
Event = dict[str, Any]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read(log: Path, seq_from: int, seq_to: int) -> list[Event]:
|
|
31
|
+
"""The events of `log` with `seq_from <= seq <= seq_to`, in order (unreadable lines skipped)."""
|
|
32
|
+
events = []
|
|
33
|
+
with contextlib.suppress(FileNotFoundError):
|
|
34
|
+
for line in log.read_text().splitlines():
|
|
35
|
+
with contextlib.suppress(ValueError, KeyError, TypeError):
|
|
36
|
+
event = json.loads(line)
|
|
37
|
+
if seq_from <= event["seq"] <= seq_to:
|
|
38
|
+
events.append(event)
|
|
39
|
+
return events
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def cut(text: str) -> str:
|
|
43
|
+
"""The first HEAD and last TAIL characters, saying how many were left out between them."""
|
|
44
|
+
if len(text) <= HEAD + TAIL:
|
|
45
|
+
return text
|
|
46
|
+
left = len(text) - HEAD - TAIL
|
|
47
|
+
return f"{text[:HEAD]}\n… ({left} characters left out) …\n{text[-TAIL:]}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def fenced(text: str, info: str = "") -> str:
|
|
51
|
+
"""A code block that the text cannot close (its fence is longer than any run of backticks)."""
|
|
52
|
+
longest = max((len(m) for m in re.findall(r"`+", text)), default=0)
|
|
53
|
+
fence = "`" * max(3, longest + 1)
|
|
54
|
+
return f"{fence}{info}\n{text}\n{fence}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def block_text(block: Any) -> str:
|
|
58
|
+
"""One ACP content block, as text."""
|
|
59
|
+
if not isinstance(block, dict):
|
|
60
|
+
return ""
|
|
61
|
+
kind = block.get("type")
|
|
62
|
+
if kind == "text":
|
|
63
|
+
return str(block.get("text", ""))
|
|
64
|
+
if kind == "resource_link":
|
|
65
|
+
return f"[link: {block.get('uri', '')}]"
|
|
66
|
+
if kind == "resource":
|
|
67
|
+
resource = block.get("resource") or {}
|
|
68
|
+
return str(resource.get("text") or f"[resource: {resource.get('uri', '')}]")
|
|
69
|
+
return f"[{kind}]"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def tool_output(call: Event) -> str:
|
|
73
|
+
"""A tool call's output: its raw output, else its content (text, diffs)."""
|
|
74
|
+
if call.get("rawOutput") is not None:
|
|
75
|
+
raw = call["rawOutput"]
|
|
76
|
+
return raw if isinstance(raw, str) else json.dumps(raw, indent=2)
|
|
77
|
+
parts = []
|
|
78
|
+
for item in call.get("content") or []:
|
|
79
|
+
if not isinstance(item, dict):
|
|
80
|
+
continue
|
|
81
|
+
if item.get("type") == "content":
|
|
82
|
+
parts.append(block_text(item.get("content")))
|
|
83
|
+
elif item.get("type") == "diff":
|
|
84
|
+
parts.append(f"diff of {item.get('path', '')}:\n{item.get('newText', '')}")
|
|
85
|
+
return "\n".join(p for p in parts if p)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def tool_call(seq: int, call: Event) -> str:
|
|
89
|
+
kind, status = call.get("kind"), call.get("status")
|
|
90
|
+
about = ", ".join(str(x) for x in (kind, status) if x)
|
|
91
|
+
lines = [f"### [{seq}] Tool call: {call.get('title') or call.get('toolCallId', '')}"]
|
|
92
|
+
if about:
|
|
93
|
+
lines[0] += f" ({about})"
|
|
94
|
+
if call.get("rawInput") is not None:
|
|
95
|
+
raw = call["rawInput"]
|
|
96
|
+
text = raw if isinstance(raw, str) else json.dumps(raw, indent=2)
|
|
97
|
+
lines.append("Input:\n" + fenced(cut(text)))
|
|
98
|
+
if output := tool_output(call):
|
|
99
|
+
lines.append("Output:\n" + fenced(cut(output)))
|
|
100
|
+
return "\n\n".join(lines)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def merged_calls(events: Sequence[Event]) -> dict[str, Event]:
|
|
104
|
+
"""toolCallId → the call as it ended: its `tool_call` with its updates merged into it."""
|
|
105
|
+
calls: dict[str, Event] = {}
|
|
106
|
+
for event in events:
|
|
107
|
+
update = event.get("update") or {}
|
|
108
|
+
if event.get("kind") == "update" and update.get("sessionUpdate") in TOOL_CALLS:
|
|
109
|
+
call_id = str(update.get("toolCallId", ""))
|
|
110
|
+
merged = calls.get(call_id, {})
|
|
111
|
+
calls[call_id] = merged | {k: v for k, v in update.items() if v is not None}
|
|
112
|
+
return calls
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def line(event: Event) -> str | None:
|
|
116
|
+
"""An event that is neither a message chunk nor a tool call, or None when not shown."""
|
|
117
|
+
seq, kind, update = event.get("seq", 0), event.get("kind"), event.get("update") or {}
|
|
118
|
+
if kind == "update" and update.get("sessionUpdate") == "plan":
|
|
119
|
+
entries = update.get("entries") or []
|
|
120
|
+
items = "\n".join(f"- [{e.get('status', '')}] {e.get('content', '')}" for e in entries)
|
|
121
|
+
return f"[{seq}] Plan:\n{items}"
|
|
122
|
+
if kind == "turn" and event.get("phase") == "end":
|
|
123
|
+
return f"[{seq}] Turn ended ({event.get('stopReason', '')})"
|
|
124
|
+
if kind != "lifecycle":
|
|
125
|
+
return None
|
|
126
|
+
detail = event.get("detail", "")
|
|
127
|
+
how = f" ({event['how']})" if event.get("how") else ""
|
|
128
|
+
shown = {
|
|
129
|
+
"started": "Agent started",
|
|
130
|
+
"stopped": f"Session stopped{how}: {detail}",
|
|
131
|
+
"permission answered": f"Permission answered: {detail}",
|
|
132
|
+
}.get(event.get("event", ""))
|
|
133
|
+
return f"[{seq}] {shown}" if shown else None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def render(events: Sequence[Event], header: Sequence[tuple[str, str]]) -> str:
|
|
137
|
+
"""The transcript: `header` (label, value) as a list, then the events. Message chunks of one
|
|
138
|
+
kind that follow each other are joined; a tool call is shown where it started, as it ended."""
|
|
139
|
+
calls = merged_calls(events)
|
|
140
|
+
parts = ["# Session transcript", "\n".join(f"- {label}: {value}" for label, value in header)]
|
|
141
|
+
chunk_kind: str | None = None
|
|
142
|
+
chunk_seq = 0
|
|
143
|
+
chunk: list[str] = []
|
|
144
|
+
shown: set[str] = set()
|
|
145
|
+
|
|
146
|
+
def flush() -> None:
|
|
147
|
+
nonlocal chunk_kind
|
|
148
|
+
if chunk_kind is not None and (text := "".join(chunk).strip()):
|
|
149
|
+
parts.append(f"## [{chunk_seq}] {CHUNKS[chunk_kind]}\n\n{text}")
|
|
150
|
+
chunk_kind = None
|
|
151
|
+
chunk.clear()
|
|
152
|
+
|
|
153
|
+
for event in events:
|
|
154
|
+
update = event.get("update") or {}
|
|
155
|
+
what = update.get("sessionUpdate") if event.get("kind") == "update" else None
|
|
156
|
+
if what in CHUNKS:
|
|
157
|
+
if what != chunk_kind:
|
|
158
|
+
flush()
|
|
159
|
+
chunk_kind, chunk_seq = what, event.get("seq", 0)
|
|
160
|
+
chunk.append(block_text(update.get("content")))
|
|
161
|
+
continue
|
|
162
|
+
shown_as = None
|
|
163
|
+
if what in TOOL_CALLS:
|
|
164
|
+
call_id = str(update.get("toolCallId", ""))
|
|
165
|
+
if call_id not in shown:
|
|
166
|
+
shown.add(call_id)
|
|
167
|
+
shown_as = tool_call(event.get("seq", 0), calls[call_id])
|
|
168
|
+
else:
|
|
169
|
+
shown_as = line(event)
|
|
170
|
+
if shown_as is not None:
|
|
171
|
+
flush()
|
|
172
|
+
parts.append(shown_as)
|
|
173
|
+
flush()
|
|
174
|
+
return "\n\n".join(parts) + "\n"
|