@agimon-ai/doompi-log 0.0.1-alpha.48 → 0.0.1-alpha.49

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 (51) hide show
  1. package/dist/adapters/hubApi.cjs +2 -0
  2. package/dist/adapters/hubApi.cjs.map +1 -0
  3. package/dist/adapters/hubApi.d.cts +16 -0
  4. package/dist/adapters/hubApi.d.cts.map +1 -0
  5. package/dist/adapters/hubApi.d.mts +16 -0
  6. package/dist/adapters/hubApi.d.mts.map +1 -0
  7. package/dist/adapters/hubApi.mjs +2 -0
  8. package/dist/adapters/hubApi.mjs.map +1 -0
  9. package/dist/adapters/node/issuesSource.cjs +2 -0
  10. package/dist/adapters/node/issuesSource.cjs.map +1 -0
  11. package/dist/adapters/node/issuesSource.mjs +2 -0
  12. package/dist/adapters/node/issuesSource.mjs.map +1 -0
  13. package/dist/adapters/node/metricsSource.cjs +1 -1
  14. package/dist/adapters/node/metricsSource.cjs.map +1 -1
  15. package/dist/adapters/node/metricsSource.d.cts.map +1 -1
  16. package/dist/adapters/node/metricsSource.d.mts.map +1 -1
  17. package/dist/adapters/node/metricsSource.mjs +1 -1
  18. package/dist/adapters/node/metricsSource.mjs.map +1 -1
  19. package/dist/adapters/pi/extension.cjs +1 -1
  20. package/dist/adapters/pi/extension.mjs +1 -1
  21. package/dist/hubApi.cjs +1 -0
  22. package/dist/hubApi.d.cts +2 -0
  23. package/dist/hubApi.d.mts +2 -0
  24. package/dist/hubApi.mjs +1 -0
  25. package/dist/types/issuesSource.d.cts +58 -0
  26. package/dist/types/issuesSource.d.cts.map +1 -0
  27. package/dist/types/issuesSource.d.mts +58 -0
  28. package/dist/types/issuesSource.d.mts.map +1 -0
  29. package/dist/types/metricsSource.d.cts +19 -1
  30. package/dist/types/metricsSource.d.cts.map +1 -1
  31. package/dist/types/metricsSource.d.mts +19 -1
  32. package/dist/types/metricsSource.d.mts.map +1 -1
  33. package/dist/types/webMetrics.cjs +2 -0
  34. package/dist/types/webMetrics.cjs.map +1 -0
  35. package/dist/types/webMetrics.mjs +2 -0
  36. package/dist/types/webMetrics.mjs.map +1 -0
  37. package/package.json +34 -6
  38. package/src/exports/webClient.ts +1 -0
  39. package/src/types/issueGrouping.ts +86 -0
  40. package/src/types/webMetrics.ts +245 -0
  41. package/src/web/IssuesDetail.tsx +147 -0
  42. package/src/web/IssuesSection.tsx +83 -0
  43. package/src/web/MetricsNotice.tsx +59 -0
  44. package/src/web/MetricsPanel.tsx +126 -0
  45. package/src/web/MetricsReportView.tsx +123 -0
  46. package/src/web/charts/GroupBars.tsx +73 -0
  47. package/src/web/charts/TimelineChart.tsx +76 -0
  48. package/src/web/charts/chartScale.ts +47 -0
  49. package/src/web/index.ts +23 -0
  50. package/src/web/metricsApi.ts +114 -0
  51. package/src/web/tsconfig.json +23 -0
