codex-flow 2.1.13__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_flow/__init__.py +28 -0
- codex_flow/__main__.py +9 -0
- codex_flow/cli.py +242 -0
- codex_flow/data/LICENSE +21 -0
- codex_flow/data/README.en.md +303 -0
- codex_flow/data/README.md +305 -0
- codex_flow/data/VERSION +1 -0
- codex_flow/data/apps/chatgpt-mcp/README.md +86 -0
- codex_flow/data/apps/chatgpt-mcp/__init__.py +1 -0
- codex_flow/data/apps/chatgpt-mcp/adapter.py +458 -0
- codex_flow/data/apps/chatgpt-mcp/server.py +358 -0
- codex_flow/data/apps/chatgpt-mcp/widget.html +927 -0
- codex_flow/data/apps/macos-overlay/README.en.md +121 -0
- codex_flow/data/apps/macos-overlay/README.md +123 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayRuntimeState.swift +126 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayScreenGeometry.swift +82 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayWindowController.swift +1052 -0
- codex_flow/data/apps/macos-overlay/Sources/Localization.swift +197 -0
- codex_flow/data/apps/macos-overlay/Sources/Models/TelemetryData.swift +1557 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/AccountSnapshotService.swift +1101 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/FlowPilotInstanceLock.swift +153 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/IPCServer.swift +298 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryQueryEngine.swift +800 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryWatcher.swift +135 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/UpdateService.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AccountView.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AnalyticsView.swift +566 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AutostartView.swift +293 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/BubbleView.swift +317 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HistoryView.swift +1124 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HoverRevealText.swift +165 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/InspectorSkillsToolsView.swift +121 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/LogoView.swift +182 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SleekSwitch.swift +117 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/StrategyModeView.swift +561 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SummaryView.swift +1273 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/UpdateView.swift +352 -0
- codex_flow/data/apps/macos-overlay/Sources/main.swift +340 -0
- codex_flow/data/apps/macos-overlay/Tests/OverlayScreenGeometryTests.swift +163 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryPhase1ContractTests.swift +357 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQueryEngineConcurrencyTests.swift +221 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQuotaSelectionTests.swift +158 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryWorkerTokenTests.swift +122 -0
- codex_flow/data/apps/macos-overlay/build.sh +75 -0
- codex_flow/data/benchmark/corpus.json +103 -0
- codex_flow/data/benchmark/manifest.example.json +41 -0
- codex_flow/data/benchmark/manifest.schema.json +137 -0
- codex_flow/data/benchmark/prices/gpt-5.6-2026-08-30.json +5 -0
- codex_flow/data/benchmark/profiles.json +90 -0
- codex_flow/data/benchmark/schema.json +77 -0
- codex_flow/data/benchmark/tasks.json +50 -0
- codex_flow/data/completions/codex-flow.bash +34 -0
- codex_flow/data/completions/codex-flow.zsh +52 -0
- codex_flow/data/glama.json +6 -0
- codex_flow/data/install-release.ps1 +126 -0
- codex_flow/data/install-release.sh +155 -0
- codex_flow/data/install.ps1 +349 -0
- codex_flow/data/install.sh +362 -0
- codex_flow/data/policy/benchmark.toml +49 -0
- codex_flow/data/policy/defaults.toml +70 -0
- codex_flow/data/scripts/analyze-benchmark.py +510 -0
- codex_flow/data/scripts/benchmark-local.py +171 -0
- codex_flow/data/scripts/check-recommendation.py +277 -0
- codex_flow/data/scripts/doctor.py +449 -0
- codex_flow/data/scripts/generate-release-manifest.py +74 -0
- codex_flow/data/scripts/localization.py +192 -0
- codex_flow/data/scripts/manage-hooks.py +448 -0
- codex_flow/data/scripts/manage-instructions.py +389 -0
- codex_flow/data/scripts/manage-shell.py +151 -0
- codex_flow/data/scripts/materialize-corpus.py +193 -0
- codex_flow/data/scripts/menu.py +646 -0
- codex_flow/data/scripts/migrations/0001_update_settings.py +80 -0
- codex_flow/data/scripts/package-release.py +132 -0
- codex_flow/data/scripts/render-benchmark-report.py +292 -0
- codex_flow/data/scripts/run-benchmark.py +829 -0
- codex_flow/data/scripts/strategies/__init__.py +28 -0
- codex_flow/data/scripts/strategies/balanced.py +115 -0
- codex_flow/data/scripts/strategies/base.py +363 -0
- codex_flow/data/scripts/strategies/efficient.py +158 -0
- codex_flow/data/scripts/strategies/lifecycle_runtime.py +590 -0
- codex_flow/data/scripts/strategies/quality.py +209 -0
- codex_flow/data/scripts/strategies/speed.py +108 -0
- codex_flow/data/scripts/strategies/task_budget_runtime.py +644 -0
- codex_flow/data/scripts/strategies/task_phase_runtime.py +341 -0
- codex_flow/data/scripts/strategies/work_unit_runtime.py +421 -0
- codex_flow/data/scripts/strategy_runtime.py +1091 -0
- codex_flow/data/scripts/telemetry.py +400 -0
- codex_flow/data/scripts/telemetry_core/__init__.py +192 -0
- codex_flow/data/scripts/telemetry_core/app_server.py +1192 -0
- codex_flow/data/scripts/telemetry_core/collector.py +1247 -0
- codex_flow/data/scripts/telemetry_core/common.py +421 -0
- codex_flow/data/scripts/telemetry_core/latency.py +593 -0
- codex_flow/data/scripts/telemetry_core/query.py +427 -0
- codex_flow/data/scripts/telemetry_core/quota_ledger.py +598 -0
- codex_flow/data/scripts/telemetry_core/render.py +460 -0
- codex_flow/data/scripts/telemetry_core/repair.py +223 -0
- codex_flow/data/scripts/ui.py +266 -0
- codex_flow/data/scripts/update-homebrew-formula.py +146 -0
- codex_flow/data/scripts/update_runtime_config.py +134 -0
- codex_flow/data/scripts/updater.py +1718 -0
- codex_flow/data/smithery.yaml +18 -0
- codex_flow/data/templates/agents/worker-explorer.toml +24 -0
- codex_flow/data/templates/agents/worker-implementer.toml +49 -0
- codex_flow/data/templates/agents/worker-reviewer.toml +25 -0
- codex_flow/data/templates/flow-pilot-instructions.md +35 -0
- codex_flow/data/templates/skills/flow-pilot/SKILL.md +577 -0
- codex_flow/mcp.py +35 -0
- codex_flow-2.1.13.dist-info/METADATA +342 -0
- codex_flow-2.1.13.dist-info/RECORD +113 -0
- codex_flow-2.1.13.dist-info/WHEEL +5 -0
- codex_flow-2.1.13.dist-info/entry_points.txt +3 -0
- codex_flow-2.1.13.dist-info/licenses/LICENSE +21 -0
- codex_flow-2.1.13.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Deterministic codex-flow telemetry collector and CLI."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import socket
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
15
|
+
|
|
16
|
+
from telemetry_core import * # noqa: F401,F403
|
|
17
|
+
from telemetry_core import (
|
|
18
|
+
AppServer,
|
|
19
|
+
LAST_FILE,
|
|
20
|
+
aggregate_usage_value,
|
|
21
|
+
atomic_json,
|
|
22
|
+
collect_hook,
|
|
23
|
+
enrich_run_metadata,
|
|
24
|
+
extract_transcript_insights,
|
|
25
|
+
fmt_duration_ms,
|
|
26
|
+
fmt_tokens,
|
|
27
|
+
format_latency_report,
|
|
28
|
+
format_repair_summary,
|
|
29
|
+
iter_run_files,
|
|
30
|
+
latency_report,
|
|
31
|
+
numeric_ms,
|
|
32
|
+
read_json_object,
|
|
33
|
+
record_latency_event,
|
|
34
|
+
repair_history,
|
|
35
|
+
repair_run,
|
|
36
|
+
run_context,
|
|
37
|
+
show_last,
|
|
38
|
+
show_list,
|
|
39
|
+
show_run,
|
|
40
|
+
show_stats,
|
|
41
|
+
telemetry_notifications_enabled,
|
|
42
|
+
telemetry_retention_days,
|
|
43
|
+
)
|
|
44
|
+
from telemetry_core.latency import LatencyError
|
|
45
|
+
import telemetry_core.collector as _collector
|
|
46
|
+
from localization import resolve_language, tr
|
|
47
|
+
|
|
48
|
+
LANG = resolve_language(Path(os.environ.get("CODEX_HOME", Path.home() / ".codex")) / "codex-flow.toml")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def T(en: str, zh: str) -> str:
|
|
52
|
+
return tr(en, zh, lang=LANG)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _same_run(left: dict | None, right: dict) -> bool:
|
|
56
|
+
if not isinstance(left, dict):
|
|
57
|
+
return False
|
|
58
|
+
return (
|
|
59
|
+
str(left.get("session_id") or "") == str(right.get("session_id") or "")
|
|
60
|
+
and str(left.get("turn_id") or "") == str(right.get("turn_id") or "")
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _persist_enriched_run(run: dict) -> None:
|
|
65
|
+
"""Make the persisted run self-contained before UI/CLI consumers reload it."""
|
|
66
|
+
insights = extract_transcript_insights(run.get("transcript_path"), run.get("turn_id"))
|
|
67
|
+
if insights:
|
|
68
|
+
for key, value in insights.items():
|
|
69
|
+
if value is not None and not run.get(key):
|
|
70
|
+
run[key] = value
|
|
71
|
+
enrich_run_metadata(run)
|
|
72
|
+
|
|
73
|
+
atomic_json(LAST_FILE, run)
|
|
74
|
+
for path in iter_run_files():
|
|
75
|
+
candidate = read_json_object(path)
|
|
76
|
+
if _same_run(candidate, run):
|
|
77
|
+
atomic_json(path, run)
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _localized_notification_body(run: dict) -> str:
|
|
82
|
+
session, project, branch = run_context(run)
|
|
83
|
+
label = project or session or T("Codex task", "Codex 任务")
|
|
84
|
+
if branch and project:
|
|
85
|
+
label = f"{label} · {branch}"
|
|
86
|
+
workers = list((run.get("workers") or {}).values())
|
|
87
|
+
parent = run.get("parent") or {}
|
|
88
|
+
usages = [parent.get("usage_delta") if isinstance(parent, dict) else None]
|
|
89
|
+
usages.extend(worker.get("usage") if isinstance(worker, dict) else None for worker in workers)
|
|
90
|
+
total_tokens = aggregate_usage_value(usages, "total_tokens")
|
|
91
|
+
worker_count = T(
|
|
92
|
+
f"{len(workers)} worker{'s' if len(workers) != 1 else ''}",
|
|
93
|
+
f"{len(workers)} 个子 Agent",
|
|
94
|
+
)
|
|
95
|
+
parts = [label, worker_count, f"{fmt_tokens(total_tokens)} tokens"]
|
|
96
|
+
started = numeric_ms(run.get("started_at_ms"))
|
|
97
|
+
finished = numeric_ms(run.get("finished_at_ms"))
|
|
98
|
+
if started is not None and finished is not None:
|
|
99
|
+
duration = fmt_duration_ms(finished - started)
|
|
100
|
+
if duration:
|
|
101
|
+
parts.append(duration)
|
|
102
|
+
return " · ".join(parts)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _notify_overlay_safely() -> None:
|
|
106
|
+
"""Push an update and wait for the daemon response before closing the socket."""
|
|
107
|
+
codex_home = os.environ.get("CODEX_HOME", os.path.expanduser("~/.codex"))
|
|
108
|
+
sock_path = os.path.join(codex_home, "codex-flow", "overlay.sock")
|
|
109
|
+
if not os.path.exists(sock_path):
|
|
110
|
+
return
|
|
111
|
+
try:
|
|
112
|
+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
|
|
113
|
+
client.settimeout(0.75)
|
|
114
|
+
client.connect(sock_path)
|
|
115
|
+
client.sendall(b"update\n")
|
|
116
|
+
try:
|
|
117
|
+
client.recv(4096)
|
|
118
|
+
except socket.timeout:
|
|
119
|
+
pass
|
|
120
|
+
except (OSError, TimeoutError):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _localized_send_system_notification(run: dict) -> None:
|
|
125
|
+
_persist_enriched_run(run)
|
|
126
|
+
_notify_overlay_safely()
|
|
127
|
+
telemetry_mod = sys.modules.get("telemetry")
|
|
128
|
+
sub_mod = getattr(telemetry_mod, "subprocess", subprocess) if telemetry_mod else subprocess
|
|
129
|
+
shutil_mod = getattr(telemetry_mod, "shutil", shutil) if telemetry_mod else shutil
|
|
130
|
+
sys_mod = getattr(telemetry_mod, "sys", sys) if telemetry_mod else sys
|
|
131
|
+
if not telemetry_notifications_enabled() or sys_mod.platform != "darwin":
|
|
132
|
+
return
|
|
133
|
+
executable = shutil_mod.which("osascript")
|
|
134
|
+
if not executable:
|
|
135
|
+
return
|
|
136
|
+
title = T("FlowPilot task finished", "FlowPilot 任务已完成")
|
|
137
|
+
script = (
|
|
138
|
+
f"display notification {_collector.applescript_literal(_localized_notification_body(run))} "
|
|
139
|
+
f"with title {_collector.applescript_literal(title)}"
|
|
140
|
+
)
|
|
141
|
+
try:
|
|
142
|
+
sub_mod.run(
|
|
143
|
+
[executable, "-e", script],
|
|
144
|
+
stdout=subprocess.DEVNULL,
|
|
145
|
+
stderr=subprocess.DEVNULL,
|
|
146
|
+
timeout=2.0,
|
|
147
|
+
check=False,
|
|
148
|
+
)
|
|
149
|
+
except (OSError, Exception):
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Quota snapshots are observational, but a transient app-server miss should not
|
|
154
|
+
# permanently erase the task delta. Retry only the read operation; never invent data.
|
|
155
|
+
_ORIGINAL_RATE_LIMITS = AppServer.rate_limits
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _has_rate_limit_snapshot(value: object) -> bool:
|
|
159
|
+
"""Return whether a response contains either supported rate-limit shape."""
|
|
160
|
+
if not isinstance(value, dict):
|
|
161
|
+
return False
|
|
162
|
+
if isinstance(value.get("rateLimits"), dict):
|
|
163
|
+
return True
|
|
164
|
+
by_id = value.get("rateLimitsByLimitId")
|
|
165
|
+
return isinstance(by_id, dict) and isinstance(by_id.get("codex"), dict)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _rate_limits_with_retry(self: AppServer):
|
|
169
|
+
last = None
|
|
170
|
+
for attempt in range(3):
|
|
171
|
+
last = _ORIGINAL_RATE_LIMITS(self)
|
|
172
|
+
if _has_rate_limit_snapshot(last):
|
|
173
|
+
return last
|
|
174
|
+
if attempt < 2:
|
|
175
|
+
time.sleep(0.08 * (attempt + 1))
|
|
176
|
+
return last
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
AppServer.rate_limits = _rate_limits_with_retry
|
|
180
|
+
_collector.AppServer.rate_limits = _rate_limits_with_retry
|
|
181
|
+
|
|
182
|
+
# Enrich and persist before the Stop summary is rendered. This makes last.json
|
|
183
|
+
# and the per-run file authoritative for live view, restart, history, and CLI.
|
|
184
|
+
_ORIGINAL_RENDER_SUMMARY = _collector.render_summary
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _render_summary_with_enrichment(run: dict) -> str:
|
|
188
|
+
_persist_enriched_run(run)
|
|
189
|
+
return _ORIGINAL_RENDER_SUMMARY(run)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# collect_hook resolves these names from telemetry_core.collector at runtime.
|
|
193
|
+
_collector.render_summary = _render_summary_with_enrichment
|
|
194
|
+
_collector.notification_body = _localized_notification_body
|
|
195
|
+
_collector.send_system_notification = _localized_send_system_notification
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _latency_option(args: list[str], name: str) -> str | None:
|
|
199
|
+
for index, value in enumerate(args):
|
|
200
|
+
if value == name:
|
|
201
|
+
if index + 1 >= len(args) or args[index + 1].startswith("-"):
|
|
202
|
+
raise LatencyError(f"{name} requires a value")
|
|
203
|
+
return args[index + 1]
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _latency_state_file(args: list[str]) -> str | None:
|
|
208
|
+
return _latency_option(args, "--state-file")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _validate_latency_options(action: str, args: list[str]) -> None:
|
|
212
|
+
value_options = {"--state-file"}
|
|
213
|
+
if action == "record":
|
|
214
|
+
value_options.update({"--event-json", "--json"})
|
|
215
|
+
flag_options = {"--json"} if action == "report" else set()
|
|
216
|
+
positionals = 0
|
|
217
|
+
seen: set[str] = set()
|
|
218
|
+
index = 0
|
|
219
|
+
while index < len(args):
|
|
220
|
+
value = args[index]
|
|
221
|
+
if value in value_options:
|
|
222
|
+
if value in seen:
|
|
223
|
+
raise LatencyError(f"{value} may be supplied only once")
|
|
224
|
+
seen.add(value)
|
|
225
|
+
if index + 1 >= len(args) or args[index + 1].startswith("-"):
|
|
226
|
+
raise LatencyError(f"{value} requires a value")
|
|
227
|
+
index += 2
|
|
228
|
+
continue
|
|
229
|
+
if value in flag_options:
|
|
230
|
+
if value in seen:
|
|
231
|
+
raise LatencyError(f"{value} may be supplied only once")
|
|
232
|
+
seen.add(value)
|
|
233
|
+
index += 1
|
|
234
|
+
continue
|
|
235
|
+
if value.startswith("-"):
|
|
236
|
+
raise LatencyError(f"unknown latency option: {value}")
|
|
237
|
+
if action != "record" or positionals:
|
|
238
|
+
raise LatencyError(f"unexpected latency argument: {value}")
|
|
239
|
+
positionals += 1
|
|
240
|
+
index += 1
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _latency_record_args(args: list[str]) -> tuple[object, str | None]:
|
|
244
|
+
state_file = _latency_state_file(args)
|
|
245
|
+
raw = _latency_option(args, "--event-json")
|
|
246
|
+
if raw is None:
|
|
247
|
+
# `--json <object>` is accepted as a convenient shell spelling for
|
|
248
|
+
# record; on report, --json remains the output-format flag.
|
|
249
|
+
raw = _latency_option(args, "--json")
|
|
250
|
+
if raw is None:
|
|
251
|
+
positional = [
|
|
252
|
+
value
|
|
253
|
+
for index, value in enumerate(args)
|
|
254
|
+
if value not in {"--event-json", "--json", "--state-file"}
|
|
255
|
+
and (index == 0 or args[index - 1] not in {"--event-json", "--json", "--state-file"})
|
|
256
|
+
and not value.startswith("-")
|
|
257
|
+
]
|
|
258
|
+
raw = positional[0] if positional else None
|
|
259
|
+
if raw is None and not sys.stdin.isatty():
|
|
260
|
+
raw = sys.stdin.read()
|
|
261
|
+
if not raw or not raw.strip():
|
|
262
|
+
raise LatencyError("latency record requires --event-json or JSON on stdin")
|
|
263
|
+
try:
|
|
264
|
+
event = json.loads(raw)
|
|
265
|
+
except json.JSONDecodeError:
|
|
266
|
+
raise LatencyError("latency record event JSON is invalid") from None
|
|
267
|
+
return event, state_file
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _latency_cli(args: list[str]) -> int:
|
|
271
|
+
if len(args) < 2 or args[1] not in {"record", "report"}:
|
|
272
|
+
print("usage: telemetry latency record|report [options]", file=sys.stderr)
|
|
273
|
+
return 2
|
|
274
|
+
action = args[1]
|
|
275
|
+
options = args[2:]
|
|
276
|
+
try:
|
|
277
|
+
_validate_latency_options(action, options)
|
|
278
|
+
state_file = _latency_state_file(options)
|
|
279
|
+
if action == "record":
|
|
280
|
+
event, state_file = _latency_record_args(options)
|
|
281
|
+
result = record_latency_event(event, state_file=state_file)
|
|
282
|
+
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
|
283
|
+
return 0
|
|
284
|
+
result = latency_report(state_file=state_file)
|
|
285
|
+
if "--json" in options:
|
|
286
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
|
287
|
+
else:
|
|
288
|
+
print(format_latency_report(result))
|
|
289
|
+
return 0
|
|
290
|
+
except (LatencyError, OSError) as exc:
|
|
291
|
+
print(f"telemetry latency: {exc}", file=sys.stderr)
|
|
292
|
+
return 2
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _check_auto_ack_restart_on_hook() -> None:
|
|
296
|
+
try:
|
|
297
|
+
import updater
|
|
298
|
+
state = updater.load_state()
|
|
299
|
+
if not state.restart_required:
|
|
300
|
+
return
|
|
301
|
+
parent_pid = os.getppid()
|
|
302
|
+
if state.pending_codex_pids:
|
|
303
|
+
if parent_pid not in state.pending_codex_pids:
|
|
304
|
+
state.restart_required = False
|
|
305
|
+
state.pending_codex_pids = []
|
|
306
|
+
state.restart_reason = None
|
|
307
|
+
updater.save_state(state)
|
|
308
|
+
except Exception:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def main() -> int:
|
|
313
|
+
args = sys.argv[1:]
|
|
314
|
+
if args:
|
|
315
|
+
cmd = args[0]
|
|
316
|
+
if cmd == "latency":
|
|
317
|
+
return _latency_cli(args)
|
|
318
|
+
if cmd == "last":
|
|
319
|
+
return show_last("--json" in args[1:])
|
|
320
|
+
if cmd == "list":
|
|
321
|
+
as_json = "--json" in args[1:]
|
|
322
|
+
today = "--today" in args[1:]
|
|
323
|
+
limit = 10
|
|
324
|
+
project = None
|
|
325
|
+
i = 1
|
|
326
|
+
while i < len(args):
|
|
327
|
+
if args[i] in ("-n", "--limit") and i + 1 < len(args):
|
|
328
|
+
try:
|
|
329
|
+
limit = int(args[i + 1])
|
|
330
|
+
except ValueError:
|
|
331
|
+
pass
|
|
332
|
+
i += 2
|
|
333
|
+
elif args[i] in ("-p", "--project") and i + 1 < len(args):
|
|
334
|
+
project = args[i + 1]
|
|
335
|
+
i += 2
|
|
336
|
+
else:
|
|
337
|
+
i += 1
|
|
338
|
+
return show_list(limit=limit, project=project, today=today, as_json=as_json)
|
|
339
|
+
if cmd == "show":
|
|
340
|
+
as_json = "--json" in args[1:]
|
|
341
|
+
target = "last"
|
|
342
|
+
for a in args[1:]:
|
|
343
|
+
if a != "--json":
|
|
344
|
+
target = a
|
|
345
|
+
break
|
|
346
|
+
return show_run(target, as_json=as_json)
|
|
347
|
+
if cmd in ("stats", "summary"):
|
|
348
|
+
as_json = "--json" in args[1:]
|
|
349
|
+
days = telemetry_retention_days()
|
|
350
|
+
project = None
|
|
351
|
+
i = 1
|
|
352
|
+
while i < len(args):
|
|
353
|
+
if args[i] in ("-d", "--days") and i + 1 < len(args):
|
|
354
|
+
try:
|
|
355
|
+
days = int(args[i + 1])
|
|
356
|
+
except ValueError:
|
|
357
|
+
pass
|
|
358
|
+
i += 2
|
|
359
|
+
elif args[i] in ("-p", "--project") and i + 1 < len(args):
|
|
360
|
+
project = args[i + 1]
|
|
361
|
+
i += 2
|
|
362
|
+
else:
|
|
363
|
+
i += 1
|
|
364
|
+
return show_stats(project=project, days=days, as_json=as_json)
|
|
365
|
+
if cmd == "repair":
|
|
366
|
+
dry_run = "--dry-run" in args[1:]
|
|
367
|
+
as_json = "--json" in args[1:]
|
|
368
|
+
stats = repair_history(dry_run=dry_run, verbose=not as_json)
|
|
369
|
+
if as_json:
|
|
370
|
+
print(json.dumps(stats, indent=2))
|
|
371
|
+
return 0
|
|
372
|
+
if not cmd.startswith("-"):
|
|
373
|
+
return show_run(cmd, as_json="--json" in args[1:])
|
|
374
|
+
|
|
375
|
+
has_data = False
|
|
376
|
+
if not sys.stdin.isatty():
|
|
377
|
+
try:
|
|
378
|
+
import select
|
|
379
|
+
r, _, _ = select.select([sys.stdin], [], [], 0.0)
|
|
380
|
+
has_data = bool(r)
|
|
381
|
+
except Exception:
|
|
382
|
+
has_data = False
|
|
383
|
+
if not has_data:
|
|
384
|
+
return show_last(as_json=False)
|
|
385
|
+
try:
|
|
386
|
+
raw = sys.stdin.read()
|
|
387
|
+
event = json.loads(raw) if raw.strip() else {}
|
|
388
|
+
except json.JSONDecodeError:
|
|
389
|
+
return 0
|
|
390
|
+
if isinstance(event, dict):
|
|
391
|
+
try:
|
|
392
|
+
collect_hook(event)
|
|
393
|
+
_check_auto_ack_restart_on_hook()
|
|
394
|
+
except Exception:
|
|
395
|
+
return 0
|
|
396
|
+
return 0
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
if __name__ == "__main__":
|
|
400
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Telemetry core package: collector, query engines, and presentation renderers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .app_server import (
|
|
6
|
+
AppServer,
|
|
7
|
+
apply_participant_metadata,
|
|
8
|
+
enrich_run_metadata,
|
|
9
|
+
extract_transcript_insights,
|
|
10
|
+
find_session_transcript,
|
|
11
|
+
merge_thread_metadata,
|
|
12
|
+
merge_usage,
|
|
13
|
+
quota_delta,
|
|
14
|
+
quota_windows,
|
|
15
|
+
session_index_metadata,
|
|
16
|
+
transcript_turn_metadata,
|
|
17
|
+
transcript_turn_usage,
|
|
18
|
+
usage_delta,
|
|
19
|
+
usage_group_identities,
|
|
20
|
+
usage_summary,
|
|
21
|
+
)
|
|
22
|
+
from .collector import (
|
|
23
|
+
absorb_worker_source,
|
|
24
|
+
collect_hook,
|
|
25
|
+
is_same_run,
|
|
26
|
+
merge_worker_values,
|
|
27
|
+
notification_body,
|
|
28
|
+
parent_candidates,
|
|
29
|
+
parent_match_score,
|
|
30
|
+
reconcile_orphan_workers,
|
|
31
|
+
run_age_timestamp_ms,
|
|
32
|
+
run_maintenance,
|
|
33
|
+
send_system_notification,
|
|
34
|
+
worker_interval,
|
|
35
|
+
worker_run_key,
|
|
36
|
+
write_stop_output,
|
|
37
|
+
)
|
|
38
|
+
from .common import (
|
|
39
|
+
CODEX_HOME,
|
|
40
|
+
DEFAULT_RETENTION_DAYS,
|
|
41
|
+
LAST_FILE,
|
|
42
|
+
LOCK_TIMEOUT,
|
|
43
|
+
RUNS_DIR,
|
|
44
|
+
SESSION_INDEX_FILE,
|
|
45
|
+
STATE_ROOT,
|
|
46
|
+
TIMEOUT,
|
|
47
|
+
WORKER_INDEX_FILE,
|
|
48
|
+
atomic_json,
|
|
49
|
+
compact_text,
|
|
50
|
+
display_width,
|
|
51
|
+
fmt_duration_ms,
|
|
52
|
+
fmt_local_timestamp,
|
|
53
|
+
fmt_percent,
|
|
54
|
+
fmt_tokens,
|
|
55
|
+
iter_run_files,
|
|
56
|
+
load_run,
|
|
57
|
+
load_worker_index,
|
|
58
|
+
now_ms,
|
|
59
|
+
numeric_ms,
|
|
60
|
+
pad_display,
|
|
61
|
+
policy_bool,
|
|
62
|
+
policy_int,
|
|
63
|
+
policy_value,
|
|
64
|
+
read_json_object,
|
|
65
|
+
remember_worker_parent,
|
|
66
|
+
run_key,
|
|
67
|
+
run_path,
|
|
68
|
+
run_path_for_key,
|
|
69
|
+
safe_key_part,
|
|
70
|
+
state_lock,
|
|
71
|
+
telemetry_notifications_enabled,
|
|
72
|
+
telemetry_retention_days,
|
|
73
|
+
text_value,
|
|
74
|
+
truncate_display,
|
|
75
|
+
window_label,
|
|
76
|
+
worker_index_entry,
|
|
77
|
+
)
|
|
78
|
+
from .query import (
|
|
79
|
+
aggregate_project_stats,
|
|
80
|
+
list_runs,
|
|
81
|
+
resolve_run_target,
|
|
82
|
+
show_last,
|
|
83
|
+
show_list,
|
|
84
|
+
show_run,
|
|
85
|
+
show_stats,
|
|
86
|
+
)
|
|
87
|
+
from .render import (
|
|
88
|
+
aggregate_usage_value,
|
|
89
|
+
participant_runtime_label,
|
|
90
|
+
render_project_stats,
|
|
91
|
+
render_run_list,
|
|
92
|
+
render_summary,
|
|
93
|
+
run_context,
|
|
94
|
+
)
|
|
95
|
+
from .repair import (
|
|
96
|
+
format_repair_summary,
|
|
97
|
+
repair_history,
|
|
98
|
+
repair_run,
|
|
99
|
+
)
|
|
100
|
+
from .latency import (
|
|
101
|
+
LATENCY_FILE_NAME,
|
|
102
|
+
LATENCY_SCHEMA_VERSION,
|
|
103
|
+
LatencyError,
|
|
104
|
+
format_latency_report,
|
|
105
|
+
latency_file,
|
|
106
|
+
latency_report,
|
|
107
|
+
record_latency_event,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
__all__ = [
|
|
111
|
+
"CODEX_HOME",
|
|
112
|
+
"STATE_ROOT",
|
|
113
|
+
"RUNS_DIR",
|
|
114
|
+
"LAST_FILE",
|
|
115
|
+
"WORKER_INDEX_FILE",
|
|
116
|
+
"SESSION_INDEX_FILE",
|
|
117
|
+
"TIMEOUT",
|
|
118
|
+
"LOCK_TIMEOUT",
|
|
119
|
+
"DEFAULT_RETENTION_DAYS",
|
|
120
|
+
"display_width",
|
|
121
|
+
"truncate_display",
|
|
122
|
+
"pad_display",
|
|
123
|
+
"policy_value",
|
|
124
|
+
"policy_bool",
|
|
125
|
+
"policy_int",
|
|
126
|
+
"telemetry_notifications_enabled",
|
|
127
|
+
"telemetry_retention_days",
|
|
128
|
+
"now_ms",
|
|
129
|
+
"atomic_json",
|
|
130
|
+
"state_lock",
|
|
131
|
+
"read_json_object",
|
|
132
|
+
"numeric_ms",
|
|
133
|
+
"safe_key_part",
|
|
134
|
+
"run_key",
|
|
135
|
+
"run_path_for_key",
|
|
136
|
+
"run_path",
|
|
137
|
+
"load_run",
|
|
138
|
+
"iter_run_files",
|
|
139
|
+
"load_worker_index",
|
|
140
|
+
"worker_index_entry",
|
|
141
|
+
"remember_worker_parent",
|
|
142
|
+
"text_value",
|
|
143
|
+
"compact_text",
|
|
144
|
+
"fmt_tokens",
|
|
145
|
+
"window_label",
|
|
146
|
+
"fmt_percent",
|
|
147
|
+
"fmt_local_timestamp",
|
|
148
|
+
"fmt_duration_ms",
|
|
149
|
+
"session_index_metadata",
|
|
150
|
+
"merge_thread_metadata",
|
|
151
|
+
"AppServer",
|
|
152
|
+
"quota_windows",
|
|
153
|
+
"usage_summary",
|
|
154
|
+
"usage_delta",
|
|
155
|
+
"transcript_turn_metadata",
|
|
156
|
+
"transcript_turn_usage",
|
|
157
|
+
"merge_usage",
|
|
158
|
+
"usage_group_identities",
|
|
159
|
+
"apply_participant_metadata",
|
|
160
|
+
"enrich_run_metadata",
|
|
161
|
+
"extract_transcript_insights",
|
|
162
|
+
"quota_delta",
|
|
163
|
+
"collect_hook",
|
|
164
|
+
"merge_worker_values",
|
|
165
|
+
"reconcile_orphan_workers",
|
|
166
|
+
"run_maintenance",
|
|
167
|
+
"send_system_notification",
|
|
168
|
+
"write_stop_output",
|
|
169
|
+
"aggregate_usage_value",
|
|
170
|
+
"participant_runtime_label",
|
|
171
|
+
"run_context",
|
|
172
|
+
"render_summary",
|
|
173
|
+
"render_run_list",
|
|
174
|
+
"render_project_stats",
|
|
175
|
+
"list_runs",
|
|
176
|
+
"resolve_run_target",
|
|
177
|
+
"aggregate_project_stats",
|
|
178
|
+
"show_last",
|
|
179
|
+
"show_list",
|
|
180
|
+
"show_run",
|
|
181
|
+
"show_stats",
|
|
182
|
+
"format_repair_summary",
|
|
183
|
+
"repair_run",
|
|
184
|
+
"repair_history",
|
|
185
|
+
"LATENCY_SCHEMA_VERSION",
|
|
186
|
+
"LATENCY_FILE_NAME",
|
|
187
|
+
"LatencyError",
|
|
188
|
+
"latency_file",
|
|
189
|
+
"record_latency_event",
|
|
190
|
+
"latency_report",
|
|
191
|
+
"format_latency_report",
|
|
192
|
+
]
|