@agimon-ai/doompi-log 0.0.1-alpha.0

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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/extension.cjs +2 -0
  5. package/dist/extension.cjs.map +1 -0
  6. package/dist/extension.d.cts +33 -0
  7. package/dist/extension.d.cts.map +1 -0
  8. package/dist/extension.d.mts +33 -0
  9. package/dist/extension.d.mts.map +1 -0
  10. package/dist/extension.mjs +2 -0
  11. package/dist/extension.mjs.map +1 -0
  12. package/dist/extensions/pi.cjs +2 -0
  13. package/dist/extensions/pi.cjs.map +1 -0
  14. package/dist/extensions/pi.d.cts +4 -0
  15. package/dist/extensions/pi.d.cts.map +1 -0
  16. package/dist/extensions/pi.d.mts +5 -0
  17. package/dist/extensions/pi.d.mts.map +1 -0
  18. package/dist/extensions/pi.mjs +2 -0
  19. package/dist/extensions/pi.mjs.map +1 -0
  20. package/dist/index.cjs +1 -0
  21. package/dist/index.d.cts +4 -0
  22. package/dist/index.d.mts +4 -0
  23. package/dist/index.mjs +1 -0
  24. package/dist/metrics.cjs +2 -0
  25. package/dist/metrics.cjs.map +1 -0
  26. package/dist/metrics.d.cts +94 -0
  27. package/dist/metrics.d.cts.map +1 -0
  28. package/dist/metrics.d.mts +94 -0
  29. package/dist/metrics.d.mts.map +1 -0
  30. package/dist/metrics.mjs +2 -0
  31. package/dist/metrics.mjs.map +1 -0
  32. package/dist/metricsSource.cjs +2 -0
  33. package/dist/metricsSource.cjs.map +1 -0
  34. package/dist/metricsSource.d.cts +25 -0
  35. package/dist/metricsSource.d.cts.map +1 -0
  36. package/dist/metricsSource.d.mts +25 -0
  37. package/dist/metricsSource.d.mts.map +1 -0
  38. package/dist/metricsSource.mjs +2 -0
  39. package/dist/metricsSource.mjs.map +1 -0
  40. package/dist/tui/doomOverlay.cjs +2 -0
  41. package/dist/tui/doomOverlay.cjs.map +1 -0
  42. package/dist/tui/doomOverlay.d.cts +63 -0
  43. package/dist/tui/doomOverlay.d.cts.map +1 -0
  44. package/dist/tui/doomOverlay.d.mts +63 -0
  45. package/dist/tui/doomOverlay.d.mts.map +1 -0
  46. package/dist/tui/doomOverlay.mjs +2 -0
  47. package/dist/tui/doomOverlay.mjs.map +1 -0
  48. package/dist/tui/metricsOverlay.cjs +2 -0
  49. package/dist/tui/metricsOverlay.cjs.map +1 -0
  50. package/dist/tui/metricsOverlay.d.cts +71 -0
  51. package/dist/tui/metricsOverlay.d.cts.map +1 -0
  52. package/dist/tui/metricsOverlay.d.mts +71 -0
  53. package/dist/tui/metricsOverlay.d.mts.map +1 -0
  54. package/dist/tui/metricsOverlay.mjs +2 -0
  55. package/dist/tui/metricsOverlay.mjs.map +1 -0
  56. package/dist/tui/rendering.cjs +2 -0
  57. package/dist/tui/rendering.cjs.map +1 -0
  58. package/dist/tui/rendering.mjs +2 -0
  59. package/dist/tui/rendering.mjs.map +1 -0
  60. package/package.json +94 -0