@@ -0,0 +1,2 @@
1
+ const e=require("./node/issuesSource.cjs"),t=require("./node/metricsSource.cjs"),n=require("../types/webMetrics.cjs");let r=require("hono");const i={session:`session`,agent:`agent`,model:`model`,provider:`provider`};function a(e){return{label:e.label,totalTokens:e.totalTokens,inputTokens:e.inputTokens,outputTokens:e.outputTokens}}function o(e){return{key:e.key,totalTokens:e.totalTokens,inputTokens:e.inputTokens,outputTokens:e.outputTokens,issueCount:e.issueCount,failed:e.failed}}function s(e){return{name:e.toolName,calls:e.invocationCount,p90TotalTokens:e.toolCallTurn.p90TotalTokens}}const c={session:`sessionId`,agent:`agentName`,model:`model`,provider:`provider`};function l(e,t){let n=e.filters;if(n===void 0)return;if(t===`session`)return n.sessionId;if(t===`agent`)return n.agentName;let r=t===`model`?n.model:n.provider;if(r!==void 0)return Array.isArray(r)?r[0]:r}function u(e){if(e instanceof Date)return e.toISOString();let t=new Date(e);return Number.isNaN(t.getTime())?new Date(0).toISOString():t.toISOString()}function d(e,t,n){let r=l(e,n);return{generatedAt:u(e.generatedAt),dimension:e.groupBy,period:e.period,bucketUnit:String(e.bucket),transport:t.lastTransport(),...r===void 0?{}:{focus:r},totals:{totalTokens:e.totals.totalTokens,inputTokens:e.totals.inputTokens,outputTokens:e.totals.outputTokens,cachedTokens:e.totals.cachedInputTokens,reasoningTokens:e.totals.reasoningOutputTokens,groupCount:e.totals.groupCount,failedGroups:e.totals.failedGroups,issueCount:e.totals.issueCount},timeline:e.timeline.map(a),groups:e.groups.map(o),tools:e.tools.rows.map(s)}}function f(e){return e.totals.totalTokens===0&&e.groups.length===0&&e.tools.length===0}function p(a={}){let o=new r.Hono,s=a.source??t.createMetricsSource(),l=a.issues??e.createIssuesSource();return o.get(`/metrics`,async e=>{let t=e.req.query(n.METRICS_QUERY_PARAMS.dimension)??`model`,r=e.req.query(n.METRICS_QUERY_PARAMS.period)??`week`;if(!n.isMetricsDimension(t))return e.json({error:`Unknown dimension '${t}'.`},400);if(!n.isMetricsPeriod(r))return e.json({error:`Unknown period '${r}'.`},400);let a=e.req.query(n.METRICS_QUERY_PARAMS.focus),o=a===void 0||a===``?void 0:{[c[t]]:a},l;try{l=await s.query({groupBy:i[t],period:r,limit:20,toolLimit:15,...o===void 0?{}:{filter:o}})}catch(t){let n={unavailable:`no-sink`,detail:t instanceof Error?t.message:`The log sink did not answer.`};return e.json(n)}let u=d(l,s,t);return f(u)?e.json({unavailable:`no-data`,detail:`The log sink is running but has recorded no usage for this period.`}):e.json(u)}),o.get(`/issues`,async e=>{let t=e.req.query(n.METRICS_QUERY_PARAMS.focus);try{let n=await l.query({limit:50,...t===void 0||t===``?{}:{sessionId:t}});return e.json(n)}catch(t){let n={unavailable:`no-sink`,detail:t instanceof Error?t.message:`The log sink did not answer.`};return e.json(n)}}),o}const m={basePath:`log`,start(e){let t=p();return{fetch:e=>t.fetch(e),close:()=>void 0}}};exports.api=m,exports.createLogHubApi=p;
2
+ //# sourceMappingURL=hubApi.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hubApi.cjs","names":["Hono","createMetricsSource","createIssuesSource","METRICS_QUERY_PARAMS","isMetricsDimension","isMetricsPeriod"],"sources":["../../src/adapters/hubApi.ts"],"sourcesContent":["import type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport type { LogMetricGroupRow, LogMetricsReport, ToolMetricRow } from '@agimon-ai/log-sink-mcp';\nimport { Hono } from 'hono';\nimport { createIssuesSource } from './node/issuesSource.ts';\nimport { createMetricsSource } from './node/metricsSource.ts';\nimport type { IssuesSource } from '../types/issuesSource.ts';\nimport type { MetricsFilter, MetricsSource } from '../types/metricsSource.ts';\nimport {\n isMetricsDimension,\n isMetricsPeriod,\n LOG_API_BASE_PATH,\n ISSUE_SAMPLE_LIMIT,\n METRICS_GROUP_LIMIT,\n METRICS_QUERY_PARAMS,\n METRICS_TOOL_LIMIT,\n type MetricsBucket,\n type MetricsDimension,\n type MetricsGroup,\n type MetricsPeriod,\n type MetricsReport,\n type MetricsTool,\n type MetricsUnavailable,\n type IssuesView,\n} from '../types/webMetrics.ts';\n\n/**\n * The cockpit's half of this package's metrics access.\n *\n * Hub-scoped rather than session-scoped: the questions this answers, where the\n * tokens went and which agents failed, are about the machine over time, and a\n * session server can only see itself. The sink is already machine-wide, so the\n * hub is the only place the two agree.\n *\n * The query path is not reimplemented here. `createMetricsSource` already owns\n * the sink's two transports and its fallback order, and the TUI overlay reads\n * through the same source, so both surfaces answer from one implementation.\n */\n\nconst DEFAULT_DIMENSION: MetricsDimension = 'model';\nconst DEFAULT_PERIOD: MetricsPeriod = 'week';\n\n/** The sink groups sessions under a different name than the page's dimension. */\nconst SINK_GROUP_BY = {\n session: 'session',\n agent: 'agent',\n model: 'model',\n provider: 'provider',\n} as const;\n\nfunction toBucket(bucket: LogMetricsReport['timeline'][number]): MetricsBucket {\n return {\n label: bucket.label,\n totalTokens: bucket.totalTokens,\n inputTokens: bucket.inputTokens,\n outputTokens: bucket.outputTokens,\n };\n}\n\nfunction toGroup(row: LogMetricGroupRow): MetricsGroup {\n return {\n key: row.key,\n totalTokens: row.totalTokens,\n inputTokens: row.inputTokens,\n outputTokens: row.outputTokens,\n issueCount: row.issueCount,\n failed: row.failed,\n };\n}\n\n/**\n * The turn's p90 rather than a sum: the sink attributes a whole turn's tokens\n * to every tool that ran in it, so adding them across calls would multiply the\n * same tokens by however many tools shared the turn.\n */\nfunction toTool(row: ToolMetricRow): MetricsTool {\n return {\n name: row.toolName,\n calls: row.invocationCount,\n p90TotalTokens: row.toolCallTurn.p90TotalTokens,\n };\n}\n\n/** Which filter field carries a focus value, per dimension. */\nconst FOCUS_FIELD: Record<MetricsDimension, keyof MetricsFilter> = {\n session: 'sessionId',\n agent: 'agentName',\n model: 'model',\n provider: 'provider',\n};\n\n/**\n * The focus the sink actually applied, read from its own echo.\n *\n * A daemon that predates a filter ignores it and answers with everything. If\n * the page trusted the request instead of this echo, it would label the whole\n * machine's numbers as one model's.\n */\nfunction appliedFocus(report: LogMetricsReport, dimension: MetricsDimension): string | undefined {\n // The daemon is a separate process on its own release cadence, so its\n // response is parsed defensively rather than trusted to carry every field.\n const filters = report.filters as LogMetricsReport['filters'] | undefined;\n if (filters === undefined) return undefined;\n if (dimension === 'session') return filters.sessionId;\n if (dimension === 'agent') return filters.agentName;\n const applied = dimension === 'model' ? filters.model : filters.provider;\n if (applied === undefined) return undefined;\n return Array.isArray(applied) ? applied[0] : applied;\n}\n\n/**\n * The sink's own type says Date, but both transports parse JSON, so what\n * arrives is an ISO string. Trusting the declared type here throws on every\n * real response, which no test using a hand-built Date would ever catch.\n */\nfunction generatedAtIso(value: LogMetricsReport['generatedAt']): string {\n if (value instanceof Date) return value.toISOString();\n const parsed = new Date(value as unknown as string);\n return Number.isNaN(parsed.getTime()) ? new Date(0).toISOString() : parsed.toISOString();\n}\n\nfunction toReport(report: LogMetricsReport, source: MetricsSource, dimension: MetricsDimension): MetricsReport {\n const focus = appliedFocus(report, dimension);\n return {\n generatedAt: generatedAtIso(report.generatedAt),\n dimension: report.groupBy as MetricsDimension,\n period: report.period as MetricsPeriod,\n bucketUnit: String(report.bucket),\n transport: source.lastTransport(),\n ...(focus === undefined ? {} : { focus }),\n totals: {\n totalTokens: report.totals.totalTokens,\n inputTokens: report.totals.inputTokens,\n outputTokens: report.totals.outputTokens,\n cachedTokens: report.totals.cachedInputTokens,\n reasoningTokens: report.totals.reasoningOutputTokens,\n groupCount: report.totals.groupCount,\n failedGroups: report.totals.failedGroups,\n issueCount: report.totals.issueCount,\n },\n timeline: report.timeline.map(toBucket),\n groups: report.groups.map(toGroup),\n tools: report.tools.rows.map(toTool),\n };\n}\n\n/**\n * A report the sink answered but with nothing in it reads as \"no data\", not as\n * an empty chart. The distinction the reader needs is whether telemetry is not\n * being recorded or simply has not been recorded yet.\n */\nfunction isEmpty(report: MetricsReport): boolean {\n return report.totals.totalTokens === 0 && report.groups.length === 0 && report.tools.length === 0;\n}\n\nexport interface HubApiOptions {\n /** Injected by tests; production resolves the machine's sink. */\n source?: MetricsSource;\n issues?: IssuesSource;\n}\n\nexport function createLogHubApi(options: HubApiOptions = {}): Hono {\n const app = new Hono();\n // One source for the life of the hub: it caches the resolved sink endpoint,\n // and rebuilding it per request would re-probe the daemon every time.\n const source = options.source ?? createMetricsSource();\n const issues = options.issues ?? createIssuesSource();\n\n app.get('/metrics', async (context) => {\n const requestedDimension = context.req.query(METRICS_QUERY_PARAMS.dimension) ?? DEFAULT_DIMENSION;\n const requestedPeriod = context.req.query(METRICS_QUERY_PARAMS.period) ?? DEFAULT_PERIOD;\n if (!isMetricsDimension(requestedDimension)) {\n return context.json({ error: `Unknown dimension '${requestedDimension}'.` }, 400);\n }\n if (!isMetricsPeriod(requestedPeriod)) {\n return context.json({ error: `Unknown period '${requestedPeriod}'.` }, 400);\n }\n\n const focus = context.req.query(METRICS_QUERY_PARAMS.focus);\n const filter: MetricsFilter | undefined =\n focus === undefined || focus === '' ? undefined : { [FOCUS_FIELD[requestedDimension]]: focus };\n\n let report: LogMetricsReport;\n try {\n report = await source.query({\n groupBy: SINK_GROUP_BY[requestedDimension],\n period: requestedPeriod,\n limit: METRICS_GROUP_LIMIT,\n toolLimit: METRICS_TOOL_LIMIT,\n ...(filter === undefined ? {} : { filter }),\n });\n } catch (error) {\n // Both transports declined. That is the ordinary state on a machine\n // where the sink has never run, so it is reported as absence rather\n // than raised as a fault the page has to render as a crash.\n const unavailable: MetricsUnavailable = {\n unavailable: 'no-sink',\n detail: error instanceof Error ? error.message : 'The log sink did not answer.',\n };\n return context.json(unavailable);\n }\n\n const body = toReport(report, source, requestedDimension);\n if (isEmpty(body)) {\n const unavailable: MetricsUnavailable = {\n unavailable: 'no-data',\n detail: 'The log sink is running but has recorded no usage for this period.',\n };\n return context.json(unavailable);\n }\n return context.json(body);\n });\n\n /**\n * The detail behind the count the report carries. Separate from /metrics\n * because it costs a subprocess: the running daemon has no issues route, so\n * folding it into every report would make the whole page as slow as its\n * slowest transport.\n */\n app.get('/issues', async (context) => {\n const focus = context.req.query(METRICS_QUERY_PARAMS.focus);\n try {\n const report = await issues.query({\n limit: ISSUE_SAMPLE_LIMIT,\n ...(focus === undefined || focus === '' ? {} : { sessionId: focus }),\n });\n return context.json(report satisfies IssuesView);\n } catch (error) {\n const unavailable: MetricsUnavailable = {\n unavailable: 'no-sink',\n detail: error instanceof Error ? error.message : 'The log sink did not answer.',\n };\n return context.json(unavailable);\n }\n });\n\n return app;\n}\n\n/** The named export a host imports from this package's built hub entry. */\nexport const api: DoomApi = {\n basePath: LOG_API_BASE_PATH,\n start(_context: DoomApiContext): DoomApiHandler {\n const app = createLogHubApi();\n return {\n fetch: (request) => app.fetch(request),\n // The source holds no handle of its own: the HTTP transport is a fetch\n // per query and the CLI transport is a subprocess bounded by its timeout.\n close: () => undefined,\n };\n },\n};\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { MetricsSource } from "../types/metricsSource.cjs";
2
+ import { IssuesSource } from "../types/issuesSource.cjs";
3
+ import { DoomApi } from "@agimon-ai/doompi-extension-contracts/package-api";
4
+ import { Hono } from "hono";
5
+ //#region src/adapters/hubApi.d.ts
6
+ interface HubApiOptions {
7
+ /** Injected by tests; production resolves the machine's sink. */
8
+ source?: MetricsSource;
9
+ issues?: IssuesSource;
10
+ }
11
+ declare function createLogHubApi(options?: HubApiOptions): Hono;
12
+ /** The named export a host imports from this package's built hub entry. */
13
+ declare const api: DoomApi;
14
+ //#endregion
15
+ export { HubApiOptions, api, createLogHubApi };
16
+ //# sourceMappingURL=hubApi.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hubApi.d.cts","names":[],"sources":["../../src/adapters/hubApi.ts"],"mappings":";;;;;UA0JiB;;EAEf,SAAS;EACT,SAAS;;iBAGK,gBAAgB,UAAS,gBAAqB;;cA+EjD,KAAK"}
@@ -0,0 +1,16 @@
1
+ import { MetricsSource } from "../types/metricsSource.mjs";
2
+ import { IssuesSource } from "../types/issuesSource.mjs";
3
+ import { Hono } from "hono";
4
+ import { DoomApi } from "@agimon-ai/doompi-extension-contracts/package-api";
5
+ //#region src/adapters/hubApi.d.ts
6
+ interface HubApiOptions {
7
+ /** Injected by tests; production resolves the machine's sink. */
8
+ source?: MetricsSource;
9
+ issues?: IssuesSource;
10
+ }
11
+ declare function createLogHubApi(options?: HubApiOptions): Hono;
12
+ /** The named export a host imports from this package's built hub entry. */
13
+ declare const api: DoomApi;
14
+ //#endregion
15
+ export { HubApiOptions, api, createLogHubApi };
16
+ //# sourceMappingURL=hubApi.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hubApi.d.mts","names":[],"sources":["../../src/adapters/hubApi.ts"],"mappings":";;;;;UA0JiB;;EAEf,SAAS;EACT,SAAS;;iBAGK,gBAAgB,UAAS,gBAAqB;;cA+EjD,KAAK"}
@@ -0,0 +1,2 @@
1
+ import{createIssuesSource as e}from"./node/issuesSource.mjs";import{createMetricsSource as t}from"./node/metricsSource.mjs";import{METRICS_QUERY_PARAMS as n,isMetricsDimension as r,isMetricsPeriod as i}from"../types/webMetrics.mjs";import{Hono as a}from"hono";const o={session:`session`,agent:`agent`,model:`model`,provider:`provider`};function s(e){return{label:e.label,totalTokens:e.totalTokens,inputTokens:e.inputTokens,outputTokens:e.outputTokens}}function c(e){return{key:e.key,totalTokens:e.totalTokens,inputTokens:e.inputTokens,outputTokens:e.outputTokens,issueCount:e.issueCount,failed:e.failed}}function l(e){return{name:e.toolName,calls:e.invocationCount,p90TotalTokens:e.toolCallTurn.p90TotalTokens}}const u={session:`sessionId`,agent:`agentName`,model:`model`,provider:`provider`};function d(e,t){let n=e.filters;if(n===void 0)return;if(t===`session`)return n.sessionId;if(t===`agent`)return n.agentName;let r=t===`model`?n.model:n.provider;if(r!==void 0)return Array.isArray(r)?r[0]:r}function f(e){if(e instanceof Date)return e.toISOString();let t=new Date(e);return Number.isNaN(t.getTime())?new Date(0).toISOString():t.toISOString()}function p(e,t,n){let r=d(e,n);return{generatedAt:f(e.generatedAt),dimension:e.groupBy,period:e.period,bucketUnit:String(e.bucket),transport:t.lastTransport(),...r===void 0?{}:{focus:r},totals:{totalTokens:e.totals.totalTokens,inputTokens:e.totals.inputTokens,outputTokens:e.totals.outputTokens,cachedTokens:e.totals.cachedInputTokens,reasoningTokens:e.totals.reasoningOutputTokens,groupCount:e.totals.groupCount,failedGroups:e.totals.failedGroups,issueCount:e.totals.issueCount},timeline:e.timeline.map(s),groups:e.groups.map(c),tools:e.tools.rows.map(l)}}function m(e){return e.totals.totalTokens===0&&e.groups.length===0&&e.tools.length===0}function h(s={}){let c=new a,l=s.source??t(),d=s.issues??e();return c.get(`/metrics`,async e=>{let t=e.req.query(n.dimension)??`model`,a=e.req.query(n.period)??`week`;if(!r(t))return e.json({error:`Unknown dimension '${t}'.`},400);if(!i(a))return e.json({error:`Unknown period '${a}'.`},400);let s=e.req.query(n.focus),c=s===void 0||s===``?void 0:{[u[t]]:s},d;try{d=await l.query({groupBy:o[t],period:a,limit:20,toolLimit:15,...c===void 0?{}:{filter:c}})}catch(t){let n={unavailable:`no-sink`,detail:t instanceof Error?t.message:`The log sink did not answer.`};return e.json(n)}let f=p(d,l,t);return m(f)?e.json({unavailable:`no-data`,detail:`The log sink is running but has recorded no usage for this period.`}):e.json(f)}),c.get(`/issues`,async e=>{let t=e.req.query(n.focus);try{let n=await d.query({limit:50,...t===void 0||t===``?{}:{sessionId:t}});return e.json(n)}catch(t){let n={unavailable:`no-sink`,detail:t instanceof Error?t.message:`The log sink did not answer.`};return e.json(n)}}),c}const g={basePath:`log`,start(e){let t=h();return{fetch:e=>t.fetch(e),close:()=>void 0}}};export{g as api,h as createLogHubApi};
2
+ //# sourceMappingURL=hubApi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hubApi.mjs","names":[],"sources":["../../src/adapters/hubApi.ts"],"sourcesContent":["import type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport type { LogMetricGroupRow, LogMetricsReport, ToolMetricRow } from '@agimon-ai/log-sink-mcp';\nimport { Hono } from 'hono';\nimport { createIssuesSource } from './node/issuesSource.ts';\nimport { createMetricsSource } from './node/metricsSource.ts';\nimport type { IssuesSource } from '../types/issuesSource.ts';\nimport type { MetricsFilter, MetricsSource } from '../types/metricsSource.ts';\nimport {\n isMetricsDimension,\n isMetricsPeriod,\n LOG_API_BASE_PATH,\n ISSUE_SAMPLE_LIMIT,\n METRICS_GROUP_LIMIT,\n METRICS_QUERY_PARAMS,\n METRICS_TOOL_LIMIT,\n type MetricsBucket,\n type MetricsDimension,\n type MetricsGroup,\n type MetricsPeriod,\n type MetricsReport,\n type MetricsTool,\n type MetricsUnavailable,\n type IssuesView,\n} from '../types/webMetrics.ts';\n\n/**\n * The cockpit's half of this package's metrics access.\n *\n * Hub-scoped rather than session-scoped: the questions this answers, where the\n * tokens went and which agents failed, are about the machine over time, and a\n * session server can only see itself. The sink is already machine-wide, so the\n * hub is the only place the two agree.\n *\n * The query path is not reimplemented here. `createMetricsSource` already owns\n * the sink's two transports and its fallback order, and the TUI overlay reads\n * through the same source, so both surfaces answer from one implementation.\n */\n\nconst DEFAULT_DIMENSION: MetricsDimension = 'model';\nconst DEFAULT_PERIOD: MetricsPeriod = 'week';\n\n/** The sink groups sessions under a different name than the page's dimension. */\nconst SINK_GROUP_BY = {\n session: 'session',\n agent: 'agent',\n model: 'model',\n provider: 'provider',\n} as const;\n\nfunction toBucket(bucket: LogMetricsReport['timeline'][number]): MetricsBucket {\n return {\n label: bucket.label,\n totalTokens: bucket.totalTokens,\n inputTokens: bucket.inputTokens,\n outputTokens: bucket.outputTokens,\n };\n}\n\nfunction toGroup(row: LogMetricGroupRow): MetricsGroup {\n return {\n key: row.key,\n totalTokens: row.totalTokens,\n inputTokens: row.inputTokens,\n outputTokens: row.outputTokens,\n issueCount: row.issueCount,\n failed: row.failed,\n };\n}\n\n/**\n * The turn's p90 rather than a sum: the sink attributes a whole turn's tokens\n * to every tool that ran in it, so adding them across calls would multiply the\n * same tokens by however many tools shared the turn.\n */\nfunction toTool(row: ToolMetricRow): MetricsTool {\n return {\n name: row.toolName,\n calls: row.invocationCount,\n p90TotalTokens: row.toolCallTurn.p90TotalTokens,\n };\n}\n\n/** Which filter field carries a focus value, per dimension. */\nconst FOCUS_FIELD: Record<MetricsDimension, keyof MetricsFilter> = {\n session: 'sessionId',\n agent: 'agentName',\n model: 'model',\n provider: 'provider',\n};\n\n/**\n * The focus the sink actually applied, read from its own echo.\n *\n * A daemon that predates a filter ignores it and answers with everything. If\n * the page trusted the request instead of this echo, it would label the whole\n * machine's numbers as one model's.\n */\nfunction appliedFocus(report: LogMetricsReport, dimension: MetricsDimension): string | undefined {\n // The daemon is a separate process on its own release cadence, so its\n // response is parsed defensively rather than trusted to carry every field.\n const filters = report.filters as LogMetricsReport['filters'] | undefined;\n if (filters === undefined) return undefined;\n if (dimension === 'session') return filters.sessionId;\n if (dimension === 'agent') return filters.agentName;\n const applied = dimension === 'model' ? filters.model : filters.provider;\n if (applied === undefined) return undefined;\n return Array.isArray(applied) ? applied[0] : applied;\n}\n\n/**\n * The sink's own type says Date, but both transports parse JSON, so what\n * arrives is an ISO string. Trusting the declared type here throws on every\n * real response, which no test using a hand-built Date would ever catch.\n */\nfunction generatedAtIso(value: LogMetricsReport['generatedAt']): string {\n if (value instanceof Date) return value.toISOString();\n const parsed = new Date(value as unknown as string);\n return Number.isNaN(parsed.getTime()) ? new Date(0).toISOString() : parsed.toISOString();\n}\n\nfunction toReport(report: LogMetricsReport, source: MetricsSource, dimension: MetricsDimension): MetricsReport {\n const focus = appliedFocus(report, dimension);\n return {\n generatedAt: generatedAtIso(report.generatedAt),\n dimension: report.groupBy as MetricsDimension,\n period: report.period as MetricsPeriod,\n bucketUnit: String(report.bucket),\n transport: source.lastTransport(),\n ...(focus === undefined ? {} : { focus }),\n totals: {\n totalTokens: report.totals.totalTokens,\n inputTokens: report.totals.inputTokens,\n outputTokens: report.totals.outputTokens,\n cachedTokens: report.totals.cachedInputTokens,\n reasoningTokens: report.totals.reasoningOutputTokens,\n groupCount: report.totals.groupCount,\n failedGroups: report.totals.failedGroups,\n issueCount: report.totals.issueCount,\n },\n timeline: report.timeline.map(toBucket),\n groups: report.groups.map(toGroup),\n tools: report.tools.rows.map(toTool),\n };\n}\n\n/**\n * A report the sink answered but with nothing in it reads as \"no data\", not as\n * an empty chart. The distinction the reader needs is whether telemetry is not\n * being recorded or simply has not been recorded yet.\n */\nfunction isEmpty(report: MetricsReport): boolean {\n return report.totals.totalTokens === 0 && report.groups.length === 0 && report.tools.length === 0;\n}\n\nexport interface HubApiOptions {\n /** Injected by tests; production resolves the machine's sink. */\n source?: MetricsSource;\n issues?: IssuesSource;\n}\n\nexport function createLogHubApi(options: HubApiOptions = {}): Hono {\n const app = new Hono();\n // One source for the life of the hub: it caches the resolved sink endpoint,\n // and rebuilding it per request would re-probe the daemon every time.\n const source = options.source ?? createMetricsSource();\n const issues = options.issues ?? createIssuesSource();\n\n app.get('/metrics', async (context) => {\n const requestedDimension = context.req.query(METRICS_QUERY_PARAMS.dimension) ?? DEFAULT_DIMENSION;\n const requestedPeriod = context.req.query(METRICS_QUERY_PARAMS.period) ?? DEFAULT_PERIOD;\n if (!isMetricsDimension(requestedDimension)) {\n return context.json({ error: `Unknown dimension '${requestedDimension}'.` }, 400);\n }\n if (!isMetricsPeriod(requestedPeriod)) {\n return context.json({ error: `Unknown period '${requestedPeriod}'.` }, 400);\n }\n\n const focus = context.req.query(METRICS_QUERY_PARAMS.focus);\n const filter: MetricsFilter | undefined =\n focus === undefined || focus === '' ? undefined : { [FOCUS_FIELD[requestedDimension]]: focus };\n\n let report: LogMetricsReport;\n try {\n report = await source.query({\n groupBy: SINK_GROUP_BY[requestedDimension],\n period: requestedPeriod,\n limit: METRICS_GROUP_LIMIT,\n toolLimit: METRICS_TOOL_LIMIT,\n ...(filter === undefined ? {} : { filter }),\n });\n } catch (error) {\n // Both transports declined. That is the ordinary state on a machine\n // where the sink has never run, so it is reported as absence rather\n // than raised as a fault the page has to render as a crash.\n const unavailable: MetricsUnavailable = {\n unavailable: 'no-sink',\n detail: error instanceof Error ? error.message : 'The log sink did not answer.',\n };\n return context.json(unavailable);\n }\n\n const body = toReport(report, source, requestedDimension);\n if (isEmpty(body)) {\n const unavailable: MetricsUnavailable = {\n unavailable: 'no-data',\n detail: 'The log sink is running but has recorded no usage for this period.',\n };\n return context.json(unavailable);\n }\n return context.json(body);\n });\n\n /**\n * The detail behind the count the report carries. Separate from /metrics\n * because it costs a subprocess: the running daemon has no issues route, so\n * folding it into every report would make the whole page as slow as its\n * slowest transport.\n */\n app.get('/issues', async (context) => {\n const focus = context.req.query(METRICS_QUERY_PARAMS.focus);\n try {\n const report = await issues.query({\n limit: ISSUE_SAMPLE_LIMIT,\n ...(focus === undefined || focus === '' ? {} : { sessionId: focus }),\n });\n return context.json(report satisfies IssuesView);\n } catch (error) {\n const unavailable: MetricsUnavailable = {\n unavailable: 'no-sink',\n detail: error instanceof Error ? error.message : 'The log sink did not answer.',\n };\n return context.json(unavailable);\n }\n });\n\n return app;\n}\n\n/** The named export a host imports from this package's built hub entry. */\nexport const api: DoomApi = {\n basePath: LOG_API_BASE_PATH,\n start(_context: DoomApiContext): DoomApiHandler {\n const app = createLogHubApi();\n return {\n fetch: (request) => app.fetch(request),\n // The source holds no handle of its own: the HTTP transport is a fetch\n // per query and the CLI transport is a subprocess bounded by its timeout.\n close: () => undefined,\n };\n },\n};\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:child_process"),n=require("node:path");n=e.__toESM(n,1);let r=require("node:module"),i=require("@agimon-ai/log-sink-mcp");function a(){let e=(0,r.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/log-sink-mcp/package.json`);return n.default.join(n.default.dirname(e),`dist`,`cli.mjs`)}function o(e,n){return new Promise((r,i)=>{(0,t.execFile)(process.execPath,[a(),`logs`,`agent-issues`,`--limit`,String(e.limit),...e.sessionId===void 0?[]:[`--session-id`,e.sessionId],...n===void 0?[]:[`--db-path`,n]],{timeout:6e4,maxBuffer:33554432},(e,t,n)=>{if(e){i(Error(n.trim()||e.message));return}try{r(JSON.parse(t))}catch{i(Error(`The log sink returned output this version cannot read.`))}})})}function s(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function c(e){if(!s(e))return{};let t=Object.entries(e).flatMap(([e,t])=>typeof t==`number`?[[e,t]]:[]);return Object.fromEntries(t)}function l(e){return typeof e==`string`?e:``}function u(e){return typeof e==`string`&&e!==``?e:null}function d(e){return s(e)?[{fingerprint:l(e.fingerprint),occurrenceCount:typeof e.occurrenceCount==`number`?e.occurrenceCount:1,category:l(e.category),timestamp:l(e.timestamp),level:l(e.level),message:l(e.message),detail:u(e.detail)??l(e.message),tool:u(e.tool),errorType:u(e.errorType),agentName:u(e.agentName),model:u(e.model),statusCode:u(e.statusCode)}]:[]}function f(e){if(!s(e))throw Error(`The log sink returned output this version cannot read.`);let t=Array.isArray(e.issues)?e.issues.flatMap(d):[];return{totalIssues:typeof e.totalIssues==`number`?e.totalIssues:0,uniqueIncidents:typeof e.uniqueIncidents==`number`?e.uniqueIncidents:0,byCategory:c(e.byCategory),byTool:c(e.byTool),byErrorType:c(e.byErrorType),samples:t}}function p(e={}){let t=(0,i.resolveLogSinkInstance)({env:e.env,cwd:e.cwd,packageName:e.packageName,serviceName:e.serviceName}),n=e.cliRunner??(e=>o(e,t?.dbPath));return{async query(e){return f(await n(e))}}}exports.createIssuesSource=p;
2
+ //# sourceMappingURL=issuesSource.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issuesSource.cjs","names":["createRequire","path","resolveLogSinkInstance"],"sources":["../../../src/adapters/node/issuesSource.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport path from 'node:path';\nimport { createRequire } from 'node:module';\nimport { resolveLogSinkInstance } from '@agimon-ai/log-sink-mcp';\nimport type { AgentIssueSample, IssuesQueryParams, IssuesReport, IssuesSource } from '../../types/issuesSource.ts';\n\n/**\n * Agent-issue analysis over the sink's CLI.\n *\n * One transport, not two, because the daemon serves only /api/metrics and\n * /api/logs: there is no issues route to prefer. Every call is a subprocess,\n * which is why the page asks for this only when a reader opens the section\n * rather than alongside each report.\n */\n\nconst PACKAGE_NAME = '@agimon-ai/log-sink-mcp';\nconst CLI_TIMEOUT_MS = 60000;\n/** The analysis scans the whole window, so its output is larger than a metrics report. */\nconst MAX_CLI_BUFFER = 32 * 1024 * 1024;\n\nexport interface IssuesSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n /** Overrides the subprocess in tests. */\n cliRunner?: (params: IssuesQueryParams) => Promise<unknown>;\n}\n\nfunction cliEntryPoint(): string {\n const require = createRequire(import.meta.url);\n const packageJson = require.resolve(`${PACKAGE_NAME}/package.json`);\n return path.join(path.dirname(packageJson), 'dist', 'cli.mjs');\n}\n\nfunction runCli(params: IssuesQueryParams, dbPath: string | undefined): Promise<unknown> {\n return new Promise((resolve, reject) => {\n execFile(\n process.execPath,\n [\n cliEntryPoint(),\n 'logs',\n 'agent-issues',\n '--limit',\n String(params.limit),\n ...(params.sessionId === undefined ? [] : ['--session-id', params.sessionId]),\n ...(dbPath === undefined ? [] : ['--db-path', dbPath]),\n ],\n { timeout: CLI_TIMEOUT_MS, maxBuffer: MAX_CLI_BUFFER },\n (error, stdout, stderr) => {\n if (error) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n try {\n resolve(JSON.parse(stdout) as unknown);\n } catch {\n reject(new Error('The log sink returned output this version cannot read.'));\n }\n },\n );\n });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction counts(value: unknown): Record<string, number> {\n if (!isRecord(value)) return {};\n const entries = Object.entries(value).flatMap(([key, count]) =>\n typeof count === 'number' ? [[key, count] as [string, number]] : [],\n );\n return Object.fromEntries(entries);\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction nullableText(value: unknown): string | null {\n return typeof value === 'string' && value !== '' ? value : null;\n}\n\nfunction toSample(value: unknown): AgentIssueSample[] {\n if (!isRecord(value)) return [];\n return [\n {\n fingerprint: text(value.fingerprint),\n occurrenceCount: typeof value.occurrenceCount === 'number' ? value.occurrenceCount : 1,\n category: text(value.category),\n timestamp: text(value.timestamp),\n level: text(value.level),\n message: text(value.message),\n // The sink leaves detail null when the message already is the detail.\n detail: nullableText(value.detail) ?? text(value.message),\n tool: nullableText(value.tool),\n errorType: nullableText(value.errorType),\n agentName: nullableText(value.agentName),\n model: nullableText(value.model),\n statusCode: nullableText(value.statusCode),\n },\n ];\n}\n\n/**\n * The CLI's report is a foreign process's JSON, so every field is narrowed\n * here rather than cast. A sink one version ahead may add fields, and a sink\n * one version behind may omit them; neither should throw.\n */\nfunction toReport(raw: unknown): IssuesReport {\n if (!isRecord(raw)) throw new Error('The log sink returned output this version cannot read.');\n const samples = Array.isArray(raw.issues) ? raw.issues.flatMap(toSample) : [];\n return {\n totalIssues: typeof raw.totalIssues === 'number' ? raw.totalIssues : 0,\n uniqueIncidents: typeof raw.uniqueIncidents === 'number' ? raw.uniqueIncidents : 0,\n byCategory: counts(raw.byCategory),\n byTool: counts(raw.byTool),\n byErrorType: counts(raw.byErrorType),\n samples,\n };\n}\n\nexport function createIssuesSource(options: IssuesSourceOptions = {}): IssuesSource {\n const instance = resolveLogSinkInstance({\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n });\n const cliRunner = options.cliRunner ?? ((params: IssuesQueryParams) => runCli(params, instance?.dbPath));\n\n return {\n async query(params) {\n return toReport(await cliRunner(params));\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{createRequire as e}from"node:module";import{execFile as t}from"node:child_process";import n from"node:path";import{resolveLogSinkInstance as r}from"@agimon-ai/log-sink-mcp";function i(){let t=e(import.meta.url).resolve(`@agimon-ai/log-sink-mcp/package.json`);return n.join(n.dirname(t),`dist`,`cli.mjs`)}function a(e,n){return new Promise((r,a)=>{t(process.execPath,[i(),`logs`,`agent-issues`,`--limit`,String(e.limit),...e.sessionId===void 0?[]:[`--session-id`,e.sessionId],...n===void 0?[]:[`--db-path`,n]],{timeout:6e4,maxBuffer:33554432},(e,t,n)=>{if(e){a(Error(n.trim()||e.message));return}try{r(JSON.parse(t))}catch{a(Error(`The log sink returned output this version cannot read.`))}})})}function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e){if(!o(e))return{};let t=Object.entries(e).flatMap(([e,t])=>typeof t==`number`?[[e,t]]:[]);return Object.fromEntries(t)}function c(e){return typeof e==`string`?e:``}function l(e){return typeof e==`string`&&e!==``?e:null}function u(e){return o(e)?[{fingerprint:c(e.fingerprint),occurrenceCount:typeof e.occurrenceCount==`number`?e.occurrenceCount:1,category:c(e.category),timestamp:c(e.timestamp),level:c(e.level),message:c(e.message),detail:l(e.detail)??c(e.message),tool:l(e.tool),errorType:l(e.errorType),agentName:l(e.agentName),model:l(e.model),statusCode:l(e.statusCode)}]:[]}function d(e){if(!o(e))throw Error(`The log sink returned output this version cannot read.`);let t=Array.isArray(e.issues)?e.issues.flatMap(u):[];return{totalIssues:typeof e.totalIssues==`number`?e.totalIssues:0,uniqueIncidents:typeof e.uniqueIncidents==`number`?e.uniqueIncidents:0,byCategory:s(e.byCategory),byTool:s(e.byTool),byErrorType:s(e.byErrorType),samples:t}}function f(e={}){let t=r({env:e.env,cwd:e.cwd,packageName:e.packageName,serviceName:e.serviceName}),n=e.cliRunner??(e=>a(e,t?.dbPath));return{async query(e){return d(await n(e))}}}export{f as createIssuesSource};
2
+ //# sourceMappingURL=issuesSource.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issuesSource.mjs","names":[],"sources":["../../../src/adapters/node/issuesSource.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport path from 'node:path';\nimport { createRequire } from 'node:module';\nimport { resolveLogSinkInstance } from '@agimon-ai/log-sink-mcp';\nimport type { AgentIssueSample, IssuesQueryParams, IssuesReport, IssuesSource } from '../../types/issuesSource.ts';\n\n/**\n * Agent-issue analysis over the sink's CLI.\n *\n * One transport, not two, because the daemon serves only /api/metrics and\n * /api/logs: there is no issues route to prefer. Every call is a subprocess,\n * which is why the page asks for this only when a reader opens the section\n * rather than alongside each report.\n */\n\nconst PACKAGE_NAME = '@agimon-ai/log-sink-mcp';\nconst CLI_TIMEOUT_MS = 60000;\n/** The analysis scans the whole window, so its output is larger than a metrics report. */\nconst MAX_CLI_BUFFER = 32 * 1024 * 1024;\n\nexport interface IssuesSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n /** Overrides the subprocess in tests. */\n cliRunner?: (params: IssuesQueryParams) => Promise<unknown>;\n}\n\nfunction cliEntryPoint(): string {\n const require = createRequire(import.meta.url);\n const packageJson = require.resolve(`${PACKAGE_NAME}/package.json`);\n return path.join(path.dirname(packageJson), 'dist', 'cli.mjs');\n}\n\nfunction runCli(params: IssuesQueryParams, dbPath: string | undefined): Promise<unknown> {\n return new Promise((resolve, reject) => {\n execFile(\n process.execPath,\n [\n cliEntryPoint(),\n 'logs',\n 'agent-issues',\n '--limit',\n String(params.limit),\n ...(params.sessionId === undefined ? [] : ['--session-id', params.sessionId]),\n ...(dbPath === undefined ? [] : ['--db-path', dbPath]),\n ],\n { timeout: CLI_TIMEOUT_MS, maxBuffer: MAX_CLI_BUFFER },\n (error, stdout, stderr) => {\n if (error) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n try {\n resolve(JSON.parse(stdout) as unknown);\n } catch {\n reject(new Error('The log sink returned output this version cannot read.'));\n }\n },\n );\n });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction counts(value: unknown): Record<string, number> {\n if (!isRecord(value)) return {};\n const entries = Object.entries(value).flatMap(([key, count]) =>\n typeof count === 'number' ? [[key, count] as [string, number]] : [],\n );\n return Object.fromEntries(entries);\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction nullableText(value: unknown): string | null {\n return typeof value === 'string' && value !== '' ? value : null;\n}\n\nfunction toSample(value: unknown): AgentIssueSample[] {\n if (!isRecord(value)) return [];\n return [\n {\n fingerprint: text(value.fingerprint),\n occurrenceCount: typeof value.occurrenceCount === 'number' ? value.occurrenceCount : 1,\n category: text(value.category),\n timestamp: text(value.timestamp),\n level: text(value.level),\n message: text(value.message),\n // The sink leaves detail null when the message already is the detail.\n detail: nullableText(value.detail) ?? text(value.message),\n tool: nullableText(value.tool),\n errorType: nullableText(value.errorType),\n agentName: nullableText(value.agentName),\n model: nullableText(value.model),\n statusCode: nullableText(value.statusCode),\n },\n ];\n}\n\n/**\n * The CLI's report is a foreign process's JSON, so every field is narrowed\n * here rather than cast. A sink one version ahead may add fields, and a sink\n * one version behind may omit them; neither should throw.\n */\nfunction toReport(raw: unknown): IssuesReport {\n if (!isRecord(raw)) throw new Error('The log sink returned output this version cannot read.');\n const samples = Array.isArray(raw.issues) ? raw.issues.flatMap(toSample) : [];\n return {\n totalIssues: typeof raw.totalIssues === 'number' ? raw.totalIssues : 0,\n uniqueIncidents: typeof raw.uniqueIncidents === 'number' ? raw.uniqueIncidents : 0,\n byCategory: counts(raw.byCategory),\n byTool: counts(raw.byTool),\n byErrorType: counts(raw.byErrorType),\n samples,\n };\n}\n\nexport function createIssuesSource(options: IssuesSourceOptions = {}): IssuesSource {\n const instance = resolveLogSinkInstance({\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n });\n const cliRunner = options.cliRunner ?? ((params: IssuesQueryParams) => runCli(params, instance?.dbPath));\n\n return {\n async query(params) {\n return toReport(await cliRunner(params));\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:child_process"),n=require("node:module"),r=require("node:path");r=e.__toESM(r,1);let i=require("@agimon-ai/log-sink-mcp");const a=`total-tokens`;function o(){let e=(0,n.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/log-sink-mcp/package.json`);return r.default.join(r.default.dirname(e),`dist`,`cli.mjs`)}function s(e){return new URLSearchParams({groupBy:e.groupBy,period:e.period,sort:a,limit:String(e.limit),toolLimit:`1`}).toString()}async function c(e,t,n){let r=await n(`${e}/api/metrics?${s(t)}`,{signal:AbortSignal.timeout(5e3)});if(!r.ok)throw Error(`sink responded ${r.status}`);let i=await r.json();if(i.error||!Array.isArray(i.timeline))throw Error(i.error??`sink is an older version`);return i}function l(e,n){return new Promise((r,i)=>{(0,t.execFile)(process.execPath,[o(),`logs`,`metrics`,`--group-by`,e.groupBy,`--period`,e.period,`--sort`,a,`--limit`,String(e.limit),`--tool-limit`,`1`,...n===void 0?[]:[`--db-path`,n]],{timeout:3e4,maxBuffer:33554432},(e,t,n)=>{if(e){i(Error(n.trim()||e.message));return}try{r(JSON.parse(t))}catch{i(Error(`metrics CLI returned unparsable output`))}})})}function u(e){try{let t=(0,i.resolveLogSinkInstance)(e);return{scope:t.scope,dbPath:t.dbPath,...t.registeredName===void 0?{}:{registeredName:t.registeredName}}}catch{return}}function d(e={}){let t=e.fetchImpl??globalThis.fetch,n={env:e.env,cwd:e.cwd,packageName:e.packageName,serviceName:e.serviceName},r,a,o=u(n),s=e.cliRunner??(e=>l(e,o?.dbPath)),d=async()=>{if(a!==void 0)return a??void 0;try{a=(await(0,i.resolveLogSinkPort)({...n,healthCheck:!0}))?.endpoint??null}catch{a=null}return a??void 0};return{lastTransport:()=>r,instance:()=>o,async query(e){let n=await d();if(n&&t)try{let i=await c(n,e,t);return r=`http`,i}catch{a=null}let i=await s(e);return r=`cli`,i}}}exports.createMetricsSource=d;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:child_process"),n=require("node:path");n=e.__toESM(n,1);let r=require("node:module"),i=require("@agimon-ai/log-sink-mcp");const a=`total-tokens`;function o(){let e=(0,r.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/log-sink-mcp/package.json`);return n.default.join(n.default.dirname(e),`dist`,`cli.mjs`)}const s={sessionId:`sessionId`,agentName:`agentName`,model:`model`,provider:`provider`},c={sessionId:`--session-id`,agentName:`--agent-name`,model:`--model`,provider:`--provider`};function l(e){let t=e.filter;return t===void 0?[]:Object.keys(s).flatMap(e=>{let n=t[e];return n===void 0||n===``?[]:[[e,n]]})}function u(e){let t=new URLSearchParams({groupBy:e.groupBy,period:e.period,sort:a,limit:String(e.limit),toolLimit:String(e.toolLimit??1)});for(let[n,r]of l(e))t.set(s[n],r);return t.toString()}async function d(e,t,n){let r=await n(`${e}/api/metrics?${u(t)}`,{signal:AbortSignal.timeout(5e3)});if(!r.ok)throw Error(`sink responded ${r.status}`);let i=await r.json();if(i.error||!Array.isArray(i.timeline))throw Error(i.error??`sink is an older version`);return i}function f(e,n){return new Promise((r,i)=>{(0,t.execFile)(process.execPath,[o(),`logs`,`metrics`,`--group-by`,e.groupBy,`--period`,e.period,`--sort`,a,`--limit`,String(e.limit),`--tool-limit`,String(e.toolLimit??1),...l(e).flatMap(([e,t])=>[c[e],t]),...n===void 0?[]:[`--db-path`,n]],{timeout:3e4,maxBuffer:33554432},(e,t,n)=>{if(e){i(Error(n.trim()||e.message));return}try{r(JSON.parse(t))}catch{i(Error(`metrics CLI returned unparsable output`))}})})}function p(e){try{let t=(0,i.resolveLogSinkInstance)(e);return{scope:t.scope,dbPath:t.dbPath,...t.registeredName===void 0?{}:{registeredName:t.registeredName}}}catch{return}}function m(e={}){let t=e.fetchImpl??globalThis.fetch,n={env:e.env,cwd:e.cwd,packageName:e.packageName,serviceName:e.serviceName},r,a,o=p(n),s=e.cliRunner??(e=>f(e,o?.dbPath)),c=async()=>{if(a!==void 0)return a??void 0;try{a=(await(0,i.resolveLogSinkPort)({...n,healthCheck:!0}))?.endpoint??null}catch{a=null}return a??void 0};return{lastTransport:()=>r,instance:()=>o,async query(e){let n=await c();if(n&&t)try{let i=await d(n,e,t);return r=`http`,i}catch{a=null}let i=await s(e);return r=`cli`,i}}}exports.createMetricsSource=m;
2
2
  //# sourceMappingURL=metricsSource.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metricsSource.cjs","names":["createRequire","path","resolveLogSinkInstance","resolveLogSinkPort"],"sources":["../../../src/adapters/node/metricsSource.ts"],"sourcesContent":["/**\n * Historical metrics access for the `SPC h l` overlay.\n *\n * Two transports, in order: the running sink's HTTP API, then the installed\n * CLI as a subprocess. The sink daemon is long-lived and often lags the\n * installed package, so a daemon that rejects newer query parameters is treated\n * as unavailable rather than fatal. The reader exported by log-sink-mcp is\n * deliberately not used in-process: better-sqlite3 is synchronous and a\n * one-day window blocks for seconds, which would freeze the TUI.\n *\n * Both transports resolve the sink instance from the same identity the Pi\n * telemetry extension writes under. Without it log-sink falls back to\n * `npm_package_name`, which a running Pi process does not set, so the reader\n * would land on the local instance while the writer exported to the global one\n * and the panel would report an empty history that no session ever filled.\n */\nimport { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type { LogMetricsReport } from '@agimon-ai/log-sink-mcp';\nimport { resolveLogSinkInstance, resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\nimport type {\n MetricsInstance,\n MetricsQueryParams,\n MetricsSource,\n MetricsTransport,\n} from '../../types/metricsSource.ts';\n\nconst PACKAGE_NAME = '@agimon-ai/log-sink-mcp';\nconst HTTP_TIMEOUT_MS = 5000;\nconst CLI_TIMEOUT_MS = 30000;\n/** A day of dev telemetry is already ~140k records; keep the payload bounded. */\nconst MAX_CLI_BUFFER = 32 * 1024 * 1024;\nconst TOOL_LIMIT = '1';\n/** The panel ranks consumers, so ask for the biggest rather than the most recent. */\nconst TOKEN_SORT = 'total-tokens';\n\n/** The identity both transports resolve their sink instance from. */\ninterface ResolveIdentity {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n}\n\nexport interface MetricsSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Identity the sink is registered under; must match the telemetry writer's. */\n packageName?: string;\n serviceName?: string;\n /** Overrides transport discovery in tests. */\n fetchImpl?: typeof fetch;\n cliRunner?: (params: MetricsQueryParams) => Promise<LogMetricsReport>;\n}\n\nfunction cliEntryPoint(): string {\n const require = createRequire(import.meta.url);\n // The published bin is CJS; the ESM build sits beside it and starts faster.\n const packageJson = require.resolve(`${PACKAGE_NAME}/package.json`);\n return path.join(path.dirname(packageJson), 'dist', 'cli.mjs');\n}\n\nfunction searchParams(params: MetricsQueryParams): string {\n return new URLSearchParams({\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: String(params.limit),\n toolLimit: TOOL_LIMIT,\n }).toString();\n}\n\nasync function queryHttp(\n endpoint: string,\n params: MetricsQueryParams,\n fetchImpl: typeof fetch,\n): Promise<LogMetricsReport> {\n const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, {\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n });\n if (!response.ok) throw new Error(`sink responded ${response.status}`);\n\n const body = (await response.json()) as LogMetricsReport & { error?: string };\n // An older daemon rejects unknown group-by values and omits the timeline.\n if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? 'sink is an older version');\n return body;\n}\n\n/**\n * The subprocess resolves an instance of its own from the inherited cwd and\n * environment, so the database is passed explicitly rather than left to agree\n * with this process by coincidence.\n */\nfunction runCli(params: MetricsQueryParams, dbPath: string | undefined): Promise<LogMetricsReport> {\n return new Promise((resolve, reject) => {\n execFile(\n process.execPath,\n [\n cliEntryPoint(),\n 'logs',\n 'metrics',\n '--group-by',\n params.groupBy,\n '--period',\n params.period,\n '--sort',\n TOKEN_SORT,\n '--limit',\n String(params.limit),\n '--tool-limit',\n TOOL_LIMIT,\n ...(dbPath === undefined ? [] : ['--db-path', dbPath]),\n ],\n { timeout: CLI_TIMEOUT_MS, maxBuffer: MAX_CLI_BUFFER },\n (error, stdout, stderr) => {\n if (error) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n try {\n resolve(JSON.parse(stdout) as LogMetricsReport);\n } catch {\n reject(new Error('metrics CLI returned unparsable output'));\n }\n },\n );\n });\n}\n\n/**\n * A malformed `.logsink.yaml` must not take the overlay down with it. Both\n * transports still run when resolution fails; they fall back to log-sink's own\n * resolution and the panel reports an unknown instance.\n */\nfunction resolveInstance(identity: ResolveIdentity): MetricsInstance | undefined {\n try {\n const resolved = resolveLogSinkInstance(identity);\n return {\n scope: resolved.scope,\n dbPath: resolved.dbPath,\n ...(resolved.registeredName === undefined ? {} : { registeredName: resolved.registeredName }),\n };\n } catch {\n return undefined;\n }\n}\n\nexport function createMetricsSource(options: MetricsSourceOptions = {}): MetricsSource {\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n const identity = {\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n };\n let transport: MetricsTransport | undefined;\n let endpoint: string | undefined | null;\n const instance = resolveInstance(identity);\n const cliRunner = options.cliRunner ?? ((params: MetricsQueryParams) => runCli(params, instance?.dbPath));\n\n const resolveEndpoint = async (): Promise<string | undefined> => {\n if (endpoint !== undefined) return endpoint ?? undefined;\n try {\n const resolved = await resolveLogSinkPort({ ...identity, healthCheck: true });\n endpoint = resolved?.endpoint ?? null;\n } catch {\n endpoint = null;\n }\n return endpoint ?? undefined;\n };\n\n return {\n lastTransport: () => transport,\n instance: () => instance,\n async query(params) {\n const httpEndpoint = await resolveEndpoint();\n if (httpEndpoint && fetchImpl) {\n try {\n const report = await queryHttp(httpEndpoint, params, fetchImpl);\n transport = 'http';\n return report;\n } catch {\n // A stale or unhealthy daemon must not hide data the CLI can still read.\n endpoint = null;\n }\n }\n\n const report = await cliRunner(params);\n transport = 'cli';\n return report;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metricsSource.cjs","names":["createRequire","path","resolveLogSinkInstance","resolveLogSinkPort"],"sources":["../../../src/adapters/node/metricsSource.ts"],"sourcesContent":["/**\n * Historical metrics access for the `SPC h l` overlay.\n *\n * Two transports, in order: the running sink's HTTP API, then the installed\n * CLI as a subprocess. The sink daemon is long-lived and often lags the\n * installed package, so a daemon that rejects newer query parameters is treated\n * as unavailable rather than fatal. The reader exported by log-sink-mcp is\n * deliberately not used in-process: better-sqlite3 is synchronous and a\n * one-day window blocks for seconds, which would freeze the TUI.\n *\n * Both transports resolve the sink instance from the same identity the Pi\n * telemetry extension writes under. Without it log-sink falls back to\n * `npm_package_name`, which a running Pi process does not set, so the reader\n * would land on the local instance while the writer exported to the global one\n * and the panel would report an empty history that no session ever filled.\n */\nimport { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type { LogMetricsReport } from '@agimon-ai/log-sink-mcp';\nimport { resolveLogSinkInstance, resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\nimport type {\n MetricsInstance,\n MetricsQueryParams,\n MetricsSource,\n MetricsTransport,\n} from '../../types/metricsSource.ts';\n\nconst PACKAGE_NAME = '@agimon-ai/log-sink-mcp';\nconst HTTP_TIMEOUT_MS = 5000;\nconst CLI_TIMEOUT_MS = 30000;\n/** A day of dev telemetry is already ~140k records; keep the payload bounded. */\nconst MAX_CLI_BUFFER = 32 * 1024 * 1024;\n/**\n * The overlay ranks one tool, but a page pairing tool calls against failure\n * counts needs a denominator for every tool that failed, so callers ask.\n */\nconst DEFAULT_TOOL_LIMIT = 1;\n/** The panel ranks consumers, so ask for the biggest rather than the most recent. */\nconst TOKEN_SORT = 'total-tokens';\n\n/** The identity both transports resolve their sink instance from. */\ninterface ResolveIdentity {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n}\n\nexport interface MetricsSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Identity the sink is registered under; must match the telemetry writer's. */\n packageName?: string;\n serviceName?: string;\n /** Overrides transport discovery in tests. */\n fetchImpl?: typeof fetch;\n cliRunner?: (params: MetricsQueryParams) => Promise<LogMetricsReport>;\n}\n\nfunction cliEntryPoint(): string {\n const require = createRequire(import.meta.url);\n // The published bin is CJS; the ESM build sits beside it and starts faster.\n const packageJson = require.resolve(`${PACKAGE_NAME}/package.json`);\n return path.join(path.dirname(packageJson), 'dist', 'cli.mjs');\n}\n\n/**\n * The sink names these the same way on the wire and on the command line, apart\n * from the case convention, so the two transports stay in step.\n */\nconst FILTER_QUERY_KEYS = {\n sessionId: 'sessionId',\n agentName: 'agentName',\n model: 'model',\n provider: 'provider',\n} as const;\n\nconst FILTER_CLI_FLAGS = {\n sessionId: '--session-id',\n agentName: '--agent-name',\n model: '--model',\n provider: '--provider',\n} as const;\n\nfunction filterEntries(params: MetricsQueryParams): [keyof typeof FILTER_QUERY_KEYS, string][] {\n const filter = params.filter;\n if (filter === undefined) return [];\n return (Object.keys(FILTER_QUERY_KEYS) as (keyof typeof FILTER_QUERY_KEYS)[]).flatMap((key) => {\n const value = filter[key];\n return value === undefined || value === '' ? [] : [[key, value] as [keyof typeof FILTER_QUERY_KEYS, string]];\n });\n}\n\nfunction searchParams(params: MetricsQueryParams): string {\n const search = new URLSearchParams({\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: String(params.limit),\n toolLimit: String(params.toolLimit ?? DEFAULT_TOOL_LIMIT),\n });\n for (const [key, value] of filterEntries(params)) search.set(FILTER_QUERY_KEYS[key], value);\n return search.toString();\n}\n\nasync function queryHttp(\n endpoint: string,\n params: MetricsQueryParams,\n fetchImpl: typeof fetch,\n): Promise<LogMetricsReport> {\n const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, {\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n });\n if (!response.ok) throw new Error(`sink responded ${response.status}`);\n\n const body = (await response.json()) as LogMetricsReport & { error?: string };\n // An older daemon rejects unknown group-by values and omits the timeline.\n if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? 'sink is an older version');\n return body;\n}\n\n/**\n * The subprocess resolves an instance of its own from the inherited cwd and\n * environment, so the database is passed explicitly rather than left to agree\n * with this process by coincidence.\n */\nfunction runCli(params: MetricsQueryParams, dbPath: string | undefined): Promise<LogMetricsReport> {\n return new Promise((resolve, reject) => {\n execFile(\n process.execPath,\n [\n cliEntryPoint(),\n 'logs',\n 'metrics',\n '--group-by',\n params.groupBy,\n '--period',\n params.period,\n '--sort',\n TOKEN_SORT,\n '--limit',\n String(params.limit),\n '--tool-limit',\n String(params.toolLimit ?? DEFAULT_TOOL_LIMIT),\n ...filterEntries(params).flatMap(([key, value]) => [FILTER_CLI_FLAGS[key], value]),\n ...(dbPath === undefined ? [] : ['--db-path', dbPath]),\n ],\n { timeout: CLI_TIMEOUT_MS, maxBuffer: MAX_CLI_BUFFER },\n (error, stdout, stderr) => {\n if (error) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n try {\n resolve(JSON.parse(stdout) as LogMetricsReport);\n } catch {\n reject(new Error('metrics CLI returned unparsable output'));\n }\n },\n );\n });\n}\n\n/**\n * A malformed `.logsink.yaml` must not take the overlay down with it. Both\n * transports still run when resolution fails; they fall back to log-sink's own\n * resolution and the panel reports an unknown instance.\n */\nfunction resolveInstance(identity: ResolveIdentity): MetricsInstance | undefined {\n try {\n const resolved = resolveLogSinkInstance(identity);\n return {\n scope: resolved.scope,\n dbPath: resolved.dbPath,\n ...(resolved.registeredName === undefined ? {} : { registeredName: resolved.registeredName }),\n };\n } catch {\n return undefined;\n }\n}\n\nexport function createMetricsSource(options: MetricsSourceOptions = {}): MetricsSource {\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n const identity = {\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n };\n let transport: MetricsTransport | undefined;\n let endpoint: string | undefined | null;\n const instance = resolveInstance(identity);\n const cliRunner = options.cliRunner ?? ((params: MetricsQueryParams) => runCli(params, instance?.dbPath));\n\n const resolveEndpoint = async (): Promise<string | undefined> => {\n if (endpoint !== undefined) return endpoint ?? undefined;\n try {\n const resolved = await resolveLogSinkPort({ ...identity, healthCheck: true });\n endpoint = resolved?.endpoint ?? null;\n } catch {\n endpoint = null;\n }\n return endpoint ?? undefined;\n };\n\n return {\n lastTransport: () => transport,\n instance: () => instance,\n async query(params) {\n const httpEndpoint = await resolveEndpoint();\n if (httpEndpoint && fetchImpl) {\n try {\n const report = await queryHttp(httpEndpoint, params, fetchImpl);\n transport = 'http';\n return report;\n } catch {\n // A stale or unhealthy daemon must not hide data the CLI can still read.\n endpoint = null;\n }\n }\n\n const report = await cliRunner(params);\n transport = 'cli';\n return report;\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"metricsSource.d.cts","names":[],"sources":["../../../src/adapters/node/metricsSource.ts"],"mappings":";;;UA6CiB;EACf,MAAM,OAAO;EACb;;EAEA;EACA;;EAEA,mBAAmB;EACnB,aAAa,QAAQ,uBAAuB,QAAQ;;iBA+FtC,oBAAoB,UAAS,uBAA4B"}
