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,927 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>FlowPilot telemetry</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
--background: #f6f7f9;
|
|
11
|
+
--surface: #ffffff;
|
|
12
|
+
--surface-muted: #eef1f5;
|
|
13
|
+
--border: #d7dce3;
|
|
14
|
+
--text: #1f2329;
|
|
15
|
+
--muted: #5d6673;
|
|
16
|
+
--accent: #1458d4;
|
|
17
|
+
--accent-contrast: #ffffff;
|
|
18
|
+
--success: #1c7c54;
|
|
19
|
+
--danger: #b42318;
|
|
20
|
+
--focus: #0b63ce;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (prefers-color-scheme: dark) {
|
|
24
|
+
:root {
|
|
25
|
+
--background: #16181d;
|
|
26
|
+
--surface: #22252b;
|
|
27
|
+
--surface-muted: #2d323a;
|
|
28
|
+
--border: #444b57;
|
|
29
|
+
--text: #f3f5f7;
|
|
30
|
+
--muted: #b4bcc8;
|
|
31
|
+
--accent: #8ab4ff;
|
|
32
|
+
--accent-contrast: #121722;
|
|
33
|
+
--success: #72d6a7;
|
|
34
|
+
--danger: #ff9b91;
|
|
35
|
+
--focus: #9bc2ff;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
* {
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
html,
|
|
44
|
+
body {
|
|
45
|
+
min-height: 100%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
body {
|
|
49
|
+
margin: 0;
|
|
50
|
+
background: var(--background);
|
|
51
|
+
color: var(--text);
|
|
52
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
53
|
+
font-size: 14px;
|
|
54
|
+
line-height: 1.4;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.widget {
|
|
58
|
+
width: min(100%, 430px);
|
|
59
|
+
margin: 0 auto;
|
|
60
|
+
padding: 12px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body[data-display-mode="fullscreen"] .widget {
|
|
64
|
+
width: min(100%, 760px);
|
|
65
|
+
padding: 24px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.shell {
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
border: 1px solid var(--border);
|
|
71
|
+
border-radius: 14px;
|
|
72
|
+
background: var(--surface);
|
|
73
|
+
box-shadow: 0 4px 18px rgb(16 24 40 / 8%);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.header {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: space-between;
|
|
80
|
+
gap: 12px;
|
|
81
|
+
padding: 14px 16px 10px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.eyebrow,
|
|
85
|
+
.mode {
|
|
86
|
+
color: var(--muted);
|
|
87
|
+
font-size: 12px;
|
|
88
|
+
letter-spacing: 0.02em;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.eyebrow {
|
|
92
|
+
display: inline-flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 7px;
|
|
95
|
+
font-weight: 650;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.signal {
|
|
99
|
+
width: 8px;
|
|
100
|
+
height: 8px;
|
|
101
|
+
flex: 0 0 auto;
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
background: var(--muted);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.signal[data-state="ready"] {
|
|
107
|
+
background: var(--success);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.signal[data-state="loading"] {
|
|
111
|
+
background: var(--accent);
|
|
112
|
+
animation: pulse 1.1s ease-in-out infinite;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.signal[data-state="error"] {
|
|
116
|
+
background: var(--danger);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@keyframes pulse {
|
|
120
|
+
50% {
|
|
121
|
+
opacity: 0.35;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.summary {
|
|
126
|
+
padding: 4px 16px 16px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.headline {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: baseline;
|
|
132
|
+
justify-content: space-between;
|
|
133
|
+
gap: 12px;
|
|
134
|
+
margin-bottom: 14px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
h1 {
|
|
138
|
+
margin: 0;
|
|
139
|
+
font-size: 18px;
|
|
140
|
+
letter-spacing: -0.01em;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.status {
|
|
144
|
+
max-width: 45%;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
color: var(--muted);
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
text-overflow: ellipsis;
|
|
149
|
+
white-space: nowrap;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.metrics {
|
|
153
|
+
display: grid;
|
|
154
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
155
|
+
gap: 8px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.metric {
|
|
159
|
+
min-width: 0;
|
|
160
|
+
padding: 10px;
|
|
161
|
+
border: 1px solid var(--border);
|
|
162
|
+
border-radius: 10px;
|
|
163
|
+
background: var(--surface-muted);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.metric-label {
|
|
167
|
+
display: block;
|
|
168
|
+
margin-bottom: 3px;
|
|
169
|
+
color: var(--muted);
|
|
170
|
+
font-size: 11px;
|
|
171
|
+
font-weight: 650;
|
|
172
|
+
letter-spacing: 0.04em;
|
|
173
|
+
text-transform: uppercase;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.metric-value {
|
|
177
|
+
display: block;
|
|
178
|
+
overflow-wrap: anywhere;
|
|
179
|
+
font-size: 14px;
|
|
180
|
+
font-weight: 650;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.controls {
|
|
184
|
+
display: flex;
|
|
185
|
+
gap: 8px;
|
|
186
|
+
padding: 12px 16px 14px;
|
|
187
|
+
border-top: 1px solid var(--border);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
button {
|
|
191
|
+
min-height: 36px;
|
|
192
|
+
border: 1px solid var(--border);
|
|
193
|
+
border-radius: 8px;
|
|
194
|
+
background: var(--surface);
|
|
195
|
+
color: var(--text);
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
font: inherit;
|
|
198
|
+
font-weight: 650;
|
|
199
|
+
padding: 7px 12px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
button:hover:not(:disabled) {
|
|
203
|
+
border-color: var(--accent);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
button:focus-visible,
|
|
207
|
+
.text-button:focus-visible {
|
|
208
|
+
outline: 3px solid color-mix(in srgb, var(--focus) 35%, transparent);
|
|
209
|
+
outline-offset: 2px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
button:disabled {
|
|
213
|
+
cursor: wait;
|
|
214
|
+
opacity: 0.58;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.primary {
|
|
218
|
+
border-color: var(--accent);
|
|
219
|
+
background: var(--accent);
|
|
220
|
+
color: var(--accent-contrast);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.primary:hover:not(:disabled) {
|
|
224
|
+
border-color: var(--accent);
|
|
225
|
+
filter: brightness(0.94);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.text-button {
|
|
229
|
+
margin: 0 16px 10px;
|
|
230
|
+
padding: 2px 0;
|
|
231
|
+
border: 0;
|
|
232
|
+
background: transparent;
|
|
233
|
+
color: var(--accent);
|
|
234
|
+
font-size: 12px;
|
|
235
|
+
font-weight: 650;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.notice,
|
|
239
|
+
.error {
|
|
240
|
+
margin: 0 16px 12px;
|
|
241
|
+
font-size: 12px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.notice {
|
|
245
|
+
color: var(--muted);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.error {
|
|
249
|
+
color: var(--danger);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.details-panel {
|
|
253
|
+
margin: 0 16px 16px;
|
|
254
|
+
padding-top: 12px;
|
|
255
|
+
border-top: 1px solid var(--border);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.details-panel[hidden] {
|
|
259
|
+
display: none;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.details-panel h2 {
|
|
263
|
+
margin: 0 0 8px;
|
|
264
|
+
font-size: 13px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.detail-line {
|
|
268
|
+
display: flex;
|
|
269
|
+
justify-content: space-between;
|
|
270
|
+
gap: 18px;
|
|
271
|
+
padding: 4px 0;
|
|
272
|
+
color: var(--muted);
|
|
273
|
+
font-size: 12px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.detail-line strong {
|
|
277
|
+
color: var(--text);
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
text-align: right;
|
|
280
|
+
overflow-wrap: anywhere;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[aria-busy="true"] .metric-value {
|
|
284
|
+
opacity: 0.72;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@media (max-width: 340px) {
|
|
288
|
+
.widget {
|
|
289
|
+
padding: 8px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.header,
|
|
293
|
+
.summary,
|
|
294
|
+
.controls {
|
|
295
|
+
padding-left: 12px;
|
|
296
|
+
padding-right: 12px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.notice,
|
|
300
|
+
.error,
|
|
301
|
+
.text-button,
|
|
302
|
+
.details-panel {
|
|
303
|
+
margin-left: 12px;
|
|
304
|
+
margin-right: 12px;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
</style>
|
|
308
|
+
</head>
|
|
309
|
+
<body data-display-mode="compact">
|
|
310
|
+
<main class="widget" id="widget" aria-labelledby="title">
|
|
311
|
+
<div class="shell">
|
|
312
|
+
<header class="header">
|
|
313
|
+
<div class="eyebrow">
|
|
314
|
+
<span class="signal" id="signal" data-state="unknown" aria-hidden="true"></span>
|
|
315
|
+
<span>FlowPilot telemetry</span>
|
|
316
|
+
</div>
|
|
317
|
+
<span class="mode" id="modeLabel">Compact view</span>
|
|
318
|
+
</header>
|
|
319
|
+
|
|
320
|
+
<section class="summary" id="summary" aria-busy="false">
|
|
321
|
+
<div class="headline">
|
|
322
|
+
<h1 id="title">Latest run</h1>
|
|
323
|
+
<span class="status" id="statusValue">Unavailable</span>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<div class="metrics" aria-label="Telemetry summary">
|
|
327
|
+
<div class="metric">
|
|
328
|
+
<span class="metric-label">Session</span>
|
|
329
|
+
<span class="metric-value" id="sessionValue">Unavailable</span>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="metric">
|
|
332
|
+
<span class="metric-label">Project</span>
|
|
333
|
+
<span class="metric-value" id="projectValue">Unavailable</span>
|
|
334
|
+
</div>
|
|
335
|
+
<div class="metric">
|
|
336
|
+
<span class="metric-label">Duration</span>
|
|
337
|
+
<span class="metric-value" id="durationValue">Unavailable</span>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="metric">
|
|
340
|
+
<span class="metric-label">Participants</span>
|
|
341
|
+
<span class="metric-value" id="participantsValue">Unavailable</span>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="metric">
|
|
344
|
+
<span class="metric-label">Tokens</span>
|
|
345
|
+
<span class="metric-value" id="tokensValue">Unavailable</span>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="metric">
|
|
348
|
+
<span class="metric-label">Quota</span>
|
|
349
|
+
<span class="metric-value" id="quotaValue">Unavailable</span>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="metric">
|
|
352
|
+
<span class="metric-label">Last updated</span>
|
|
353
|
+
<span class="metric-value" id="updatedValue">Unavailable</span>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
</section>
|
|
357
|
+
|
|
358
|
+
<div class="controls" aria-label="Telemetry controls">
|
|
359
|
+
<button class="primary" id="pinButton" type="button" aria-label="Keep telemetry visible in Picture-in-picture">Pin</button>
|
|
360
|
+
<button id="refreshButton" type="button" aria-label="Refresh latest FlowPilot telemetry">Refresh</button>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<button class="text-button" id="detailsButton" type="button" aria-label="Expand telemetry details">Details / expand</button>
|
|
364
|
+
<p class="notice" id="notice" role="status" aria-live="polite"></p>
|
|
365
|
+
<p class="error" id="error" role="alert" aria-live="assertive" hidden></p>
|
|
366
|
+
|
|
367
|
+
<section class="details-panel" id="detailsPanel" aria-labelledby="detailsTitle" hidden>
|
|
368
|
+
<h2 id="detailsTitle">Available details</h2>
|
|
369
|
+
<div class="detail-line"><span>Parent model</span><strong id="modelValue">Unavailable</strong></div>
|
|
370
|
+
<div class="detail-line"><span>Branch</span><strong id="branchValue">Unavailable</strong></div>
|
|
371
|
+
<div class="detail-line"><span>Quota reset</span><strong id="resetValue">Unavailable</strong></div>
|
|
372
|
+
</section>
|
|
373
|
+
</div>
|
|
374
|
+
</main>
|
|
375
|
+
|
|
376
|
+
<script>
|
|
377
|
+
(() => {
|
|
378
|
+
"use strict";
|
|
379
|
+
|
|
380
|
+
const UNAVAILABLE = "Unavailable";
|
|
381
|
+
const state = {
|
|
382
|
+
data: null,
|
|
383
|
+
detailsOpen: false,
|
|
384
|
+
displayMode: "compact",
|
|
385
|
+
error: "",
|
|
386
|
+
loading: false,
|
|
387
|
+
notice: ""
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const elements = {
|
|
391
|
+
branch: document.getElementById("branchValue"),
|
|
392
|
+
detailsButton: document.getElementById("detailsButton"),
|
|
393
|
+
detailsPanel: document.getElementById("detailsPanel"),
|
|
394
|
+
error: document.getElementById("error"),
|
|
395
|
+
modeLabel: document.getElementById("modeLabel"),
|
|
396
|
+
model: document.getElementById("modelValue"),
|
|
397
|
+
notice: document.getElementById("notice"),
|
|
398
|
+
pinButton: document.getElementById("pinButton"),
|
|
399
|
+
project: document.getElementById("projectValue"),
|
|
400
|
+
quota: document.getElementById("quotaValue"),
|
|
401
|
+
refreshButton: document.getElementById("refreshButton"),
|
|
402
|
+
reset: document.getElementById("resetValue"),
|
|
403
|
+
session: document.getElementById("sessionValue"),
|
|
404
|
+
signal: document.getElementById("signal"),
|
|
405
|
+
status: document.getElementById("statusValue"),
|
|
406
|
+
summary: document.getElementById("summary"),
|
|
407
|
+
tokens: document.getElementById("tokensValue"),
|
|
408
|
+
updated: document.getElementById("updatedValue"),
|
|
409
|
+
duration: document.getElementById("durationValue"),
|
|
410
|
+
participants: document.getElementById("participantsValue")
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const DIRECT_KEYS = new Set([
|
|
414
|
+
"status", "state", "session", "session_id", "sessionId", "project", "project_name",
|
|
415
|
+
"projectName", "duration", "duration_ms", "durationMs", "participants", "workers",
|
|
416
|
+
"parent", "tokens", "total_tokens", "totalTokens", "usage", "quota", "quota_windows",
|
|
417
|
+
"quotaWindows", "last_updated", "lastUpdated", "updated_at", "updatedAt", "thread",
|
|
418
|
+
"started_at_ms", "startedAtMs", "finished_at_ms", "finishedAtMs"
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
const ENVELOPE_KEYS = new Set([
|
|
422
|
+
"type", "method", "event", "name", "id", "jsonrpc", "params", "result", "payload",
|
|
423
|
+
"data", "content", "structuredContent", "notification", "text", "isError", "error",
|
|
424
|
+
"telemetry", "toolResult", "tool_result"
|
|
425
|
+
]);
|
|
426
|
+
|
|
427
|
+
function isRecord(value) {
|
|
428
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function hasValue(value) {
|
|
432
|
+
return value !== undefined && value !== null && value !== "";
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function parseJson(value) {
|
|
436
|
+
if (typeof value !== "string") return value;
|
|
437
|
+
const text = value.trim();
|
|
438
|
+
if (!text || (text[0] !== "{" && text[0] !== "[")) return value;
|
|
439
|
+
try {
|
|
440
|
+
return JSON.parse(text);
|
|
441
|
+
} catch (_error) {
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// MCP Apps hosts and tool bridges have used both direct structuredContent
|
|
447
|
+
// and result/content envelopes. Keep the parser permissive but bounded.
|
|
448
|
+
function extractStructuredContent(value, depth = 0) {
|
|
449
|
+
if (depth > 8 || value === undefined || value === null) return null;
|
|
450
|
+
const parsed = parseJson(value);
|
|
451
|
+
if (parsed !== value) return extractStructuredContent(parsed, depth + 1);
|
|
452
|
+
if (Array.isArray(value)) {
|
|
453
|
+
for (const item of value) {
|
|
454
|
+
const found = extractStructuredContent(item, depth + 1);
|
|
455
|
+
if (found) return found;
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
if (!isRecord(value)) return null;
|
|
460
|
+
|
|
461
|
+
if (Object.prototype.hasOwnProperty.call(value, "structuredContent")) {
|
|
462
|
+
const found = extractStructuredContent(value.structuredContent, depth + 1);
|
|
463
|
+
if (found) return found;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
for (const key of ["result", "payload", "params", "data", "json", "telemetry", "toolResult", "tool_result"]) {
|
|
467
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
468
|
+
const found = extractStructuredContent(value[key], depth + 1);
|
|
469
|
+
if (found) return found;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (Object.prototype.hasOwnProperty.call(value, "content")) {
|
|
474
|
+
const found = extractStructuredContent(value.content, depth + 1);
|
|
475
|
+
if (found) return found;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (typeof value.text === "string") {
|
|
479
|
+
const found = extractStructuredContent(value.text, depth + 1);
|
|
480
|
+
if (found) return found;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const keys = Object.keys(value);
|
|
484
|
+
const hasTelemetryKey = keys.some((key) => DIRECT_KEYS.has(key));
|
|
485
|
+
if (hasTelemetryKey || keys.some((key) => !ENVELOPE_KEYS.has(key))) return value;
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function getHost() {
|
|
490
|
+
try {
|
|
491
|
+
return isRecord(window.openai) ? window.openai : null;
|
|
492
|
+
} catch (_error) {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function getAt(object, path) {
|
|
498
|
+
return path.split(".").reduce((current, key) => {
|
|
499
|
+
if (current === undefined || current === null) return undefined;
|
|
500
|
+
return current[key];
|
|
501
|
+
}, object);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function firstValue(object, paths) {
|
|
505
|
+
for (const path of paths) {
|
|
506
|
+
const value = getAt(object, path);
|
|
507
|
+
if (hasValue(value)) return value;
|
|
508
|
+
}
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function asNumber(value) {
|
|
513
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
514
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
515
|
+
const parsed = Number(value);
|
|
516
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
517
|
+
}
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function asText(value) {
|
|
522
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
523
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
524
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
525
|
+
return UNAVAILABLE;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function formatNumber(value) {
|
|
529
|
+
const number = asNumber(value);
|
|
530
|
+
if (number === null) return UNAVAILABLE;
|
|
531
|
+
return new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(number);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function formatTokens(value) {
|
|
535
|
+
const number = asNumber(value);
|
|
536
|
+
if (number === null) return UNAVAILABLE;
|
|
537
|
+
if (Math.abs(number) < 1000) return formatNumber(number);
|
|
538
|
+
const units = ["k", "M", "B"];
|
|
539
|
+
let scaled = number;
|
|
540
|
+
let unit = "";
|
|
541
|
+
for (const nextUnit of units) {
|
|
542
|
+
if (Math.abs(scaled) < 1000) break;
|
|
543
|
+
scaled /= 1000;
|
|
544
|
+
unit = nextUnit;
|
|
545
|
+
}
|
|
546
|
+
return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(scaled)}${unit}`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function formatDuration(milliseconds) {
|
|
550
|
+
const value = asNumber(milliseconds);
|
|
551
|
+
if (value === null || value < 0) return UNAVAILABLE;
|
|
552
|
+
const totalSeconds = Math.floor(value / 1000);
|
|
553
|
+
if (totalSeconds < 1) return "<1s";
|
|
554
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
555
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
556
|
+
const seconds = totalSeconds % 60;
|
|
557
|
+
const parts = [];
|
|
558
|
+
if (hours) parts.push(`${hours}h`);
|
|
559
|
+
if (minutes || hours) parts.push(`${minutes}m`);
|
|
560
|
+
if (!hours && seconds) parts.push(`${seconds}s`);
|
|
561
|
+
return parts.join(" ");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function formatTimestamp(value) {
|
|
565
|
+
const number = asNumber(value);
|
|
566
|
+
const date = number === null
|
|
567
|
+
? new Date(typeof value === "string" ? value : NaN)
|
|
568
|
+
: new Date(number < 100000000000 ? number * 1000 : number);
|
|
569
|
+
if (Number.isNaN(date.getTime())) return asText(value);
|
|
570
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
571
|
+
dateStyle: "medium",
|
|
572
|
+
timeStyle: "short"
|
|
573
|
+
}).format(date);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function basename(path) {
|
|
577
|
+
if (typeof path !== "string" || !path.trim()) return UNAVAILABLE;
|
|
578
|
+
const pieces = path.replace(/[\\/]+$/, "").split(/[\\/]/);
|
|
579
|
+
return pieces[pieces.length - 1] || UNAVAILABLE;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function sessionText(data) {
|
|
583
|
+
return asText(firstValue(data, [
|
|
584
|
+
"session", "session_name", "sessionName", "session_id", "sessionId",
|
|
585
|
+
"thread.name", "thread.title", "thread.preview", "thread.id"
|
|
586
|
+
]));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function projectText(data) {
|
|
590
|
+
const project = firstValue(data, [
|
|
591
|
+
"project", "project_name", "projectName", "thread.project", "thread.projectName"
|
|
592
|
+
]);
|
|
593
|
+
const branch = firstValue(data, ["branch", "git_branch", "gitBranch", "thread.gitInfo.branch"]);
|
|
594
|
+
const projectValue = hasValue(project) ? asText(project) : basename(firstValue(data, ["cwd", "thread.cwd"]));
|
|
595
|
+
if (projectValue === UNAVAILABLE) return UNAVAILABLE;
|
|
596
|
+
return hasValue(branch) ? `${projectValue} · ${asText(branch)}` : projectValue;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function durationText(data) {
|
|
600
|
+
const direct = firstValue(data, ["duration", "duration_text", "durationText"]);
|
|
601
|
+
if (typeof direct === "string" && direct.trim()) return direct.trim();
|
|
602
|
+
const milliseconds = firstValue(data, ["duration_ms", "durationMs", "elapsed_ms", "elapsedMs"]);
|
|
603
|
+
if (asNumber(milliseconds) !== null) return formatDuration(milliseconds);
|
|
604
|
+
const seconds = firstValue(data, ["duration_seconds", "durationSeconds"]);
|
|
605
|
+
if (asNumber(seconds) !== null) return formatDuration(asNumber(seconds) * 1000);
|
|
606
|
+
const started = asNumber(firstValue(data, ["started_at_ms", "startedAtMs", "started_at", "startedAt"]));
|
|
607
|
+
const finished = asNumber(firstValue(data, ["finished_at_ms", "finishedAtMs", "finished_at", "finishedAt"]));
|
|
608
|
+
if (started !== null && finished !== null) {
|
|
609
|
+
const startMs = started < 100000000000 ? started * 1000 : started;
|
|
610
|
+
const finishMs = finished < 100000000000 ? finished * 1000 : finished;
|
|
611
|
+
return formatDuration(finishMs - startMs);
|
|
612
|
+
}
|
|
613
|
+
return UNAVAILABLE;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function countCollection(value) {
|
|
617
|
+
if (Array.isArray(value)) return value.length;
|
|
618
|
+
if (isRecord(value)) return Object.keys(value).length;
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function participantsText(data) {
|
|
623
|
+
const direct = firstValue(data, ["participants", "participant_count", "participantCount"]);
|
|
624
|
+
if (typeof direct === "string" || typeof direct === "number") return asText(direct);
|
|
625
|
+
if (Array.isArray(direct)) return formatNumber(direct.length);
|
|
626
|
+
if (isRecord(direct)) {
|
|
627
|
+
const total = firstValue(direct, ["total", "count", "total_count", "totalCount"]);
|
|
628
|
+
if (hasValue(total)) return formatNumber(total);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const hasParent = isRecord(data.parent) || hasValue(data.parent);
|
|
632
|
+
const workerCount = countCollection(data.workers);
|
|
633
|
+
if (hasParent && workerCount !== null) {
|
|
634
|
+
return `1 parent + ${formatNumber(workerCount)} worker${workerCount === 1 ? "" : "s"}`;
|
|
635
|
+
}
|
|
636
|
+
if (workerCount !== null) return formatNumber(workerCount);
|
|
637
|
+
return UNAVAILABLE;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function usageTotal(data) {
|
|
641
|
+
const direct = firstValue(data, [
|
|
642
|
+
"total_tokens", "totalTokens", "attributed_tokens", "attributedTokens",
|
|
643
|
+
"tokens.total", "tokens.total_tokens", "tokens.totalTokens", "usage.total_tokens", "usage.totalTokens"
|
|
644
|
+
]);
|
|
645
|
+
if (asNumber(direct) !== null) return asNumber(direct);
|
|
646
|
+
|
|
647
|
+
const usages = [];
|
|
648
|
+
if (isRecord(data.parent)) usages.push(firstValue(data.parent, ["usage_delta.total_tokens", "usage_delta.totalTokens", "usage.total_tokens", "usage.totalTokens"]));
|
|
649
|
+
if (isRecord(data.workers)) {
|
|
650
|
+
for (const worker of Object.values(data.workers)) {
|
|
651
|
+
if (isRecord(worker)) usages.push(firstValue(worker, ["usage.total_tokens", "usage.totalTokens", "usage_delta.total_tokens", "usage_delta.totalTokens"]));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (usages.length && usages.every((value) => asNumber(value) !== null)) {
|
|
655
|
+
return usages.reduce((sum, value) => sum + asNumber(value), 0);
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function tokensText(data) {
|
|
661
|
+
const total = usageTotal(data);
|
|
662
|
+
if (total !== null) return `${formatTokens(total)} total`;
|
|
663
|
+
const input = firstValue(data, ["tokens.input", "tokens.input_tokens", "usage.input_tokens", "usage.inputTokens"]);
|
|
664
|
+
const output = firstValue(data, ["tokens.output", "tokens.output_tokens", "usage.output_tokens", "usage.outputTokens"]);
|
|
665
|
+
const parts = [];
|
|
666
|
+
if (asNumber(input) !== null) parts.push(`${formatTokens(input)} in`);
|
|
667
|
+
if (asNumber(output) !== null) parts.push(`${formatTokens(output)} out`);
|
|
668
|
+
return parts.length ? parts.join(" · ") : UNAVAILABLE;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function quotaWindows(data) {
|
|
672
|
+
const candidates = [
|
|
673
|
+
firstValue(data, ["quota.windows", "quotaWindows", "quota_windows", "account_quota.windows"]),
|
|
674
|
+
firstValue(data, ["quota_change_during_run", "quota_before", "quota_after"]),
|
|
675
|
+
firstValue(data, ["quota", "account_quota"])
|
|
676
|
+
];
|
|
677
|
+
for (const candidate of candidates) {
|
|
678
|
+
if (Array.isArray(candidate)) return candidate.filter(isRecord);
|
|
679
|
+
if (isRecord(candidate)) {
|
|
680
|
+
if (Array.isArray(candidate.windows)) return candidate.windows.filter(isRecord);
|
|
681
|
+
if (firstValue(candidate, ["used_percent", "usedPercent", "remaining", "remaining_percent", "remainingPercent"]) !== undefined) return [candidate];
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return [];
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function windowLabel(window) {
|
|
688
|
+
const explicit = firstValue(window, ["label", "name", "window"]);
|
|
689
|
+
if (hasValue(explicit) && typeof explicit !== "object") return asText(explicit);
|
|
690
|
+
const minutes = asNumber(firstValue(window, ["window_duration_mins", "windowDurationMins", "duration_mins", "durationMins"]));
|
|
691
|
+
if (minutes === null) return "Quota";
|
|
692
|
+
if (minutes % 10080 === 0) return `${minutes / 10080}w`;
|
|
693
|
+
if (minutes % 1440 === 0) return `${minutes / 1440}d`;
|
|
694
|
+
if (minutes % 60 === 0) return `${minutes / 60}h`;
|
|
695
|
+
return `${minutes}m`;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function percentText(value) {
|
|
699
|
+
const number = asNumber(value);
|
|
700
|
+
if (number === null) return UNAVAILABLE;
|
|
701
|
+
return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(number)}%`;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function quotaText(data) {
|
|
705
|
+
const pieces = quotaWindows(data).map((window) => {
|
|
706
|
+
const label = windowLabel(window);
|
|
707
|
+
const used = firstValue(window, ["used_percent", "usedPercent", "used"]);
|
|
708
|
+
const remaining = firstValue(window, ["remaining_percent", "remainingPercent", "remaining"]);
|
|
709
|
+
if (asNumber(used) !== null) return `${label}: ${percentText(used)} used`;
|
|
710
|
+
if (asNumber(remaining) !== null) return `${label}: ${percentText(remaining)} remaining`;
|
|
711
|
+
return null;
|
|
712
|
+
}).filter(Boolean);
|
|
713
|
+
return pieces.length ? pieces.join(" · ") : UNAVAILABLE;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function latestTimestamp(data) {
|
|
717
|
+
return firstValue(data, [
|
|
718
|
+
"last_updated", "lastUpdated", "updated_at", "updatedAt", "finished_at_ms", "finishedAtMs",
|
|
719
|
+
"completed_at_ms", "completedAtMs"
|
|
720
|
+
]);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function resetText(data) {
|
|
724
|
+
const window = quotaWindows(data)[0];
|
|
725
|
+
return window ? formatTimestamp(firstValue(window, ["resets_at", "resetsAt", "reset_at", "resetAt"])) : UNAVAILABLE;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function updateText(element, value) {
|
|
729
|
+
element.textContent = hasValue(value) ? String(value) : UNAVAILABLE;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function displayModeFrom(value) {
|
|
733
|
+
if (!isRecord(value)) return null;
|
|
734
|
+
const candidate = firstValue(value, ["displayMode", "display_mode", "displayMode.mode", "display_mode.mode", "mode", "params.displayMode", "params.display_mode"]);
|
|
735
|
+
if (typeof candidate === "string") return candidate.toLowerCase();
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function applyDisplayMode(mode) {
|
|
740
|
+
if (typeof mode !== "string" || !mode) return;
|
|
741
|
+
state.displayMode = mode.toLowerCase();
|
|
742
|
+
if (state.displayMode === "fullscreen") state.detailsOpen = true;
|
|
743
|
+
document.body.dataset.displayMode = state.displayMode;
|
|
744
|
+
elements.modeLabel.textContent = state.displayMode === "pip" ? "Picture-in-picture" : state.displayMode === "fullscreen" ? "Expanded view" : "Compact view";
|
|
745
|
+
elements.pinButton.textContent = state.displayMode === "pip" ? "Keep visible" : "Pin";
|
|
746
|
+
elements.pinButton.setAttribute("aria-pressed", String(state.displayMode === "pip"));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function render() {
|
|
750
|
+
const data = isRecord(state.data) ? state.data : {};
|
|
751
|
+
updateText(elements.status, firstValue(data, ["status", "state", "run_status", "runStatus"]));
|
|
752
|
+
updateText(elements.session, sessionText(data));
|
|
753
|
+
updateText(elements.project, projectText(data));
|
|
754
|
+
updateText(elements.duration, durationText(data));
|
|
755
|
+
updateText(elements.participants, participantsText(data));
|
|
756
|
+
updateText(elements.tokens, tokensText(data));
|
|
757
|
+
updateText(elements.quota, quotaText(data));
|
|
758
|
+
const timestamp = latestTimestamp(data);
|
|
759
|
+
updateText(elements.updated, hasValue(timestamp) ? formatTimestamp(timestamp) : UNAVAILABLE);
|
|
760
|
+
updateText(elements.model, firstValue(data, ["parent.model", "model"]));
|
|
761
|
+
updateText(elements.branch, firstValue(data, ["branch", "git_branch", "gitBranch", "thread.gitInfo.branch"]));
|
|
762
|
+
updateText(elements.reset, resetText(data));
|
|
763
|
+
|
|
764
|
+
elements.summary.setAttribute("aria-busy", String(state.loading));
|
|
765
|
+
elements.refreshButton.disabled = state.loading;
|
|
766
|
+
elements.refreshButton.textContent = state.loading ? "Loading…" : "Refresh";
|
|
767
|
+
elements.detailsPanel.hidden = !state.detailsOpen;
|
|
768
|
+
elements.notice.textContent = state.notice;
|
|
769
|
+
elements.error.textContent = state.error;
|
|
770
|
+
elements.error.hidden = !state.error;
|
|
771
|
+
elements.signal.dataset.state = state.loading ? "loading" : state.error ? "error" : state.data ? "ready" : "unknown";
|
|
772
|
+
applyDisplayMode(state.displayMode);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async function refreshTelemetry(silent = false) {
|
|
776
|
+
if (state.loading) return;
|
|
777
|
+
const host = getHost();
|
|
778
|
+
if (!host || typeof host.callTool !== "function") {
|
|
779
|
+
if (!silent) {
|
|
780
|
+
state.error = "Refresh is unavailable outside the ChatGPT host.";
|
|
781
|
+
state.notice = "The compact view remains available.";
|
|
782
|
+
render();
|
|
783
|
+
}
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
state.loading = true;
|
|
788
|
+
state.error = "";
|
|
789
|
+
state.notice = "Loading latest telemetry…";
|
|
790
|
+
render();
|
|
791
|
+
try {
|
|
792
|
+
const result = await host.callTool("flowpilot_get_telemetry", { target: "last" });
|
|
793
|
+
const structured = extractStructuredContent(result);
|
|
794
|
+
if (!structured) throw new Error("No structured telemetry result");
|
|
795
|
+
state.data = structured;
|
|
796
|
+
state.notice = "Telemetry updated.";
|
|
797
|
+
} catch (_error) {
|
|
798
|
+
state.error = "Unable to load the latest telemetry.";
|
|
799
|
+
state.notice = "The last available view is still shown.";
|
|
800
|
+
} finally {
|
|
801
|
+
state.loading = false;
|
|
802
|
+
render();
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async function pinWidget() {
|
|
807
|
+
if (state.displayMode === "pip") {
|
|
808
|
+
state.notice = "Telemetry is already kept visible.";
|
|
809
|
+
state.error = "";
|
|
810
|
+
render();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const host = getHost();
|
|
814
|
+
if (!host || typeof host.requestDisplayMode !== "function") {
|
|
815
|
+
state.notice = "Pin is unavailable in this host; the compact view remains available.";
|
|
816
|
+
state.error = "";
|
|
817
|
+
render();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
state.notice = "Requesting Picture-in-picture…";
|
|
821
|
+
state.error = "";
|
|
822
|
+
render();
|
|
823
|
+
try {
|
|
824
|
+
const result = await host.requestDisplayMode({ mode: "pip" });
|
|
825
|
+
applyDisplayMode(displayModeFrom(result) || displayModeFrom(host) || "pip");
|
|
826
|
+
state.notice = state.displayMode === "pip" ? "Telemetry will stay visible in Picture-in-picture." : "Pin was not granted; telemetry remains available.";
|
|
827
|
+
} catch (_error) {
|
|
828
|
+
state.notice = "Pin was declined; telemetry remains available here.";
|
|
829
|
+
}
|
|
830
|
+
render();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async function expandDetails() {
|
|
834
|
+
if (state.displayMode === "fullscreen") {
|
|
835
|
+
state.detailsOpen = !state.detailsOpen;
|
|
836
|
+
render();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const host = getHost();
|
|
840
|
+
if (host && typeof host.requestDisplayMode === "function") {
|
|
841
|
+
state.notice = "Requesting expanded details…";
|
|
842
|
+
state.error = "";
|
|
843
|
+
render();
|
|
844
|
+
try {
|
|
845
|
+
const result = await host.requestDisplayMode({ mode: "fullscreen" });
|
|
846
|
+
const mode = displayModeFrom(result) || displayModeFrom(host) || "fullscreen";
|
|
847
|
+
if (mode) applyDisplayMode(mode);
|
|
848
|
+
state.detailsOpen = true;
|
|
849
|
+
state.notice = mode === "fullscreen" ? "Expanded details are available." : "Fullscreen was not granted; showing available details.";
|
|
850
|
+
} catch (_error) {
|
|
851
|
+
state.detailsOpen = true;
|
|
852
|
+
state.notice = "Fullscreen was not granted; showing available details.";
|
|
853
|
+
}
|
|
854
|
+
render();
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (document.documentElement && typeof document.documentElement.requestFullscreen === "function") {
|
|
859
|
+
try {
|
|
860
|
+
await document.documentElement.requestFullscreen();
|
|
861
|
+
applyDisplayMode("fullscreen");
|
|
862
|
+
state.notice = "Expanded details are available.";
|
|
863
|
+
} catch (_error) {
|
|
864
|
+
state.detailsOpen = true;
|
|
865
|
+
state.notice = "Fullscreen is unavailable; showing available details.";
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
state.detailsOpen = true;
|
|
869
|
+
state.notice = "Fullscreen is unavailable; showing available details.";
|
|
870
|
+
}
|
|
871
|
+
render();
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function isToolResultMessage(value) {
|
|
875
|
+
if (!isRecord(value)) return false;
|
|
876
|
+
const label = [value.type, value.method, value.event, value.name].filter((item) => typeof item === "string").join(" ").toLowerCase();
|
|
877
|
+
return label.includes("ui/notifications/tool-result") || label.includes("tool-result") || label.includes("tool_result");
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function handleHostMessage(event) {
|
|
881
|
+
const message = parseJson(event && event.data);
|
|
882
|
+
if (!isRecord(message)) return;
|
|
883
|
+
const mode = displayModeFrom(message);
|
|
884
|
+
const label = [message.type, message.method, message.event, message.name].filter((item) => typeof item === "string").join(" ").toLowerCase();
|
|
885
|
+
if (mode && (label.includes("display-mode") || label.includes("display_mode"))) applyDisplayMode(mode);
|
|
886
|
+
if (isToolResultMessage(message)) {
|
|
887
|
+
const structured = extractStructuredContent(message);
|
|
888
|
+
if (structured) {
|
|
889
|
+
state.data = structured;
|
|
890
|
+
state.error = "";
|
|
891
|
+
state.notice = "Telemetry updated by the host.";
|
|
892
|
+
render();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function handleToolResultEvent(event) {
|
|
898
|
+
const structured = extractStructuredContent(event && (event.detail || event.data || event));
|
|
899
|
+
if (structured) {
|
|
900
|
+
state.data = structured;
|
|
901
|
+
state.error = "";
|
|
902
|
+
state.notice = "Telemetry updated by the host.";
|
|
903
|
+
render();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
elements.pinButton.addEventListener("click", () => { void pinWidget(); });
|
|
908
|
+
elements.refreshButton.addEventListener("click", () => { void refreshTelemetry(); });
|
|
909
|
+
elements.detailsButton.addEventListener("click", () => { void expandDetails(); });
|
|
910
|
+
window.addEventListener("message", handleHostMessage);
|
|
911
|
+
window.addEventListener("ui/notifications/tool-result", handleToolResultEvent);
|
|
912
|
+
window.addEventListener("tool-result", handleToolResultEvent);
|
|
913
|
+
document.addEventListener("fullscreenchange", () => {
|
|
914
|
+
if (document.fullscreenElement) applyDisplayMode("fullscreen");
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
const host = getHost();
|
|
918
|
+
const initial = host ? extractStructuredContent(host.toolOutput) : null;
|
|
919
|
+
if (initial) state.data = initial;
|
|
920
|
+
const initialMode = host ? displayModeFrom(host) : null;
|
|
921
|
+
if (initialMode) applyDisplayMode(initialMode);
|
|
922
|
+
render();
|
|
923
|
+
if (!initial && host && typeof host.callTool === "function") void refreshTelemetry(true);
|
|
924
|
+
})();
|
|
925
|
+
</script>
|
|
926
|
+
</body>
|
|
927
|
+
</html>
|