@@ -0,0 +1,2 @@
1
+ import{DOOM_OVERLAY_ACCENT as e,DoomOverlay as t}from"./doomOverlay.mjs";import{matchesKey as n,truncateToWidth as r,visibleWidth as i}from"@earendil-works/pi-tui";const a=[`session`,`agent`,`workflow-name`,`step`],o=[`day`,`week`,`month`,`all`],s=a[0],c=o[0],l=`TOOL LATENCY · p95 duration_ms by tool.name`,u=`EVENT VOLUME · by log record name`,d=`RECENT ERRORS`,f=`SINK STATUS`,p=`COLLECTION`,m=`AGENT_TELEMETRY_DISABLED`,h=`this session`,g=[0,1,2],_=1e3,v=1e6,y=1e3;function b(e,t){let n=i(e);return n>=t?r(e,t,`…`):e+` `.repeat(t-n)}function x(e,t){let n=i(e);return n>=t?r(e,t):` `.repeat(t-n)+e}function S(e){return e.toLocaleString(`en-US`)}function C(e){return e>=v?`${(e/v).toFixed(1)}M`:e>=y?`${Math.round(e/y)}k`:String(e)}function w(e){return e===void 0?`—`:e>=_?`${(e/_).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 r(b(e,14)+t,n)}function O(e,t,n,r){return[b(e,r),b(t,r),b(n,r)]}function k(e,t){let n=Math.max(1,Math.floor(t/6)),r=e.toolLatency[0],i=(e.errors/Math.max(e.events,1)*100).toFixed(2),a=[O(`EVENTS`,S(e.events),h,n),O(`ERRORS`,S(e.errors),`${i}%`,n),O(`TOOL CALLS`,S(e.toolCalls),`${e.failedToolCalls} failed`,n),O(`P95 TOOL`,w(r?.p95Ms),r?.name??`—`,n),O(`TOKENS`,C(e.tokens.total),`↑${C(e.tokens.input)} ↓${C(e.tokens.output)}`,n),O(`COST`,`$${e.cost.toFixed(2)}`,h,n)];return g.map(e=>b(a.map(t=>t[e]??``).join(``),t))}function A(e,t){let n=t>0?e/t:0;return n>=.66?`error`:n>=.33?`warning`:`success`}function j(e,t,n){let i=e.toolLatency.slice(0,6),a=Math.max(1,t-10-8-1),o=Math.max(...i.map(e=>e.p95Ms??0),1);return i.map(e=>{let i=n.fg(A(e.p95Ms??0,o),E(e.p95Ms??0,o,a));return r(`${b(e.name,10)}${i} ${x(w(e.p95Ms),8)}`,t)})}function M(e,t){let n=e.eventVolume.slice(0,5),i=Math.max(1,t-19-8-1),a=Math.max(...n.map(e=>e.count),1);return n.map(e=>r(`${b(e.name,19)}${E(e.count,a,i)} ${x(S(e.count),8)}`,t))}function N(e,t){let n=Math.max(1,t-6-12-5);return e.recentErrors.slice(0,5).map(e=>r(b(T(e.at),6)+b(e.event,12)+b(e.message,n)+x(e.code,5),t))}function P(e,t,n){let r=n?[D(`diagnostic`,n,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),...r]:[D(`status`,`not connected`,t),D(`reason`,`no sink handle this session`,t),D(`metrics`,`aggregated in-process anyway`,t),...r]}function F(e,t,n){let i=e?.groups.slice(0,6)??[];if(i.length===0)return[];let a=Math.max(1,t-20-12-8-1),o=Math.max(...i.map(e=>e.totalTokens),1);return i.map(e=>{let i=n.fg(`accent`,E(e.totalTokens,o,a));return r(`${b(e.key,20)}${b(e.agentName??`—`,12)}${i} ${x(C(e.totalTokens),8)}`,t)})}function I(e,t,n){let i=e?.timeline.slice(-8)??[];if(i.length===0)return[];let a=Math.max(1,t-17-8-1),o=Math.max(...i.map(e=>e.totalTokens),1);return i.map(e=>{let i=n.fg(`success`,E(e.totalTokens,o,a));return r(`${b(e.label,17)}${i} ${x(C(e.totalTokens),8)}`,t)})}function L(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=>b(e,t))}function R(e){return[D(`source`,`in-process Pi events`,e),D(`scope`,`current session only`,e),...L(`Headline counters are aggregated in-process from this session. The TOKENS and TOKEN BURN panels come from the sink database, so they span every agent and workflow in the selected period, not just this session.`,e)]}var z=class extends t{getView;done;timer;query;disposed=!1;groupIndex=0;periodIndex=0;requestId=0;loading=!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(n(e,`escape`)||n(e,`ctrl+c`)||n(e,`q`)){this.done(void 0);return}let t=e.toLowerCase();if(t===`g`){this.groupIndex=(this.groupIndex+1)%a.length,this.refresh();return}if(t===`p`){this.periodIndex=(this.periodIndex+1)%o.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())})}tokenPanel(e){let t=this.theme.fg(`accent`,`TOKENS · by ${this.groupBy()} · ${this.period()}`),n=F(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=I(this.report,e,this.theme);return[n,...r.length>0?r:[this.theme.fg(`dim`,this.historyStatus())]]}groupBy(){return a[this.groupIndex]??s}period(){return o[this.periodIndex]??c}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:e,breadcrumb:`SPC › h / help › l / logs`,headerRight:`doom-log · service pi · this session`,footer:`r refresh · g group · p period · esc close`,footerHints:[[`r`,`refresh`],[`g`,`group`],[`p`,`period`],[`esc`,`close`]]}}renderBody(e,t){let n=this.getView();return n.disabled?this.disabledBody(e):this.metricsBody(n,e,t)}disabledBody(e){return[r(this.theme.fg(`warning`,` Metrics collection is disabled for this session.`),e),r(this.theme.fg(`dim`,` ${m}=1 is set, so no log records are produced.`),e),r(this.theme.fg(`dim`,` Unset ${m} and restart Pi to collect metrics.`),e)]}metricsBody(e,t,n){let r=k(e.snapshot,t);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.tokenPanel(o),``,this.theme.fg(`accent`,l),...j(e.snapshot,o,this.theme),``,this.theme.fg(`accent`,u),...M(e.snapshot,o)],m=[...this.burnPanel(s),``,this.theme.fg(`accent`,d),...N(e.snapshot,s),``,this.theme.fg(`accent`,f),...P(e.sink,s,e.lastDiagnostic),``,this.theme.fg(`accent`,p),...R(s)],h=Math.max(0,n-r.length-1),g=[this.theme.fg(`borderMuted`,`${`─`.repeat(a)}┬${`─`.repeat(i)}`)];for(let e=0;e<h;e++)g.push(b(` ${c[e]??``}`,a)+this.theme.fg(`borderMuted`,`│`)+b(` ${m[e]??``}`,i));return[...r,...g].slice(0,n)}singleColumnPanels(e,t){return[this.theme.fg(`accent`,l),...j(e.snapshot,t,this.theme),``,this.theme.fg(`accent`,u),...M(e.snapshot,t),``,this.theme.fg(`accent`,d),...N(e.snapshot,t),``,this.theme.fg(`accent`,f),...P(e.sink,t,e.lastDiagnostic),``,this.theme.fg(`accent`,p),...R(t)]}};export{z as LogMetricsOverlayComponent};
2
+ //# sourceMappingURL=metricsOverlay.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsOverlay.mjs","names":[],"sources":["../../src/tui/metricsOverlay.ts"],"sourcesContent":["/**\n * The `SPC h l` Log Metrics overlay.\n *\n * Rendering stays a pure `render(width)` so the layout can be asserted as text\n * without a live terminal. Every number comes from the in-process aggregator,\n * so the panels keep working when no sink is connected; only SINK STATUS\n * degrades.\n */\nimport type { Theme } from '@earendil-works/pi-coding-agent';\nimport { DOOM_OVERLAY_ACCENT, DoomOverlay, type DoomOverlayChrome, type DoomOverlayTui } from './doomOverlay.ts';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { LogMetricsGroupBy, LogMetricsPeriod, LogMetricsReport } from '@agimon-ai/log-sink-mcp';\nimport type { LogMetricsSnapshot } from '../metrics.ts';\nimport type { MetricsQuery, MetricsTransport } from '../metricsSource.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 · esc close';\nconst TOKENS_TITLE = 'TOKENS';\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 LATENCY_TITLE = 'TOOL LATENCY · p95 duration_ms by tool.name';\nconst VOLUME_TITLE = 'EVENT VOLUME · by log record name';\nconst ERRORS_TITLE = 'RECENT ERRORS';\nconst SINK_TITLE = 'SINK STATUS';\nconst COLLECTION_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';\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;\n/** Wide enough for the longest record name the extension emits. */\nconst RECORD_NAME_WIDTH = 19;\nconst VALUE_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 MAX_TOOL_ROWS = 6;\nconst MAX_VOLUME_ROWS = 5;\nconst MAX_ERROR_ROWS = 5;\n/** Bar color thresholds, as a fraction of the panel's slowest tool. */\nconst LATENCY_WARN_RATIO = 0.33;\nconst LATENCY_ERROR_RATIO = 0.66;\n\nconst SECOND_MS = 1000;\nconst MILLION = 1_000_000;\nconst THOUSAND = 1000;\nconst PERCENT = 100;\nconst PERCENT_DECIMALS = 2;\nconst COMPACT_DECIMALS = 1;\nconst DURATION_DECIMALS = 2;\nconst COST_DECIMALS = 2;\nconst TIME_SLICE_END = 5;\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(value);\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 statCell(label: string, value: string, sublabel: string, width: number): string[] {\n return [pad(label, width), pad(value, width), pad(sublabel, width)];\n}\n\n/** The six headline cells, laid out as three full-width rows. */\nfunction statRows(snapshot: LogMetricsSnapshot, width: number): string[] {\n const cellWidth = Math.max(1, Math.floor(width / STAT_CELLS));\n const slowest = snapshot.toolLatency[0];\n const errorRate = ((snapshot.errors / Math.max(snapshot.events, 1)) * PERCENT).toFixed(PERCENT_DECIMALS);\n const cells = [\n statCell('EVENTS', formatCount(snapshot.events), THIS_SESSION, cellWidth),\n statCell('ERRORS', formatCount(snapshot.errors), `${errorRate}%`, cellWidth),\n statCell('TOOL CALLS', formatCount(snapshot.toolCalls), `${snapshot.failedToolCalls} failed`, cellWidth),\n statCell('P95 TOOL', formatDuration(slowest?.p95Ms), slowest?.name ?? EMPTY_VALUE, cellWidth),\n statCell(\n 'TOKENS',\n formatCompact(snapshot.tokens.total),\n `↑${formatCompact(snapshot.tokens.input)} ↓${formatCompact(snapshot.tokens.output)}`,\n cellWidth,\n ),\n statCell('COST', `$${snapshot.cost.toFixed(COST_DECIMALS)}`, THIS_SESSION, cellWidth),\n ];\n return STAT_ROW_INDEXES.map((row) => pad(cells.map((cell) => cell[row] ?? '').join(''), width));\n}\n\n/** Fast tools read as `success`, slow ones as `error`, matching the mockup's green-to-red bar gradient. */\nfunction latencyColor(value: number, max: number): 'success' | 'warning' | 'error' {\n const ratio = max > 0 ? value / max : 0;\n if (ratio >= LATENCY_ERROR_RATIO) return 'error';\n if (ratio >= LATENCY_WARN_RATIO) return 'warning';\n return 'success';\n}\n\nfunction latencyRows(snapshot: LogMetricsSnapshot, width: number, theme: Theme): string[] {\n const entries = snapshot.toolLatency.slice(0, MAX_TOOL_ROWS);\n const barWidth = Math.max(1, width - TOOL_NAME_WIDTH - VALUE_WIDTH - 1);\n const max = Math.max(...entries.map((entry) => entry.p95Ms ?? 0), 1);\n return entries.map((entry) => {\n const barText = theme.fg(latencyColor(entry.p95Ms ?? 0, max), bar(entry.p95Ms ?? 0, max, barWidth));\n return truncateToWidth(\n `${pad(entry.name, TOOL_NAME_WIDTH)}${barText} ${padStart(formatDuration(entry.p95Ms), VALUE_WIDTH)}`,\n width,\n );\n });\n}\n\nfunction volumeRows(snapshot: LogMetricsSnapshot, width: number): string[] {\n const entries = snapshot.eventVolume.slice(0, MAX_VOLUME_ROWS);\n const barWidth = Math.max(1, width - RECORD_NAME_WIDTH - VALUE_WIDTH - 1);\n const max = Math.max(...entries.map((entry) => entry.count), 1);\n return entries.map((entry) =>\n truncateToWidth(\n `${pad(entry.name, RECORD_NAME_WIDTH)}${bar(entry.count, max, barWidth)} ${padStart(formatCount(entry.count), VALUE_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 * Only fields NodeTelemetryHandle can actually supply. It exposes no queue\n * counters, so none are invented here.\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('reason', 'no sink handle this session', width),\n labelled('metrics', 'aggregated in-process anyway', width),\n ...diagnosticRows,\n ];\n }\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 ...diagnosticRows,\n ];\n}\n\nconst COLLECTION_NOTE =\n 'Headline counters are aggregated in-process from this session. The TOKENS and ' +\n 'TOKEN BURN panels come from the sink database, so they span every agent and ' +\n 'workflow in the selected period, not just this session.';\n\n/** Ranked token consumers for the selected dimension. */\nfunction tokenRows(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('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 a single static paragraph. */\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\nfunction collectionRows(width: number): string[] {\n return [\n labelled('source', 'in-process Pi events', width),\n labelled('scope', 'current session only', width),\n ...wrapText(COLLECTION_NOTE, width),\n ];\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 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 === '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 tokenPanel(width: number): string[] {\n const heading = this.theme.fg('accent', `${TOKENS_TITLE} · by ${this.groupBy()} · ${this.period()}`);\n const rows = tokenRows(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 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 ['esc', 'close'],\n ],\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n const view = this.getView();\n return view.disabled ? this.disabledBody(width) : 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 private metricsBody(view: LogMetricsView, width: number, height: number): string[] {\n const stats = statRows(view.snapshot, width);\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.tokenPanel(leftContent),\n '',\n this.theme.fg('accent', LATENCY_TITLE),\n ...latencyRows(view.snapshot, leftContent, this.theme),\n '',\n this.theme.fg('accent', VOLUME_TITLE),\n ...volumeRows(view.snapshot, leftContent),\n ];\n const right = [\n ...this.burnPanel(rightContent),\n '',\n this.theme.fg('accent', ERRORS_TITLE),\n ...errorRows(view.snapshot, rightContent),\n '',\n this.theme.fg('accent', SINK_TITLE),\n ...sinkRows(view.sink, rightContent, view.lastDiagnostic),\n '',\n this.theme.fg('accent', COLLECTION_TITLE),\n ...collectionRows(rightContent),\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.theme.fg('accent', LATENCY_TITLE),\n ...latencyRows(view.snapshot, width, this.theme),\n '',\n this.theme.fg('accent', VOLUME_TITLE),\n ...volumeRows(view.snapshot, width),\n '',\n this.theme.fg('accent', ERRORS_TITLE),\n ...errorRows(view.snapshot, width),\n '',\n this.theme.fg('accent', SINK_TITLE),\n ...sinkRows(view.sink, width, view.lastDiagnostic),\n '',\n this.theme.fg('accent', COLLECTION_TITLE),\n ...collectionRows(width),\n ];\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ let e=require("@earendil-works/pi-tui");function t(t,n){return(0,e.truncateToWidth)(t,Math.max(0,n),``)}function n(n,r){let i=Math.max(0,r),a=t(n,i);return`${a}${` `.repeat(Math.max(0,i-(0,e.visibleWidth)(a)))}`}function r(r,i,a=`│`,o=`│`){let s=Math.max(0,i),c=(0,e.visibleWidth)(a)+(0,e.visibleWidth)(o);return s<=c?t(`${a}${o}`,s):`${a}${n(r,s-c)}${o}`}exports.fitLine=t,exports.frameLine=r,exports.padLine=n;
2
+ //# sourceMappingURL=rendering.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rendering.cjs","names":["truncateToWidth","visibleWidth"],"sources":["../../src/tui/rendering.ts"],"sourcesContent":["import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\n\nexport function fitLine(text: string, width: number): string {\n return truncateToWidth(text, Math.max(0, width), '');\n}\n\nexport function padLine(text: string, width: number): string {\n const safeWidth = Math.max(0, width);\n const fitted = fitLine(text, safeWidth);\n return `${fitted}${' '.repeat(Math.max(0, safeWidth - visibleWidth(fitted)))}`;\n}\n\nexport function frameLine(content: string, width: number, left = '│', right = '│'): string {\n const safeWidth = Math.max(0, width);\n const frameWidth = visibleWidth(left) + visibleWidth(right);\n if (safeWidth <= frameWidth) return fitLine(`${left}${right}`, safeWidth);\n return `${left}${padLine(content, safeWidth - frameWidth)}${right}`;\n}\n"],"mappings":"wCAEA,SAAgB,EAAQ,EAAc,EAAuB,CAC3D,OAAA,EAAOA,EAAAA,gBAAAA,CAAgB,EAAM,KAAK,IAAI,EAAG,CAAK,EAAG,EAAE,CACrD,CAEA,SAAgB,EAAQ,EAAc,EAAuB,CAC3D,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EAC7B,EAAS,EAAQ,EAAM,CAAS,EACtC,MAAO,GAAG,IAAS,IAAI,OAAO,KAAK,IAAI,EAAG,GAAA,EAAYC,EAAAA,aAAAA,CAAa,CAAM,CAAC,CAAC,GAC7E,CAEA,SAAgB,EAAU,EAAiB,EAAe,EAAO,IAAK,EAAQ,IAAa,CACzF,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EAC7B,GAAA,EAAaA,EAAAA,aAAAA,CAAa,CAAI,GAAA,EAAIA,EAAAA,aAAAA,CAAa,CAAK,EAE1D,OADI,GAAa,EAAmB,EAAQ,GAAG,IAAO,IAAS,CAAS,EACjE,GAAG,IAAO,EAAQ,EAAS,EAAY,CAAU,IAAI,GAC9D"}
@@ -0,0 +1,2 @@
1
+ import{truncateToWidth as e,visibleWidth as t}from"@earendil-works/pi-tui";function n(t,n){return e(t,Math.max(0,n),``)}function r(e,r){let i=Math.max(0,r),a=n(e,i);return`${a}${` `.repeat(Math.max(0,i-t(a)))}`}function i(e,i,a=`│`,o=`│`){let s=Math.max(0,i),c=t(a)+t(o);return s<=c?n(`${a}${o}`,s):`${a}${r(e,s-c)}${o}`}export{n as fitLine,i as frameLine,r as padLine};
2
+ //# sourceMappingURL=rendering.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rendering.mjs","names":[],"sources":["../../src/tui/rendering.ts"],"sourcesContent":["import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\n\nexport function fitLine(text: string, width: number): string {\n return truncateToWidth(text, Math.max(0, width), '');\n}\n\nexport function padLine(text: string, width: number): string {\n const safeWidth = Math.max(0, width);\n const fitted = fitLine(text, safeWidth);\n return `${fitted}${' '.repeat(Math.max(0, safeWidth - visibleWidth(fitted)))}`;\n}\n\nexport function frameLine(content: string, width: number, left = '│', right = '│'): string {\n const safeWidth = Math.max(0, width);\n const frameWidth = visibleWidth(left) + visibleWidth(right);\n if (safeWidth <= frameWidth) return fitLine(`${left}${right}`, safeWidth);\n return `${left}${padLine(content, safeWidth - frameWidth)}${right}`;\n}\n"],"mappings":"2EAEA,SAAgB,EAAQ,EAAc,EAAuB,CAC3D,OAAO,EAAgB,EAAM,KAAK,IAAI,EAAG,CAAK,EAAG,EAAE,CACrD,CAEA,SAAgB,EAAQ,EAAc,EAAuB,CAC3D,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EAC7B,EAAS,EAAQ,EAAM,CAAS,EACtC,MAAO,GAAG,IAAS,IAAI,OAAO,KAAK,IAAI,EAAG,EAAY,EAAa,CAAM,CAAC,CAAC,GAC7E,CAEA,SAAgB,EAAU,EAAiB,EAAe,EAAO,IAAK,EAAQ,IAAa,CACzF,IAAM,EAAY,KAAK,IAAI,EAAG,CAAK,EAC7B,EAAa,EAAa,CAAI,EAAI,EAAa,CAAK,EAE1D,OADI,GAAa,EAAmB,EAAQ,GAAG,IAAO,IAAS,CAAS,EACjE,GAAG,IAAO,EAAQ,EAAS,EAAY,CAAU,IAAI,GAC9D"}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@agimon-ai/doompi-log",
3
+ "version": "0.0.1-alpha.0",
4
+ "description": "Pi log-sink telemetry extension with session-local metrics and a Log Metrics overlay",
5
+ "homepage": "https://agimon.ai",
6
+ "license": "MIT",
7
+ "author": "Vuong Ngo",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "types": "./dist/index.d.mts",
14
+ "jsnext:main": "./dist/index.mjs",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.mts",
18
+ "import": "./dist/index.mjs",
19
+ "require": "./dist/index.cjs"
20
+ },
21
+ "./extension": {
22
+ "types": "./dist/extension.d.mts",
23
+ "import": "./dist/extension.mjs",
24
+ "require": "./dist/extension.cjs"
25
+ },
26
+ "./extensions/pi": {
27
+ "types": "./dist/extensions/pi.d.mts",
28
+ "import": "./dist/extensions/pi.mjs",
29
+ "require": "./dist/extensions/pi.cjs"
30
+ },
31
+ "./metrics": {
32
+ "types": "./dist/metrics.d.mts",
33
+ "import": "./dist/metrics.mjs",
34
+ "require": "./dist/metrics.cjs"
35
+ },
36
+ "./metricsSource": {
37
+ "types": "./dist/metricsSource.d.mts",
38
+ "import": "./dist/metricsSource.mjs",
39
+ "require": "./dist/metricsSource.cjs"
40
+ },
41
+ "./tui/metricsOverlay": {
42
+ "types": "./dist/tui/metricsOverlay.d.mts",
43
+ "import": "./dist/tui/metricsOverlay.mjs",
44
+ "require": "./dist/tui/metricsOverlay.cjs"
45
+ },
46
+ "./package.json": "./package.json"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "registry": "https://registry.npmjs.org/"
51
+ },
52
+ "dependencies": {
53
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.0",
54
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.0",
55
+ "@agimon-ai/log-sink-mcp": "0.28.9"
56
+ },
57
+ "devDependencies": {
58
+ "@earendil-works/pi-coding-agent": "0.84.1",
59
+ "@earendil-works/pi-tui": "0.84.1",
60
+ "@types/node": "26.1.1",
61
+ "@vitest/coverage-v8": "4.1.10",
62
+ "tsdown": "0.22.14",
63
+ "typescript": "6.0.3",
64
+ "vitest": "4.1.10",
65
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.0"
66
+ },
67
+ "peerDependencies": {
68
+ "@earendil-works/pi-coding-agent": "0.84.1",
69
+ "@earendil-works/pi-tui": "0.84.1"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@earendil-works/pi-coding-agent": {
73
+ "optional": true
74
+ },
75
+ "@earendil-works/pi-tui": {
76
+ "optional": true
77
+ }
78
+ },
79
+ "engines": {
80
+ "node": ">=22.19.0"
81
+ },
82
+ "pi": {
83
+ "extensions": [
84
+ "./dist/extensions/pi.mjs"
85
+ ]
86
+ },
87
+ "scripts": {
88
+ "build": "tsdown",
89
+ "test": "vitest --run",
90
+ "typecheck": "tsc --noEmit",
91
+ "lint": "oxlint . && oxfmt . --check",
92
+ "fixcode": "oxlint . --fix && oxfmt ."
93
+ }
94
+ }