1
+ {"version":3,"file":"metricsSource.d.cts","names":[],"sources":["../../../src/adapters/node/metricsSource.ts"],"mappings":";;;UAiDiB;EACf,MAAM,OAAO;EACb;;EAEA;EACA;;EAEA,mBAAmB;EACnB,aAAa,QAAQ,uBAAuB,QAAQ;;iBA6HtC,oBAAoB,UAAS,uBAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"metricsSource.d.mts","names":[],"sources":["../../../src/adapters/node/metricsSource.ts"],"mappings":";;;UA6CiB;EACf,MAAM,OAAO;EACb;;EAEA;EACA;;EAEA,mBAAmB;EACnB,aAAa,QAAQ,uBAAuB,QAAQ;;iBA+FtC,oBAAoB,UAAS,uBAA4B"}
1
+ {"version":3,"file":"metricsSource.d.mts","names":[],"sources":["../../../src/adapters/node/metricsSource.ts"],"mappings":";;;UAiDiB;EACf,MAAM,OAAO;EACb;;EAEA;EACA;;EAEA,mBAAmB;EACnB,aAAa,QAAQ,uBAAuB,QAAQ;;iBA6HtC,oBAAoB,UAAS,uBAA4B"}
@@ -1,2 +1,2 @@
1
- import{createRequire as e}from"node:module";import{execFile as t}from"node:child_process";import n from"node:path";import{resolveLogSinkInstance as r,resolveLogSinkPort as i}from"@agimon-ai/log-sink-mcp";const a=`total-tokens`;function o(){let t=e(import.meta.url).resolve(`@agimon-ai/log-sink-mcp/package.json`);return n.join(n.dirname(t),`dist`,`cli.mjs`)}function s(e){return new URLSearchParams({groupBy:e.groupBy,period:e.period,sort:a,limit:String(e.limit),toolLimit:`1`}).toString()}async function c(e,t,n){let r=await n(`${e}/api/metrics?${s(t)}`,{signal:AbortSignal.timeout(5e3)});if(!r.ok)throw Error(`sink responded ${r.status}`);let i=await r.json();if(i.error||!Array.isArray(i.timeline))throw Error(i.error??`sink is an older version`);return i}function l(e,n){return new Promise((r,i)=>{t(process.execPath,[o(),`logs`,`metrics`,`--group-by`,e.groupBy,`--period`,e.period,`--sort`,a,`--limit`,String(e.limit),`--tool-limit`,`1`,...n===void 0?[]:[`--db-path`,n]],{timeout:3e4,maxBuffer:33554432},(e,t,n)=>{if(e){i(Error(n.trim()||e.message));return}try{r(JSON.parse(t))}catch{i(Error(`metrics CLI returned unparsable output`))}})})}function u(e){try{let t=r(e);return{scope:t.scope,dbPath:t.dbPath,...t.registeredName===void 0?{}:{registeredName:t.registeredName}}}catch{return}}function d(e={}){let t=e.fetchImpl??globalThis.fetch,n={env:e.env,cwd:e.cwd,packageName:e.packageName,serviceName:e.serviceName},r,a,o=u(n),s=e.cliRunner??(e=>l(e,o?.dbPath)),d=async()=>{if(a!==void 0)return a??void 0;try{a=(await i({...n,healthCheck:!0}))?.endpoint??null}catch{a=null}return a??void 0};return{lastTransport:()=>r,instance:()=>o,async query(e){let n=await d();if(n&&t)try{let i=await c(n,e,t);return r=`http`,i}catch{a=null}let i=await s(e);return r=`cli`,i}}}export{d as createMetricsSource};
1
+ import{createRequire as e}from"node:module";import{execFile as t}from"node:child_process";import n from"node:path";import{resolveLogSinkInstance as r,resolveLogSinkPort as i}from"@agimon-ai/log-sink-mcp";const a=`total-tokens`;function o(){let t=e(import.meta.url).resolve(`@agimon-ai/log-sink-mcp/package.json`);return n.join(n.dirname(t),`dist`,`cli.mjs`)}const s={sessionId:`sessionId`,agentName:`agentName`,model:`model`,provider:`provider`},c={sessionId:`--session-id`,agentName:`--agent-name`,model:`--model`,provider:`--provider`};function l(e){let t=e.filter;return t===void 0?[]:Object.keys(s).flatMap(e=>{let n=t[e];return n===void 0||n===``?[]:[[e,n]]})}function u(e){let t=new URLSearchParams({groupBy:e.groupBy,period:e.period,sort:a,limit:String(e.limit),toolLimit:String(e.toolLimit??1)});for(let[n,r]of l(e))t.set(s[n],r);return t.toString()}async function d(e,t,n){let r=await n(`${e}/api/metrics?${u(t)}`,{signal:AbortSignal.timeout(5e3)});if(!r.ok)throw Error(`sink responded ${r.status}`);let i=await r.json();if(i.error||!Array.isArray(i.timeline))throw Error(i.error??`sink is an older version`);return i}function f(e,n){return new Promise((r,i)=>{t(process.execPath,[o(),`logs`,`metrics`,`--group-by`,e.groupBy,`--period`,e.period,`--sort`,a,`--limit`,String(e.limit),`--tool-limit`,String(e.toolLimit??1),...l(e).flatMap(([e,t])=>[c[e],t]),...n===void 0?[]:[`--db-path`,n]],{timeout:3e4,maxBuffer:33554432},(e,t,n)=>{if(e){i(Error(n.trim()||e.message));return}try{r(JSON.parse(t))}catch{i(Error(`metrics CLI returned unparsable output`))}})})}function p(e){try{let t=r(e);return{scope:t.scope,dbPath:t.dbPath,...t.registeredName===void 0?{}:{registeredName:t.registeredName}}}catch{return}}function m(e={}){let t=e.fetchImpl??globalThis.fetch,n={env:e.env,cwd:e.cwd,packageName:e.packageName,serviceName:e.serviceName},r,a,o=p(n),s=e.cliRunner??(e=>f(e,o?.dbPath)),c=async()=>{if(a!==void 0)return a??void 0;try{a=(await i({...n,healthCheck:!0}))?.endpoint??null}catch{a=null}return a??void 0};return{lastTransport:()=>r,instance:()=>o,async query(e){let n=await c();if(n&&t)try{let i=await d(n,e,t);return r=`http`,i}catch{a=null}let i=await s(e);return r=`cli`,i}}}export{m as createMetricsSource};
2
2
  //# sourceMappingURL=metricsSource.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"metricsSource.mjs","names":[],"sources":["../../../src/adapters/node/metricsSource.ts"],"sourcesContent":["/**\n * Historical metrics access for the `SPC h l` overlay.\n *\n * Two transports, in order: the running sink's HTTP API, then the installed\n * CLI as a subprocess. The sink daemon is long-lived and often lags the\n * installed package, so a daemon that rejects newer query parameters is treated\n * as unavailable rather than fatal. The reader exported by log-sink-mcp is\n * deliberately not used in-process: better-sqlite3 is synchronous and a\n * one-day window blocks for seconds, which would freeze the TUI.\n *\n * Both transports resolve the sink instance from the same identity the Pi\n * telemetry extension writes under. Without it log-sink falls back to\n * `npm_package_name`, which a running Pi process does not set, so the reader\n * would land on the local instance while the writer exported to the global one\n * and the panel would report an empty history that no session ever filled.\n */\nimport { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type { LogMetricsReport } from '@agimon-ai/log-sink-mcp';\nimport { resolveLogSinkInstance, resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\nimport type {\n MetricsInstance,\n MetricsQueryParams,\n MetricsSource,\n MetricsTransport,\n} from '../../types/metricsSource.ts';\n\nconst PACKAGE_NAME = '@agimon-ai/log-sink-mcp';\nconst HTTP_TIMEOUT_MS = 5000;\nconst CLI_TIMEOUT_MS = 30000;\n/** A day of dev telemetry is already ~140k records; keep the payload bounded. */\nconst MAX_CLI_BUFFER = 32 * 1024 * 1024;\nconst TOOL_LIMIT = '1';\n/** The panel ranks consumers, so ask for the biggest rather than the most recent. */\nconst TOKEN_SORT = 'total-tokens';\n\n/** The identity both transports resolve their sink instance from. */\ninterface ResolveIdentity {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n}\n\nexport interface MetricsSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Identity the sink is registered under; must match the telemetry writer's. */\n packageName?: string;\n serviceName?: string;\n /** Overrides transport discovery in tests. */\n fetchImpl?: typeof fetch;\n cliRunner?: (params: MetricsQueryParams) => Promise<LogMetricsReport>;\n}\n\nfunction cliEntryPoint(): string {\n const require = createRequire(import.meta.url);\n // The published bin is CJS; the ESM build sits beside it and starts faster.\n const packageJson = require.resolve(`${PACKAGE_NAME}/package.json`);\n return path.join(path.dirname(packageJson), 'dist', 'cli.mjs');\n}\n\nfunction searchParams(params: MetricsQueryParams): string {\n return new URLSearchParams({\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: String(params.limit),\n toolLimit: TOOL_LIMIT,\n }).toString();\n}\n\nasync function queryHttp(\n endpoint: string,\n params: MetricsQueryParams,\n fetchImpl: typeof fetch,\n): Promise<LogMetricsReport> {\n const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, {\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n });\n if (!response.ok) throw new Error(`sink responded ${response.status}`);\n\n const body = (await response.json()) as LogMetricsReport & { error?: string };\n // An older daemon rejects unknown group-by values and omits the timeline.\n if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? 'sink is an older version');\n return body;\n}\n\n/**\n * The subprocess resolves an instance of its own from the inherited cwd and\n * environment, so the database is passed explicitly rather than left to agree\n * with this process by coincidence.\n */\nfunction runCli(params: MetricsQueryParams, dbPath: string | undefined): Promise<LogMetricsReport> {\n return new Promise((resolve, reject) => {\n execFile(\n process.execPath,\n [\n cliEntryPoint(),\n 'logs',\n 'metrics',\n '--group-by',\n params.groupBy,\n '--period',\n params.period,\n '--sort',\n TOKEN_SORT,\n '--limit',\n String(params.limit),\n '--tool-limit',\n TOOL_LIMIT,\n ...(dbPath === undefined ? [] : ['--db-path', dbPath]),\n ],\n { timeout: CLI_TIMEOUT_MS, maxBuffer: MAX_CLI_BUFFER },\n (error, stdout, stderr) => {\n if (error) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n try {\n resolve(JSON.parse(stdout) as LogMetricsReport);\n } catch {\n reject(new Error('metrics CLI returned unparsable output'));\n }\n },\n );\n });\n}\n\n/**\n * A malformed `.logsink.yaml` must not take the overlay down with it. Both\n * transports still run when resolution fails; they fall back to log-sink's own\n * resolution and the panel reports an unknown instance.\n */\nfunction resolveInstance(identity: ResolveIdentity): MetricsInstance | undefined {\n try {\n const resolved = resolveLogSinkInstance(identity);\n return {\n scope: resolved.scope,\n dbPath: resolved.dbPath,\n ...(resolved.registeredName === undefined ? {} : { registeredName: resolved.registeredName }),\n };\n } catch {\n return undefined;\n }\n}\n\nexport function createMetricsSource(options: MetricsSourceOptions = {}): MetricsSource {\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n const identity = {\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n };\n let transport: MetricsTransport | undefined;\n let endpoint: string | undefined | null;\n const instance = resolveInstance(identity);\n const cliRunner = options.cliRunner ?? ((params: MetricsQueryParams) => runCli(params, instance?.dbPath));\n\n const resolveEndpoint = async (): Promise<string | undefined> => {\n if (endpoint !== undefined) return endpoint ?? undefined;\n try {\n const resolved = await resolveLogSinkPort({ ...identity, healthCheck: true });\n endpoint = resolved?.endpoint ?? null;\n } catch {\n endpoint = null;\n }\n return endpoint ?? undefined;\n };\n\n return {\n lastTransport: () => transport,\n instance: () => instance,\n async query(params) {\n const httpEndpoint = await resolveEndpoint();\n if (httpEndpoint && fetchImpl) {\n try {\n const report = await queryHttp(httpEndpoint, params, fetchImpl);\n transport = 'http';\n return report;\n } catch {\n // A stale or unhealthy daemon must not hide data the CLI can still read.\n endpoint = null;\n }\n }\n\n const report = await cliRunner(params);\n transport = 'cli';\n return report;\n },\n };\n}\n"],"mappings":"4MA4BA,MAOM,EAAa,eAqBnB,SAAS,GAAwB,CAG/B,IAAM,EAFU,EAAc,YAAY,GAEhB,CAAC,CAAC,QAAQ,sCAA8B,EAClE,OAAO,EAAK,KAAK,EAAK,QAAQ,CAAW,EAAG,OAAQ,SAAS,CAC/D,CAEA,SAAS,EAAa,EAAoC,CACxD,OAAO,IAAI,gBAAgB,CACzB,QAAS,EAAO,QAChB,OAAQ,EAAO,OACf,KAAM,EACN,MAAO,OAAO,EAAO,KAAK,EAC1B,UAAW,GACb,CAAC,CAAC,CAAC,SAAS,CACd,CAEA,eAAe,EACb,EACA,EACA,EAC2B,CAC3B,IAAM,EAAW,MAAM,EAAU,GAAG,EAAS,eAAe,EAAa,CAAM,IAAK,CAClF,OAAQ,YAAY,QAAQ,GAAe,CAC7C,CAAC,EACD,GAAI,CAAC,EAAS,GAAI,MAAU,MAAM,kBAAkB,EAAS,QAAQ,EAErE,IAAM,EAAQ,MAAM,EAAS,KAAK,EAElC,GAAI,EAAK,OAAS,CAAC,MAAM,QAAQ,EAAK,QAAQ,EAAG,MAAU,MAAM,EAAK,OAAS,0BAA0B,EACzG,OAAO,CACT,CAOA,SAAS,EAAO,EAA4B,EAAuD,CACjG,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,EACE,QAAQ,SACR,CACE,EAAc,EACd,OACA,UACA,aACA,EAAO,QACP,WACA,EAAO,OACP,SACA,EACA,UACA,OAAO,EAAO,KAAK,EACnB,eACA,IACA,GAAI,IAAW,IAAA,GAAY,CAAC,EAAI,CAAC,YAAa,CAAM,CACtD,EACA,CAAE,QAAS,IAAgB,UAAW,QAAe,GACpD,EAAO,EAAQ,IAAW,CACzB,GAAI,EAAO,CACT,EAAW,MAAM,EAAO,KAAK,GAAK,EAAM,OAAO,CAAC,EAChD,MACF,CACA,GAAI,CACF,EAAQ,KAAK,MAAM,CAAM,CAAqB,CAChD,MAAQ,CACN,EAAW,MAAM,wCAAwC,CAAC,CAC5D,CACF,CACF,CACF,CAAC,CACH,CAOA,SAAS,EAAgB,EAAwD,CAC/E,GAAI,CACF,IAAM,EAAW,EAAuB,CAAQ,EAChD,MAAO,CACL,MAAO,EAAS,MAChB,OAAQ,EAAS,OACjB,GAAI,EAAS,iBAAmB,IAAA,GAAY,CAAC,EAAI,CAAE,eAAgB,EAAS,cAAe,CAC7F,CACF,MAAQ,CACN,MACF,CACF,CAEA,SAAgB,EAAoB,EAAgC,CAAC,EAAkB,CACrF,IAAM,EAAY,EAAQ,WAAa,WAAW,MAC5C,EAAW,CACf,IAAK,EAAQ,IACb,IAAK,EAAQ,IACb,YAAa,EAAQ,YACrB,YAAa,EAAQ,WACvB,EACI,EACA,EACE,EAAW,EAAgB,CAAQ,EACnC,EAAY,EAAQ,YAAe,GAA+B,EAAO,EAAQ,GAAU,MAAM,GAEjG,EAAkB,SAAyC,CAC/D,GAAI,IAAa,IAAA,GAAW,OAAO,GAAY,IAAA,GAC/C,GAAI,CAEF,GAAW,MADY,EAAmB,CAAE,GAAG,EAAU,YAAa,EAAK,CAAC,EAAA,EACvD,UAAY,IACnC,MAAQ,CACN,EAAW,IACb,CACA,OAAO,GAAY,IAAA,EACrB,EAEA,MAAO,CACL,kBAAqB,EACrB,aAAgB,EAChB,MAAM,MAAM,EAAQ,CAClB,IAAM,EAAe,MAAM,EAAgB,EAC3C,GAAI,GAAgB,EAClB,GAAI,CACF,IAAM,EAAS,MAAM,EAAU,EAAc,EAAQ,CAAS,EAE9D,MADA,GAAY,OACL,CACT,MAAQ,CAEN,EAAW,IACb,CAGF,IAAM,EAAS,MAAM,EAAU,CAAM,EAErC,MADA,GAAY,MACL,CACT,CACF,CACF"}
1
+ {"version":3,"file":"metricsSource.mjs","names":[],"sources":["../../../src/adapters/node/metricsSource.ts"],"sourcesContent":["/**\n * Historical metrics access for the `SPC h l` overlay.\n *\n * Two transports, in order: the running sink's HTTP API, then the installed\n * CLI as a subprocess. The sink daemon is long-lived and often lags the\n * installed package, so a daemon that rejects newer query parameters is treated\n * as unavailable rather than fatal. The reader exported by log-sink-mcp is\n * deliberately not used in-process: better-sqlite3 is synchronous and a\n * one-day window blocks for seconds, which would freeze the TUI.\n *\n * Both transports resolve the sink instance from the same identity the Pi\n * telemetry extension writes under. Without it log-sink falls back to\n * `npm_package_name`, which a running Pi process does not set, so the reader\n * would land on the local instance while the writer exported to the global one\n * and the panel would report an empty history that no session ever filled.\n */\nimport { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type { LogMetricsReport } from '@agimon-ai/log-sink-mcp';\nimport { resolveLogSinkInstance, resolveLogSinkPort } from '@agimon-ai/log-sink-mcp';\nimport type {\n MetricsInstance,\n MetricsQueryParams,\n MetricsSource,\n MetricsTransport,\n} from '../../types/metricsSource.ts';\n\nconst PACKAGE_NAME = '@agimon-ai/log-sink-mcp';\nconst HTTP_TIMEOUT_MS = 5000;\nconst CLI_TIMEOUT_MS = 30000;\n/** A day of dev telemetry is already ~140k records; keep the payload bounded. */\nconst MAX_CLI_BUFFER = 32 * 1024 * 1024;\n/**\n * The overlay ranks one tool, but a page pairing tool calls against failure\n * counts needs a denominator for every tool that failed, so callers ask.\n */\nconst DEFAULT_TOOL_LIMIT = 1;\n/** The panel ranks consumers, so ask for the biggest rather than the most recent. */\nconst TOKEN_SORT = 'total-tokens';\n\n/** The identity both transports resolve their sink instance from. */\ninterface ResolveIdentity {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n}\n\nexport interface MetricsSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Identity the sink is registered under; must match the telemetry writer's. */\n packageName?: string;\n serviceName?: string;\n /** Overrides transport discovery in tests. */\n fetchImpl?: typeof fetch;\n cliRunner?: (params: MetricsQueryParams) => Promise<LogMetricsReport>;\n}\n\nfunction cliEntryPoint(): string {\n const require = createRequire(import.meta.url);\n // The published bin is CJS; the ESM build sits beside it and starts faster.\n const packageJson = require.resolve(`${PACKAGE_NAME}/package.json`);\n return path.join(path.dirname(packageJson), 'dist', 'cli.mjs');\n}\n\n/**\n * The sink names these the same way on the wire and on the command line, apart\n * from the case convention, so the two transports stay in step.\n */\nconst FILTER_QUERY_KEYS = {\n sessionId: 'sessionId',\n agentName: 'agentName',\n model: 'model',\n provider: 'provider',\n} as const;\n\nconst FILTER_CLI_FLAGS = {\n sessionId: '--session-id',\n agentName: '--agent-name',\n model: '--model',\n provider: '--provider',\n} as const;\n\nfunction filterEntries(params: MetricsQueryParams): [keyof typeof FILTER_QUERY_KEYS, string][] {\n const filter = params.filter;\n if (filter === undefined) return [];\n return (Object.keys(FILTER_QUERY_KEYS) as (keyof typeof FILTER_QUERY_KEYS)[]).flatMap((key) => {\n const value = filter[key];\n return value === undefined || value === '' ? [] : [[key, value] as [keyof typeof FILTER_QUERY_KEYS, string]];\n });\n}\n\nfunction searchParams(params: MetricsQueryParams): string {\n const search = new URLSearchParams({\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: String(params.limit),\n toolLimit: String(params.toolLimit ?? DEFAULT_TOOL_LIMIT),\n });\n for (const [key, value] of filterEntries(params)) search.set(FILTER_QUERY_KEYS[key], value);\n return search.toString();\n}\n\nasync function queryHttp(\n endpoint: string,\n params: MetricsQueryParams,\n fetchImpl: typeof fetch,\n): Promise<LogMetricsReport> {\n const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, {\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n });\n if (!response.ok) throw new Error(`sink responded ${response.status}`);\n\n const body = (await response.json()) as LogMetricsReport & { error?: string };\n // An older daemon rejects unknown group-by values and omits the timeline.\n if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? 'sink is an older version');\n return body;\n}\n\n/**\n * The subprocess resolves an instance of its own from the inherited cwd and\n * environment, so the database is passed explicitly rather than left to agree\n * with this process by coincidence.\n */\nfunction runCli(params: MetricsQueryParams, dbPath: string | undefined): Promise<LogMetricsReport> {\n return new Promise((resolve, reject) => {\n execFile(\n process.execPath,\n [\n cliEntryPoint(),\n 'logs',\n 'metrics',\n '--group-by',\n params.groupBy,\n '--period',\n params.period,\n '--sort',\n TOKEN_SORT,\n '--limit',\n String(params.limit),\n '--tool-limit',\n String(params.toolLimit ?? DEFAULT_TOOL_LIMIT),\n ...filterEntries(params).flatMap(([key, value]) => [FILTER_CLI_FLAGS[key], value]),\n ...(dbPath === undefined ? [] : ['--db-path', dbPath]),\n ],\n { timeout: CLI_TIMEOUT_MS, maxBuffer: MAX_CLI_BUFFER },\n (error, stdout, stderr) => {\n if (error) {\n reject(new Error(stderr.trim() || error.message));\n return;\n }\n try {\n resolve(JSON.parse(stdout) as LogMetricsReport);\n } catch {\n reject(new Error('metrics CLI returned unparsable output'));\n }\n },\n );\n });\n}\n\n/**\n * A malformed `.logsink.yaml` must not take the overlay down with it. Both\n * transports still run when resolution fails; they fall back to log-sink's own\n * resolution and the panel reports an unknown instance.\n */\nfunction resolveInstance(identity: ResolveIdentity): MetricsInstance | undefined {\n try {\n const resolved = resolveLogSinkInstance(identity);\n return {\n scope: resolved.scope,\n dbPath: resolved.dbPath,\n ...(resolved.registeredName === undefined ? {} : { registeredName: resolved.registeredName }),\n };\n } catch {\n return undefined;\n }\n}\n\nexport function createMetricsSource(options: MetricsSourceOptions = {}): MetricsSource {\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n const identity = {\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n };\n let transport: MetricsTransport | undefined;\n let endpoint: string | undefined | null;\n const instance = resolveInstance(identity);\n const cliRunner = options.cliRunner ?? ((params: MetricsQueryParams) => runCli(params, instance?.dbPath));\n\n const resolveEndpoint = async (): Promise<string | undefined> => {\n if (endpoint !== undefined) return endpoint ?? undefined;\n try {\n const resolved = await resolveLogSinkPort({ ...identity, healthCheck: true });\n endpoint = resolved?.endpoint ?? null;\n } catch {\n endpoint = null;\n }\n return endpoint ?? undefined;\n };\n\n return {\n lastTransport: () => transport,\n instance: () => instance,\n async query(params) {\n const httpEndpoint = await resolveEndpoint();\n if (httpEndpoint && fetchImpl) {\n try {\n const report = await queryHttp(httpEndpoint, params, fetchImpl);\n transport = 'http';\n return report;\n } catch {\n // A stale or unhealthy daemon must not hide data the CLI can still read.\n endpoint = null;\n }\n }\n\n const report = await cliRunner(params);\n transport = 'cli';\n return report;\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../../services/metrics.cjs"),t=require("../../tui/logMetricsOverlay.cjs"),n=require("../node/metricsSource.cjs");let r=require("@agimon-ai/doompi-extension-contracts/cordis-host"),i=require("@agimon-ai/doompi-extension-contracts/readiness"),a=require("@agimon-ai/doompi-extension-contracts/ui-hub"),o=require("@agimon-ai/doompi-telemetry");const s=`@agimon-ai/doompi-log`,c=`log-metrics`,l=new Set([`error`,`aborted`]);function u(e){return e===`1`||e?.toLowerCase()===`true`||e?.toLowerCase()===`yes`}function d(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function f(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function p(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function m(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function h(e,t,n={},r=!1){let a=n.env??process.env,c=n.metrics,h=new Map,g=!0,_=0,v,y,b,x,S=Promise.resolve(),C=e=>{S=S.then(e).catch(()=>void 0)};e.effect(()=>async()=>{g=!1,_+=1,x=void 0,h.clear(),v=void 0;let e=y;y=void 0,b=void 0,await e?.shutdown()},`${s}/telemetry`);let w=async(e,t,n)=>{!g||!y||(e===`debug`?await y.recordDebug(t,n):e===`warn`?await y.recordWarning(t,void 0,n):e===`error`?await y.recordError(t,void 0,n):await y.recordEvent(t,n))},T=e=>{if(!g)throw Error(`Log telemetry runtime is disposed.`);let t=e.sessionManager.getSessionId();if(y&&b!==t){let e=y;y=void 0,b=void 0,C(()=>e.shutdown())}return y||=(b=t,(0,o.createDoomTelemetry)({serviceName:n.serviceName??`pi`,packageName:s,cwd:e.cwd,env:{...a,PI_SESSION_ID:t},allowFileFallback:n.allowFileFallback,telemetryFactory:n.telemetryFactory,warn:n.onDiagnostic,enableLogs:!0,enableTraces:a.AGENT_OTEL_TRACES===void 0||u(a.AGENT_OTEL_TRACES),onRecord:e=>{g&&c?.record(e.event,e.attributes)},onStatus:e=>{g&&n.onSinkStatus?.(m(e))}})),g&&n.onSinkStatus?.(m(y.status())),y},E=async e=>{let t=x;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!g||t!==x)throw Error(`Log readiness belongs to a stale extension generation.`)}},D=(e,t)=>{let n=x,i=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!g||n!==x||n.sessionManager!==e.sessionManager)return}else if(!g)return;await t()};if(!r)return i();C(i)};return t.on(`session_start`,(t,n)=>{if(!g)return;let a=++_;h.clear(),v=void 0;let o=async e=>{e?.throwIfAborted(),T(n),await w(`info`,`pi.session.started`,{...d(n),"pi.session.reason":t.reason,"pi.session.previous":t.previousSessionFile!==void 0}),e?.throwIfAborted()},c=r?(0,i.readDoomReadinessCoordinator)(e):void 0;if(!c)return o();let l=x,u=(async()=>{l?.coordinator===c&&await l.operation.catch(()=>void 0),!(!g||a!==_)&&await c.start(s,`${n.sessionManager.getSessionId()}:${a}`,async e=>(await o(e),{value:void 0})).wait()})();u.catch(()=>void 0),x={sessionManager:n.sessionManager,coordinator:c,operation:u}}),t.on(`before_agent_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.user_prompt`,{...d(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})}),t.on(`agent_start`,(e,t)=>{if(g)return D(t,async()=>{v=Date.now(),T(t),await w(`info`,`pi.agent.started`,d(t))})}),t.on(`turn_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.turn.started`,{...d(t),"pi.turn.index":e.turnIndex})})}),t.on(`turn_end`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n=f(e.message);if(await w(`info`,`pi.turn.finished`,{...d(t),"pi.turn.index":e.turnIndex,"pi.tool_result.count":e.toolResults.length,...n}),e.message.role===`assistant`&&l.has(e.message.stopReason)&&await w(e.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...d(t),"pi.turn.index":e.turnIndex,...p(e.message),...n}),n)for(let r of e.toolResults)await w(`debug`,`pi.tool_token_sample`,{...d(t),...n,"tool.name":r.toolName,token_attribution:`toolCallTurn`})})}),t.on(`tool_execution_start`,(e,t)=>{if(g)return D(t,async()=>{h.set(e.toolCallId,Date.now()),T(t),await w(`debug`,`pi.tool_call`,{...d(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})}),t.on(`tool_execution_end`,(e,t)=>{if(g)return D(t,async()=>{let n=h.get(e.toolCallId);h.delete(e.toolCallId),T(t),await w(`info`,`pi.tool_result`,{...d(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})}),t.on(`after_provider_response`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n={...d(t),"http.response.status_code":e.status};e.status>=400?await w(`error`,`pi.api_error`,n):await w(`debug`,`pi.api_response`,n)})}),t.on(`model_select`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.model.selected`,{...d(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})}),t.on(`agent_end`,(e,t)=>{if(!g)return;let n=_;return D(t,async()=>{T(t),await w(`info`,`pi.agent.finished`,{...d(t),"pi.message.count":e.messages.length,...v===void 0?{}:{"pi.agent.duration_ms":Date.now()-v}}),!(!g||n!==_)&&(v=void 0,await y?.flush())})}),t.on(`agent_settled`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.agent.settled`,d(t))})}),{waitForSession:E,async finishSession(e,t){if(!g)return;await S;let n=x;n&&await n.operation.catch(()=>void 0),g&&(n&&n!==x||y&&(T(t),await w(`info`,`pi.session.finished`,{...d(t),"pi.session.reason":e})))}}}function g(e,t={}){let n=e.registerLeader({source:t.source??`@agimon-ai/doompi-log`,bindings:[{id:`log.metrics`,path:[{key:`h`,label:`help`,detail:`package docs and logs`,order:70},{key:`l`,label:`logs`,detail:`telemetry`}],command:{name:c}}]});return()=>n.dispose()}async function _(e,n,r){await e.ui.custom((e,i,a,o)=>new t.LogMetricsOverlayComponent(e,i,n,o,r),{overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}})}function v(t,r,i={},o=!1){let l=i.env??process.env,d=new e.LogMetricsAggregator,f=i.metricsSource,p=e=>f??=n.createMetricsSource({env:l,cwd:e.cwd,packageName:s,serviceName:i.serviceName??`pi`}),m=!0,v,y;t.effect(()=>()=>{m=!1,v=void 0,y=void 0},`${s}/runtime`);let b=h(t,r,{...i,metrics:d,onDiagnostic:e=>{m&&(y=e,i.onDiagnostic?i.onDiagnostic(e):l.AGENT_OTEL_DIAGNOSTICS===`stderr`&&process.emitWarning(e))},onSinkStatus:e=>{m&&(v=e,i.onSinkStatus?.(e))}},o);return t.inject([a.DOOM_UI_HUB_SERVICE],e=>g((0,a.requireDoomUiHub)(e),{source:i.leaderSource})),r.registerCommand(c,{description:`Show session-local log metrics`,handler:async(e,t)=>{if(!m||(await b.waitForSession(t),!m))return;let n=p(t);await _(t,()=>({disabled:u(l.AGENT_TELEMETRY_DISABLED)||u(l.OTEL_SDK_DISABLED),snapshot:d.snapshot(),sink:v,transport:n.lastTransport(),instance:n.instance?.(),lastDiagnostic:y}),n.query)}}),b}function y(e,{pi:t}){let n=v(e,t,{},!0),r;t.on(`session_shutdown`,(e,t)=>(r??=n.finishSession(e.reason,t),r))}async function b(e){let t=await(0,r.connectDoomCordisHost)(e,s),n=t.root.plugin(y,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}exports.doomLogExtension=b,exports.installDoomLogRuntime=v,exports.installPiTelemetryRuntime=h,exports.openLogMetricsOverlay=_,exports.registerLogMetricsLeaderBinding=g;
1
+ const e=require("../node/metricsSource.cjs"),t=require("../../services/metrics.cjs"),n=require("../../tui/logMetricsOverlay.cjs");let r=require("@agimon-ai/doompi-extension-contracts/cordis-host"),i=require("@agimon-ai/doompi-extension-contracts/readiness"),a=require("@agimon-ai/doompi-extension-contracts/ui-hub"),o=require("@agimon-ai/doompi-telemetry");const s=`@agimon-ai/doompi-log`,c=`log-metrics`,l=new Set([`error`,`aborted`]);function u(e){return e===`1`||e?.toLowerCase()===`true`||e?.toLowerCase()===`yes`}function d(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function f(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function p(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function m(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function h(e,t,n={},r=!1){let a=n.env??process.env,c=n.metrics,h=new Map,g=!0,_=0,v,y,b,x,S=Promise.resolve(),C=e=>{S=S.then(e).catch(()=>void 0)};e.effect(()=>async()=>{g=!1,_+=1,x=void 0,h.clear(),v=void 0;let e=y;y=void 0,b=void 0,await e?.shutdown()},`${s}/telemetry`);let w=async(e,t,n)=>{!g||!y||(e===`debug`?await y.recordDebug(t,n):e===`warn`?await y.recordWarning(t,void 0,n):e===`error`?await y.recordError(t,void 0,n):await y.recordEvent(t,n))},T=e=>{if(!g)throw Error(`Log telemetry runtime is disposed.`);let t=e.sessionManager.getSessionId();if(y&&b!==t){let e=y;y=void 0,b=void 0,C(()=>e.shutdown())}return y||=(b=t,(0,o.createDoomTelemetry)({serviceName:n.serviceName??`pi`,packageName:s,cwd:e.cwd,env:{...a,PI_SESSION_ID:t},allowFileFallback:n.allowFileFallback,telemetryFactory:n.telemetryFactory,warn:n.onDiagnostic,enableLogs:!0,enableTraces:a.AGENT_OTEL_TRACES===void 0||u(a.AGENT_OTEL_TRACES),onRecord:e=>{g&&c?.record(e.event,e.attributes)},onStatus:e=>{g&&n.onSinkStatus?.(m(e))}})),g&&n.onSinkStatus?.(m(y.status())),y},E=async e=>{let t=x;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!g||t!==x)throw Error(`Log readiness belongs to a stale extension generation.`)}},D=(e,t)=>{let n=x,i=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!g||n!==x||n.sessionManager!==e.sessionManager)return}else if(!g)return;await t()};if(!r)return i();C(i)};return t.on(`session_start`,(t,n)=>{if(!g)return;let a=++_;h.clear(),v=void 0;let o=async e=>{e?.throwIfAborted(),T(n),await w(`info`,`pi.session.started`,{...d(n),"pi.session.reason":t.reason,"pi.session.previous":t.previousSessionFile!==void 0}),e?.throwIfAborted()},c=r?(0,i.readDoomReadinessCoordinator)(e):void 0;if(!c)return o();let l=x,u=(async()=>{l?.coordinator===c&&await l.operation.catch(()=>void 0),!(!g||a!==_)&&await c.start(s,`${n.sessionManager.getSessionId()}:${a}`,async e=>(await o(e),{value:void 0})).wait()})();u.catch(()=>void 0),x={sessionManager:n.sessionManager,coordinator:c,operation:u}}),t.on(`before_agent_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.user_prompt`,{...d(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})}),t.on(`agent_start`,(e,t)=>{if(g)return D(t,async()=>{v=Date.now(),T(t),await w(`info`,`pi.agent.started`,d(t))})}),t.on(`turn_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.turn.started`,{...d(t),"pi.turn.index":e.turnIndex})})}),t.on(`turn_end`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n=f(e.message);if(await w(`info`,`pi.turn.finished`,{...d(t),"pi.turn.index":e.turnIndex,"pi.tool_result.count":e.toolResults.length,...n}),e.message.role===`assistant`&&l.has(e.message.stopReason)&&await w(e.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...d(t),"pi.turn.index":e.turnIndex,...p(e.message),...n}),n)for(let r of e.toolResults)await w(`debug`,`pi.tool_token_sample`,{...d(t),...n,"tool.name":r.toolName,token_attribution:`toolCallTurn`})})}),t.on(`tool_execution_start`,(e,t)=>{if(g)return D(t,async()=>{h.set(e.toolCallId,Date.now()),T(t),await w(`debug`,`pi.tool_call`,{...d(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})}),t.on(`tool_execution_end`,(e,t)=>{if(g)return D(t,async()=>{let n=h.get(e.toolCallId);h.delete(e.toolCallId),T(t),await w(`info`,`pi.tool_result`,{...d(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})}),t.on(`after_provider_response`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n={...d(t),"http.response.status_code":e.status};e.status>=400?await w(`error`,`pi.api_error`,n):await w(`debug`,`pi.api_response`,n)})}),t.on(`model_select`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.model.selected`,{...d(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})}),t.on(`agent_end`,(e,t)=>{if(!g)return;let n=_;return D(t,async()=>{T(t),await w(`info`,`pi.agent.finished`,{...d(t),"pi.message.count":e.messages.length,...v===void 0?{}:{"pi.agent.duration_ms":Date.now()-v}}),!(!g||n!==_)&&(v=void 0,await y?.flush())})}),t.on(`agent_settled`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.agent.settled`,d(t))})}),{waitForSession:E,async finishSession(e,t){if(!g)return;await S;let n=x;n&&await n.operation.catch(()=>void 0),g&&(n&&n!==x||y&&(T(t),await w(`info`,`pi.session.finished`,{...d(t),"pi.session.reason":e})))}}}function g(e,t={}){let n=e.registerLeader({source:t.source??`@agimon-ai/doompi-log`,bindings:[{id:`log.metrics`,path:[{key:`h`,label:`help`,detail:`package docs and logs`,order:70},{key:`l`,label:`logs`,detail:`telemetry`}],command:{name:c}}]});return()=>n.dispose()}async function _(e,t,r){await e.ui.custom((e,i,a,o)=>new n.LogMetricsOverlayComponent(e,i,t,o,r),{overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}})}function v(n,r,i={},o=!1){let l=i.env??process.env,d=new t.LogMetricsAggregator,f=i.metricsSource,p=t=>f??=e.createMetricsSource({env:l,cwd:t.cwd,packageName:s,serviceName:i.serviceName??`pi`}),m=!0,v,y;n.effect(()=>()=>{m=!1,v=void 0,y=void 0},`${s}/runtime`);let b=h(n,r,{...i,metrics:d,onDiagnostic:e=>{m&&(y=e,i.onDiagnostic?i.onDiagnostic(e):l.AGENT_OTEL_DIAGNOSTICS===`stderr`&&process.emitWarning(e))},onSinkStatus:e=>{m&&(v=e,i.onSinkStatus?.(e))}},o);return n.inject([a.DOOM_UI_HUB_SERVICE],e=>g((0,a.requireDoomUiHub)(e),{source:i.leaderSource})),r.registerCommand(c,{description:`Show session-local log metrics`,handler:async(e,t)=>{if(!m||(await b.waitForSession(t),!m))return;let n=p(t);await _(t,()=>({disabled:u(l.AGENT_TELEMETRY_DISABLED)||u(l.OTEL_SDK_DISABLED),snapshot:d.snapshot(),sink:v,transport:n.lastTransport(),instance:n.instance?.(),lastDiagnostic:y}),n.query)}}),b}function y(e,{pi:t}){let n=v(e,t,{},!0),r;t.on(`session_shutdown`,(e,t)=>(r??=n.finishSession(e.reason,t),r))}async function b(e){let t=await(0,r.connectDoomCordisHost)(e,s),n=t.root.plugin(y,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}exports.doomLogExtension=b,exports.installDoomLogRuntime=v,exports.installPiTelemetryRuntime=h,exports.openLogMetricsOverlay=_,exports.registerLogMetricsLeaderBinding=g;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1,2 +1,2 @@
1
- import{LogMetricsAggregator as e}from"../../services/metrics.mjs";import{LogMetricsOverlayComponent as t}from"../../tui/logMetricsOverlay.mjs";import{createMetricsSource as n}from"../node/metricsSource.mjs";import{connectDoomCordisHost as r}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{readDoomReadinessCoordinator as i}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as a,requireDoomUiHub as o}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{createDoomTelemetry as s}from"@agimon-ai/doompi-telemetry";const c=`@agimon-ai/doompi-log`,l=`log-metrics`,u=new Set([`error`,`aborted`]);function d(e){return e===`1`||e?.toLowerCase()===`true`||e?.toLowerCase()===`yes`}function f(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function p(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function m(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function h(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function g(e,t,n={},r=!1){let a=n.env??process.env,o=n.metrics,l=new Map,g=!0,_=0,v,y,b,x,S=Promise.resolve(),C=e=>{S=S.then(e).catch(()=>void 0)};e.effect(()=>async()=>{g=!1,_+=1,x=void 0,l.clear(),v=void 0;let e=y;y=void 0,b=void 0,await e?.shutdown()},`${c}/telemetry`);let w=async(e,t,n)=>{!g||!y||(e===`debug`?await y.recordDebug(t,n):e===`warn`?await y.recordWarning(t,void 0,n):e===`error`?await y.recordError(t,void 0,n):await y.recordEvent(t,n))},T=e=>{if(!g)throw Error(`Log telemetry runtime is disposed.`);let t=e.sessionManager.getSessionId();if(y&&b!==t){let e=y;y=void 0,b=void 0,C(()=>e.shutdown())}return y||=(b=t,s({serviceName:n.serviceName??`pi`,packageName:c,cwd:e.cwd,env:{...a,PI_SESSION_ID:t},allowFileFallback:n.allowFileFallback,telemetryFactory:n.telemetryFactory,warn:n.onDiagnostic,enableLogs:!0,enableTraces:a.AGENT_OTEL_TRACES===void 0||d(a.AGENT_OTEL_TRACES),onRecord:e=>{g&&o?.record(e.event,e.attributes)},onStatus:e=>{g&&n.onSinkStatus?.(h(e))}})),g&&n.onSinkStatus?.(h(y.status())),y},E=async e=>{let t=x;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!g||t!==x)throw Error(`Log readiness belongs to a stale extension generation.`)}},D=(e,t)=>{let n=x,i=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!g||n!==x||n.sessionManager!==e.sessionManager)return}else if(!g)return;await t()};if(!r)return i();C(i)};return t.on(`session_start`,(t,n)=>{if(!g)return;let a=++_;l.clear(),v=void 0;let o=async e=>{e?.throwIfAborted(),T(n),await w(`info`,`pi.session.started`,{...f(n),"pi.session.reason":t.reason,"pi.session.previous":t.previousSessionFile!==void 0}),e?.throwIfAborted()},s=r?i(e):void 0;if(!s)return o();let u=x,d=(async()=>{u?.coordinator===s&&await u.operation.catch(()=>void 0),!(!g||a!==_)&&await s.start(c,`${n.sessionManager.getSessionId()}:${a}`,async e=>(await o(e),{value:void 0})).wait()})();d.catch(()=>void 0),x={sessionManager:n.sessionManager,coordinator:s,operation:d}}),t.on(`before_agent_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.user_prompt`,{...f(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})}),t.on(`agent_start`,(e,t)=>{if(g)return D(t,async()=>{v=Date.now(),T(t),await w(`info`,`pi.agent.started`,f(t))})}),t.on(`turn_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.turn.started`,{...f(t),"pi.turn.index":e.turnIndex})})}),t.on(`turn_end`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n=p(e.message);if(await w(`info`,`pi.turn.finished`,{...f(t),"pi.turn.index":e.turnIndex,"pi.tool_result.count":e.toolResults.length,...n}),e.message.role===`assistant`&&u.has(e.message.stopReason)&&await w(e.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...f(t),"pi.turn.index":e.turnIndex,...m(e.message),...n}),n)for(let r of e.toolResults)await w(`debug`,`pi.tool_token_sample`,{...f(t),...n,"tool.name":r.toolName,token_attribution:`toolCallTurn`})})}),t.on(`tool_execution_start`,(e,t)=>{if(g)return D(t,async()=>{l.set(e.toolCallId,Date.now()),T(t),await w(`debug`,`pi.tool_call`,{...f(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})}),t.on(`tool_execution_end`,(e,t)=>{if(g)return D(t,async()=>{let n=l.get(e.toolCallId);l.delete(e.toolCallId),T(t),await w(`info`,`pi.tool_result`,{...f(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})}),t.on(`after_provider_response`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n={...f(t),"http.response.status_code":e.status};e.status>=400?await w(`error`,`pi.api_error`,n):await w(`debug`,`pi.api_response`,n)})}),t.on(`model_select`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.model.selected`,{...f(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})}),t.on(`agent_end`,(e,t)=>{if(!g)return;let n=_;return D(t,async()=>{T(t),await w(`info`,`pi.agent.finished`,{...f(t),"pi.message.count":e.messages.length,...v===void 0?{}:{"pi.agent.duration_ms":Date.now()-v}}),!(!g||n!==_)&&(v=void 0,await y?.flush())})}),t.on(`agent_settled`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.agent.settled`,f(t))})}),{waitForSession:E,async finishSession(e,t){if(!g)return;await S;let n=x;n&&await n.operation.catch(()=>void 0),g&&(n&&n!==x||y&&(T(t),await w(`info`,`pi.session.finished`,{...f(t),"pi.session.reason":e})))}}}function _(e,t={}){let n=e.registerLeader({source:t.source??`@agimon-ai/doompi-log`,bindings:[{id:`log.metrics`,path:[{key:`h`,label:`help`,detail:`package docs and logs`,order:70},{key:`l`,label:`logs`,detail:`telemetry`}],command:{name:l}}]});return()=>n.dispose()}async function v(e,n,r){await e.ui.custom((e,i,a,o)=>new t(e,i,n,o,r),{overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}})}function y(t,r,i={},s=!1){let u=i.env??process.env,f=new e,p=i.metricsSource,m=e=>p??=n({env:u,cwd:e.cwd,packageName:c,serviceName:i.serviceName??`pi`}),h=!0,y,b;t.effect(()=>()=>{h=!1,y=void 0,b=void 0},`${c}/runtime`);let x=g(t,r,{...i,metrics:f,onDiagnostic:e=>{h&&(b=e,i.onDiagnostic?i.onDiagnostic(e):u.AGENT_OTEL_DIAGNOSTICS===`stderr`&&process.emitWarning(e))},onSinkStatus:e=>{h&&(y=e,i.onSinkStatus?.(e))}},s);return t.inject([a],e=>_(o(e),{source:i.leaderSource})),r.registerCommand(l,{description:`Show session-local log metrics`,handler:async(e,t)=>{if(!h||(await x.waitForSession(t),!h))return;let n=m(t);await v(t,()=>({disabled:d(u.AGENT_TELEMETRY_DISABLED)||d(u.OTEL_SDK_DISABLED),snapshot:f.snapshot(),sink:y,transport:n.lastTransport(),instance:n.instance?.(),lastDiagnostic:b}),n.query)}}),x}function b(e,{pi:t}){let n=y(e,t,{},!0),r;t.on(`session_shutdown`,(e,t)=>(r??=n.finishSession(e.reason,t),r))}async function x(e){let t=await r(e,c),n=t.root.plugin(b,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}export{x as doomLogExtension,y as installDoomLogRuntime,g as installPiTelemetryRuntime,v as openLogMetricsOverlay,_ as registerLogMetricsLeaderBinding};
1
+ import{createMetricsSource as e}from"../node/metricsSource.mjs";import{LogMetricsAggregator as t}from"../../services/metrics.mjs";import{LogMetricsOverlayComponent as n}from"../../tui/logMetricsOverlay.mjs";import{connectDoomCordisHost as r}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{readDoomReadinessCoordinator as i}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as a,requireDoomUiHub as o}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{createDoomTelemetry as s}from"@agimon-ai/doompi-telemetry";const c=`@agimon-ai/doompi-log`,l=`log-metrics`,u=new Set([`error`,`aborted`]);function d(e){return e===`1`||e?.toLowerCase()===`true`||e?.toLowerCase()===`yes`}function f(e){let t={"pi.mode":e.mode,"pi.session.id":e.sessionManager.getSessionId()};return e.thinkingLevel&&(t[`pi.thinking_level`]=e.thinkingLevel),e.model&&(t[`gen_ai.provider.name`]=e.model.provider,t[`gen_ai.request.model`]=e.model.id),t}function p(e){if(e.role!==`assistant`)return;let t={"gen_ai.response.model":e.responseModel??e.model,"gen_ai.response.finish_reasons":e.stopReason,"gen_ai.usage.input_tokens":e.usage.input,"gen_ai.usage.output_tokens":e.usage.output,"gen_ai.usage.cache_read_tokens":e.usage.cacheRead,"gen_ai.usage.cache_write_tokens":e.usage.cacheWrite,"gen_ai.usage.total_tokens":e.usage.totalTokens,"gen_ai.usage.cost":e.usage.cost.total};return e.usage.reasoning!==void 0&&(t[`gen_ai.usage.reasoning_tokens`]=e.usage.reasoning),t}function m(e){if(e.role!==`assistant`)return{"error.type":`AgentError`,outcome:`error`};let t=e.stopReason===`aborted`;return{"error.type":`ProviderError`,"error.code":e.stopReason,outcome:t?`aborted`:`error`}}function h(e){return{service:e.serviceName,backend:e.backend,endpoint:e.endpoint??`none`,endpointSource:e.endpointSource,traces:e.traces,redaction:!0,fileFallback:e.fileFallback}}function g(e,t,n={},r=!1){let a=n.env??process.env,o=n.metrics,l=new Map,g=!0,_=0,v,y,b,x,S=Promise.resolve(),C=e=>{S=S.then(e).catch(()=>void 0)};e.effect(()=>async()=>{g=!1,_+=1,x=void 0,l.clear(),v=void 0;let e=y;y=void 0,b=void 0,await e?.shutdown()},`${c}/telemetry`);let w=async(e,t,n)=>{!g||!y||(e===`debug`?await y.recordDebug(t,n):e===`warn`?await y.recordWarning(t,void 0,n):e===`error`?await y.recordError(t,void 0,n):await y.recordEvent(t,n))},T=e=>{if(!g)throw Error(`Log telemetry runtime is disposed.`);let t=e.sessionManager.getSessionId();if(y&&b!==t){let e=y;y=void 0,b=void 0,C(()=>e.shutdown())}return y||=(b=t,s({serviceName:n.serviceName??`pi`,packageName:c,cwd:e.cwd,env:{...a,PI_SESSION_ID:t},allowFileFallback:n.allowFileFallback,telemetryFactory:n.telemetryFactory,warn:n.onDiagnostic,enableLogs:!0,enableTraces:a.AGENT_OTEL_TRACES===void 0||d(a.AGENT_OTEL_TRACES),onRecord:e=>{g&&o?.record(e.event,e.attributes)},onStatus:e=>{g&&n.onSinkStatus?.(h(e))}})),g&&n.onSinkStatus?.(h(y.status())),y},E=async e=>{let t=x;if(t){if(t.sessionManager!==e.sessionManager)throw Error(`Log readiness belongs to a stale Pi session.`);if(await t.operation,!g||t!==x)throw Error(`Log readiness belongs to a stale extension generation.`)}},D=(e,t)=>{let n=x,i=async()=>{if(n){if(!await n.operation.then(()=>!0,()=>!1)||!g||n!==x||n.sessionManager!==e.sessionManager)return}else if(!g)return;await t()};if(!r)return i();C(i)};return t.on(`session_start`,(t,n)=>{if(!g)return;let a=++_;l.clear(),v=void 0;let o=async e=>{e?.throwIfAborted(),T(n),await w(`info`,`pi.session.started`,{...f(n),"pi.session.reason":t.reason,"pi.session.previous":t.previousSessionFile!==void 0}),e?.throwIfAborted()},s=r?i(e):void 0;if(!s)return o();let u=x,d=(async()=>{u?.coordinator===s&&await u.operation.catch(()=>void 0),!(!g||a!==_)&&await s.start(c,`${n.sessionManager.getSessionId()}:${a}`,async e=>(await o(e),{value:void 0})).wait()})();d.catch(()=>void 0),x={sessionManager:n.sessionManager,coordinator:s,operation:d}}),t.on(`before_agent_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.user_prompt`,{...f(t),"pi.user_message.length":e.prompt.length,"pi.user_message.image_count":e.images?.length??0})})}),t.on(`agent_start`,(e,t)=>{if(g)return D(t,async()=>{v=Date.now(),T(t),await w(`info`,`pi.agent.started`,f(t))})}),t.on(`turn_start`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.turn.started`,{...f(t),"pi.turn.index":e.turnIndex})})}),t.on(`turn_end`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n=p(e.message);if(await w(`info`,`pi.turn.finished`,{...f(t),"pi.turn.index":e.turnIndex,"pi.tool_result.count":e.toolResults.length,...n}),e.message.role===`assistant`&&u.has(e.message.stopReason)&&await w(e.message.stopReason===`aborted`?`warn`:`error`,`pi.turn.failed`,{...f(t),"pi.turn.index":e.turnIndex,...m(e.message),...n}),n)for(let r of e.toolResults)await w(`debug`,`pi.tool_token_sample`,{...f(t),...n,"tool.name":r.toolName,token_attribution:`toolCallTurn`})})}),t.on(`tool_execution_start`,(e,t)=>{if(g)return D(t,async()=>{l.set(e.toolCallId,Date.now()),T(t),await w(`debug`,`pi.tool_call`,{...f(t),"tool.name":e.toolName,"tool.call.id":e.toolCallId})})}),t.on(`tool_execution_end`,(e,t)=>{if(g)return D(t,async()=>{let n=l.get(e.toolCallId);l.delete(e.toolCallId),T(t),await w(`info`,`pi.tool_result`,{...f(t),success:!e.isError,"tool.name":e.toolName,"tool.call.id":e.toolCallId,"tool.result.error":e.isError,...n===void 0?{}:{"tool.duration_ms":Date.now()-n}})})}),t.on(`after_provider_response`,(e,t)=>{if(g)return D(t,async()=>{T(t);let n={...f(t),"http.response.status_code":e.status};e.status>=400?await w(`error`,`pi.api_error`,n):await w(`debug`,`pi.api_response`,n)})}),t.on(`model_select`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`info`,`pi.model.selected`,{...f(t),"pi.model.source":e.source,"gen_ai.provider.name":e.model.provider,"gen_ai.request.model":e.model.id})})}),t.on(`agent_end`,(e,t)=>{if(!g)return;let n=_;return D(t,async()=>{T(t),await w(`info`,`pi.agent.finished`,{...f(t),"pi.message.count":e.messages.length,...v===void 0?{}:{"pi.agent.duration_ms":Date.now()-v}}),!(!g||n!==_)&&(v=void 0,await y?.flush())})}),t.on(`agent_settled`,(e,t)=>{if(g)return D(t,async()=>{T(t),await w(`debug`,`pi.agent.settled`,f(t))})}),{waitForSession:E,async finishSession(e,t){if(!g)return;await S;let n=x;n&&await n.operation.catch(()=>void 0),g&&(n&&n!==x||y&&(T(t),await w(`info`,`pi.session.finished`,{...f(t),"pi.session.reason":e})))}}}function _(e,t={}){let n=e.registerLeader({source:t.source??`@agimon-ai/doompi-log`,bindings:[{id:`log.metrics`,path:[{key:`h`,label:`help`,detail:`package docs and logs`,order:70},{key:`l`,label:`logs`,detail:`telemetry`}],command:{name:l}}]});return()=>n.dispose()}async function v(e,t,r){await e.ui.custom((e,i,a,o)=>new n(e,i,t,o,r),{overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}})}function y(n,r,i={},s=!1){let u=i.env??process.env,f=new t,p=i.metricsSource,m=t=>p??=e({env:u,cwd:t.cwd,packageName:c,serviceName:i.serviceName??`pi`}),h=!0,y,b;n.effect(()=>()=>{h=!1,y=void 0,b=void 0},`${c}/runtime`);let x=g(n,r,{...i,metrics:f,onDiagnostic:e=>{h&&(b=e,i.onDiagnostic?i.onDiagnostic(e):u.AGENT_OTEL_DIAGNOSTICS===`stderr`&&process.emitWarning(e))},onSinkStatus:e=>{h&&(y=e,i.onSinkStatus?.(e))}},s);return n.inject([a],e=>_(o(e),{source:i.leaderSource})),r.registerCommand(l,{description:`Show session-local log metrics`,handler:async(e,t)=>{if(!h||(await x.waitForSession(t),!h))return;let n=m(t);await v(t,()=>({disabled:d(u.AGENT_TELEMETRY_DISABLED)||d(u.OTEL_SDK_DISABLED),snapshot:f.snapshot(),sink:y,transport:n.lastTransport(),instance:n.instance?.(),lastDiagnostic:b}),n.query)}}),x}function b(e,{pi:t}){let n=y(e,t,{},!0),r;t.on(`session_shutdown`,(e,t)=>(r??=n.finishSession(e.reason,t),r))}async function x(e){let t=await r(e,c),n=t.root.plugin(b,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}export{x as doomLogExtension,y as installDoomLogRuntime,g as installPiTelemetryRuntime,v as openLogMetricsOverlay,_ as registerLogMetricsLeaderBinding};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/hubApi.cjs");exports.api=e.api,exports.createLogHubApi=e.createLogHubApi;
@@ -0,0 +1,2 @@
1
+ import { HubApiOptions, api, createLogHubApi } from "./adapters/hubApi.cjs";
2
+ export { type HubApiOptions, api, createLogHubApi };
@@ -0,0 +1,2 @@
1
+ import { HubApiOptions, api, createLogHubApi } from "./adapters/hubApi.mjs";
2
+ export { type HubApiOptions, api, createLogHubApi };
@@ -0,0 +1 @@
1
+ import{api as e,createLogHubApi as t}from"./adapters/hubApi.mjs";export{e as api,t as createLogHubApi};
@@ -0,0 +1,58 @@
1
+ //#region src/types/issuesSource.d.ts
2
+ /**
3
+ * The sink's agent-issue analysis, which is a different report from its
4
+ * metrics one and reached a different way.
5
+ *
6
+ * The running daemon exposes only /api/metrics and /api/logs over HTTP, so
7
+ * there is no fast path for this: the CLI subcommand is the only transport.
8
+ * That is why the page fetches it on demand rather than with every report.
9
+ */
10
+ interface IssuesQueryParams {
11
+ /** Narrows to one hashed session, matching the metrics session dimension. */
12
+ sessionId?: string;
13
+ /** How many incidents to return; the counts are unaffected. */
14
+ limit: number;
15
+ }
16
+ /**
17
+ * One recurring problem, as the sink fingerprints it.
18
+ *
19
+ * The sink already collapses occurrences into incidents, so `occurrenceCount`
20
+ * is how many times this exact problem happened. That number is the reason
21
+ * this report exists: a page that only totals tokens tells nobody what to fix,
22
+ * and "this same failure happened twenty times" does.
23
+ *
24
+ * `detail` is the actionable line. `message` is often a record name such as
25
+ * 'pi.tool_result', while detail carries the spawn that failed or the path
26
+ * that was missing.
27
+ */
28
+ interface AgentIssueSample {
29
+ /** The sink's grouping key. Not unique in its output, so callers re-merge. */
30
+ fingerprint: string;
31
+ occurrenceCount: number;
32
+ category: string;
33
+ timestamp: string;
34
+ level: string;
35
+ message: string;
36
+ detail: string;
37
+ tool: string | null;
38
+ errorType: string | null;
39
+ agentName: string | null;
40
+ model: string | null;
41
+ statusCode: string | null;
42
+ }
43
+ interface IssuesReport {
44
+ totalIssues: number;
45
+ /** Distinct incidents after the sink groups by fingerprint. */
46
+ uniqueIncidents: number;
47
+ byCategory: Record<string, number>;
48
+ /** Failures per tool, which the metrics report's tool rows do not carry. */
49
+ byTool: Record<string, number>;
50
+ byErrorType: Record<string, number>;
51
+ samples: AgentIssueSample[];
52
+ }
53
+ interface IssuesSource {
54
+ query(params: IssuesQueryParams): Promise<IssuesReport>;
55
+ }
56
+ //#endregion
57
+ export { AgentIssueSample, IssuesQueryParams, IssuesReport, IssuesSource };
58
+ //# sourceMappingURL=issuesSource.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issuesSource.d.cts","names":[],"sources":["../../src/types/issuesSource.ts"],"mappings":";;;;;;;;;UASiB;;EAEf;;EAEA;;;;;;;;;;;;;;UAee;;EAEf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;;EAEA;EACA,YAAY;;EAEZ,QAAQ;EACR,aAAa;EACb,SAAS;;UAGM;EACf,MAAM,QAAQ,oBAAoB,QAAQ"}
@@ -0,0 +1,58 @@
1
+ //#region src/types/issuesSource.d.ts
2
+ /**
3
+ * The sink's agent-issue analysis, which is a different report from its
4
+ * metrics one and reached a different way.
5
+ *
6
+ * The running daemon exposes only /api/metrics and /api/logs over HTTP, so
7
+ * there is no fast path for this: the CLI subcommand is the only transport.
8
+ * That is why the page fetches it on demand rather than with every report.
9
+ */
10
+ interface IssuesQueryParams {
11
+ /** Narrows to one hashed session, matching the metrics session dimension. */
12
+ sessionId?: string;
13
+ /** How many incidents to return; the counts are unaffected. */
14
+ limit: number;
15
+ }
16
+ /**
17
+ * One recurring problem, as the sink fingerprints it.
18
+ *
19
+ * The sink already collapses occurrences into incidents, so `occurrenceCount`
20
+ * is how many times this exact problem happened. That number is the reason
21
+ * this report exists: a page that only totals tokens tells nobody what to fix,
22
+ * and "this same failure happened twenty times" does.
23
+ *
24
+ * `detail` is the actionable line. `message` is often a record name such as
25
+ * 'pi.tool_result', while detail carries the spawn that failed or the path
26
+ * that was missing.
27
+ */
28
+ interface AgentIssueSample {
29
+ /** The sink's grouping key. Not unique in its output, so callers re-merge. */
30
+ fingerprint: string;
31
+ occurrenceCount: number;
32
+ category: string;
33
+ timestamp: string;
34
+ level: string;
35
+ message: string;
36
+ detail: string;
37
+ tool: string | null;
38
+ errorType: string | null;
39
+ agentName: string | null;
40
+ model: string | null;
41
+ statusCode: string | null;
42
+ }
43
+ interface IssuesReport {
44
+ totalIssues: number;
45
+ /** Distinct incidents after the sink groups by fingerprint. */
46
+ uniqueIncidents: number;
47
+ byCategory: Record<string, number>;
48
+ /** Failures per tool, which the metrics report's tool rows do not carry. */
49
+ byTool: Record<string, number>;
50
+ byErrorType: Record<string, number>;
51
+ samples: AgentIssueSample[];
52
+ }
53
+ interface IssuesSource {
54
+ query(params: IssuesQueryParams): Promise<IssuesReport>;
55
+ }
56
+ //#endregion
57
+ export { AgentIssueSample, IssuesQueryParams, IssuesReport, IssuesSource };
58
+ //# sourceMappingURL=issuesSource.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issuesSource.d.mts","names":[],"sources":["../../src/types/issuesSource.ts"],"mappings":";;;;;;;;;UASiB;;EAEf;;EAEA;;;;;;;;;;;;;;UAee;;EAEf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;;EAEA;EACA,YAAY;;EAEZ,QAAQ;EACR,aAAa;EACb,SAAS;;UAGM;EACf,MAAM,QAAQ,oBAAoB,QAAQ"}
@@ -1,9 +1,27 @@
1
1
  import { LogMetricsGroupBy, LogMetricsPeriod, LogMetricsReport } from "@agimon-ai/log-sink-mcp";
2
2
  //#region src/types/metricsSource.d.ts
3
+ /**
4
+ * Narrowing one query to a single group, for a reader who clicked a row.
5
+ *
6
+ * Every field is optional and callers that pass none behave exactly as before,
7
+ * which is what keeps the TUI overlay unaffected. The sink echoes the filters
8
+ * it actually applied back on the report, so a caller can tell an honoured
9
+ * filter from one an older daemon ignored rather than presenting unfiltered
10
+ * numbers as filtered ones.
11
+ */
12
+ interface MetricsFilter {
13
+ sessionId?: string;
14
+ agentName?: string;
15
+ model?: string;
16
+ provider?: string;
17
+ }
3
18
  interface MetricsQueryParams {
4
19
  groupBy: LogMetricsGroupBy;
5
20
  period: LogMetricsPeriod;
6
21
  limit: number;
22
+ /** How many tool rows to rank; defaults to the overlay's one. */
23
+ toolLimit?: number;
24
+ filter?: MetricsFilter;
7
25
  }
8
26
  type MetricsQuery = (params: MetricsQueryParams) => Promise<LogMetricsReport>;
9
27
  type MetricsTransport = 'http' | 'cli';
@@ -27,5 +45,5 @@ interface MetricsSource {
27
45
  instance?(): MetricsInstance | undefined;
28
46
  }
29
47
  //#endregion
30
- export { MetricsInstance, MetricsQuery, MetricsQueryParams, MetricsSource, MetricsTransport };
48
+ export { MetricsFilter, MetricsInstance, MetricsQuery, MetricsQueryParams, MetricsSource, MetricsTransport };
31
49
  //# sourceMappingURL=metricsSource.d.cts.map