@agimon-ai/doompi-log 0.0.1-alpha.2 → 0.0.1-alpha.21
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.
- package/README.md +42 -19
- package/dist/adapters/node/metricsSource.cjs +2 -0
- package/dist/adapters/node/metricsSource.cjs.map +1 -0
- package/dist/adapters/node/metricsSource.d.cts +14 -0
- package/dist/adapters/node/metricsSource.d.cts.map +1 -0
- package/dist/adapters/node/metricsSource.d.mts +14 -0
- package/dist/adapters/node/metricsSource.d.mts.map +1 -0
- package/dist/adapters/node/metricsSource.mjs +2 -0
- package/dist/adapters/node/metricsSource.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.d.cts +34 -0
- package/dist/adapters/pi/extension.d.cts.map +1 -0
- package/dist/adapters/pi/extension.d.mts +34 -0
- package/dist/adapters/pi/extension.d.mts.map +1 -0
- package/dist/adapters/pi/extension.mjs +2 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/extension.cjs +1 -2
- package/dist/extension.d.cts +2 -33
- package/dist/extension.d.mts +2 -33
- package/dist/extension.mjs +1 -2
- package/dist/extensions/pi.cjs +1 -2
- package/dist/extensions/pi.d.cts +2 -4
- package/dist/extensions/pi.d.mts +2 -5
- package/dist/extensions/pi.mjs +1 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.mts +5 -4
- package/dist/index.mjs +1 -1
- package/dist/metrics.cjs +1 -2
- package/dist/metrics.d.cts +3 -94
- package/dist/metrics.d.mts +3 -94
- package/dist/metrics.mjs +1 -2
- package/dist/metricsSource.cjs +1 -2
- package/dist/metricsSource.d.cts +3 -25
- package/dist/metricsSource.d.mts +3 -25
- package/dist/metricsSource.mjs +1 -2
- package/dist/services/findings.cjs +2 -0
- package/dist/services/findings.cjs.map +1 -0
- package/dist/services/findings.d.cts +23 -0
- package/dist/services/findings.d.cts.map +1 -0
- package/dist/services/findings.d.mts +23 -0
- package/dist/services/findings.d.mts.map +1 -0
- package/dist/services/findings.mjs +2 -0
- package/dist/services/findings.mjs.map +1 -0
- package/dist/services/metrics.cjs +2 -0
- package/dist/services/metrics.cjs.map +1 -0
- package/dist/services/metrics.d.cts +155 -0
- package/dist/services/metrics.d.cts.map +1 -0
- package/dist/services/metrics.d.mts +155 -0
- package/dist/services/metrics.d.mts.map +1 -0
- package/dist/services/metrics.mjs +2 -0
- package/dist/services/metrics.mjs.map +1 -0
- package/dist/tui/logMetricsOverlay.cjs +2 -0
- package/dist/tui/logMetricsOverlay.cjs.map +1 -0
- package/dist/tui/logMetricsOverlay.d.cts +80 -0
- package/dist/tui/logMetricsOverlay.d.cts.map +1 -0
- package/dist/tui/logMetricsOverlay.d.mts +80 -0
- package/dist/tui/logMetricsOverlay.d.mts.map +1 -0
- package/dist/tui/logMetricsOverlay.mjs +2 -0
- package/dist/tui/logMetricsOverlay.mjs.map +1 -0
- package/dist/tui/metricsOverlay.cjs +1 -2
- package/dist/tui/metricsOverlay.d.cts +2 -71
- package/dist/tui/metricsOverlay.d.mts +2 -71
- package/dist/tui/metricsOverlay.mjs +1 -2
- package/dist/types/metricsSource.d.cts +16 -0
- package/dist/types/metricsSource.d.cts.map +1 -0
- package/dist/types/metricsSource.d.mts +16 -0
- package/dist/types/metricsSource.d.mts.map +1 -0
- package/package.json +16 -9
- package/dist/extension.cjs.map +0 -1
- package/dist/extension.d.cts.map +0 -1
- package/dist/extension.d.mts.map +0 -1
- package/dist/extension.mjs.map +0 -1
- package/dist/extensions/pi.cjs.map +0 -1
- package/dist/extensions/pi.d.cts.map +0 -1
- package/dist/extensions/pi.d.mts.map +0 -1
- package/dist/extensions/pi.mjs.map +0 -1
- package/dist/metrics.cjs.map +0 -1
- package/dist/metrics.d.cts.map +0 -1
- package/dist/metrics.d.mts.map +0 -1
- package/dist/metrics.mjs.map +0 -1
- package/dist/metricsSource.cjs.map +0 -1
- package/dist/metricsSource.d.cts.map +0 -1
- package/dist/metricsSource.d.mts.map +0 -1
- package/dist/metricsSource.mjs.map +0 -1
- package/dist/tui/metricsOverlay.cjs.map +0 -1
- package/dist/tui/metricsOverlay.d.cts.map +0 -1
- package/dist/tui/metricsOverlay.d.mts.map +0 -1
- package/dist/tui/metricsOverlay.mjs.map +0 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { LogMetricsSnapshot } from "../services/metrics.mjs";
|
|
2
|
+
import { MetricsQuery, MetricsTransport } from "../types/metricsSource.mjs";
|
|
3
|
+
import { DoomOverlay, DoomOverlayChrome, DoomOverlayTui } from "./doomOverlay.mjs";
|
|
4
|
+
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
//#region src/tui/logMetricsOverlay.d.ts
|
|
6
|
+
interface SinkStatus {
|
|
7
|
+
service: string;
|
|
8
|
+
backend: string;
|
|
9
|
+
endpoint: string;
|
|
10
|
+
endpointSource: string;
|
|
11
|
+
traces: boolean;
|
|
12
|
+
redaction: boolean;
|
|
13
|
+
fileFallback: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface LogMetricsView {
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
snapshot: LogMetricsSnapshot;
|
|
18
|
+
sink: SinkStatus | undefined;
|
|
19
|
+
/** Which transport answered the last history query, for the panel status line. */
|
|
20
|
+
transport?: MetricsTransport;
|
|
21
|
+
/**
|
|
22
|
+
* Most recent telemetry diagnostic. Surfaced here because writing it to stderr
|
|
23
|
+
* would corrupt the TUI frame.
|
|
24
|
+
*/
|
|
25
|
+
lastDiagnostic?: string;
|
|
26
|
+
}
|
|
27
|
+
declare class LogMetricsOverlayComponent extends DoomOverlay {
|
|
28
|
+
private readonly getView;
|
|
29
|
+
private readonly done;
|
|
30
|
+
private readonly timer;
|
|
31
|
+
private readonly query;
|
|
32
|
+
private disposed;
|
|
33
|
+
private groupIndex;
|
|
34
|
+
private periodIndex;
|
|
35
|
+
private requestId;
|
|
36
|
+
private loading;
|
|
37
|
+
private showDetail;
|
|
38
|
+
private report;
|
|
39
|
+
private reportError;
|
|
40
|
+
constructor(tui: DoomOverlayTui, theme: Theme, getView: () => LogMetricsView, done: (result: undefined) => void, query?: MetricsQuery);
|
|
41
|
+
handleInput(data: string): void;
|
|
42
|
+
/** Re-reads the live session counters, and restarts the history query behind them. */
|
|
43
|
+
private refresh;
|
|
44
|
+
/**
|
|
45
|
+
* Sink history is fetched out of band: the query crosses a process boundary
|
|
46
|
+
* and takes seconds on a busy database, so render never waits on it. Stale
|
|
47
|
+
* responses are dropped by sequence number when the dimension changes mid
|
|
48
|
+
* flight.
|
|
49
|
+
*/
|
|
50
|
+
private fetchReport;
|
|
51
|
+
/** Ranked consumers for the selected dimension, sourced from the sink database. */
|
|
52
|
+
private consumerPanel;
|
|
53
|
+
private burnPanel;
|
|
54
|
+
private findingsPanel;
|
|
55
|
+
private toolCostPanel;
|
|
56
|
+
private errorsPanel;
|
|
57
|
+
private groupBy;
|
|
58
|
+
private period;
|
|
59
|
+
/** One status line for the history panels: what is shown, from where. */
|
|
60
|
+
private historyStatus;
|
|
61
|
+
private transportLabel;
|
|
62
|
+
dispose(): void;
|
|
63
|
+
protected getChrome(): DoomOverlayChrome;
|
|
64
|
+
protected renderBody(width: number, height: number): string[];
|
|
65
|
+
/**
|
|
66
|
+
* An explicit disabled state: zeroed stat cells would read as a real session
|
|
67
|
+
* that happened to do nothing.
|
|
68
|
+
*/
|
|
69
|
+
private disabledBody;
|
|
70
|
+
/**
|
|
71
|
+
* The `?` pane. Sink wiring and the caveats behind each panel live here so the
|
|
72
|
+
* default view can spend its rows on numbers instead of explaining itself.
|
|
73
|
+
*/
|
|
74
|
+
private detailBody;
|
|
75
|
+
private metricsBody;
|
|
76
|
+
private singleColumnPanels;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { LogMetricsOverlayComponent, LogMetricsView, SinkStatus };
|
|
80
|
+
//# sourceMappingURL=logMetricsOverlay.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logMetricsOverlay.d.mts","names":[],"sources":["../../src/tui/logMetricsOverlay.ts"],"mappings":";;;;;UAsBiB;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA,UAAU;EACV,MAAM;;EAEN,YAAY;;;;;EAKZ;;cAoYW,mCAAmC;mBAC7B;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;UACA;EAGN,YAAA,KAAK,gBACL,OAAO,OACP,eAAe,gBACf,OAAO,4BACP,QAAQ;EAaV,YAAY;;UA0BJ;;;;;;;UAWA;;UAuBA;UAMA;UAOA;UAQA;UAWA;UAQA;UAIA;;UAKA;UAOA;EAIR;YAKU,aAAa;YAiBb,WAAW,eAAe;;;;;UAW5B;;;;;UAYA;UAgBA;UA0CA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{deriveFindings as e}from"../services/findings.mjs";import{DOOM_OVERLAY_ACCENT as t,DoomOverlay as n}from"./doomOverlay.mjs";import{matchesKey as r,truncateToWidth as i,visibleWidth as a}from"@earendil-works/pi-tui";const o=[`session`,`agent`,`workflow-name`,`step`],s=[`day`,`week`,`month`,`all`],c=o[0],l=s[0],u=`SINK STATUS`,d=`AGENT_TELEMETRY_DISABLED`,f=`not connected`,p=[0,1,2],m=1e3,h=1e6,g=1e3,_={critical:`▲`,warning:`●`,info:`·`},v={critical:`error`,warning:`warning`,info:`dim`};function y(e,t){let n=a(e);return n>=t?i(e,t,`…`):e+` `.repeat(t-n)}function b(e,t){let n=a(e);return n>=t?i(e,t):` `.repeat(t-n)+e}function x(e){return e.toLocaleString(`en-US`)}function S(e){return e>=h?`${(e/h).toFixed(1)}M`:e>=g?`${Math.round(e/g)}k`:String(Math.round(e))}function C(e){return`${Math.round(e*100)}%`}function w(e){return e===void 0?`—`:e>=m?`${(e/m).toFixed(2)}s`:`${Math.round(e)}ms`}function T(e){return new Date(e).toTimeString().slice(0,5)}function E(e,t,n){let r=Math.min(n,Math.max(+(e>0),Math.round(e/t*n)));return`█`.repeat(r)+`░`.repeat(n-r)}function D(e,t,n){return i(y(e,14)+t,n)}function O(e,t){return t>0?e/t:0}function k(e,t,n,r){return[y(e,r),y(t,r),y(n,r)]}function A(e,t,n,r,i,a){return[y(e,i),y(a.fg(r?`success`:`warning`,t),i),y(n,i)]}function j(e,t,n){let r=Math.max(1,Math.floor(t/6)),{tokens:i,turns:a}=e,o=O(i.cacheRead,i.cacheRead+i.input),s=O(e.failedToolCalls,e.toolCalls),c=e.errors+e.abortedTurns+e.failedTurns,l=e.failureCategories[0],u=[k(`TOKENS`,S(i.total),`↑${S(i.input)} ↓${S(i.output)}`,r),A(`CACHE`,C(o),`${S(i.cacheRead)} read`,o>=.3,r,n),k(`COST`,`$${e.cost.toFixed(2)}`,a>0?`$${(e.cost/a).toFixed(3)}/turn`:`this session`,r),k(`TURNS`,x(a),a>0?`${S(i.total/a)} tok/turn`:`—`,r),A(`TOOL FAIL`,C(s),`${e.failedToolCalls} of ${e.toolCalls}`,s<=.05,r,n),A(`ISSUES`,x(c),l?l.name:`clean`,c===0,r,n)];return p.map(e=>y(u.map(t=>t[e]??``).join(``),t))}function M(e,t){let n=t>0?e/t:0;return n>=.66?`error`:n>=.33?`warning`:`success`}function N(e,t,n){let r=Math.max(1,t-2-9-25);return e.slice(0,5).map(e=>i(n.fg(v[e.severity],y(_[e.severity],2))+y(e.subject,9)+y(e.detail,25)+n.fg(`dim`,y(e.action,r)),t))}function P(e,t,n,r){let a=e.slice(0,6),o=Math.max(1,n-10-8-6-6-8-1),s=Math.max(...a.map(e=>e.p90Tokens??0),1);return a.map(e=>{let a=e.p90Tokens??0,c=r.fg(M(a,s),E(a,s,o)),l=O(e.failed,e.calls),u=l>0?r.fg(`error`,b(C(l),6)):b(`0%`,6);return i(y(e.name,10)+c+` `+b(e.p90Tokens===void 0?`—`:S(a),8)+b(x(e.calls),6)+u+r.fg(`dim`,b(w(t.get(e.name)),8)),n)})}function F(e,t){let n=Math.max(1,t-6-12-5);return e.recentErrors.slice(0,5).map(e=>i(y(T(e.at),6)+y(e.event,12)+y(e.message,n)+b(e.code,5),t))}function I(e,t,n){let r=n?[D(`diagnostic`,n,t)]:[];return e?[D(`backend`,`${e.backend} · ${e.endpointSource}`,t),D(`endpoint`,e.endpoint,t),...r]:[D(`status`,f,t),D(`metrics`,`aggregated in-process anyway`,t),...r]}function L(e,t){return e?[D(`service`,e.service,t),D(`backend`,e.backend,t),D(`endpoint`,`${e.endpointSource} · ${e.endpoint}`,t),D(`traces`,`${e.traces?`on`:`off`} · AGENT_OTEL_TRACES`,t),D(`redaction`,`${e.redaction?`on`:`off`} · metadata only`,t),D(`file fallback`,e.fileFallback?`allowed`:`disabled`,t)]:[D(`status`,f,t),D(`reason`,`no sink handle this session`,t)]}function R(e,t,n){let r=e?.groups.slice(0,6)??[];if(r.length===0)return[];let a=Math.max(1,t-20-12-8-1),o=Math.max(...r.map(e=>e.totalTokens),1);return r.map(e=>{let r=n.fg(e.issueCount>0?`error`:`accent`,E(e.totalTokens,o,a));return i(`${y(e.key,20)}${y(e.agentName??`—`,12)}${r} ${b(S(e.totalTokens),8)}`,t)})}function z(e,t,n){let r=e?.timeline.slice(-8)??[];if(r.length===0)return[];let a=Math.max(1,t-17-8-1),o=Math.max(...r.map(e=>e.totalTokens),1);return r.map(e=>{let r=n.fg(`success`,E(e.totalTokens,o,a));return i(`${y(e.label,17)}${r} ${b(S(e.totalTokens),8)}`,t)})}function B(e,t){if(t<1)return[e];let n=[],r=``;for(let i of e.split(` `)){let e=r.length===0?i:`${r} ${i}`;e.length>t&&r.length>0?(n.push(r),r=i):r=e}return r.length>0&&n.push(r),n.map(e=>y(e,t))}var V=class extends n{getView;done;timer;query;disposed=!1;groupIndex=0;periodIndex=0;requestId=0;loading=!1;showDetail=!1;report;reportError;constructor(e,t,n,r,i){super(e,t),this.getView=n,this.done=r,this.query=i,this.timer=setInterval(()=>{this.disposed||this.tui.requestRender()},1e3),this.timer.unref?.(),this.fetchReport()}handleInput(e){if(r(e,`escape`)||r(e,`ctrl+c`)||r(e,`q`)){this.done(void 0);return}let t=e.toLowerCase();if(t===`?`){this.showDetail=!this.showDetail,this.tui.requestRender();return}if(t===`g`){this.groupIndex=(this.groupIndex+1)%o.length,this.refresh();return}if(t===`p`){this.periodIndex=(this.periodIndex+1)%s.length,this.refresh();return}t===`r`&&this.refresh()}refresh(){this.fetchReport(),this.tui.requestRender()}fetchReport(){if(!this.query)return;let e=++this.requestId;this.loading=!0,this.reportError=void 0,this.query({groupBy:this.groupBy(),period:this.period(),limit:6}).then(t=>{this.disposed||e!==this.requestId||(this.report=t,this.loading=!1,this.tui.requestRender())}).catch(t=>{this.disposed||e!==this.requestId||(this.report=void 0,this.reportError=t instanceof Error?t.message:String(t),this.loading=!1,this.tui.requestRender())})}consumerPanel(e){let t=this.theme.fg(`accent`,`TOP CONSUMERS · by ${this.groupBy()} · ${this.period()}`),n=R(this.report,e,this.theme);return[t,...n.length>0?n:[this.theme.fg(`dim`,this.historyStatus())]]}burnPanel(e){let t=this.report?.bucket??``,n=this.theme.fg(`accent`,`TOKEN BURN${t?` · per ${t}`:``}`),r=z(this.report,e,this.theme);return[n,...r.length>0?r:[this.theme.fg(`dim`,this.historyStatus())]]}findingsPanel(t,n){let r=N(e(t),n,this.theme);return[this.theme.fg(`accent`,`WHAT TO FIX · derived from this session`),...r.length>0?r:[this.theme.fg(`dim`,`no records yet`)]]}toolCostPanel(e,t){let n=new Map(e.toolLatency.flatMap(e=>e.p95Ms===void 0?[]:[[e.name,e.p95Ms]])),r=P(e.toolCost,n,t,this.theme);return[this.theme.fg(`accent`,`TOOL COST · p90 turn tokens · calls · fail% · p95`),...r.length>0?r:[this.theme.fg(`dim`,`no tool calls yet`)]]}errorsPanel(e,t){let n=F(e,t);return[this.theme.fg(`accent`,`RECENT ERRORS`),...n.length>0?n:[this.theme.fg(`dim`,`none`)]]}groupBy(){return o[this.groupIndex]??c}period(){return s[this.periodIndex]??l}historyStatus(){return this.loading?`loading…`:this.reportError?this.reportError:this.report?`${this.transportLabel()} · ${this.report.totals.groupCount} groups`:`no sink history available`}transportLabel(){return this.getView().transport??`sink`}dispose(){this.disposed=!0,clearInterval(this.timer)}getChrome(){return{title:`LOG METRICS`,accent:t,breadcrumb:`SPC › h / help › l / logs`,headerRight:`doom-log · service pi · this session`,footer:`r refresh · g group · p period · ? detail · esc close`,footerHints:[[`r`,`refresh`],[`g`,`group`],[`p`,`period`],[`?`,`detail`],[`esc`,`close`]]}}renderBody(e,t){let n=this.getView();return n.disabled?this.disabledBody(e):this.showDetail?this.detailBody(n,e).slice(0,t):this.metricsBody(n,e,t)}disabledBody(e){return[i(this.theme.fg(`warning`,` Metrics collection is disabled for this session.`),e),i(this.theme.fg(`dim`,` ${d}=1 is set, so no log records are produced.`),e),i(this.theme.fg(`dim`,` Unset ${d} and restart Pi to collect metrics.`),e)]}detailBody(e,t){let n=Math.max(1,t-1);return[this.theme.fg(`accent`,u),...L(e.sink,n),``,this.theme.fg(`accent`,`COLLECTION`),D(`source`,`in-process Pi events`,n),D(`scope`,`current session only`,n),...B(`Headline counters are aggregated in-process from this session. TOP CONSUMERS and TOKEN BURN come from the sink database, so they span every agent and workflow in the selected period, not just this session.`,n),``,this.theme.fg(`accent`,`TOOL COST ATTRIBUTION`),...B(`TOOL COST reports the p90 total tokens of the turns a tool took part in, which is the only attribution Pi emits. A turn running several tools attributes its total to each of them, so the column ranks tools by the weight of the turns they appear in — it is not a per-tool share of spend.`,n)].map(e=>y(` ${e}`,t))}metricsBody(e,t,n){let r=j(e.snapshot,t,this.theme);if(n<=r.length)return r.slice(0,n);if(t<72)return[...r,``,...this.singleColumnPanels(e,t)].slice(0,n);let i=Math.max(24,Math.floor(t*.4)),a=Math.max(1,t-i-1),o=Math.max(1,a-1),s=Math.max(1,i-1),c=[...this.findingsPanel(e.snapshot,o),``,...this.toolCostPanel(e.snapshot,o),``,...this.consumerPanel(o)],l=[...this.burnPanel(s),``,...this.errorsPanel(e.snapshot,s),``,this.theme.fg(`accent`,u),...I(e.sink,s,e.lastDiagnostic)],d=Math.max(0,n-r.length-1),f=[this.theme.fg(`borderMuted`,`${`─`.repeat(a)}┬${`─`.repeat(i)}`)];for(let e=0;e<d;e++)f.push(y(` ${c[e]??``}`,a)+this.theme.fg(`borderMuted`,`│`)+y(` ${l[e]??``}`,i));return[...r,...f].slice(0,n)}singleColumnPanels(e,t){return[...this.findingsPanel(e.snapshot,t),``,...this.toolCostPanel(e.snapshot,t),``,...this.errorsPanel(e.snapshot,t),``,this.theme.fg(`accent`,u),...I(e.sink,t,e.lastDiagnostic)]}};export{V as LogMetricsOverlayComponent};
|
|
2
|
+
//# sourceMappingURL=logMetricsOverlay.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logMetricsOverlay.mjs","names":[],"sources":["../../src/tui/logMetricsOverlay.ts"],"sourcesContent":["/**\n * The `SPC h l` Log Metrics overlay.\n *\n * The panel answers two questions and skips everything else: what is this\n * session costing, and what should change next. Raw plumbing counters\n * (per-record volume, sink wiring) are either derived into a finding or moved\n * behind `?`, because a number nobody can act on still costs a row.\n *\n * Rendering stays a pure `render(width)` so the layout can be asserted as text\n * without a live terminal. Every headline number comes from the in-process\n * aggregator, so the panels keep working when no sink is connected; only the\n * history panels degrade.\n */\n\nimport type { LogMetricsGroupBy, LogMetricsPeriod, LogMetricsReport } from '@agimon-ai/log-sink-mcp';\nimport type { Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { deriveFindings, type LogMetricsFinding, type LogMetricsFindingSeverity } from '../services/findings.ts';\nimport type { LogMetricsSnapshot, LogMetricsToolCost } from '../services/metrics';\nimport type { MetricsQuery, MetricsTransport } from '../types/metricsSource.ts';\nimport { DOOM_OVERLAY_ACCENT, DoomOverlay, type DoomOverlayChrome, type DoomOverlayTui } from './doomOverlay.ts';\n\nexport interface SinkStatus {\n service: string;\n backend: string;\n endpoint: string;\n endpointSource: string;\n traces: boolean;\n redaction: boolean;\n fileFallback: boolean;\n}\n\nexport interface LogMetricsView {\n disabled: boolean;\n snapshot: LogMetricsSnapshot;\n sink: SinkStatus | undefined;\n /** Which transport answered the last history query, for the panel status line. */\n transport?: MetricsTransport;\n /**\n * Most recent telemetry diagnostic. Surfaced here because writing it to stderr\n * would corrupt the TUI frame.\n */\n lastDiagnostic?: string;\n}\n\nconst TITLE = 'LOG METRICS';\nconst BREADCRUMB = 'SPC › h / help › l / logs';\nconst SUBTITLE = 'doom-log · service pi · this session';\nconst FOOTER = 'r refresh · g group · p period · ? detail · esc close';\nconst CONSUMERS_TITLE = 'TOP CONSUMERS';\nconst BURN_TITLE = 'TOKEN BURN';\n/** Dimensions that actually carry attribution today; workflow/step stay for runner-driven work. */\nconst GROUP_CYCLE = ['session', 'agent', 'workflow-name', 'step'] as const satisfies readonly LogMetricsGroupBy[];\nconst PERIOD_CYCLE = ['day', 'week', 'month', 'all'] as const satisfies readonly LogMetricsPeriod[];\n/** Head of each cycle, so the fallback never restates a literal. */\nconst DEFAULT_GROUP = GROUP_CYCLE[0];\nconst DEFAULT_PERIOD = PERIOD_CYCLE[0];\nconst MAX_TOKEN_ROWS = 6;\nconst MAX_BURN_ROWS = 8;\nconst GROUP_KEY_WIDTH = 20;\nconst AGENT_WIDTH = 12;\nconst BURN_LABEL_WIDTH = 17;\nconst LOADING_TEXT = 'loading…';\nconst NO_HISTORY_TEXT = 'no sink history available';\nconst FINDINGS_TITLE = 'WHAT TO FIX · derived from this session';\nconst NO_FINDINGS_TEXT = 'no records yet';\nconst TOOL_COST_TITLE = 'TOOL COST · p90 turn tokens · calls · fail% · p95';\nconst NO_TOOL_COST_TEXT = 'no tool calls yet';\nconst ERRORS_TITLE = 'RECENT ERRORS';\nconst NO_ERRORS_TEXT = 'none';\nconst SINK_TITLE = 'SINK STATUS';\nconst DETAIL_TITLE = 'COLLECTION';\nconst DISABLED_ENV = 'AGENT_TELEMETRY_DISABLED';\nconst NOT_CONNECTED = 'not connected';\nconst EMPTY_VALUE = '—';\nconst ELLIPSIS = '…';\nconst THIS_SESSION = 'this session';\nconst TOGGLE_ON = 'on';\nconst TOGGLE_OFF = 'off';\nconst CLEAN_TEXT = 'clean';\n\nconst MIN_TWO_COLUMN_WIDTH = 72;\nconst RIGHT_COLUMN_RATIO = 0.4;\nconst MIN_RIGHT_COLUMN = 24;\nconst REFRESH_MS = 1000;\n\nconst STAT_CELLS = 6;\nconst STAT_ROW_INDEXES = [0, 1, 2];\nconst TOOL_NAME_WIDTH = 10;\nconst VALUE_WIDTH = 8;\nconst CALLS_WIDTH = 6;\nconst FAIL_WIDTH = 6;\nconst LATENCY_WIDTH = 8;\nconst SINK_LABEL_WIDTH = 14;\nconst ERROR_TIME_WIDTH = 6;\n/** One wider than the longest event name, so the message never abuts it. */\nconst ERROR_EVENT_WIDTH = 12;\nconst ERROR_CODE_WIDTH = 5;\nconst FINDING_GLYPH_WIDTH = 2;\nconst FINDING_SUBJECT_WIDTH = 9;\nconst FINDING_DETAIL_WIDTH = 25;\nconst MAX_TOOL_ROWS = 6;\nconst MAX_FINDING_ROWS = 5;\nconst MAX_ERROR_ROWS = 5;\n/** Bar color thresholds, as a fraction of the panel's most expensive tool. */\nconst COST_WARN_RATIO = 0.33;\nconst COST_ERROR_RATIO = 0.66;\n/** Below this the session is not caching effectively; above it, it is working. */\nconst CACHE_WARN_RATIO = 0.3;\nconst TOOL_FAIL_WARN_RATIO = 0.05;\n\nconst SECOND_MS = 1000;\nconst MILLION = 1_000_000;\nconst THOUSAND = 1000;\nconst PERCENT = 100;\nconst COMPACT_DECIMALS = 1;\nconst DURATION_DECIMALS = 2;\nconst COST_DECIMALS = 2;\nconst PER_TURN_DECIMALS = 3;\nconst TIME_SLICE_END = 5;\n\nconst SEVERITY_GLYPH: Record<LogMetricsFindingSeverity, string> = { critical: '▲', warning: '●', info: '·' };\nconst SEVERITY_COLOR: Record<LogMetricsFindingSeverity, 'error' | 'warning' | 'dim'> = {\n critical: 'error',\n warning: 'warning',\n info: 'dim',\n};\n\nfunction pad(text: string, width: number): string {\n const visible = visibleWidth(text);\n // An explicit ellipsis: the default '...' also emits a colour reset, which\n // leaks escape codes into the middle of a padded column.\n return visible >= width ? truncateToWidth(text, width, ELLIPSIS) : text + ' '.repeat(width - visible);\n}\n\nfunction padStart(text: string, width: number): string {\n const visible = visibleWidth(text);\n return visible >= width ? truncateToWidth(text, width) : ' '.repeat(width - visible) + text;\n}\n\nfunction formatCount(value: number): string {\n return value.toLocaleString('en-US');\n}\n\nfunction formatCompact(value: number): string {\n if (value >= MILLION) return `${(value / MILLION).toFixed(COMPACT_DECIMALS)}M`;\n if (value >= THOUSAND) return `${Math.round(value / THOUSAND)}k`;\n return String(Math.round(value));\n}\n\nfunction formatPercent(ratio: number): string {\n return `${Math.round(ratio * PERCENT)}%`;\n}\n\nfunction formatDuration(ms: number | undefined): string {\n if (ms === undefined) return EMPTY_VALUE;\n if (ms >= SECOND_MS) return `${(ms / SECOND_MS).toFixed(DURATION_DECIMALS)}s`;\n return `${Math.round(ms)}ms`;\n}\n\nfunction formatTime(at: number): string {\n return new Date(at).toTimeString().slice(0, TIME_SLICE_END);\n}\n\nfunction bar(value: number, max: number, width: number): string {\n const filled = Math.min(width, Math.max(value > 0 ? 1 : 0, Math.round((value / max) * width)));\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\nfunction labelled(label: string, value: string, width: number): string {\n return truncateToWidth(pad(label, SINK_LABEL_WIDTH) + value, width);\n}\n\nfunction ratio(numerator: number, denominator: number): number {\n return denominator > 0 ? numerator / denominator : 0;\n}\n\nfunction statCell(label: string, value: string, sublabel: string, width: number): string[] {\n return [pad(label, width), pad(value, width), pad(sublabel, width)];\n}\n\n/**\n * Colours the headline value so the two cells with a defensible threshold —\n * cache hit rate and tool failure rate — read at a glance. The others are\n * workload-dependent, and a colour on them would imply a target that does not\n * exist.\n */\nfunction judgedCell(\n label: string,\n value: string,\n sublabel: string,\n healthy: boolean,\n width: number,\n theme: Theme,\n): string[] {\n return [pad(label, width), pad(theme.fg(healthy ? 'success' : 'warning', value), width), pad(sublabel, width)];\n}\n\n/**\n * The six headline cells. Every one is a lever the user can pull: what the\n * session cost, how much of it was avoidable, and how much was thrown away.\n * Raw event and record counts were dropped — they moved with the workload and\n * nothing followed from them.\n */\nfunction statRows(snapshot: LogMetricsSnapshot, width: number, theme: Theme): string[] {\n const cellWidth = Math.max(1, Math.floor(width / STAT_CELLS));\n const { tokens, turns } = snapshot;\n const cacheHit = ratio(tokens.cacheRead, tokens.cacheRead + tokens.input);\n const failRatio = ratio(snapshot.failedToolCalls, snapshot.toolCalls);\n const issues = snapshot.errors + snapshot.abortedTurns + snapshot.failedTurns;\n const topCategory = snapshot.failureCategories[0];\n const cells = [\n statCell(\n 'TOKENS',\n formatCompact(tokens.total),\n `↑${formatCompact(tokens.input)} ↓${formatCompact(tokens.output)}`,\n cellWidth,\n ),\n judgedCell(\n 'CACHE',\n formatPercent(cacheHit),\n `${formatCompact(tokens.cacheRead)} read`,\n cacheHit >= CACHE_WARN_RATIO,\n cellWidth,\n theme,\n ),\n statCell(\n 'COST',\n `$${snapshot.cost.toFixed(COST_DECIMALS)}`,\n turns > 0 ? `$${(snapshot.cost / turns).toFixed(PER_TURN_DECIMALS)}/turn` : THIS_SESSION,\n cellWidth,\n ),\n statCell(\n 'TURNS',\n formatCount(turns),\n turns > 0 ? `${formatCompact(tokens.total / turns)} tok/turn` : EMPTY_VALUE,\n cellWidth,\n ),\n judgedCell(\n 'TOOL FAIL',\n formatPercent(failRatio),\n `${snapshot.failedToolCalls} of ${snapshot.toolCalls}`,\n failRatio <= TOOL_FAIL_WARN_RATIO,\n cellWidth,\n theme,\n ),\n judgedCell(\n 'ISSUES',\n formatCount(issues),\n topCategory ? topCategory.name : CLEAN_TEXT,\n issues === 0,\n cellWidth,\n theme,\n ),\n ];\n return STAT_ROW_INDEXES.map((row) => pad(cells.map((cell) => cell[row] ?? '').join(''), width));\n}\n\n/** Cheap tools read as `success`, expensive ones as `error`. */\nfunction costColor(value: number, max: number): 'success' | 'warning' | 'error' {\n const share = max > 0 ? value / max : 0;\n if (share >= COST_ERROR_RATIO) return 'error';\n if (share >= COST_WARN_RATIO) return 'warning';\n return 'success';\n}\n\nfunction findingRows(findings: readonly LogMetricsFinding[], width: number, theme: Theme): string[] {\n const actionWidth = Math.max(1, width - FINDING_GLYPH_WIDTH - FINDING_SUBJECT_WIDTH - FINDING_DETAIL_WIDTH);\n return findings\n .slice(0, MAX_FINDING_ROWS)\n .map((finding) =>\n truncateToWidth(\n theme.fg(SEVERITY_COLOR[finding.severity], pad(SEVERITY_GLYPH[finding.severity], FINDING_GLYPH_WIDTH)) +\n pad(finding.subject, FINDING_SUBJECT_WIDTH) +\n pad(finding.detail, FINDING_DETAIL_WIDTH) +\n theme.fg('dim', pad(finding.action, actionWidth)),\n width,\n ),\n );\n}\n\n/**\n * Ranked by cost per call. Latency stays as the trailing column because it is\n * occasionally the answer, but it no longer decides the ordering: a 55ms grep\n * that drags 38k tokens into the turn is the row worth reading.\n */\nfunction toolCostRows(\n tools: readonly LogMetricsToolCost[],\n latency: Map<string, number>,\n width: number,\n theme: Theme,\n): string[] {\n const entries = tools.slice(0, MAX_TOOL_ROWS);\n const barWidth = Math.max(1, width - TOOL_NAME_WIDTH - VALUE_WIDTH - CALLS_WIDTH - FAIL_WIDTH - LATENCY_WIDTH - 1);\n const max = Math.max(...entries.map((entry) => entry.p90Tokens ?? 0), 1);\n return entries.map((entry) => {\n const tokens = entry.p90Tokens ?? 0;\n const barText = theme.fg(costColor(tokens, max), bar(tokens, max, barWidth));\n const failRatio = ratio(entry.failed, entry.calls);\n const fail =\n failRatio > 0 ? theme.fg('error', padStart(formatPercent(failRatio), FAIL_WIDTH)) : padStart('0%', FAIL_WIDTH);\n return truncateToWidth(\n pad(entry.name, TOOL_NAME_WIDTH) +\n barText +\n ' ' +\n padStart(entry.p90Tokens === undefined ? EMPTY_VALUE : formatCompact(tokens), VALUE_WIDTH) +\n padStart(formatCount(entry.calls), CALLS_WIDTH) +\n fail +\n theme.fg('dim', padStart(formatDuration(latency.get(entry.name)), LATENCY_WIDTH)),\n width,\n );\n });\n}\n\nfunction errorRows(snapshot: LogMetricsSnapshot, width: number): string[] {\n const messageWidth = Math.max(1, width - ERROR_TIME_WIDTH - ERROR_EVENT_WIDTH - ERROR_CODE_WIDTH);\n return snapshot.recentErrors\n .slice(0, MAX_ERROR_ROWS)\n .map((entry) =>\n truncateToWidth(\n pad(formatTime(entry.at), ERROR_TIME_WIDTH) +\n pad(entry.event, ERROR_EVENT_WIDTH) +\n pad(entry.message, messageWidth) +\n padStart(entry.code, ERROR_CODE_WIDTH),\n width,\n ),\n );\n}\n\n/**\n * Three lines instead of six. Backend and endpoint answer \"is anything being\n * written\"; the rest of the wiring only matters when it is broken, so it moved\n * behind `?`.\n */\nfunction sinkRows(sink: SinkStatus | undefined, width: number, lastDiagnostic?: string): string[] {\n /** Diagnostics can land before a handle exists, so both branches report them. */\n const diagnosticRows = lastDiagnostic ? [labelled('diagnostic', lastDiagnostic, width)] : [];\n if (!sink) {\n return [\n labelled('status', NOT_CONNECTED, width),\n labelled('metrics', 'aggregated in-process anyway', width),\n ...diagnosticRows,\n ];\n }\n return [\n labelled('backend', `${sink.backend} · ${sink.endpointSource}`, width),\n labelled('endpoint', sink.endpoint, width),\n ...diagnosticRows,\n ];\n}\n\n/** Only fields NodeTelemetryHandle can actually supply; it exposes no queue counters. */\nfunction sinkDetailRows(sink: SinkStatus | undefined, width: number): string[] {\n if (!sink)\n return [labelled('status', NOT_CONNECTED, width), labelled('reason', 'no sink handle this session', width)];\n return [\n labelled('service', sink.service, width),\n labelled('backend', sink.backend, width),\n labelled('endpoint', `${sink.endpointSource} · ${sink.endpoint}`, width),\n labelled('traces', `${sink.traces ? TOGGLE_ON : TOGGLE_OFF} · AGENT_OTEL_TRACES`, width),\n labelled('redaction', `${sink.redaction ? TOGGLE_ON : TOGGLE_OFF} · metadata only`, width),\n labelled('file fallback', sink.fileFallback ? 'allowed' : 'disabled', width),\n ];\n}\n\nconst COLLECTION_NOTE =\n 'Headline counters are aggregated in-process from this session. TOP CONSUMERS and ' +\n 'TOKEN BURN come from the sink database, so they span every agent and workflow in ' +\n 'the selected period, not just this session.';\n\nconst ATTRIBUTION_NOTE =\n 'TOOL COST reports the p90 total tokens of the turns a tool took part in, which is the ' +\n 'only attribution Pi emits. A turn running several tools attributes its total to each of ' +\n 'them, so the column ranks tools by the weight of the turns they appear in — it is not a ' +\n 'per-tool share of spend.';\n\n/** Ranked token consumers for the selected dimension. */\nfunction consumerRows(report: LogMetricsReport | undefined, width: number, theme: Theme): string[] {\n const groups = report?.groups.slice(0, MAX_TOKEN_ROWS) ?? [];\n if (groups.length === 0) return [];\n\n const barWidth = Math.max(1, width - GROUP_KEY_WIDTH - AGENT_WIDTH - VALUE_WIDTH - 1);\n const max = Math.max(...groups.map((group) => group.totalTokens), 1);\n return groups.map((group) => {\n const barText = theme.fg(group.issueCount > 0 ? 'error' : 'accent', bar(group.totalTokens, max, barWidth));\n return truncateToWidth(\n `${pad(group.key, GROUP_KEY_WIDTH)}${pad(group.agentName ?? EMPTY_VALUE, AGENT_WIDTH)}${barText} ${padStart(\n formatCompact(group.totalTokens),\n VALUE_WIDTH,\n )}`,\n width,\n );\n });\n}\n\n/** Token burn per timeline bucket, newest last so the trend reads left to right. */\nfunction burnRows(report: LogMetricsReport | undefined, width: number, theme: Theme): string[] {\n const buckets = report?.timeline.slice(-MAX_BURN_ROWS) ?? [];\n if (buckets.length === 0) return [];\n\n const barWidth = Math.max(1, width - BURN_LABEL_WIDTH - VALUE_WIDTH - 1);\n const max = Math.max(...buckets.map((bucket) => bucket.totalTokens), 1);\n return buckets.map((bucket) => {\n const barText = theme.fg('success', bar(bucket.totalTokens, max, barWidth));\n // `label` is the local-time rendering; bucketStart serialises to UTC.\n return truncateToWidth(\n `${pad(bucket.label, BURN_LABEL_WIDTH)}${barText} ${padStart(formatCompact(bucket.totalTokens), VALUE_WIDTH)}`,\n width,\n );\n });\n}\n\n/** Greedy word wrap: no library dependency for two static paragraphs. */\nfunction wrapText(text: string, width: number): string[] {\n if (width < 1) return [text];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(' ')) {\n const candidate = current.length === 0 ? word : `${current} ${word}`;\n if (candidate.length > width && current.length > 0) {\n lines.push(current);\n current = word;\n } else {\n current = candidate;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines.map((line) => pad(line, width));\n}\n\nexport class LogMetricsOverlayComponent extends DoomOverlay {\n private readonly getView: () => LogMetricsView;\n private readonly done: (result: undefined) => void;\n private readonly timer: ReturnType<typeof setInterval>;\n private readonly query: MetricsQuery | undefined;\n private disposed = false;\n private groupIndex = 0;\n private periodIndex = 0;\n private requestId = 0;\n private loading = false;\n private showDetail = false;\n private report: LogMetricsReport | undefined;\n private reportError: string | undefined;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n getView: () => LogMetricsView,\n done: (result: undefined) => void,\n query?: MetricsQuery,\n ) {\n super(tui, theme);\n this.getView = getView;\n this.done = done;\n this.query = query;\n this.timer = setInterval(() => {\n if (!this.disposed) this.tui.requestRender();\n }, REFRESH_MS);\n this.timer.unref?.();\n this.fetchReport();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || matchesKey(data, 'q')) {\n this.done(undefined);\n return;\n }\n\n const key = data.toLowerCase();\n if (key === '?') {\n this.showDetail = !this.showDetail;\n this.tui.requestRender();\n return;\n }\n if (key === 'g') {\n this.groupIndex = (this.groupIndex + 1) % GROUP_CYCLE.length;\n this.refresh();\n return;\n }\n if (key === 'p') {\n this.periodIndex = (this.periodIndex + 1) % PERIOD_CYCLE.length;\n this.refresh();\n return;\n }\n if (key === 'r') this.refresh();\n }\n\n /** Re-reads the live session counters, and restarts the history query behind them. */\n private refresh(): void {\n this.fetchReport();\n this.tui.requestRender();\n }\n\n /**\n * Sink history is fetched out of band: the query crosses a process boundary\n * and takes seconds on a busy database, so render never waits on it. Stale\n * responses are dropped by sequence number when the dimension changes mid\n * flight.\n */\n private fetchReport(): void {\n if (!this.query) return;\n\n const requestId = ++this.requestId;\n this.loading = true;\n this.reportError = undefined;\n this.query({ groupBy: this.groupBy(), period: this.period(), limit: MAX_TOKEN_ROWS })\n .then((report) => {\n if (this.disposed || requestId !== this.requestId) return;\n this.report = report;\n this.loading = false;\n this.tui.requestRender();\n })\n .catch((error: unknown) => {\n if (this.disposed || requestId !== this.requestId) return;\n this.report = undefined;\n this.reportError = error instanceof Error ? error.message : String(error);\n this.loading = false;\n this.tui.requestRender();\n });\n }\n\n /** Ranked consumers for the selected dimension, sourced from the sink database. */\n private consumerPanel(width: number): string[] {\n const heading = this.theme.fg('accent', `${CONSUMERS_TITLE} · by ${this.groupBy()} · ${this.period()}`);\n const rows = consumerRows(this.report, width, this.theme);\n return [heading, ...(rows.length > 0 ? rows : [this.theme.fg('dim', this.historyStatus())])];\n }\n\n private burnPanel(width: number): string[] {\n const bucket = this.report?.bucket ?? '';\n const heading = this.theme.fg('accent', `${BURN_TITLE}${bucket ? ` · per ${bucket}` : ''}`);\n const rows = burnRows(this.report, width, this.theme);\n return [heading, ...(rows.length > 0 ? rows : [this.theme.fg('dim', this.historyStatus())])];\n }\n\n private findingsPanel(snapshot: LogMetricsSnapshot, width: number): string[] {\n const rows = findingRows(deriveFindings(snapshot), width, this.theme);\n return [\n this.theme.fg('accent', FINDINGS_TITLE),\n ...(rows.length > 0 ? rows : [this.theme.fg('dim', NO_FINDINGS_TEXT)]),\n ];\n }\n\n private toolCostPanel(snapshot: LogMetricsSnapshot, width: number): string[] {\n const latency = new Map(\n snapshot.toolLatency.flatMap((entry) => (entry.p95Ms === undefined ? [] : [[entry.name, entry.p95Ms] as const])),\n );\n const rows = toolCostRows(snapshot.toolCost, latency, width, this.theme);\n return [\n this.theme.fg('accent', TOOL_COST_TITLE),\n ...(rows.length > 0 ? rows : [this.theme.fg('dim', NO_TOOL_COST_TEXT)]),\n ];\n }\n\n private errorsPanel(snapshot: LogMetricsSnapshot, width: number): string[] {\n const rows = errorRows(snapshot, width);\n return [\n this.theme.fg('accent', ERRORS_TITLE),\n ...(rows.length > 0 ? rows : [this.theme.fg('dim', NO_ERRORS_TEXT)]),\n ];\n }\n\n private groupBy(): LogMetricsGroupBy {\n return GROUP_CYCLE[this.groupIndex] ?? DEFAULT_GROUP;\n }\n\n private period(): LogMetricsPeriod {\n return PERIOD_CYCLE[this.periodIndex] ?? DEFAULT_PERIOD;\n }\n\n /** One status line for the history panels: what is shown, from where. */\n private historyStatus(): string {\n if (this.loading) return LOADING_TEXT;\n if (this.reportError) return this.reportError;\n if (!this.report) return NO_HISTORY_TEXT;\n return `${this.transportLabel()} · ${this.report.totals.groupCount} groups`;\n }\n\n private transportLabel(): MetricsTransport | 'sink' {\n return this.getView().transport ?? 'sink';\n }\n\n dispose(): void {\n this.disposed = true;\n clearInterval(this.timer);\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb: BREADCRUMB,\n headerRight: SUBTITLE,\n footer: FOOTER,\n footerHints: [\n ['r', 'refresh'],\n ['g', 'group'],\n ['p', 'period'],\n ['?', 'detail'],\n ['esc', 'close'],\n ],\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const view = this.getView();\n if (view.disabled) return this.disabledBody(width);\n if (this.showDetail) return this.detailBody(view, width).slice(0, height);\n return this.metricsBody(view, width, height);\n }\n\n /**\n * An explicit disabled state: zeroed stat cells would read as a real session\n * that happened to do nothing.\n */\n private disabledBody(width: number): string[] {\n return [\n truncateToWidth(this.theme.fg('warning', ' Metrics collection is disabled for this session.'), width),\n truncateToWidth(this.theme.fg('dim', ` ${DISABLED_ENV}=1 is set, so no log records are produced.`), width),\n truncateToWidth(this.theme.fg('dim', ` Unset ${DISABLED_ENV} and restart Pi to collect metrics.`), width),\n ];\n }\n\n /**\n * The `?` pane. Sink wiring and the caveats behind each panel live here so the\n * default view can spend its rows on numbers instead of explaining itself.\n */\n private detailBody(view: LogMetricsView, width: number): string[] {\n const content = Math.max(1, width - 1);\n return [\n this.theme.fg('accent', SINK_TITLE),\n ...sinkDetailRows(view.sink, content),\n '',\n this.theme.fg('accent', DETAIL_TITLE),\n labelled('source', 'in-process Pi events', content),\n labelled('scope', 'current session only', content),\n ...wrapText(COLLECTION_NOTE, content),\n '',\n this.theme.fg('accent', 'TOOL COST ATTRIBUTION'),\n ...wrapText(ATTRIBUTION_NOTE, content),\n ].map((line) => pad(` ${line}`, width));\n }\n\n private metricsBody(view: LogMetricsView, width: number, height: number): string[] {\n const stats = statRows(view.snapshot, width, this.theme);\n if (height <= stats.length) return stats.slice(0, height);\n\n if (width < MIN_TWO_COLUMN_WIDTH) {\n return [...stats, '', ...this.singleColumnPanels(view, width)].slice(0, height);\n }\n\n const rightWidth = Math.max(MIN_RIGHT_COLUMN, Math.floor(width * RIGHT_COLUMN_RATIO));\n const leftWidth = Math.max(1, width - rightWidth - 1);\n\n // Each column reserves one leading space, so rows are built one narrower.\n const leftContent = Math.max(1, leftWidth - 1);\n const rightContent = Math.max(1, rightWidth - 1);\n const left = [\n ...this.findingsPanel(view.snapshot, leftContent),\n '',\n ...this.toolCostPanel(view.snapshot, leftContent),\n '',\n ...this.consumerPanel(leftContent),\n ];\n const right = [\n ...this.burnPanel(rightContent),\n '',\n ...this.errorsPanel(view.snapshot, rightContent),\n '',\n this.theme.fg('accent', SINK_TITLE),\n ...sinkRows(view.sink, rightContent, view.lastDiagnostic),\n ];\n\n const bodyHeight = Math.max(0, height - stats.length - 1);\n const body = [this.theme.fg('borderMuted', `${'─'.repeat(leftWidth)}┬${'─'.repeat(rightWidth)}`)];\n for (let index = 0; index < bodyHeight; index++) {\n body.push(\n pad(` ${left[index] ?? ''}`, leftWidth) +\n this.theme.fg('borderMuted', '│') +\n pad(` ${right[index] ?? ''}`, rightWidth),\n );\n }\n return [...stats, ...body].slice(0, height);\n }\n\n private singleColumnPanels(view: LogMetricsView, width: number): string[] {\n return [\n ...this.findingsPanel(view.snapshot, width),\n '',\n ...this.toolCostPanel(view.snapshot, width),\n '',\n ...this.errorsPanel(view.snapshot, width),\n '',\n this.theme.fg('accent', SINK_TITLE),\n ...sinkRows(view.sink, width, view.lastDiagnostic),\n ];\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
2
|
-
//# sourceMappingURL=metricsOverlay.cjs.map
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./logMetricsOverlay.cjs");exports.LogMetricsOverlayComponent=e.LogMetricsOverlayComponent;
|
|
@@ -1,71 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { DoomOverlay, DoomOverlayChrome, DoomOverlayTui } from "./doomOverlay.cjs";
|
|
4
|
-
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
//#region src/tui/metricsOverlay.d.ts
|
|
6
|
-
interface SinkStatus {
|
|
7
|
-
service: string;
|
|
8
|
-
backend: string;
|
|
9
|
-
endpoint: string;
|
|
10
|
-
endpointSource: string;
|
|
11
|
-
traces: boolean;
|
|
12
|
-
redaction: boolean;
|
|
13
|
-
fileFallback: boolean;
|
|
14
|
-
}
|
|
15
|
-
interface LogMetricsView {
|
|
16
|
-
disabled: boolean;
|
|
17
|
-
snapshot: LogMetricsSnapshot;
|
|
18
|
-
sink: SinkStatus | undefined;
|
|
19
|
-
/** Which transport answered the last history query, for the panel status line. */
|
|
20
|
-
transport?: MetricsTransport;
|
|
21
|
-
/**
|
|
22
|
-
* Most recent telemetry diagnostic. Surfaced here because writing it to stderr
|
|
23
|
-
* would corrupt the TUI frame.
|
|
24
|
-
*/
|
|
25
|
-
lastDiagnostic?: string;
|
|
26
|
-
}
|
|
27
|
-
declare class LogMetricsOverlayComponent extends DoomOverlay {
|
|
28
|
-
private readonly getView;
|
|
29
|
-
private readonly done;
|
|
30
|
-
private readonly timer;
|
|
31
|
-
private readonly query;
|
|
32
|
-
private disposed;
|
|
33
|
-
private groupIndex;
|
|
34
|
-
private periodIndex;
|
|
35
|
-
private requestId;
|
|
36
|
-
private loading;
|
|
37
|
-
private report;
|
|
38
|
-
private reportError;
|
|
39
|
-
constructor(tui: DoomOverlayTui, theme: Theme, getView: () => LogMetricsView, done: (result: undefined) => void, query?: MetricsQuery);
|
|
40
|
-
handleInput(data: string): void;
|
|
41
|
-
/** Re-reads the live session counters, and restarts the history query behind them. */
|
|
42
|
-
private refresh;
|
|
43
|
-
/**
|
|
44
|
-
* Sink history is fetched out of band: the query crosses a process boundary
|
|
45
|
-
* and takes seconds on a busy database, so render never waits on it. Stale
|
|
46
|
-
* responses are dropped by sequence number when the dimension changes mid
|
|
47
|
-
* flight.
|
|
48
|
-
*/
|
|
49
|
-
private fetchReport;
|
|
50
|
-
/** Ranked consumers for the selected dimension, sourced from the sink database. */
|
|
51
|
-
private tokenPanel;
|
|
52
|
-
private burnPanel;
|
|
53
|
-
private groupBy;
|
|
54
|
-
private period;
|
|
55
|
-
/** One status line for the history panels: what is shown, from where. */
|
|
56
|
-
private historyStatus;
|
|
57
|
-
private transportLabel;
|
|
58
|
-
dispose(): void;
|
|
59
|
-
protected getChrome(): DoomOverlayChrome;
|
|
60
|
-
protected renderBody(width: number, height: number): string[];
|
|
61
|
-
/**
|
|
62
|
-
* An explicit disabled state: zeroed stat cells would read as a real session
|
|
63
|
-
* that happened to do nothing.
|
|
64
|
-
*/
|
|
65
|
-
private disabledBody;
|
|
66
|
-
private metricsBody;
|
|
67
|
-
private singleColumnPanels;
|
|
68
|
-
}
|
|
69
|
-
//#endregion
|
|
70
|
-
export { LogMetricsOverlayComponent, LogMetricsView, SinkStatus };
|
|
71
|
-
//# sourceMappingURL=metricsOverlay.d.cts.map
|
|
1
|
+
import { LogMetricsOverlayComponent, LogMetricsView, SinkStatus } from "./logMetricsOverlay.cjs";
|
|
2
|
+
export { LogMetricsOverlayComponent, type LogMetricsView, type SinkStatus };
|
|
@@ -1,71 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { DoomOverlay, DoomOverlayChrome, DoomOverlayTui } from "./doomOverlay.mjs";
|
|
4
|
-
import { Theme } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
//#region src/tui/metricsOverlay.d.ts
|
|
6
|
-
interface SinkStatus {
|
|
7
|
-
service: string;
|
|
8
|
-
backend: string;
|
|
9
|
-
endpoint: string;
|
|
10
|
-
endpointSource: string;
|
|
11
|
-
traces: boolean;
|
|
12
|
-
redaction: boolean;
|
|
13
|
-
fileFallback: boolean;
|
|
14
|
-
}
|
|
15
|
-
interface LogMetricsView {
|
|
16
|
-
disabled: boolean;
|
|
17
|
-
snapshot: LogMetricsSnapshot;
|
|
18
|
-
sink: SinkStatus | undefined;
|
|
19
|
-
/** Which transport answered the last history query, for the panel status line. */
|
|
20
|
-
transport?: MetricsTransport;
|
|
21
|
-
/**
|
|
22
|
-
* Most recent telemetry diagnostic. Surfaced here because writing it to stderr
|
|
23
|
-
* would corrupt the TUI frame.
|
|
24
|
-
*/
|
|
25
|
-
lastDiagnostic?: string;
|
|
26
|
-
}
|
|
27
|
-
declare class LogMetricsOverlayComponent extends DoomOverlay {
|
|
28
|
-
private readonly getView;
|
|
29
|
-
private readonly done;
|
|
30
|
-
private readonly timer;
|
|
31
|
-
private readonly query;
|
|
32
|
-
private disposed;
|
|
33
|
-
private groupIndex;
|
|
34
|
-
private periodIndex;
|
|
35
|
-
private requestId;
|
|
36
|
-
private loading;
|
|
37
|
-
private report;
|
|
38
|
-
private reportError;
|
|
39
|
-
constructor(tui: DoomOverlayTui, theme: Theme, getView: () => LogMetricsView, done: (result: undefined) => void, query?: MetricsQuery);
|
|
40
|
-
handleInput(data: string): void;
|
|
41
|
-
/** Re-reads the live session counters, and restarts the history query behind them. */
|
|
42
|
-
private refresh;
|
|
43
|
-
/**
|
|
44
|
-
* Sink history is fetched out of band: the query crosses a process boundary
|
|
45
|
-
* and takes seconds on a busy database, so render never waits on it. Stale
|
|
46
|
-
* responses are dropped by sequence number when the dimension changes mid
|
|
47
|
-
* flight.
|
|
48
|
-
*/
|
|
49
|
-
private fetchReport;
|
|
50
|
-
/** Ranked consumers for the selected dimension, sourced from the sink database. */
|
|
51
|
-
private tokenPanel;
|
|
52
|
-
private burnPanel;
|
|
53
|
-
private groupBy;
|
|
54
|
-
private period;
|
|
55
|
-
/** One status line for the history panels: what is shown, from where. */
|
|
56
|
-
private historyStatus;
|
|
57
|
-
private transportLabel;
|
|
58
|
-
dispose(): void;
|
|
59
|
-
protected getChrome(): DoomOverlayChrome;
|
|
60
|
-
protected renderBody(width: number, height: number): string[];
|
|
61
|
-
/**
|
|
62
|
-
* An explicit disabled state: zeroed stat cells would read as a real session
|
|
63
|
-
* that happened to do nothing.
|
|
64
|
-
*/
|
|
65
|
-
private disabledBody;
|
|
66
|
-
private metricsBody;
|
|
67
|
-
private singleColumnPanels;
|
|
68
|
-
}
|
|
69
|
-
//#endregion
|
|
70
|
-
export { LogMetricsOverlayComponent, LogMetricsView, SinkStatus };
|
|
71
|
-
//# sourceMappingURL=metricsOverlay.d.mts.map
|
|
1
|
+
import { LogMetricsOverlayComponent, LogMetricsView, SinkStatus } from "./logMetricsOverlay.mjs";
|
|
2
|
+
export { LogMetricsOverlayComponent, type LogMetricsView, type SinkStatus };
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import{
|
|
2
|
-
//# sourceMappingURL=metricsOverlay.mjs.map
|
|
1
|
+
import{LogMetricsOverlayComponent as e}from"./logMetricsOverlay.mjs";export{e as LogMetricsOverlayComponent};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { LogMetricsGroupBy, LogMetricsPeriod, LogMetricsReport } from "@agimon-ai/log-sink-mcp";
|
|
2
|
+
//#region src/types/metricsSource.d.ts
|
|
3
|
+
interface MetricsQueryParams {
|
|
4
|
+
groupBy: LogMetricsGroupBy;
|
|
5
|
+
period: LogMetricsPeriod;
|
|
6
|
+
limit: number;
|
|
7
|
+
}
|
|
8
|
+
type MetricsQuery = (params: MetricsQueryParams) => Promise<LogMetricsReport>;
|
|
9
|
+
type MetricsTransport = 'http' | 'cli';
|
|
10
|
+
interface MetricsSource {
|
|
11
|
+
query: MetricsQuery;
|
|
12
|
+
lastTransport(): MetricsTransport | undefined;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { MetricsQuery, MetricsQueryParams, MetricsSource, MetricsTransport };
|
|
16
|
+
//# sourceMappingURL=metricsSource.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsSource.d.cts","names":[],"sources":["../../src/types/metricsSource.ts"],"mappings":";;UAEiB;EACf,SAAS;EACT,QAAQ;EACR;;KAGU,gBAAgB,QAAQ,uBAAuB,QAAQ;KAEvD;UAEK;EACf,OAAO;EACP,iBAAiB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { LogMetricsGroupBy, LogMetricsPeriod, LogMetricsReport } from "@agimon-ai/log-sink-mcp";
|
|
2
|
+
//#region src/types/metricsSource.d.ts
|
|
3
|
+
interface MetricsQueryParams {
|
|
4
|
+
groupBy: LogMetricsGroupBy;
|
|
5
|
+
period: LogMetricsPeriod;
|
|
6
|
+
limit: number;
|
|
7
|
+
}
|
|
8
|
+
type MetricsQuery = (params: MetricsQueryParams) => Promise<LogMetricsReport>;
|
|
9
|
+
type MetricsTransport = 'http' | 'cli';
|
|
10
|
+
interface MetricsSource {
|
|
11
|
+
query: MetricsQuery;
|
|
12
|
+
lastTransport(): MetricsTransport | undefined;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { MetricsQuery, MetricsQueryParams, MetricsSource, MetricsTransport };
|
|
16
|
+
//# sourceMappingURL=metricsSource.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsSource.d.mts","names":[],"sources":["../../src/types/metricsSource.ts"],"mappings":";;UAEiB;EACf,SAAS;EACT,QAAQ;EACR;;KAGU,gBAAgB,QAAQ,uBAAuB,QAAQ;KAEvD;UAEK;EACf,OAAO;EACP,iBAAiB"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-log",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.21",
|
|
4
4
|
"description": "Pi log-sink telemetry extension with session-local metrics and a Log Metrics overlay",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"coding-agent",
|
|
8
|
+
"developer-tools",
|
|
9
|
+
"doompi",
|
|
10
|
+
"pi-package"
|
|
11
|
+
],
|
|
5
12
|
"homepage": "https://agimon.ai",
|
|
6
13
|
"license": "MIT",
|
|
7
14
|
"author": "Vuong Ngo",
|
|
@@ -50,23 +57,23 @@
|
|
|
50
57
|
"registry": "https://registry.npmjs.org/"
|
|
51
58
|
},
|
|
52
59
|
"dependencies": {
|
|
53
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
54
|
-
"@agimon-ai/
|
|
55
|
-
"@agimon-ai/
|
|
60
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.21",
|
|
61
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.21",
|
|
62
|
+
"@agimon-ai/log-sink-mcp": "0.29.11"
|
|
56
63
|
},
|
|
57
64
|
"devDependencies": {
|
|
58
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
59
|
-
"@earendil-works/pi-tui": "0.84.
|
|
65
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
66
|
+
"@earendil-works/pi-tui": "0.84.2",
|
|
60
67
|
"@types/node": "26.1.1",
|
|
61
68
|
"@vitest/coverage-v8": "4.1.10",
|
|
62
69
|
"tsdown": "0.22.14",
|
|
63
70
|
"typescript": "6.0.3",
|
|
64
71
|
"vitest": "4.1.10",
|
|
65
|
-
"@agimon-ai/doompi-ui": "0.0.1-alpha.
|
|
72
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.21"
|
|
66
73
|
},
|
|
67
74
|
"peerDependencies": {
|
|
68
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
69
|
-
"@earendil-works/pi-tui": "0.84.
|
|
75
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
76
|
+
"@earendil-works/pi-tui": "0.84.2"
|
|
70
77
|
},
|
|
71
78
|
"peerDependenciesMeta": {
|
|
72
79
|
"@earendil-works/pi-coding-agent": {
|
package/dist/extension.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.cjs","names":["createDoomTelemetry","registerDoomLeaderContribution","LogMetricsOverlayComponent","LogMetricsAggregator","createMetricsSource"],"sources":["../src/extension.ts"],"sourcesContent":["/**\n * Pi telemetry wiring for the Doom harness.\n *\n * This extension deliberately emits metadata-only records. Prompt, message,\n * tool payload, file path, diagnostic text, exception, and stack data never\n * cross the shared telemetry boundary.\n */\nimport type { ExtensionAPI, ExtensionContext, TurnEndEvent } from '@earendil-works/pi-coding-agent';\nimport { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\nimport { registerDoomLeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\nimport { LogMetricsAggregator, type LogMetricsRecorder } from './metrics.ts';\nimport { createMetricsSource, type MetricsQuery, type MetricsSource } from './metricsSource.ts';\nimport { LogMetricsOverlayComponent, type LogMetricsView, type SinkStatus } from './tui/metricsOverlay.ts';\n\nconst SERVICE_NAME = 'pi';\nconst PACKAGE_NAME = '@agimon-ai/doompi-log';\n/** Opt back into stderr diagnostics when debugging outside a live TUI. */\nconst DIAGNOSTICS_STDERR = 'stderr';\nconst LEADER_SOURCE = '@agimon-ai/doompi-log';\nconst LOG_METRICS_COMMAND = 'log-metrics';\nconst LOG_METRICS_DESCRIPTION = 'Show session-local log metrics';\nconst HELP_GROUP_ORDER = 70;\nconst NO_ENDPOINT = 'none';\nconst API_ERROR_STATUS = 400;\nconst FAILURE_STOP_REASONS = new Set(['error', 'aborted']);\n\ntype TelemetryFactory = NonNullable<DoomTelemetryOptions['telemetryFactory']>;\ntype TelemetryAttributes = Record<string, string | number | boolean>;\ntype RecordLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface PiTelemetryExtensionOptions {\n allowFileFallback?: boolean;\n /** Overrides the telemetry service identity. */\n serviceName?: string;\n /** Overrides the leader contribution owner. */\n leaderSource?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: TelemetryFactory;\n /** Receives every sanitized record, whether or not a sink is live. */\n metrics?: LogMetricsRecorder;\n /** Called once the sink backend is resolved, so the overlay can report it. */\n onSinkStatus?: (status: SinkStatus | undefined) => void;\n /** Overrides the sink-history transport; injected in tests. */\n metricsSource?: MetricsSource;\n /** Receives telemetry lifecycle failures without writing into the TUI. */\n onDiagnostic?: (message: string) => void;\n}\n\nfunction isEnabled(value: string | undefined): boolean {\n return value === '1' || value?.toLowerCase() === 'true' || value?.toLowerCase() === 'yes';\n}\n\nfunction contextAttributes(ctx: ExtensionContext): TelemetryAttributes {\n const attributes: TelemetryAttributes = {\n 'pi.mode': ctx.mode,\n 'pi.session.id': ctx.sessionManager.getSessionId(),\n };\n if (ctx.thinkingLevel) attributes['pi.thinking_level'] = ctx.thinkingLevel;\n if (ctx.model) {\n attributes['gen_ai.provider.name'] = ctx.model.provider;\n attributes['gen_ai.request.model'] = ctx.model.id;\n }\n return attributes;\n}\n\nfunction usageAttributes(message: TurnEndEvent['message']): TelemetryAttributes | undefined {\n if (message.role !== 'assistant') return undefined;\n const attributes: TelemetryAttributes = {\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n 'gen_ai.usage.input_tokens': message.usage.input,\n 'gen_ai.usage.output_tokens': message.usage.output,\n 'gen_ai.usage.cache_read_tokens': message.usage.cacheRead,\n 'gen_ai.usage.cache_write_tokens': message.usage.cacheWrite,\n 'gen_ai.usage.total_tokens': message.usage.totalTokens,\n 'gen_ai.usage.cost': message.usage.cost.total,\n };\n if (message.usage.reasoning !== undefined) attributes['gen_ai.usage.reasoning_tokens'] = message.usage.reasoning;\n return attributes;\n}\n\nfunction failureAttributes(message: TurnEndEvent['message']): TelemetryAttributes {\n if (message.role !== 'assistant') return { 'error.type': 'AgentError', outcome: 'error' };\n const aborted = message.stopReason === 'aborted';\n return {\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: aborted ? 'aborted' : 'error',\n };\n}\n\nfunction statusFromTelemetry(status: ReturnType<DoomTelemetry['status']>): SinkStatus {\n return {\n service: status.serviceName,\n backend: status.backend,\n endpoint: status.endpoint ?? NO_ENDPOINT,\n endpointSource: status.endpointSource,\n traces: status.traces,\n redaction: true,\n fileFallback: status.fileFallback,\n };\n}\n\nexport function createPiTelemetryExtension(options: PiTelemetryExtensionOptions = {}) {\n return function registerPiTelemetry(pi: ExtensionAPI): void {\n const env = options.env ?? process.env;\n const metrics = options.metrics;\n const toolStartedAt = new Map<string, number>();\n let agentStartedAt: number | undefined;\n let telemetry: DoomTelemetry | undefined;\n\n const emit = async (level: RecordLevel, name: string, attributes: TelemetryAttributes): Promise<void> => {\n if (!telemetry) return;\n if (level === 'debug') await telemetry.recordDebug(name, attributes);\n else if (level === 'warn') await telemetry.recordWarning(name, undefined, attributes);\n else if (level === 'error') await telemetry.recordError(name, undefined, attributes);\n else await telemetry.recordEvent(name, attributes);\n };\n\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: options.serviceName ?? SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: ctx.cwd,\n env,\n allowFileFallback: options.allowFileFallback,\n telemetryFactory: options.telemetryFactory,\n warn: options.onDiagnostic,\n enableLogs: true,\n enableTraces: env.AGENT_OTEL_TRACES === undefined || isEnabled(env.AGENT_OTEL_TRACES),\n onRecord: (record) => metrics?.record(record.event, record.attributes),\n onStatus: (status) => options.onSinkStatus?.(statusFromTelemetry(status)),\n });\n options.onSinkStatus?.(statusFromTelemetry(telemetry.status()));\n return telemetry;\n };\n\n pi.on('session_start', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.session.started', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n 'pi.session.previous': event.previousSessionFile !== undefined,\n });\n });\n\n pi.on('before_agent_start', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.user_prompt', {\n ...contextAttributes(ctx),\n 'pi.user_message.length': event.prompt.length,\n 'pi.user_message.image_count': event.images?.length ?? 0,\n });\n });\n\n pi.on('agent_start', async (_event, ctx) => {\n agentStartedAt = Date.now();\n getTelemetry(ctx);\n await emit('info', 'pi.agent.started', contextAttributes(ctx));\n });\n\n pi.on('turn_start', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('debug', 'pi.turn.started', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n });\n });\n\n pi.on('turn_end', async (event, ctx) => {\n getTelemetry(ctx);\n const usage = usageAttributes(event.message);\n await emit('info', 'pi.turn.finished', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n 'pi.tool_result.count': event.toolResults.length,\n ...usage,\n });\n if (event.message.role === 'assistant' && FAILURE_STOP_REASONS.has(event.message.stopReason)) {\n await emit(event.message.stopReason === 'aborted' ? 'warn' : 'error', 'pi.turn.failed', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n ...failureAttributes(event.message),\n ...usage,\n });\n }\n if (!usage) return;\n for (const result of event.toolResults) {\n await emit('debug', 'pi.tool_token_sample', {\n ...contextAttributes(ctx),\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n });\n\n pi.on('tool_execution_start', async (event, ctx) => {\n toolStartedAt.set(event.toolCallId, Date.now());\n getTelemetry(ctx);\n await emit('debug', 'pi.tool_call', {\n ...contextAttributes(ctx),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n });\n\n pi.on('tool_execution_end', async (event, ctx) => {\n const startedAt = toolStartedAt.get(event.toolCallId);\n toolStartedAt.delete(event.toolCallId);\n getTelemetry(ctx);\n await emit('info', 'pi.tool_result', {\n ...contextAttributes(ctx),\n success: !event.isError,\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n 'tool.result.error': event.isError,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n });\n\n pi.on('after_provider_response', async (event, ctx) => {\n getTelemetry(ctx);\n const attributes = {\n ...contextAttributes(ctx),\n 'http.response.status_code': event.status,\n };\n if (event.status >= API_ERROR_STATUS) await emit('error', 'pi.api_error', attributes);\n else await emit('debug', 'pi.api_response', attributes);\n });\n\n pi.on('model_select', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.model.selected', {\n ...contextAttributes(ctx),\n 'pi.model.source': event.source,\n 'gen_ai.provider.name': event.model.provider,\n 'gen_ai.request.model': event.model.id,\n });\n });\n\n pi.on('agent_end', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.agent.finished', {\n ...contextAttributes(ctx),\n 'pi.message.count': event.messages.length,\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n agentStartedAt = undefined;\n await telemetry?.flush();\n });\n\n pi.on('agent_settled', async (_event, ctx) => {\n getTelemetry(ctx);\n await emit('debug', 'pi.agent.settled', contextAttributes(ctx));\n });\n\n pi.on('session_shutdown', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.session.finished', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n });\n await telemetry?.shutdown();\n });\n };\n}\n\nexport function registerLogMetricsLeaderBinding(\n pi: Pick<ExtensionAPI, 'events' | 'on'>,\n options: { source?: string } = {},\n): () => void {\n return registerDoomLeaderContribution(pi, {\n source: options.source ?? LEADER_SOURCE,\n bindings: [\n {\n id: 'log.metrics',\n path: [\n { key: 'h', label: 'help', order: HELP_GROUP_ORDER },\n { key: 'l', label: 'logs', detail: 'telemetry' },\n ],\n command: { name: LOG_METRICS_COMMAND },\n },\n ],\n });\n}\n\nexport async function openLogMetricsOverlay(\n ctx: ExtensionContext,\n getView: () => LogMetricsView,\n query?: MetricsQuery,\n): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LogMetricsOverlayComponent(tui, theme, getView, done, query),\n {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n },\n );\n}\n\nexport function createDoomLogExtension(options: PiTelemetryExtensionOptions = {}) {\n return function registerDoomLog(pi: ExtensionAPI): void {\n const env = options.env ?? process.env;\n const metrics = new LogMetricsAggregator();\n const metricsSource = options.metricsSource ?? createMetricsSource({ env });\n let sink: SinkStatus | undefined;\n let lastDiagnostic: string | undefined;\n\n createPiTelemetryExtension({\n ...options,\n metrics,\n // Always supply a sink so doom-telemetry never falls back to\n // process.emitWarning, which writes raw stderr over the TUI frame.\n // Diagnostics surface in the Log Metrics overlay instead.\n onDiagnostic: (message) => {\n lastDiagnostic = message;\n if (options.onDiagnostic) options.onDiagnostic(message);\n else if (env.AGENT_OTEL_DIAGNOSTICS === DIAGNOSTICS_STDERR) process.emitWarning(message);\n },\n onSinkStatus: (status) => {\n sink = status;\n options.onSinkStatus?.(status);\n },\n })(pi);\n\n registerLogMetricsLeaderBinding(pi, { source: options.leaderSource });\n pi.registerCommand(LOG_METRICS_COMMAND, {\n description: LOG_METRICS_DESCRIPTION,\n handler: async (_args, ctx) => {\n await openLogMetricsOverlay(\n ctx,\n () => ({\n disabled: isEnabled(env.AGENT_TELEMETRY_DISABLED) || isEnabled(env.OTEL_SDK_DISABLED),\n snapshot: metrics.snapshot(),\n sink,\n transport: metricsSource.lastTransport(),\n lastDiagnostic,\n }),\n metricsSource.query,\n );\n },\n });\n };\n}\n"],"mappings":"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"}
|
package/dist/extension.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;;;;;KA0BK,mBAAmB,YAAY;UAInB;EACf;;EAEA;;EAEA;EACA,MAAM,OAAO;EACb,mBAAmB;;EAEnB,UAAU;;EAEV,gBAAgB,QAAQ;;EAExB,gBAAgB;;EAEhB,gBAAgB;;iBA0DF,2BAA2B,UAAS,+BACd,IAAI;iBAoK1B,gCACd,IAAI,KAAK,gCACT;EAAW;;iBAiBS,sBACpB,KAAK,kBACL,eAAe,gBACf,QAAQ,eACP;iBAea,uBAAuB,UAAS,+BACd,IAAI"}
|
package/dist/extension.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;;;;;KA0BK,mBAAmB,YAAY;UAInB;EACf;;EAEA;;EAEA;EACA,MAAM,OAAO;EACb,mBAAmB;;EAEnB,UAAU;;EAEV,gBAAgB,QAAQ;;EAExB,gBAAgB;;EAEhB,gBAAgB;;iBA0DF,2BAA2B,UAAS,+BACd,IAAI;iBAoK1B,gCACd,IAAI,KAAK,gCACT;EAAW;;iBAiBS,sBACpB,KAAK,kBACL,eAAe,gBACf,QAAQ,eACP;iBAea,uBAAuB,UAAS,+BACd,IAAI"}
|
package/dist/extension.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.mjs","names":[],"sources":["../src/extension.ts"],"sourcesContent":["/**\n * Pi telemetry wiring for the Doom harness.\n *\n * This extension deliberately emits metadata-only records. Prompt, message,\n * tool payload, file path, diagnostic text, exception, and stack data never\n * cross the shared telemetry boundary.\n */\nimport type { ExtensionAPI, ExtensionContext, TurnEndEvent } from '@earendil-works/pi-coding-agent';\nimport { createDoomTelemetry, type DoomTelemetry, type DoomTelemetryOptions } from '@agimon-ai/doompi-telemetry';\nimport { registerDoomLeaderContribution } from '@agimon-ai/doompi-extension-contracts/leader';\nimport { LogMetricsAggregator, type LogMetricsRecorder } from './metrics.ts';\nimport { createMetricsSource, type MetricsQuery, type MetricsSource } from './metricsSource.ts';\nimport { LogMetricsOverlayComponent, type LogMetricsView, type SinkStatus } from './tui/metricsOverlay.ts';\n\nconst SERVICE_NAME = 'pi';\nconst PACKAGE_NAME = '@agimon-ai/doompi-log';\n/** Opt back into stderr diagnostics when debugging outside a live TUI. */\nconst DIAGNOSTICS_STDERR = 'stderr';\nconst LEADER_SOURCE = '@agimon-ai/doompi-log';\nconst LOG_METRICS_COMMAND = 'log-metrics';\nconst LOG_METRICS_DESCRIPTION = 'Show session-local log metrics';\nconst HELP_GROUP_ORDER = 70;\nconst NO_ENDPOINT = 'none';\nconst API_ERROR_STATUS = 400;\nconst FAILURE_STOP_REASONS = new Set(['error', 'aborted']);\n\ntype TelemetryFactory = NonNullable<DoomTelemetryOptions['telemetryFactory']>;\ntype TelemetryAttributes = Record<string, string | number | boolean>;\ntype RecordLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface PiTelemetryExtensionOptions {\n allowFileFallback?: boolean;\n /** Overrides the telemetry service identity. */\n serviceName?: string;\n /** Overrides the leader contribution owner. */\n leaderSource?: string;\n env?: NodeJS.ProcessEnv;\n telemetryFactory?: TelemetryFactory;\n /** Receives every sanitized record, whether or not a sink is live. */\n metrics?: LogMetricsRecorder;\n /** Called once the sink backend is resolved, so the overlay can report it. */\n onSinkStatus?: (status: SinkStatus | undefined) => void;\n /** Overrides the sink-history transport; injected in tests. */\n metricsSource?: MetricsSource;\n /** Receives telemetry lifecycle failures without writing into the TUI. */\n onDiagnostic?: (message: string) => void;\n}\n\nfunction isEnabled(value: string | undefined): boolean {\n return value === '1' || value?.toLowerCase() === 'true' || value?.toLowerCase() === 'yes';\n}\n\nfunction contextAttributes(ctx: ExtensionContext): TelemetryAttributes {\n const attributes: TelemetryAttributes = {\n 'pi.mode': ctx.mode,\n 'pi.session.id': ctx.sessionManager.getSessionId(),\n };\n if (ctx.thinkingLevel) attributes['pi.thinking_level'] = ctx.thinkingLevel;\n if (ctx.model) {\n attributes['gen_ai.provider.name'] = ctx.model.provider;\n attributes['gen_ai.request.model'] = ctx.model.id;\n }\n return attributes;\n}\n\nfunction usageAttributes(message: TurnEndEvent['message']): TelemetryAttributes | undefined {\n if (message.role !== 'assistant') return undefined;\n const attributes: TelemetryAttributes = {\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n 'gen_ai.usage.input_tokens': message.usage.input,\n 'gen_ai.usage.output_tokens': message.usage.output,\n 'gen_ai.usage.cache_read_tokens': message.usage.cacheRead,\n 'gen_ai.usage.cache_write_tokens': message.usage.cacheWrite,\n 'gen_ai.usage.total_tokens': message.usage.totalTokens,\n 'gen_ai.usage.cost': message.usage.cost.total,\n };\n if (message.usage.reasoning !== undefined) attributes['gen_ai.usage.reasoning_tokens'] = message.usage.reasoning;\n return attributes;\n}\n\nfunction failureAttributes(message: TurnEndEvent['message']): TelemetryAttributes {\n if (message.role !== 'assistant') return { 'error.type': 'AgentError', outcome: 'error' };\n const aborted = message.stopReason === 'aborted';\n return {\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: aborted ? 'aborted' : 'error',\n };\n}\n\nfunction statusFromTelemetry(status: ReturnType<DoomTelemetry['status']>): SinkStatus {\n return {\n service: status.serviceName,\n backend: status.backend,\n endpoint: status.endpoint ?? NO_ENDPOINT,\n endpointSource: status.endpointSource,\n traces: status.traces,\n redaction: true,\n fileFallback: status.fileFallback,\n };\n}\n\nexport function createPiTelemetryExtension(options: PiTelemetryExtensionOptions = {}) {\n return function registerPiTelemetry(pi: ExtensionAPI): void {\n const env = options.env ?? process.env;\n const metrics = options.metrics;\n const toolStartedAt = new Map<string, number>();\n let agentStartedAt: number | undefined;\n let telemetry: DoomTelemetry | undefined;\n\n const emit = async (level: RecordLevel, name: string, attributes: TelemetryAttributes): Promise<void> => {\n if (!telemetry) return;\n if (level === 'debug') await telemetry.recordDebug(name, attributes);\n else if (level === 'warn') await telemetry.recordWarning(name, undefined, attributes);\n else if (level === 'error') await telemetry.recordError(name, undefined, attributes);\n else await telemetry.recordEvent(name, attributes);\n };\n\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: options.serviceName ?? SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: ctx.cwd,\n env,\n allowFileFallback: options.allowFileFallback,\n telemetryFactory: options.telemetryFactory,\n warn: options.onDiagnostic,\n enableLogs: true,\n enableTraces: env.AGENT_OTEL_TRACES === undefined || isEnabled(env.AGENT_OTEL_TRACES),\n onRecord: (record) => metrics?.record(record.event, record.attributes),\n onStatus: (status) => options.onSinkStatus?.(statusFromTelemetry(status)),\n });\n options.onSinkStatus?.(statusFromTelemetry(telemetry.status()));\n return telemetry;\n };\n\n pi.on('session_start', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.session.started', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n 'pi.session.previous': event.previousSessionFile !== undefined,\n });\n });\n\n pi.on('before_agent_start', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.user_prompt', {\n ...contextAttributes(ctx),\n 'pi.user_message.length': event.prompt.length,\n 'pi.user_message.image_count': event.images?.length ?? 0,\n });\n });\n\n pi.on('agent_start', async (_event, ctx) => {\n agentStartedAt = Date.now();\n getTelemetry(ctx);\n await emit('info', 'pi.agent.started', contextAttributes(ctx));\n });\n\n pi.on('turn_start', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('debug', 'pi.turn.started', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n });\n });\n\n pi.on('turn_end', async (event, ctx) => {\n getTelemetry(ctx);\n const usage = usageAttributes(event.message);\n await emit('info', 'pi.turn.finished', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n 'pi.tool_result.count': event.toolResults.length,\n ...usage,\n });\n if (event.message.role === 'assistant' && FAILURE_STOP_REASONS.has(event.message.stopReason)) {\n await emit(event.message.stopReason === 'aborted' ? 'warn' : 'error', 'pi.turn.failed', {\n ...contextAttributes(ctx),\n 'pi.turn.index': event.turnIndex,\n ...failureAttributes(event.message),\n ...usage,\n });\n }\n if (!usage) return;\n for (const result of event.toolResults) {\n await emit('debug', 'pi.tool_token_sample', {\n ...contextAttributes(ctx),\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n });\n\n pi.on('tool_execution_start', async (event, ctx) => {\n toolStartedAt.set(event.toolCallId, Date.now());\n getTelemetry(ctx);\n await emit('debug', 'pi.tool_call', {\n ...contextAttributes(ctx),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n });\n\n pi.on('tool_execution_end', async (event, ctx) => {\n const startedAt = toolStartedAt.get(event.toolCallId);\n toolStartedAt.delete(event.toolCallId);\n getTelemetry(ctx);\n await emit('info', 'pi.tool_result', {\n ...contextAttributes(ctx),\n success: !event.isError,\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n 'tool.result.error': event.isError,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n });\n\n pi.on('after_provider_response', async (event, ctx) => {\n getTelemetry(ctx);\n const attributes = {\n ...contextAttributes(ctx),\n 'http.response.status_code': event.status,\n };\n if (event.status >= API_ERROR_STATUS) await emit('error', 'pi.api_error', attributes);\n else await emit('debug', 'pi.api_response', attributes);\n });\n\n pi.on('model_select', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.model.selected', {\n ...contextAttributes(ctx),\n 'pi.model.source': event.source,\n 'gen_ai.provider.name': event.model.provider,\n 'gen_ai.request.model': event.model.id,\n });\n });\n\n pi.on('agent_end', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.agent.finished', {\n ...contextAttributes(ctx),\n 'pi.message.count': event.messages.length,\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n agentStartedAt = undefined;\n await telemetry?.flush();\n });\n\n pi.on('agent_settled', async (_event, ctx) => {\n getTelemetry(ctx);\n await emit('debug', 'pi.agent.settled', contextAttributes(ctx));\n });\n\n pi.on('session_shutdown', async (event, ctx) => {\n getTelemetry(ctx);\n await emit('info', 'pi.session.finished', {\n ...contextAttributes(ctx),\n 'pi.session.reason': event.reason,\n });\n await telemetry?.shutdown();\n });\n };\n}\n\nexport function registerLogMetricsLeaderBinding(\n pi: Pick<ExtensionAPI, 'events' | 'on'>,\n options: { source?: string } = {},\n): () => void {\n return registerDoomLeaderContribution(pi, {\n source: options.source ?? LEADER_SOURCE,\n bindings: [\n {\n id: 'log.metrics',\n path: [\n { key: 'h', label: 'help', order: HELP_GROUP_ORDER },\n { key: 'l', label: 'logs', detail: 'telemetry' },\n ],\n command: { name: LOG_METRICS_COMMAND },\n },\n ],\n });\n}\n\nexport async function openLogMetricsOverlay(\n ctx: ExtensionContext,\n getView: () => LogMetricsView,\n query?: MetricsQuery,\n): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LogMetricsOverlayComponent(tui, theme, getView, done, query),\n {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n },\n );\n}\n\nexport function createDoomLogExtension(options: PiTelemetryExtensionOptions = {}) {\n return function registerDoomLog(pi: ExtensionAPI): void {\n const env = options.env ?? process.env;\n const metrics = new LogMetricsAggregator();\n const metricsSource = options.metricsSource ?? createMetricsSource({ env });\n let sink: SinkStatus | undefined;\n let lastDiagnostic: string | undefined;\n\n createPiTelemetryExtension({\n ...options,\n metrics,\n // Always supply a sink so doom-telemetry never falls back to\n // process.emitWarning, which writes raw stderr over the TUI frame.\n // Diagnostics surface in the Log Metrics overlay instead.\n onDiagnostic: (message) => {\n lastDiagnostic = message;\n if (options.onDiagnostic) options.onDiagnostic(message);\n else if (env.AGENT_OTEL_DIAGNOSTICS === DIAGNOSTICS_STDERR) process.emitWarning(message);\n },\n onSinkStatus: (status) => {\n sink = status;\n options.onSinkStatus?.(status);\n },\n })(pi);\n\n registerLogMetricsLeaderBinding(pi, { source: options.leaderSource });\n pi.registerCommand(LOG_METRICS_COMMAND, {\n description: LOG_METRICS_DESCRIPTION,\n handler: async (_args, ctx) => {\n await openLogMetricsOverlay(\n ctx,\n () => ({\n disabled: isEnabled(env.AGENT_TELEMETRY_DISABLED) || isEnabled(env.OTEL_SDK_DISABLED),\n snapshot: metrics.snapshot(),\n sink,\n transport: metricsSource.lastTransport(),\n lastDiagnostic,\n }),\n metricsSource.query,\n );\n },\n });\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.cjs","names":["createDoomLogExtension"],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { createDoomLogExtension } from '../extension.ts';\n\nexport default createDoomLogExtension();\n"],"mappings":"AAEA,IAAA,4BAAeA,CAAAA,CAAAA,uBAAuB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.d.cts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.d.mts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.mjs","names":[],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { createDoomLogExtension } from '../extension.ts';\n\nexport default createDoomLogExtension();\n"],"mappings":"0DAEA,IAAA,EAAe,EAAuB"}
|