Skip to content

Commit 8a9db58

Browse files
committed
added support for AI agents [WIP]
1 parent aea7245 commit 8a9db58

File tree

12 files changed

+696
-14
lines changed

12 files changed

+696
-14
lines changed

src/Tracy/Bar/Bar.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ public function renderLoader(DeferredContent $defer): void
6565
}
6666

6767

68+
/**
69+
* Renders debug bar as plain text (markdown).
70+
*/
71+
public function renderAsText(): void
72+
{
73+
$time = microtime(true) - Debugger::$time;
74+
$memory = memory_get_peak_usage() / 1_000_000;
75+
$warningsPanel = $this->panels['Tracy:warnings'] ?? null;
76+
require __DIR__ . '/dist/markdown.phtml';
77+
}
78+
79+
6880
/**
6981
* Renders debug bar.
7082
*/
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{*
2+
* Tracy Bar rendered as HTML comment (for AI agents).
3+
*
4+
* @var float $time
5+
* @var float $memory
6+
* @var ?Tracy\DefaultBarPanel $warningsPanel
7+
*}
8+
9+
<!-- tracy
10+
Tracy Bar | {=number_format($time * 1000, 1)} ms | {=number_format($memory, 2)} MB
11+
{if $warningsPanel instanceof \Tracy\DefaultBarPanel && $warningsPanel->data}
12+
13+
## Warnings
14+
15+
{foreach $warningsPanel->data as $key => $count}
16+
{do [$file, $line, $message] = explode('|', $key, 3)}
17+
- {=$message . ' in ' . $file . ':' . $line . ($count > 1 ? " (×" . $count . ')' : '')}
18+
{/foreach}
19+
{/if}
20+
-->

src/Tracy/Bar/dist/markdown.phtml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
declare(strict_types=1);
3+
?>
4+
<?php /**
5+
* Tracy Bar rendered as HTML comment (for AI agents).
6+
*
7+
* @var float $time
8+
* @var float $memory
9+
* @var ?Tracy\DefaultBarPanel $warningsPanel
10+
*/ ?>
11+
12+
<!-- tracy
13+
Tracy Bar | <?= number_format($time * 1000, 1) ?> ms | <?= number_format($memory, 2) ?> MB
14+
<?php if ($warningsPanel instanceof \Tracy\DefaultBarPanel && $warningsPanel->data): ?>
15+
16+
## Warnings
17+
18+
<?php foreach ($warningsPanel->data as $key => $count): ?>
19+
<?php [$file, $line, $message] = explode('|', $key, 3) ?>
20+
- <?= $message . ' in ' . $file . ':' . $line . ($count > 1 ? "" . $count . ')' : '') ?>
21+
22+
<?php endforeach ?>
23+
<?php endif ?>
24+
-->

src/Tracy/BlueScreen/BlueScreen.php

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,36 @@ public function render(\Throwable $exception): void
124124
}
125125

126126

