codex-autorunner 0.1.2__py3-none-any.whl → 1.0.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.
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +118 -30
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +136 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +16 -35
- codex_autorunner/cli.py +157 -139
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +390 -100
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +278 -262
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +15 -9
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/utils.py +29 -2
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +576 -92
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +141 -167
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +175 -0
- codex_autorunner/integrations/telegram/constants.py +16 -1
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
- codex_autorunner/integrations/telegram/helpers.py +88 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +214 -40
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +36 -3
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +23 -14
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +81 -109
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/system.py +6 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +1 -0
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +25 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +162 -196
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +41 -118
- codex_autorunner/static/index.html +787 -858
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -211
- codex_autorunner/static/styles.css +7567 -3865
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/app.py +269 -91
- codex_autorunner/web/middleware.py +3 -4
- codex_autorunner/web/schemas.py +89 -109
- codex_autorunner/web/static_assets.py +1 -44
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { docActionsUI } from "./docsElements.js";
|
|
2
|
-
import { COPYABLE_DOCS, PASTEABLE_DOCS, docsState, getActiveDoc, getDraft, hasDraft, isDraftPreview, } from "./docsState.js";
|
|
3
|
-
export function renderDiffHtml(diffText) {
|
|
4
|
-
if (!diffText)
|
|
5
|
-
return "";
|
|
6
|
-
const lines = diffText.split("\n");
|
|
7
|
-
let oldLineNum = 0;
|
|
8
|
-
let newLineNum = 0;
|
|
9
|
-
const htmlLines = lines.map((line) => {
|
|
10
|
-
const escaped = line
|
|
11
|
-
.replace(/&/g, "&")
|
|
12
|
-
.replace(/</g, "<")
|
|
13
|
-
.replace(/>/g, ">");
|
|
14
|
-
if (line.startsWith("@@") && line.includes("@@")) {
|
|
15
|
-
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
16
|
-
if (match) {
|
|
17
|
-
oldLineNum = parseInt(match[1], 10);
|
|
18
|
-
newLineNum = parseInt(match[2], 10);
|
|
19
|
-
}
|
|
20
|
-
return `<div class="diff-line diff-hunk"><span class="diff-gutter diff-gutter-hunk">···</span><span class="diff-content">${escaped}</span></div>`;
|
|
21
|
-
}
|
|
22
|
-
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
23
|
-
return `<div class="diff-line diff-file"><span class="diff-gutter"></span><span class="diff-content">${escaped}</span></div>`;
|
|
24
|
-
}
|
|
25
|
-
if (line.startsWith("+")) {
|
|
26
|
-
const lineNum = newLineNum++;
|
|
27
|
-
const content = escaped.substring(1);
|
|
28
|
-
const isEmpty = content.trim() === "";
|
|
29
|
-
const displayContent = isEmpty
|
|
30
|
-
? `<span class="diff-empty-marker">↵</span>`
|
|
31
|
-
: content;
|
|
32
|
-
return `<div class="diff-line diff-add"><span class="diff-gutter diff-gutter-add">${lineNum}</span><span class="diff-sign">+</span><span class="diff-content">${displayContent}</span></div>`;
|
|
33
|
-
}
|
|
34
|
-
if (line.startsWith("-")) {
|
|
35
|
-
const lineNum = oldLineNum++;
|
|
36
|
-
const content = escaped.substring(1);
|
|
37
|
-
const isEmpty = content.trim() === "";
|
|
38
|
-
const displayContent = isEmpty
|
|
39
|
-
? `<span class="diff-empty-marker">↵</span>`
|
|
40
|
-
: content;
|
|
41
|
-
return `<div class="diff-line diff-del"><span class="diff-gutter diff-gutter-del">${lineNum}</span><span class="diff-sign">−</span><span class="diff-content">${displayContent}</span></div>`;
|
|
42
|
-
}
|
|
43
|
-
if (line.startsWith(" ") ||
|
|
44
|
-
(line.length > 0 && !line.startsWith("\\") && oldLineNum > 0)) {
|
|
45
|
-
const oLine = oldLineNum++;
|
|
46
|
-
newLineNum += 1;
|
|
47
|
-
const content = escaped.startsWith(" ") ? escaped.substring(1) : escaped;
|
|
48
|
-
return `<div class="diff-line diff-ctx"><span class="diff-gutter diff-gutter-ctx">${oLine}</span><span class="diff-sign"> </span><span class="diff-content">${content}</span></div>`;
|
|
49
|
-
}
|
|
50
|
-
return `<div class="diff-line diff-meta"><span class="diff-gutter"></span><span class="diff-content diff-note">${escaped}</span></div>`;
|
|
51
|
-
});
|
|
52
|
-
return `<div class="diff-view">${htmlLines.join("")}</div>`;
|
|
53
|
-
}
|
|
54
|
-
export function autoResizeTextarea(textarea) {
|
|
55
|
-
textarea.style.height = "auto";
|
|
56
|
-
textarea.style.height = textarea.scrollHeight + "px";
|
|
57
|
-
}
|
|
58
|
-
export function getDocTextarea() {
|
|
59
|
-
return document.getElementById("doc-content");
|
|
60
|
-
}
|
|
61
|
-
export function updateCopyButton(button, text, disabled = false) {
|
|
62
|
-
if (!button)
|
|
63
|
-
return;
|
|
64
|
-
const hasText = Boolean((text || "").trim());
|
|
65
|
-
button.disabled = disabled || !hasText;
|
|
66
|
-
}
|
|
67
|
-
export function getDocCopyText(kind = getActiveDoc()) {
|
|
68
|
-
const textarea = getDocTextarea();
|
|
69
|
-
if (textarea && getActiveDoc() === kind) {
|
|
70
|
-
return textarea.value || "";
|
|
71
|
-
}
|
|
72
|
-
if (kind === "snapshot") {
|
|
73
|
-
return docsState.snapshotCache.content || "";
|
|
74
|
-
}
|
|
75
|
-
return docsState.docsCache[kind] || "";
|
|
76
|
-
}
|
|
77
|
-
export function updateStandardActionButtons(kind = getActiveDoc()) {
|
|
78
|
-
if (docActionsUI.copy) {
|
|
79
|
-
const canCopy = COPYABLE_DOCS.includes(kind);
|
|
80
|
-
docActionsUI.copy.classList.toggle("hidden", !canCopy);
|
|
81
|
-
updateCopyButton(docActionsUI.copy, canCopy ? getDocCopyText(kind) : "");
|
|
82
|
-
}
|
|
83
|
-
if (docActionsUI.paste) {
|
|
84
|
-
const canPaste = PASTEABLE_DOCS.includes(kind);
|
|
85
|
-
docActionsUI.paste.classList.toggle("hidden", !canPaste);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
export function formatDraftTimestamp(value) {
|
|
89
|
-
if (!value)
|
|
90
|
-
return "";
|
|
91
|
-
const parsed = new Date(value);
|
|
92
|
-
if (Number.isNaN(parsed.getTime()))
|
|
93
|
-
return value;
|
|
94
|
-
return parsed.toLocaleString([], {
|
|
95
|
-
month: "short",
|
|
96
|
-
day: "numeric",
|
|
97
|
-
hour: "2-digit",
|
|
98
|
-
minute: "2-digit",
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
export function updateDocVisibility() {
|
|
102
|
-
const docContent = getDocTextarea();
|
|
103
|
-
if (!docContent)
|
|
104
|
-
return;
|
|
105
|
-
const specHasPatch = getActiveDoc() === "spec" && !!(docsState.specIngestState.patch || "").trim();
|
|
106
|
-
docContent.classList.toggle("hidden", specHasPatch);
|
|
107
|
-
}
|
|
108
|
-
export function updateDocStatus(kind) {
|
|
109
|
-
const status = document.getElementById("doc-status");
|
|
110
|
-
if (!status)
|
|
111
|
-
return;
|
|
112
|
-
if (kind === "snapshot") {
|
|
113
|
-
status.textContent = docsState.snapshotBusy ? "Working…" : "Viewing SNAPSHOT";
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const draft = getDraft(kind);
|
|
117
|
-
if (draft && isDraftPreview(kind)) {
|
|
118
|
-
status.textContent = `Previewing ${kind.toUpperCase()} draft`;
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
status.textContent = `Editing ${kind.toUpperCase()}`;
|
|
122
|
-
}
|
|
123
|
-
export function syncDocEditor(kind, { force = false } = {}) {
|
|
124
|
-
const textarea = getDocTextarea();
|
|
125
|
-
if (!textarea)
|
|
126
|
-
return;
|
|
127
|
-
if (kind === "snapshot") {
|
|
128
|
-
textarea.readOnly = true;
|
|
129
|
-
textarea.classList.remove("doc-preview");
|
|
130
|
-
textarea.value = docsState.snapshotCache.content || "";
|
|
131
|
-
textarea.placeholder = "(snapshot will appear here)";
|
|
132
|
-
updateDocStatus(kind);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const draft = getDraft(kind);
|
|
136
|
-
const previewing = !!draft && isDraftPreview(kind);
|
|
137
|
-
const nextValue = previewing ? (draft?.content || "") : docsState.docsCache[kind] || "";
|
|
138
|
-
if (force || textarea.value !== nextValue) {
|
|
139
|
-
textarea.value = nextValue;
|
|
140
|
-
}
|
|
141
|
-
textarea.readOnly = previewing;
|
|
142
|
-
textarea.classList.toggle("doc-preview", previewing);
|
|
143
|
-
textarea.placeholder = previewing ? "(draft preview)" : "";
|
|
144
|
-
updateDocStatus(kind);
|
|
145
|
-
}
|
|
146
|
-
export function updateDocControls(kind = getActiveDoc()) {
|
|
147
|
-
const saveBtn = document.getElementById("save-doc");
|
|
148
|
-
if (saveBtn) {
|
|
149
|
-
const previewing = hasDraft(kind) && isDraftPreview(kind);
|
|
150
|
-
saveBtn.disabled = kind === "snapshot" || previewing;
|
|
151
|
-
}
|
|
152
|
-
updateStandardActionButtons(kind);
|
|
153
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { flash } from "./utils.js";
|
|
2
|
-
import { initVoiceInput } from "./voice.js";
|
|
3
|
-
import { chatUI } from "./docsElements.js";
|
|
4
|
-
import { VOICE_TRANSCRIPT_DISCLAIMER_TEXT } from "./docsState.js";
|
|
5
|
-
import { autoResizeTextarea } from "./docsUi.js";
|
|
6
|
-
function wrapInjectedContext(text) {
|
|
7
|
-
return `<injected context>\n${text}\n</injected context>`;
|
|
8
|
-
}
|
|
9
|
-
function appendVoiceTranscriptDisclaimer(text) {
|
|
10
|
-
const base = text === undefined || text === null ? "" : String(text);
|
|
11
|
-
if (!base.trim())
|
|
12
|
-
return base;
|
|
13
|
-
const injection = wrapInjectedContext(VOICE_TRANSCRIPT_DISCLAIMER_TEXT);
|
|
14
|
-
if (base.includes(VOICE_TRANSCRIPT_DISCLAIMER_TEXT) || base.includes(injection)) {
|
|
15
|
-
return base;
|
|
16
|
-
}
|
|
17
|
-
const separator = base.endsWith("\n") ? "\n" : "\n\n";
|
|
18
|
-
return `${base}${separator}${injection}`;
|
|
19
|
-
}
|
|
20
|
-
function applyVoiceTranscript(text) {
|
|
21
|
-
if (!text) {
|
|
22
|
-
flash("Voice capture returned no transcript", "error");
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const current = chatUI.input.value.trim();
|
|
26
|
-
const prefix = current ? current + " " : "";
|
|
27
|
-
let next = `${prefix}${text}`.trim();
|
|
28
|
-
next = appendVoiceTranscriptDisclaimer(next);
|
|
29
|
-
chatUI.input.value = next;
|
|
30
|
-
autoResizeTextarea(chatUI.input);
|
|
31
|
-
chatUI.input.focus();
|
|
32
|
-
flash("Voice transcript added");
|
|
33
|
-
}
|
|
34
|
-
export async function initDocVoice() {
|
|
35
|
-
if (!chatUI.voiceBtn || !chatUI.input) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
await initVoiceInput({
|
|
39
|
-
button: chatUI.voiceBtn,
|
|
40
|
-
input: chatUI.input,
|
|
41
|
-
statusEl: chatUI.voiceStatus,
|
|
42
|
-
onTranscript: applyVoiceTranscript,
|
|
43
|
-
onError: (msg) => {
|
|
44
|
-
if (msg) {
|
|
45
|
-
flash(msg, "error");
|
|
46
|
-
if (chatUI.voiceStatus) {
|
|
47
|
-
chatUI.voiceStatus.textContent = msg;
|
|
48
|
-
chatUI.voiceStatus.classList.remove("hidden");
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
}).catch((err) => {
|
|
53
|
-
console.error("Voice init failed", err);
|
|
54
|
-
flash("Voice capture unavailable", "error");
|
|
55
|
-
});
|
|
56
|
-
}
|