127+
private static function exceptionTitle(\Throwable $exception): string
128+
{
129+
return $exception instanceof \ErrorException
130+
? Helpers::errorTypeToString($exception->getSeverity())
131+
: get_debug_type($exception);
132+
}
133+
134+
135+
/**
136+
* @param array<int, array<string, mixed>> $trace
137+
* @return array<int, array<string, mixed>>
138+
* @internal
139+
*/
140+
public static function cleanStackTrace(array $trace): array
141+
{
142+
if (in_array($trace[0]['class'] ?? null, [DevelopmentStrategy::class, ProductionStrategy::class], true)) {
143+
array_shift($trace);
144+
}
145+
146+
if (
147+
($trace[0]['class'] ?? null) === Debugger::class
148+
&& in_array($trace[0]['function'], ['shutdownHandler', 'errorHandler'], true)
149+
) {
150+
array_shift($trace);
151+
}
152+
153+
return $trace;
154+
}
155+
156+
127157
/** @internal */
128158
public function renderToAjax(\Throwable $exception, DeferredContent $defer): void
129159
{
@@ -161,9 +191,7 @@ private function renderTemplate(\Throwable $exception, string $template, bool $t
161191
$showEnvironment = $this->showEnvironment && (!str_contains($exception->getMessage(), 'Allowed memory size'));
162192
$info = array_filter($this->info);
163193
$source = Helpers::getSource();
164-
$title = $exception instanceof \ErrorException
165-
? Helpers::errorTypeToString($exception->getSeverity())
166-
: get_debug_type($exception);
194+
$title = self::exceptionTitle($exception);
167195
$lastError = $exception instanceof \ErrorException || $exception instanceof \Error
168196
? null
169197
: error_get_last();
@@ -207,6 +235,33 @@ private function renderTemplate(\Throwable $exception, string $template, bool $t
207235
}
208236

209237

238+
/**
239+
* Renders blue screen as plain text (markdown).
240+
*/
241+
public function renderAsText(\Throwable $exception): void
242+
{
243+
if (!headers_sent()) {
244+
header('Content-Type: text/markdown; charset=UTF-8');
245+
}
246+
247+
$showEnvironment = $this->showEnvironment && !str_contains($exception->getMessage(), 'Allowed memory size');
248+
$lastError = $exception instanceof \ErrorException || $exception instanceof \Error
249+
? null
250+
: error_get_last();
251+
$source = Helpers::getSource();
252+
253+
if (function_exists('apache_request_headers')) {
254+
$httpHeaders = apache_request_headers();
255+
} else {
256+
$httpHeaders = array_filter($_SERVER, fn($k) => str_starts_with($k, 'HTTP_'), ARRAY_FILTER_USE_KEY);
257+
$httpHeaders = array_combine(array_map(fn($k) => strtolower(strtr(substr($k, 5), '_', '-')), array_keys($httpHeaders)), $httpHeaders);
258+
}
259+
260+
$blueScreen = $this;
261+
require __DIR__ . '/dist/markdown.phtml';
262+
}
263+
264+
210265
/**
211266
* @return list<\stdClass>
212267
*/
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{use Tracy\BlueScreen}
2+
{use Tracy\Debugger}
3+
{use Tracy\Dumper}
4+
{use Tracy\Helpers}
5+
{*
6+
* BlueScreen rendered as markdown text (for AI agents and CLI).
7+
*
8+
* @var Throwable $exception
9+
* @var bool $showEnvironment
10+
* @var ?array{type: int, message: string, file: string, line: int} $lastError
11+
* @var array<string, string> $httpHeaders
12+
* @var string $source
13+
* @var BlueScreen $blueScreen
14+
*}
15+
This is an error page generated by Tracy (https://tracy.nette.org).
16+
17+
{do $_blocks = []}
18+
{define renderSource $file, $line}
19+
{do $srcLines = @file($file)}
20+
{if $srcLines}
21+
{do $start = (int) max(0, $line - 6)}
22+
{do $end = (int) min(count($srcLines), $line + 4)}
23+
24+
```php
25+
{foreach array_slice($srcLines, $start, $end - $start, true) as $i => $srcLine}
26+
{=sprintf('%s%4d | %s', $i + 1 === $line ? ' > ' : ' ', $i + 1, rtrim($srcLine))}
27+
{/foreach}
28+
```
29+
{do $mapped = Debugger::mapSource($file, $line)}
30+
{if $mapped && @is_file($mapped['file'])}
31+
{do $mLine = $mapped['line']}
32+
33+
Mapped source: {=$mapped['file'] . ($mLine ? ':' . $mLine : '')}
34+
{do $mappedLines = @file($mapped['file'])}
35+
{if $mappedLines && $mLine}
36+
{do $mStart = (int) max(0, $mLine - 6)}
37+
{do $mEnd = (int) min(count($mappedLines), $mLine + 4)}
38+
39+
```
40+
{foreach array_slice($mappedLines, $mStart, $mEnd - $mStart, true) as $i => $srcLine}
41+
{=sprintf('%s%4d | %s', $i + 1 === $mLine ? ' > ' : ' ', $i + 1, rtrim($srcLine))}
42+
{/foreach}
43+
```
44+
{/if}
45+
{/if}
46+
{/if}
47+
{/define}
48+
{foreach Helpers::getExceptionChain($exception) as $i => $ex}
49+
{do $title = $ex instanceof \ErrorException ? Helpers::errorTypeToString($ex->getSeverity()) : get_debug_type($ex)}
50+
{do $code = $ex->getCode() ? ' #' . $ex->getCode() : ''}
51+
{if $i === 0}
52+
# {$title}: {$ex->getMessage()}{$code}
53+
{else}
54+
55+
## Caused by: {$title}: {$ex->getMessage()}{$code}
56+
{/if}
57+
58+
in {$ex->getFile()}:{$ex->getLine()}
59+
{include renderSource $ex->getFile(), $ex->getLine()}
60+
{do $base = new \Exception}
61+
{if count(get_mangled_object_vars($ex)) > count(get_mangled_object_vars($base))}
62+
63+
## Exception Properties
64+
65+
{=Dumper::toText($ex, [Dumper::DEPTH => $blueScreen->maxDepth, Dumper::TRUNCATE => $blueScreen->maxLength, Dumper::ITEMS => $blueScreen->maxItems, Dumper::SCRUBBER => $blueScreen->scrubber, Dumper::KEYS_TO_HIDE => $blueScreen->keysToHide])}
66+
{/if}
67+
{do $stack = BlueScreen::cleanStackTrace($ex->getTrace())}
68+
{if $stack}
69+
70+
## Stack Trace
71+
72+
{foreach $stack as $j => $row}
73+
{do $call = isset($row['class']) ? $row['class'] . ($row['type'] ?? '::') . $row['function'] . '()' : $row['function'] . '()'}
74+
{do $location = isset($row['file']) ? $row['file'] . ':' . ($row['line'] ?? '?') : 'inner-code'}
75+
{=$j + 1}. {$call} {$location}
76+
{if !empty($row['args'])}
77+
{try}
78+
{do $params = isset($row['class']) ? (new \ReflectionMethod($row['class'], $row['function']))->getParameters() : (new \ReflectionFunction($row['function']))->getParameters()}
79+
{rollback}
80+
{do $params = []}
81+
{/try}
82+
{foreach $row['args'] as $k => $v}
83+
{do $name = isset($params[$k]) && !$params[$k]->isVariadic() ? '$' . $params[$k]->getName() : '#' . $k}
84+
{=' ' . $name . ' = ' . Dumper::toText($v, [Dumper::DEPTH => 2, Dumper::TRUNCATE => 100])}
85+
{/foreach}
86+
{/if}
87+
{/foreach}
88+
{do $shown = 0}
89+
{foreach $stack as $row}
90+
{continueIf $shown >= 2}
91+
{if isset($row['file'], $row['line']) && @is_file($row['file'])}
92+
{include renderSource $row['file'], $row['line']}
93+
{do $shown++}
94+
{/if}
95+
{/foreach}
96+
{/if}
97+
{/foreach}
98+
{if $lastError}
99+
100+
## Last Muted Error
101+
102+
{=Helpers::errorTypeToString($lastError['type'])}: {$lastError['message']}
103+
in {$lastError['file']}:{=$lastError['line']}
104+
{if @is_file($lastError['file'])}
105+
{include renderSource $lastError['file'], $lastError['line']}
106+
{/if}
107+
{/if}
108+
{if !Helpers::isCli() && isset($_SERVER['REQUEST_METHOD'])}
109+
110+
## HTTP Request
111+
112+
{$_SERVER['REQUEST_METHOD']} {$source}
113+
{if $httpHeaders}
114+
115+
### Headers
116+
117+
{foreach $httpHeaders as $k => $v}
118+
- {$k}: {$v}
119+
{/foreach}
120+
{/if}
121+
{if !empty($_GET)}
122+
123+
### $_GET
124+
125+
{=Dumper::toText($_GET, [Dumper::DEPTH => 3, Dumper::TRUNCATE => 200])}
126+
{/if}
127+
{if !empty($_POST)}
128+
129+
### $_POST
130+
131+
{=Dumper::toText($_POST, [Dumper::DEPTH => 3, Dumper::TRUNCATE => 200, Dumper::SCRUBBER => $blueScreen->scrubber, Dumper::KEYS_TO_HIDE => $blueScreen->keysToHide])}
132+
{/if}
133+
{if !empty($_COOKIE)}
134+
135+
### $_COOKIE
136+
137+
{=Dumper::toText($_COOKIE, [Dumper::DEPTH => 3, Dumper::TRUNCATE => 200, Dumper::SCRUBBER => $blueScreen->scrubber, Dumper::KEYS_TO_HIDE => $blueScreen->keysToHide])}
138+
{/if}
139+
{/if}
140+
{if $showEnvironment}
141+
142+
## Environment
143+
144+
| Key | Value |
145+
|-----|-------|
146+
| PHP | {=PHP_VERSION . ' (' . (PHP_ZTS ? 'TS' : 'NTS') . ')'} |
147+
{if isset($_SERVER['REQUEST_METHOD'])}
148+
| Method | {$_SERVER['REQUEST_METHOD']} |
149+
{/if}
150+
| Source | {$source} |
151+
{if isset($_SERVER['SERVER_SOFTWARE'])}
152+
| Server | {$_SERVER['SERVER_SOFTWARE']} |
153+
{/if}
154+
| Tracy | {=Debugger::Version} |
155+
{/if}

src/Tracy/BlueScreen/assets/section-stack-exception.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,7 @@
1212
* @var BlueScreen $blueScreen
1313
*/
1414

15-
$stack = $ex->getTrace();
16-
if (in_array($stack[0]['class'] ?? null, [DevelopmentStrategy::class, ProductionStrategy::class], true)) {
17-
array_shift($stack);
18-
}
19-
if (
20-
($stack[0]['class'] ?? null) === Debugger::class
21-
&& in_array($stack[0]['function'], ['shutdownHandler', 'errorHandler'], true)
22-
) {
23-
array_shift($stack);
24-
}
15+
$stack = BlueScreen::cleanStackTrace($ex->getTrace());
2516

2617
$expanded = null;
2718
if (

0 commit comments

Comments
 (0)