@agimon-ai/doompi-log 0.0.1-alpha.96 → 0.0.1-alpha.97

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.
@@ -26,7 +26,8 @@ const Unavailable = typebox.Type.Object({
26
26
  unavailable: literals([
27
27
  "no-sink",
28
28
  "no-data",
29
- "no-api"
29
+ "no-api",
30
+ "query-error"
30
31
  ]),
31
32
  detail: S
32
33
  });
@@ -37,12 +38,16 @@ const Tokens = {
37
38
  };
38
39
  const MetricsResponseSchema = typebox.Type.Union([Unavailable, typebox.Type.Object({
39
40
  generatedAt: S,
41
+ startTime: O(S),
42
+ endTime: O(S),
40
43
  dimension: Dimension,
41
44
  period: Period,
42
45
  bucketUnit: S,
43
46
  focus: O(S),
44
- transport: O(literals(["http", "cli"])),
47
+ transport: O(literals(["http", "worker"])),
45
48
  totals: typebox.Type.Object({
49
+ usageEventCount: O(N),
50
+ totalRecords: O(N),
46
51
  ...Tokens,
47
52
  cachedTokens: N,
48
53
  reasoningTokens: N,
@@ -129,12 +134,26 @@ const apiContracts = (0, _agimon_ai_doompi_core_apiContracts.defineApiContract)(
129
134
  method: "GET",
130
135
  authentication: "owner",
131
136
  description: "Read log issue details.",
132
- parameters: [{
133
- name: "focus",
134
- in: "query",
135
- required: false,
136
- schema: S
137
- }],
137
+ parameters: [
138
+ {
139
+ name: "focus",
140
+ in: "query",
141
+ required: false,
142
+ schema: S
143
+ },
144
+ {
145
+ name: "startTime",
146
+ in: "query",
147
+ required: false,
148
+ schema: S
149
+ },
150
+ {
151
+ name: "endTime",
152
+ in: "query",
153
+ required: false,
154
+ schema: S
155
+ }
156
+ ],
138
157
  responses: (0, _agimon_ai_doompi_core_apiContracts.jsonApiResponses)(IssuesResponseSchema)
139
158
  }])
140
159
  });
@@ -1 +1 @@
1
- {"version":3,"file":"apiContracts.cjs","names":["Type","defineApiContract","jsonApiResponses"],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/doompi-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Dimension = literals(['session', 'agent', 'model', 'provider']);\nconst Period = literals(['day', 'week', 'month', 'all']);\nconst Unavailable = Type.Object({ unavailable: literals(['no-sink', 'no-data', 'no-api']), detail: S });\nconst Tokens = { totalTokens: N, inputTokens: N, outputTokens: N };\nexport const MetricsResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n generatedAt: S,\n dimension: Dimension,\n period: Period,\n bucketUnit: S,\n focus: O(S),\n transport: O(literals(['http', 'cli'])),\n totals: Type.Object({\n ...Tokens,\n cachedTokens: N,\n reasoningTokens: N,\n groupCount: N,\n failedGroups: N,\n issueCount: N,\n }),\n timeline: Type.Array(Type.Object({ label: S, ...Tokens })),\n groups: Type.Array(Type.Object({ key: S, ...Tokens, issueCount: N, failed: B })),\n tools: Type.Array(Type.Object({ name: S, calls: N, p90TotalTokens: N })),\n }),\n]);\nconst Nullable = Type.Union([S, Type.Null()]);\nexport const IssuesResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n totalIssues: N,\n uniqueIncidents: N,\n byCategory: Type.Record(S, N),\n byTool: Type.Record(S, N),\n byErrorType: Type.Record(S, N),\n samples: Type.Array(\n Type.Object({\n fingerprint: S,\n occurrenceCount: N,\n category: S,\n timestamp: S,\n level: S,\n message: S,\n detail: S,\n tool: Nullable,\n errorType: Nullable,\n agentName: Nullable,\n model: Nullable,\n statusCode: Nullable,\n }),\n ),\n }),\n]);\nexport const apiContracts = defineApiContract({\n version: 1,\n sockets: [],\n dynamic: [],\n http: (['global', 'workspace'] as const).flatMap((scope) => [\n {\n id: 'log.metrics',\n scope,\n basePath: 'log',\n path: '/metrics',\n method: 'GET',\n authentication: 'owner',\n description: 'Read usage metrics from the configured log sink.',\n parameters: [\n { name: 'dimension', in: 'query', required: false, schema: Dimension },\n { name: 'period', in: 'query', required: false, schema: Period },\n { name: 'focus', in: 'query', required: false, schema: S },\n ],\n responses: jsonApiResponses(MetricsResponseSchema),\n },\n {\n id: 'log.issues',\n scope,\n basePath: 'log',\n path: '/issues',\n method: 'GET',\n authentication: 'owner',\n description: 'Read log issue details.',\n parameters: [{ name: 'focus', in: 'query', required: false, schema: S }],\n responses: jsonApiResponses(IssuesResponseSchema),\n },\n ]),\n});\nexport default apiContracts;\n"],"mappings":"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"}
1
+ {"version":3,"file":"apiContracts.cjs","names":["Type","defineApiContract","jsonApiResponses"],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/doompi-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Dimension = literals(['session', 'agent', 'model', 'provider']);\nconst Period = literals(['day', 'week', 'month', 'all']);\nconst Unavailable = Type.Object({ unavailable: literals(['no-sink', 'no-data', 'no-api', 'query-error']), detail: S });\nconst Tokens = { totalTokens: N, inputTokens: N, outputTokens: N };\nexport const MetricsResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n generatedAt: S,\n startTime: O(S),\n endTime: O(S),\n dimension: Dimension,\n period: Period,\n bucketUnit: S,\n focus: O(S),\n transport: O(literals(['http', 'worker'])),\n totals: Type.Object({\n usageEventCount: O(N),\n totalRecords: O(N),\n ...Tokens,\n cachedTokens: N,\n reasoningTokens: N,\n groupCount: N,\n failedGroups: N,\n issueCount: N,\n }),\n timeline: Type.Array(Type.Object({ label: S, ...Tokens })),\n groups: Type.Array(Type.Object({ key: S, ...Tokens, issueCount: N, failed: B })),\n tools: Type.Array(Type.Object({ name: S, calls: N, p90TotalTokens: N })),\n }),\n]);\nconst Nullable = Type.Union([S, Type.Null()]);\nexport const IssuesResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n totalIssues: N,\n uniqueIncidents: N,\n byCategory: Type.Record(S, N),\n byTool: Type.Record(S, N),\n byErrorType: Type.Record(S, N),\n samples: Type.Array(\n Type.Object({\n fingerprint: S,\n occurrenceCount: N,\n category: S,\n timestamp: S,\n level: S,\n message: S,\n detail: S,\n tool: Nullable,\n errorType: Nullable,\n agentName: Nullable,\n model: Nullable,\n statusCode: Nullable,\n }),\n ),\n }),\n]);\nexport const apiContracts = defineApiContract({\n version: 1,\n sockets: [],\n dynamic: [],\n http: (['global', 'workspace'] as const).flatMap((scope) => [\n {\n id: 'log.metrics',\n scope,\n basePath: 'log',\n path: '/metrics',\n method: 'GET',\n authentication: 'owner',\n description: 'Read usage metrics from the configured log sink.',\n parameters: [\n { name: 'dimension', in: 'query', required: false, schema: Dimension },\n { name: 'period', in: 'query', required: false, schema: Period },\n { name: 'focus', in: 'query', required: false, schema: S },\n ],\n responses: jsonApiResponses(MetricsResponseSchema),\n },\n {\n id: 'log.issues',\n scope,\n basePath: 'log',\n path: '/issues',\n method: 'GET',\n authentication: 'owner',\n description: 'Read log issue details.',\n parameters: [\n { name: 'focus', in: 'query', required: false, schema: S },\n { name: 'startTime', in: 'query', required: false, schema: S },\n { name: 'endTime', in: 'query', required: false, schema: S },\n ],\n responses: jsonApiResponses(IssuesResponseSchema),\n },\n ]),\n});\nexport default apiContracts;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"apiContracts.d.mts","names":[],"sources":["../src/schemas/apiContracts.ts"],"mappings":";;qBA6Da"}
1
+ {"version":3,"file":"apiContracts.d.mts","names":[],"sources":["../src/schemas/apiContracts.ts"],"mappings":";;qBAiEa"}
@@ -22,7 +22,8 @@ const Unavailable = Type.Object({
22
22
  unavailable: literals([
23
23
  "no-sink",
24
24
  "no-data",
25
- "no-api"
25
+ "no-api",
26
+ "query-error"
26
27
  ]),
27
28
  detail: S
28
29
  });
@@ -33,12 +34,16 @@ const Tokens = {
33
34
  };
34
35
  const MetricsResponseSchema = Type.Union([Unavailable, Type.Object({
35
36
  generatedAt: S,
37
+ startTime: O(S),
38
+ endTime: O(S),
36
39
  dimension: Dimension,
37
40
  period: Period,
38
41
  bucketUnit: S,
39
42
  focus: O(S),
40
- transport: O(literals(["http", "cli"])),
43
+ transport: O(literals(["http", "worker"])),
41
44
  totals: Type.Object({
45
+ usageEventCount: O(N),
46
+ totalRecords: O(N),
42
47
  ...Tokens,
43
48
  cachedTokens: N,
44
49
  reasoningTokens: N,
@@ -125,12 +130,26 @@ const apiContracts = defineApiContract({
125
130
  method: "GET",
126
131
  authentication: "owner",
127
132
  description: "Read log issue details.",
128
- parameters: [{
129
- name: "focus",
130
- in: "query",
131
- required: false,
132
- schema: S
133
- }],
133
+ parameters: [
134
+ {
135
+ name: "focus",
136
+ in: "query",
137
+ required: false,
138
+ schema: S
139
+ },
140
+ {
141
+ name: "startTime",
142
+ in: "query",
143
+ required: false,
144
+ schema: S
145
+ },
146
+ {
147
+ name: "endTime",
148
+ in: "query",
149
+ required: false,
150
+ schema: S
151
+ }
152
+ ],
134
153
  responses: jsonApiResponses(IssuesResponseSchema)
135
154
  }])
136
155
  });
@@ -1 +1 @@
1
- {"version":3,"file":"apiContracts.mjs","names":[],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/doompi-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Dimension = literals(['session', 'agent', 'model', 'provider']);\nconst Period = literals(['day', 'week', 'month', 'all']);\nconst Unavailable = Type.Object({ unavailable: literals(['no-sink', 'no-data', 'no-api']), detail: S });\nconst Tokens = { totalTokens: N, inputTokens: N, outputTokens: N };\nexport const MetricsResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n generatedAt: S,\n dimension: Dimension,\n period: Period,\n bucketUnit: S,\n focus: O(S),\n transport: O(literals(['http', 'cli'])),\n totals: Type.Object({\n ...Tokens,\n cachedTokens: N,\n reasoningTokens: N,\n groupCount: N,\n failedGroups: N,\n issueCount: N,\n }),\n timeline: Type.Array(Type.Object({ label: S, ...Tokens })),\n groups: Type.Array(Type.Object({ key: S, ...Tokens, issueCount: N, failed: B })),\n tools: Type.Array(Type.Object({ name: S, calls: N, p90TotalTokens: N })),\n }),\n]);\nconst Nullable = Type.Union([S, Type.Null()]);\nexport const IssuesResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n totalIssues: N,\n uniqueIncidents: N,\n byCategory: Type.Record(S, N),\n byTool: Type.Record(S, N),\n byErrorType: Type.Record(S, N),\n samples: Type.Array(\n Type.Object({\n fingerprint: S,\n occurrenceCount: N,\n category: S,\n timestamp: S,\n level: S,\n message: S,\n detail: S,\n tool: Nullable,\n errorType: Nullable,\n agentName: Nullable,\n model: Nullable,\n statusCode: Nullable,\n }),\n ),\n }),\n]);\nexport const apiContracts = defineApiContract({\n version: 1,\n sockets: [],\n dynamic: [],\n http: (['global', 'workspace'] as const).flatMap((scope) => [\n {\n id: 'log.metrics',\n scope,\n basePath: 'log',\n path: '/metrics',\n method: 'GET',\n authentication: 'owner',\n description: 'Read usage metrics from the configured log sink.',\n parameters: [\n { name: 'dimension', in: 'query', required: false, schema: Dimension },\n { name: 'period', in: 'query', required: false, schema: Period },\n { name: 'focus', in: 'query', required: false, schema: S },\n ],\n responses: jsonApiResponses(MetricsResponseSchema),\n },\n {\n id: 'log.issues',\n scope,\n basePath: 'log',\n path: '/issues',\n method: 'GET',\n authentication: 'owner',\n description: 'Read log issue details.',\n parameters: [{ name: 'focus', in: 'query', required: false, schema: S }],\n responses: jsonApiResponses(IssuesResponseSchema),\n },\n ]),\n});\nexport default apiContracts;\n"],"mappings":";;;AAGA,MAAM,IAAI,KAAK,OAAO;AACtB,MAAM,IAAI,KAAK,OAAO;AACtB,MAAM,IAAI,KAAK,QAAQ;AACvB,MAAM,IAAI,KAAK;AACf,MAAM,YAA8B,WAAgB,KAAK,MAAM,OAAO,KAAK,UAAU,KAAK,QAAQ,KAAK,CAAC,CAAC;AACzG,MAAM,YAAY,SAAS;CAAC;CAAW;CAAS;CAAS;AAAU,CAAC;AACpE,MAAM,SAAS,SAAS;CAAC;CAAO;CAAQ;CAAS;AAAK,CAAC;AACvD,MAAM,cAAc,KAAK,OAAO;CAAE,aAAa,SAAS;EAAC;EAAW;EAAW;CAAQ,CAAC;CAAG,QAAQ;AAAE,CAAC;AACtG,MAAM,SAAS;CAAE,aAAa;CAAG,aAAa;CAAG,cAAc;AAAE;AACjE,MAAa,wBAAwB,KAAK,MAAM,CAC9C,aACA,KAAK,OAAO;CACV,aAAa;CACb,WAAW;CACX,QAAQ;CACR,YAAY;CACZ,OAAO,EAAE,CAAC;CACV,WAAW,EAAE,SAAS,CAAC,QAAQ,KAAK,CAAC,CAAC;CACtC,QAAQ,KAAK,OAAO;EAClB,GAAG;EACH,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,cAAc;EACd,YAAY;CACd,CAAC;CACD,UAAU,KAAK,MAAM,KAAK,OAAO;EAAE,OAAO;EAAG,GAAG;CAAO,CAAC,CAAC;CACzD,QAAQ,KAAK,MAAM,KAAK,OAAO;EAAE,KAAK;EAAG,GAAG;EAAQ,YAAY;EAAG,QAAQ;CAAE,CAAC,CAAC;CAC/E,OAAO,KAAK,MAAM,KAAK,OAAO;EAAE,MAAM;EAAG,OAAO;EAAG,gBAAgB;CAAE,CAAC,CAAC;AACzE,CAAC,CACH,CAAC;AACD,MAAM,WAAW,KAAK,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AAC5C,MAAa,uBAAuB,KAAK,MAAM,CAC7C,aACA,KAAK,OAAO;CACV,aAAa;CACb,iBAAiB;CACjB,YAAY,KAAK,OAAO,GAAG,CAAC;CAC5B,QAAQ,KAAK,OAAO,GAAG,CAAC;CACxB,aAAa,KAAK,OAAO,GAAG,CAAC;CAC7B,SAAS,KAAK,MACZ,KAAK,OAAO;EACV,aAAa;EACb,iBAAiB;EACjB,UAAU;EACV,WAAW;EACX,OAAO;EACP,SAAS;EACT,QAAQ;EACR,MAAM;EACN,WAAW;EACX,WAAW;EACX,OAAO;EACP,YAAY;CACd,CAAC,CACH;AACF,CAAC,CACH,CAAC;AACD,MAAa,eAAe,kBAAkB;CAC5C,SAAS;CACT,SAAS,CAAC;CACV,SAAS,CAAC;CACV,MAAO,CAAC,UAAU,WAAW,CAAC,CAAW,SAAS,UAAU,CAC1D;EACE,IAAI;EACJ;EACA,UAAU;EACV,MAAM;EACN,QAAQ;EACR,gBAAgB;EAChB,aAAa;EACb,YAAY;GACV;IAAE,MAAM;IAAa,IAAI;IAAS,UAAU;IAAO,QAAQ;GAAU;GACrE;IAAE,MAAM;IAAU,IAAI;IAAS,UAAU;IAAO,QAAQ;GAAO;GAC/D;IAAE,MAAM;IAAS,IAAI;IAAS,UAAU;IAAO,QAAQ;GAAE;EAC3D;EACA,WAAW,iBAAiB,qBAAqB;CACnD,GACA;EACE,IAAI;EACJ;EACA,UAAU;EACV,MAAM;EACN,QAAQ;EACR,gBAAgB;EAChB,aAAa;EACb,YAAY,CAAC;GAAE,MAAM;GAAS,IAAI;GAAS,UAAU;GAAO,QAAQ;EAAE,CAAC;EACvE,WAAW,iBAAiB,oBAAoB;CAClD,CACF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"apiContracts.mjs","names":[],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/doompi-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Dimension = literals(['session', 'agent', 'model', 'provider']);\nconst Period = literals(['day', 'week', 'month', 'all']);\nconst Unavailable = Type.Object({ unavailable: literals(['no-sink', 'no-data', 'no-api', 'query-error']), detail: S });\nconst Tokens = { totalTokens: N, inputTokens: N, outputTokens: N };\nexport const MetricsResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n generatedAt: S,\n startTime: O(S),\n endTime: O(S),\n dimension: Dimension,\n period: Period,\n bucketUnit: S,\n focus: O(S),\n transport: O(literals(['http', 'worker'])),\n totals: Type.Object({\n usageEventCount: O(N),\n totalRecords: O(N),\n ...Tokens,\n cachedTokens: N,\n reasoningTokens: N,\n groupCount: N,\n failedGroups: N,\n issueCount: N,\n }),\n timeline: Type.Array(Type.Object({ label: S, ...Tokens })),\n groups: Type.Array(Type.Object({ key: S, ...Tokens, issueCount: N, failed: B })),\n tools: Type.Array(Type.Object({ name: S, calls: N, p90TotalTokens: N })),\n }),\n]);\nconst Nullable = Type.Union([S, Type.Null()]);\nexport const IssuesResponseSchema = Type.Union([\n Unavailable,\n Type.Object({\n totalIssues: N,\n uniqueIncidents: N,\n byCategory: Type.Record(S, N),\n byTool: Type.Record(S, N),\n byErrorType: Type.Record(S, N),\n samples: Type.Array(\n Type.Object({\n fingerprint: S,\n occurrenceCount: N,\n category: S,\n timestamp: S,\n level: S,\n message: S,\n detail: S,\n tool: Nullable,\n errorType: Nullable,\n agentName: Nullable,\n model: Nullable,\n statusCode: Nullable,\n }),\n ),\n }),\n]);\nexport const apiContracts = defineApiContract({\n version: 1,\n sockets: [],\n dynamic: [],\n http: (['global', 'workspace'] as const).flatMap((scope) => [\n {\n id: 'log.metrics',\n scope,\n basePath: 'log',\n path: '/metrics',\n method: 'GET',\n authentication: 'owner',\n description: 'Read usage metrics from the configured log sink.',\n parameters: [\n { name: 'dimension', in: 'query', required: false, schema: Dimension },\n { name: 'period', in: 'query', required: false, schema: Period },\n { name: 'focus', in: 'query', required: false, schema: S },\n ],\n responses: jsonApiResponses(MetricsResponseSchema),\n },\n {\n id: 'log.issues',\n scope,\n basePath: 'log',\n path: '/issues',\n method: 'GET',\n authentication: 'owner',\n description: 'Read log issue details.',\n parameters: [\n { name: 'focus', in: 'query', required: false, schema: S },\n { name: 'startTime', in: 'query', required: false, schema: S },\n { name: 'endTime', in: 'query', required: false, schema: S },\n ],\n responses: jsonApiResponses(IssuesResponseSchema),\n },\n ]),\n});\nexport default apiContracts;\n"],"mappings":"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"}
@@ -272,6 +272,8 @@ function runCli(params, dbPath) {
272
272
  "--limit",
273
273
  String(params.limit),
274
274
  ...params.sessionId === void 0 ? [] : ["--session-id", params.sessionId],
275
+ ...params.startTime === void 0 ? [] : ["--start-time", params.startTime],
276
+ ...params.endTime === void 0 ? [] : ["--end-time", params.endTime],
275
277
  ...dbPath === void 0 ? [] : ["--db-path", dbPath]
276
278
  ], {
277
279
  timeout: CLI_TIMEOUT_MS,
@@ -509,12 +511,16 @@ function toReport(report, source, dimension) {
509
511
  const focus = appliedFocus(report, dimension);
510
512
  return {
511
513
  generatedAt: generatedAtIso(report.generatedAt),
514
+ ...report.filters?.startTime === void 0 ? {} : { startTime: generatedAtIso(report.filters.startTime) },
515
+ endTime: generatedAtIso(report.filters?.endTime ?? report.generatedAt),
512
516
  dimension: report.groupBy,
513
517
  period: report.period,
514
518
  bucketUnit: String(report.bucket),
515
519
  transport: source.lastTransport(),
516
520
  ...focus === void 0 ? {} : { focus },
517
521
  totals: {
522
+ usageEventCount: report.totals.usageEventCount,
523
+ totalRecords: report.totals.totalRecords,
518
524
  totalTokens: report.totals.totalTokens,
519
525
  inputTokens: report.totals.inputTokens,
520
526
  outputTokens: report.totals.outputTokens,
@@ -584,9 +590,15 @@ function createLogHubApi(options = {}) {
584
590
  */
585
591
  app.get(apiRoutes_default.issues.path, async (context) => {
586
592
  const focus = context.req.query(METRICS_QUERY_PARAMS.focus);
593
+ const startTime = context.req.query("startTime");
594
+ const endTime = context.req.query("endTime");
595
+ const validTime = (value) => value === void 0 || /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && Number.isFinite(Date.parse(value));
596
+ if (!validTime(startTime) || !validTime(endTime) || startTime !== void 0 && endTime !== void 0 && Date.parse(startTime) > Date.parse(endTime)) return context.json({ error: "Issue time bounds must be valid ISO instants in chronological order." }, 400);
587
597
  try {
588
598
  const report = await issues.query({
589
599
  limit: 50,
600
+ ...startTime === void 0 ? {} : { startTime },
601
+ ...endTime === void 0 ? {} : { endTime },
590
602
  ...focus === void 0 || focus === "" ? {} : { sessionId: focus }
591
603
  });
592
604
  return context.json(report);
@@ -1 +1 @@
1
- {"version":3,"file":"server.cjs","names":["isRecord","PACKAGE_NAME","createDoomTelemetry","isEnabled","defineRoot","createRequire","path","toReport","resolveLogSinkInstance","defineApiRoutes","apiResponse","Hono","PACKAGE_NAME","createMetricsSource","routes","defineRoute","defineServerCommand","defineHook","defineHook","defineHook","defineHook","defineHook","defineHook","defineHook","defineHook","defineResource","AGENT_DIAGNOSTIC_SKILL","HELP_WHEN","packageResourcePath","readPackageResource","defineTool","createAgentDiagnosticsTool","PACKAGE_NAME","defineServerPlugin","apiRoute","rootSession","composeRootHooks","commandLogMetrics","hookAgentSettled","hookAgentStart","hookSessionShutdown","hookSessionStart","hookToolExecutionEnd","hookToolExecutionStart","hookTurnEnd","hookTurnStart","resourceDoompiDebugAgent","toolDiagnoseAgent"],"sources":["../../src/services/serverTelemetry/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/services/issuesSource/index.ts","../../src/types/webMetrics.ts","../../src/types/apiRoutes.ts","../../src/services/hubApi/index.ts","../../src/extensions/(backend)/api/log/route.server.ts","../../src/extensions/workspaces/sessions/(backend)/command/log-metrics.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/agent-settled.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/agent-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/session-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/turn-end.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/turn-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/doompi-debug-agent.server.ts","../../src/extensions/workspaces/sessions/(backend)/tool/diagnose_agent.server.ts","../../generated/server.ts"],"sourcesContent":["import type {\n DoomHeadlessActivity,\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessHook,\n} from '@agimon-ai/doompi-core/headless';\nimport { isRecord } from '@agimon-ai/doompi-core/runtimeJson';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\n\nimport { PACKAGE_NAME, SERVICE_NAME } from '../../constants/telemetry';\nimport { isEnabled } from '../telemetryEnabled';\n\nfunction contextAttributes(execution: DoomHeadlessExecutionContext): Record<string, unknown> {\n return {\n 'pi.session.id': execution.sessionId,\n ...(execution.model === undefined\n ? {}\n : {\n 'gen_ai.provider.name': execution.model.provider,\n 'gen_ai.request.model': execution.model.id,\n }),\n };\n}\n\n/** Headless hooks carry untrusted records, not Pi's typed assistant messages. */\nfunction usageAttributes(message: Record<string, unknown>): Record<string, unknown> {\n const usage = isRecord(message.usage) ? message.usage : {};\n const attributes: Record<string, unknown> = {};\n for (const [field, attribute] of Object.entries({\n input: 'gen_ai.usage.input_tokens',\n output: 'gen_ai.usage.output_tokens',\n cacheRead: 'gen_ai.usage.cache_read_tokens',\n cacheWrite: 'gen_ai.usage.cache_write_tokens',\n totalTokens: 'gen_ai.usage.total_tokens',\n reasoning: 'gen_ai.usage.reasoning_tokens',\n })) {\n const value = usage[field];\n if (typeof value === 'number' && Number.isFinite(value) && value >= 0) attributes[attribute] = value;\n }\n const cost = isRecord(usage.cost) ? usage.cost.total : undefined;\n if (typeof cost === 'number' && Number.isFinite(cost) && cost >= 0) attributes['gen_ai.usage.cost'] = cost;\n return attributes;\n}\n\nexport function createServerTelemetry() {\n let telemetry: DoomTelemetry | undefined;\n const turns = new Map<string, number>();\n const tools = new Map<string, number>();\n let agentStartedAt: number | undefined;\n const activity: DoomHeadlessActivity = {\n name: PACKAGE_NAME,\n start(execution) {\n const current = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: execution.cwd,\n env: { ...execution.environment, PI_SESSION_ID: execution.sessionId },\n enableLogs: true,\n enableTraces:\n execution.environment.AGENT_OTEL_TRACES === undefined || isEnabled(execution.environment.AGENT_OTEL_TRACES),\n });\n telemetry = current;\n return async () => {\n if (telemetry === current) {\n telemetry = undefined;\n turns.clear();\n tools.clear();\n agentStartedAt = undefined;\n }\n await current.shutdown();\n };\n },\n };\n\n const command: DoomHeadlessCommand = {\n name: 'log-metrics',\n description: 'Show headless telemetry sink status.',\n async execute(_args, execution) {\n await execution.client.notify({\n title: 'DoomPi telemetry',\n body: JSON.stringify(telemetry?.status() ?? { active: false }, null, 2),\n level: 'info',\n });\n },\n };\n\n const hooks: DoomHeadlessHook[] = [\n {\n event: 'session_start',\n async handle(_event, execution) {\n await telemetry?.recordEvent('pi.session.started', contextAttributes(execution));\n },\n },\n {\n event: 'agent_settled',\n async handle(_event, execution) {\n const current = telemetry;\n await current?.recordEvent('pi.agent.settled', {\n ...contextAttributes(execution),\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n agentStartedAt = undefined;\n turns.clear();\n await current?.flush();\n },\n },\n {\n event: 'session_shutdown',\n async handle(_event, execution) {\n await telemetry?.recordEvent('pi.session.stopped', contextAttributes(execution));\n execution.client.setStatus('doompi-log', undefined);\n },\n },\n {\n event: 'turn_start',\n async handle(event, execution) {\n if (!telemetry) return;\n if (typeof event.turnId === 'string') turns.set(event.turnId, Date.now());\n await telemetry.recordDebug('pi.turn.started', { ...contextAttributes(execution), 'pi.turn.id': event.turnId });\n },\n },\n {\n event: 'turn_end',\n async handle(event, execution) {\n const current = telemetry;\n if (!current) return;\n const startedAt = typeof event.turnId === 'string' ? turns.get(event.turnId) : undefined;\n if (typeof event.turnId === 'string') turns.delete(event.turnId);\n const message = isRecord(event.message) && event.message.role === 'assistant' ? event.message : undefined;\n const usage = message === undefined ? {} : usageAttributes(message);\n const attributes = {\n ...contextAttributes(execution),\n 'pi.turn.id': event.turnId,\n ...(message === undefined\n ? {}\n : {\n 'gen_ai.provider.name': message.provider ?? execution.model?.provider,\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n }),\n };\n const results = Array.isArray(event.toolResults) ? event.toolResults.filter(isRecord) : [];\n await current.recordEvent('pi.turn.finished', {\n ...attributes,\n ...usage,\n 'pi.tool_result.count': results.length,\n ...(startedAt === undefined ? {} : { 'pi.turn.duration_ms': Date.now() - startedAt }),\n });\n if (message?.stopReason === 'error' || message?.stopReason === 'aborted') {\n const failure = {\n ...attributes,\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: message.stopReason === 'aborted' ? 'aborted' : 'error',\n };\n // Usage belongs to the finished record only, not the failure marker.\n if (message.stopReason === 'aborted') await current.recordWarning('pi.turn.failed', undefined, failure);\n else await current.recordError('pi.turn.failed', undefined, failure);\n }\n if (Object.keys(usage).length > 0) {\n for (const result of results) {\n await current.recordDebug('pi.tool_token_sample', {\n ...attributes,\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n }\n },\n },\n {\n event: 'tool_execution_start',\n async handle(event, execution) {\n if (!telemetry) return;\n if (typeof event.toolCallId === 'string') tools.set(event.toolCallId, Date.now());\n await telemetry.recordDebug('pi.tool_call', {\n ...contextAttributes(execution),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n },\n },\n {\n event: 'tool_execution_end',\n async handle(event, execution) {\n const current = telemetry;\n if (!current) return;\n const startedAt = typeof event.toolCallId === 'string' ? tools.get(event.toolCallId) : undefined;\n if (typeof event.toolCallId === 'string') tools.delete(event.toolCallId);\n await current.recordEvent('pi.tool_result', {\n ...contextAttributes(execution),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n success: event.isError !== true,\n 'tool.result.error': event.isError === true,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n // External MCP calls have no agent_settled event to flush their results.\n if (typeof event.toolCallId === 'string' && event.toolCallId.startsWith('external-')) await current.flush();\n },\n },\n {\n event: 'agent_start',\n async handle(_event, execution) {\n if (!telemetry) return;\n agentStartedAt = Date.now();\n await telemetry.recordEvent('pi.agent.started', contextAttributes(execution));\n },\n },\n ];\n // Telemetry sink status is operator information, not model-actionable.\n return { activities: [activity], commands: [command], hooks };\n}\n","import { createServerTelemetry } from '../../../../../services/serverTelemetry';\nexport const createLogServerRoot = () => {\n const runtime = createServerTelemetry();\n return { value: runtime, activities: runtime.activities };\n};\nexport type LogServerScope = Awaited<ReturnType<typeof createLogServerRoot>>['value'];\n","import { defineRoot } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { createLogServerRoot } from './_lib/serverRoot';\n\nexport default defineRoot(createLogServerRoot);\n","import { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nimport { resolveLogSinkInstance } from '@agimon-ai/log-sink-mcp';\n\nimport type { AgentIssueSample, IssuesQueryParams, IssuesReport, IssuesSource } from '../../types/issuesSource';\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","/**\n * The metrics API, shared by this package's hub-scoped route and its cockpit\n * plugin. The two halves run in different processes, so the wire vocabulary is\n * declared here: the `(frontend)` half may reach `src/types` and nothing else\n * on the node side.\n *\n * The shapes are narrower than log-sink-mcp's own report, which carries filter\n * echoes, span counts and token distributions the page never draws.\n * Re-exporting the sink's types would also tie the browser bundle to a node\n * package. What crosses the wire is what a chart reads.\n *\n * Two things the sink does not have, so this contract does not pretend to:\n * money, and per-tool failures. The sink records tokens only, and its tool rows\n * carry an invocation count with no error field.\n */\n\n/**\n * The segment this package's API is mounted under.\n *\n * Read by the hub's `DoomApi`, which still declares its own base path. The\n * browser does not read it: the generated client is handed the same segment by\n * the build, off the `api/log/` folder that creates the mount, so no URL is\n * spelled twice.\n */\nexport const LOG_API_BASE_PATH = 'log';\n\n/**\n * The dimensions the page offers.\n *\n * log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and\n * 'step'. Those are omitted on purpose: no DoomPi package emits the matching\n * attributes today, so offering them would draw an empty chart and read as a\n * bug rather than as a gap.\n */\nexport const METRICS_DIMENSIONS = ['session', 'agent', 'model', 'provider'] as const;\nexport type MetricsDimension = (typeof METRICS_DIMENSIONS)[number];\n\nexport const METRICS_PERIODS = ['day', 'week', 'month', 'all'] as const;\nexport type MetricsPeriod = (typeof METRICS_PERIODS)[number];\n\nexport const METRICS_QUERY_PARAMS = {\n dimension: 'dimension',\n period: 'period',\n /** Narrows the whole report to one value of the current dimension. */\n focus: 'focus',\n} as const;\n\n/** How many groups and tools one response carries; the page ranks, it does not page. */\nexport const METRICS_GROUP_LIMIT = 20;\n\n/** One point on the token timeline. */\nexport interface MetricsBucket {\n /**\n * The sink's own local-time label ('YYYY-MM-DD', or 'YYYY-MM-DD HH:00' for an\n * hour bucket). Carried rather than derived: bucket boundaries are aligned to\n * local time, so slicing the ISO instant renders the wrong day east of\n * Greenwich.\n */\n label: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** One row of the selected dimension. */\nexport interface MetricsGroup {\n /**\n * The group's identity as the sink reports it. For the session dimension\n * this is an opaque hash, never a session the cockpit can open, because\n * doompi-telemetry hashes identifier-shaped attributes before export.\n */\n key: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Problems the sink detected for this group; 0 when it saw none. */\n issueCount: number;\n failed: boolean;\n}\n\n/**\n * One tool's activity.\n *\n * `calls` counts token-attributed samples, not every execution. External MCP\n * calls without model usage remain queryable as logs but have no sample here.\n * `p90TotalTokens` attributes the whole turn's tokens to each tool in that turn,\n * so it ranks tools rather than measuring their own consumption.\n *\n * There is no failure count here because the sink's tool rows do not carry\n * one. Per-tool failures live in its separate agent-issues report, which the\n * running daemon does not expose over HTTP.\n */\nexport interface MetricsTool {\n name: string;\n calls: number;\n p90TotalTokens: number;\n}\n\n/** Which transport answered, so the page can say where its numbers came from. */\nexport type MetricsTransport = 'http' | 'worker';\n\nexport interface MetricsTotals {\n /**\n * The providers' own reported total. It is not inputTokens + outputTokens:\n * on a cached agent workload it is dominated by cache traffic, and those two\n * fields are a fraction of a percent of it. The page must never present the\n * three as a breakdown.\n */\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Cache reads, usually the largest real component of totalTokens. */\n cachedTokens: number;\n reasoningTokens: number;\n groupCount: number;\n /** Groups the sink flagged as having failed, out of groupCount. */\n failedGroups: number;\n issueCount: number;\n}\n\nexport interface MetricsReport {\n generatedAt: string;\n dimension: MetricsDimension;\n period: MetricsPeriod;\n /**\n * The width of one timeline bucket, chosen by the sink from the period. A\n * long period over a short history collapses to a single bucket, so the page\n * says which unit it is drawing rather than implying a series it does not\n * have.\n */\n bucketUnit: string;\n /**\n * The dimension value this report is narrowed to, echoed back from the sink\n * rather than from the request. A daemon older than the filter ignores it\n * and returns everything, so trusting the request would label unfiltered\n * numbers as filtered; absence here means the drill-down did not happen.\n */\n focus?: string;\n /** Undefined when the source could not say which transport answered. */\n transport?: MetricsTransport;\n totals: MetricsTotals;\n timeline: MetricsBucket[];\n groups: MetricsGroup[];\n tools: MetricsTool[];\n}\n\n/**\n * Why the page has nothing to draw.\n *\n * The sink is a separate daemon that may be missing or simply empty, and those\n * are different things to tell a reader. Neither is an error: the metrics\n * source already treats a rejecting daemon as unavailable rather than fatal.\n *\n * 'no-api' is the third case and belongs to the cockpit rather than the sink.\n * A hub serving a bundle without package APIs mounted answers this route with\n * the SPA shell, and a 200 of HTML must read as a feature that is not\n * installed, not as a corrupt response.\n */\nexport type MetricsUnavailableReason = 'no-sink' | 'no-data' | 'no-api' | 'query-error';\n\nexport interface MetricsUnavailable {\n unavailable: MetricsUnavailableReason;\n detail: string;\n}\n\nexport type MetricsResponse = MetricsReport | MetricsUnavailable;\n\nexport function isMetricsUnavailable(response: MetricsResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\nexport function isMetricsDimension(value: string): value is MetricsDimension {\n return (METRICS_DIMENSIONS as readonly string[]).includes(value);\n}\n\nexport function isMetricsPeriod(value: string): value is MetricsPeriod {\n return (METRICS_PERIODS as readonly string[]).includes(value);\n}\n\n/** One tool's failure count, paired with its call count from the metrics report. */\nexport interface IssueToolRow {\n name: string;\n failures: number;\n}\n\n/**\n * One recurring problem, as the sink fingerprints it.\n *\n * `occurrenceCount` is how many times it happened, which is the number a\n * reader acts on. `detail` is the actionable line; `message` is often only a\n * record name such as 'pi.tool_result'.\n */\nexport interface IssueSample {\n fingerprint: string;\n occurrenceCount: number;\n category: string;\n timestamp: string;\n level: string;\n message: string;\n detail: string;\n tool: string | null;\n errorType: string | null;\n agentName: string | null;\n model: string | null;\n statusCode: string | null;\n}\n\n/**\n * The detail behind the issue count.\n *\n * Fetched separately from the report because the running sink exposes no\n * issues route over HTTP, so this costs a subprocess. The page asks for it\n * when a reader opens the section, not with every refresh.\n */\nexport interface IssuesView {\n totalIssues: number;\n uniqueIncidents: number;\n byCategory: Record<string, number>;\n byTool: Record<string, number>;\n byErrorType: Record<string, number>;\n samples: IssueSample[];\n}\n\nexport type IssuesResponse = IssuesView | MetricsUnavailable;\n\nexport function isIssuesUnavailable(response: IssuesResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\n/** How many incidents the issues section ranks. */\nexport const ISSUE_SAMPLE_LIMIT = 50;\n\n/** How many tool rows the report ranks, so a failing tool has a denominator. */\nexport const METRICS_TOOL_LIMIT = 15;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-core/web';\n\nimport { METRICS_QUERY_PARAMS, type IssuesResponse, type MetricsResponse } from './webMetrics';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/(backend)/api/log/`, so the mount is stated once, by the\n * folder that creates it. They used to be written here, in the contract, and\n * again inside every URL the page built, and the copies were free to disagree.\n *\n * This is the one file the routes, the Hono app and the browser all read, so a\n * path cannot move on one side alone.\n */\nexport default defineApiRoutes({\n metrics: {\n method: 'GET',\n path: '/metrics',\n query: [METRICS_QUERY_PARAMS.dimension, METRICS_QUERY_PARAMS.period, METRICS_QUERY_PARAMS.focus],\n response: apiResponse<MetricsResponse>(),\n },\n /**\n * Separate from the report because the hub answers it from a subprocess: the\n * running daemon exposes no issues route, so folding it in would make every\n * refresh wait on the slowest transport.\n */\n issues: {\n method: 'GET',\n path: '/issues',\n query: [METRICS_QUERY_PARAMS.focus],\n response: apiResponse<IssuesResponse>(),\n },\n});\n","import type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-core/packageApi';\nimport type { LogMetricGroupRow, LogMetricsReport, ToolMetricRow } from '@agimon-ai/log-sink-mcp';\nimport { Hono } from 'hono';\n\nimport { PACKAGE_NAME, SERVICE_NAME } from '../../constants/telemetry';\nimport { createIssuesSource } from '../../services/issuesSource';\nimport { createMetricsSource } from '../../services/metricsSource';\nimport routes from '../../types/apiRoutes';\nimport type { IssuesSource } from '../../types/issuesSource';\nimport type { MetricsFilter, MetricsSource } from '../../types/metricsSource';\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';\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 with nothing in it reads as \"no data\", not as an empty chart. The\n * reader may have used the HTTP sink or a worker against an empty history, so\n * the empty-state detail must not claim the sink is running.\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 env?: NodeJS.ProcessEnv;\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 identity = {\n packageName: PACKAGE_NAME,\n serviceName: SERVICE_NAME,\n ...(options.env === undefined ? {} : { env: options.env }),\n };\n const source = options.source ?? createMetricsSource(identity);\n const issues = options.issues ?? createIssuesSource(identity);\n\n app.get(routes.metrics.path, 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 // A missing daemon can still be read through its database. Failure of both\n // transports is a query fault, including a broken worker dependency graph.\n const unavailable: MetricsUnavailable = {\n unavailable: 'query-error',\n detail: error instanceof Error ? error.message : 'The metrics query failed.',\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: 'No recorded usage was found 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(routes.issues.path, 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: 'query-error',\n detail: error instanceof Error ? error.message : 'The issues query failed.',\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 env = { ...context.environment };\n const source = createMetricsSource({ env, packageName: PACKAGE_NAME, serviceName: SERVICE_NAME });\n const app = createLogHubApi({ source, env });\n return {\n fetch: (request) => app.fetch(request),\n close: () => source.close?.(),\n };\n },\n};\n","import { defineRoute } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { api } from '../../../../services/hubApi';\n\nexport default defineRoute(api);\n","import { defineServerCommand, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineServerCommand(\n (context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.commands[0]!,\n);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[1]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[7]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[2]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[0]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[6]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[5]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[4]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[3]!);\n","import { defineResource } from '@agimon-ai/doompi-core/extensionFile';\nimport { DOOM_HELP_WHEN as HELP_WHEN } from '@agimon-ai/doompi-core/help';\nimport { packageResourcePath, readPackageResource } from '@agimon-ai/doompi-core/serverFacet';\n\nimport { AGENT_DIAGNOSTIC_SKILL } from '../../../../../constants/diagnostics';\n\nexport default defineResource({\n ...AGENT_DIAGNOSTIC_SKILL,\n when: HELP_WHEN,\n kind: 'skill' as const,\n path: packageResourcePath(import.meta.url, 'src/prompts/doompi-debug-agent/SKILL.md'),\n read: () => readPackageResource(import.meta.url, 'src/prompts/doompi-debug-agent/SKILL.md'),\n});\n","import { defineTool } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport { PACKAGE_NAME } from '../../../../../constants/telemetry';\nimport { createAgentDiagnosticsTool } from '../../../../../services/agentDiagnostics';\n\nexport default defineTool(({ agent, signal: ownerSignal }: DoomServerPluginContext) => {\n if (!agent) throw new Error('Agent diagnostics require a session host.');\n return createAgentDiagnosticsTool(\n () => ({\n sessionId: agent.context.sessionId,\n options: {\n cwd: agent.context.cwd,\n env: { ...agent.context.environment, PI_SESSION_ID: agent.context.sessionId },\n },\n }),\n (signal) => {\n ownerSignal.throwIfAborted();\n signal?.throwIfAborted();\n agent.assertActive(PACKAGE_NAME);\n if (!(agent.context.selection.state?.['minor-mode'] ?? []).includes('help'))\n throw new Error('Help mode is not active.');\n return signal ? AbortSignal.any([signal, ownerSignal]) : ownerSignal;\n },\n );\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/doompi-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport apiRoute from '../src/extensions/(backend)/api/log/route.server';\nimport commandLogMetrics from '../src/extensions/workspaces/sessions/(backend)/command/log-metrics.server';\nimport hookAgentSettled from '../src/extensions/workspaces/sessions/(backend)/hook/agent-settled.server';\nimport hookAgentStart from '../src/extensions/workspaces/sessions/(backend)/hook/agent-start.server';\nimport hookSessionShutdown from '../src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server';\nimport hookSessionStart from '../src/extensions/workspaces/sessions/(backend)/hook/session-start.server';\nimport hookToolExecutionEnd from '../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server';\nimport hookToolExecutionStart from '../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server';\nimport hookTurnEnd from '../src/extensions/workspaces/sessions/(backend)/hook/turn-end.server';\nimport hookTurnStart from '../src/extensions/workspaces/sessions/(backend)/hook/turn-start.server';\nimport resourceDoompiDebugAgent from '../src/extensions/workspaces/sessions/(backend)/resource/doompi-debug-agent.server';\nimport toolDiagnoseAgent from '../src/extensions/workspaces/sessions/(backend)/tool/diagnose_agent.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n name: '@agimon-ai/doompi-log',\n global: (context) => ({\n get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, context)]; },\n }),\n workspace: (context) => ({\n get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, context)]; },\n }),\n session: async (context) => {\n const scopeSession = await rootSession(context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, context)]; },\n get commands(): DoomServerSessionPlugin['commands'] { return [via({ name: 'log-metrics' }, at(commandLogMetrics, contextSession))]; },\n get hooks(): DoomServerSessionPlugin['hooks'] { return [via({ event: 'agent_settled' }, at(hookAgentSettled, contextSession)), via({ event: 'agent_start' }, at(hookAgentStart, contextSession)), via({ event: 'session_shutdown' }, at(hookSessionShutdown, contextSession)), via({ event: 'session_start' }, at(hookSessionStart, contextSession)), via({ event: 'tool_execution_end' }, at(hookToolExecutionEnd, contextSession)), via({ event: 'tool_execution_start' }, at(hookToolExecutionStart, contextSession)), via({ event: 'turn_end' }, at(hookTurnEnd, contextSession)), via({ event: 'turn_start' }, at(hookTurnStart, contextSession))]; },\n get resources(): DoomServerSessionPlugin['resources'] { return [at(resourceDoompiDebugAgent, contextSession)]; },\n get tools(): DoomServerSessionPlugin['tools'] { return [via({ name: 'diagnose_agent' }, at(toolDiagnoseAgent, contextSession))]; },\n services: [...(scopeSession.services ?? [])],\n get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default facet;\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.cjs","names":["isRecord","PACKAGE_NAME","createDoomTelemetry","isEnabled","defineRoot","createRequire","path","toReport","resolveLogSinkInstance","defineApiRoutes","apiResponse","Hono","PACKAGE_NAME","createMetricsSource","routes","defineRoute","defineServerCommand","defineHook","defineHook","defineHook","defineHook","defineHook","defineHook","defineHook","defineHook","defineResource","AGENT_DIAGNOSTIC_SKILL","HELP_WHEN","packageResourcePath","readPackageResource","defineTool","createAgentDiagnosticsTool","PACKAGE_NAME","defineServerPlugin","apiRoute","rootSession","composeRootHooks","commandLogMetrics","hookAgentSettled","hookAgentStart","hookSessionShutdown","hookSessionStart","hookToolExecutionEnd","hookToolExecutionStart","hookTurnEnd","hookTurnStart","resourceDoompiDebugAgent","toolDiagnoseAgent"],"sources":["../../src/services/serverTelemetry/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/services/issuesSource/index.ts","../../src/types/webMetrics.ts","../../src/types/apiRoutes.ts","../../src/services/hubApi/index.ts","../../src/extensions/(backend)/api/log/route.server.ts","../../src/extensions/workspaces/sessions/(backend)/command/log-metrics.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/agent-settled.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/agent-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/session-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/turn-end.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/turn-start.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/doompi-debug-agent.server.ts","../../src/extensions/workspaces/sessions/(backend)/tool/diagnose_agent.server.ts","../../generated/server.ts"],"sourcesContent":["import type {\n DoomHeadlessActivity,\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessHook,\n} from '@agimon-ai/doompi-core/headless';\nimport { isRecord } from '@agimon-ai/doompi-core/runtimeJson';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\n\nimport { PACKAGE_NAME, SERVICE_NAME } from '../../constants/telemetry';\nimport { isEnabled } from '../telemetryEnabled';\n\nfunction contextAttributes(execution: DoomHeadlessExecutionContext): Record<string, unknown> {\n return {\n 'pi.session.id': execution.sessionId,\n ...(execution.model === undefined\n ? {}\n : {\n 'gen_ai.provider.name': execution.model.provider,\n 'gen_ai.request.model': execution.model.id,\n }),\n };\n}\n\n/** Headless hooks carry untrusted records, not Pi's typed assistant messages. */\nfunction usageAttributes(message: Record<string, unknown>): Record<string, unknown> {\n const usage = isRecord(message.usage) ? message.usage : {};\n const attributes: Record<string, unknown> = {};\n for (const [field, attribute] of Object.entries({\n input: 'gen_ai.usage.input_tokens',\n output: 'gen_ai.usage.output_tokens',\n cacheRead: 'gen_ai.usage.cache_read_tokens',\n cacheWrite: 'gen_ai.usage.cache_write_tokens',\n totalTokens: 'gen_ai.usage.total_tokens',\n reasoning: 'gen_ai.usage.reasoning_tokens',\n })) {\n const value = usage[field];\n if (typeof value === 'number' && Number.isFinite(value) && value >= 0) attributes[attribute] = value;\n }\n const cost = isRecord(usage.cost) ? usage.cost.total : undefined;\n if (typeof cost === 'number' && Number.isFinite(cost) && cost >= 0) attributes['gen_ai.usage.cost'] = cost;\n return attributes;\n}\n\nexport function createServerTelemetry() {\n let telemetry: DoomTelemetry | undefined;\n const turns = new Map<string, number>();\n const tools = new Map<string, number>();\n let agentStartedAt: number | undefined;\n const activity: DoomHeadlessActivity = {\n name: PACKAGE_NAME,\n start(execution) {\n const current = createDoomTelemetry({\n serviceName: SERVICE_NAME,\n packageName: PACKAGE_NAME,\n cwd: execution.cwd,\n env: { ...execution.environment, PI_SESSION_ID: execution.sessionId },\n enableLogs: true,\n enableTraces:\n execution.environment.AGENT_OTEL_TRACES === undefined || isEnabled(execution.environment.AGENT_OTEL_TRACES),\n });\n telemetry = current;\n return async () => {\n if (telemetry === current) {\n telemetry = undefined;\n turns.clear();\n tools.clear();\n agentStartedAt = undefined;\n }\n await current.shutdown();\n };\n },\n };\n\n const command: DoomHeadlessCommand = {\n name: 'log-metrics',\n description: 'Show headless telemetry sink status.',\n async execute(_args, execution) {\n await execution.client.notify({\n title: 'DoomPi telemetry',\n body: JSON.stringify(telemetry?.status() ?? { active: false }, null, 2),\n level: 'info',\n });\n },\n };\n\n const hooks: DoomHeadlessHook[] = [\n {\n event: 'session_start',\n async handle(_event, execution) {\n await telemetry?.recordEvent('pi.session.started', contextAttributes(execution));\n },\n },\n {\n event: 'agent_settled',\n async handle(_event, execution) {\n const current = telemetry;\n await current?.recordEvent('pi.agent.settled', {\n ...contextAttributes(execution),\n ...(agentStartedAt === undefined ? {} : { 'pi.agent.duration_ms': Date.now() - agentStartedAt }),\n });\n agentStartedAt = undefined;\n turns.clear();\n await current?.flush();\n },\n },\n {\n event: 'session_shutdown',\n async handle(_event, execution) {\n await telemetry?.recordEvent('pi.session.stopped', contextAttributes(execution));\n execution.client.setStatus('doompi-log', undefined);\n },\n },\n {\n event: 'turn_start',\n async handle(event, execution) {\n if (!telemetry) return;\n if (typeof event.turnId === 'string') turns.set(event.turnId, Date.now());\n await telemetry.recordDebug('pi.turn.started', { ...contextAttributes(execution), 'pi.turn.id': event.turnId });\n },\n },\n {\n event: 'turn_end',\n async handle(event, execution) {\n const current = telemetry;\n if (!current) return;\n const startedAt = typeof event.turnId === 'string' ? turns.get(event.turnId) : undefined;\n if (typeof event.turnId === 'string') turns.delete(event.turnId);\n const message = isRecord(event.message) && event.message.role === 'assistant' ? event.message : undefined;\n const usage = message === undefined ? {} : usageAttributes(message);\n const attributes = {\n ...contextAttributes(execution),\n 'pi.turn.id': event.turnId,\n ...(message === undefined\n ? {}\n : {\n 'gen_ai.provider.name': message.provider ?? execution.model?.provider,\n 'gen_ai.response.model': message.responseModel ?? message.model,\n 'gen_ai.response.finish_reasons': message.stopReason,\n }),\n };\n const results = Array.isArray(event.toolResults) ? event.toolResults.filter(isRecord) : [];\n await current.recordEvent('pi.turn.finished', {\n ...attributes,\n ...usage,\n 'pi.tool_result.count': results.length,\n ...(startedAt === undefined ? {} : { 'pi.turn.duration_ms': Date.now() - startedAt }),\n });\n if (message?.stopReason === 'error' || message?.stopReason === 'aborted') {\n const failure = {\n ...attributes,\n 'error.type': 'ProviderError',\n 'error.code': message.stopReason,\n outcome: message.stopReason === 'aborted' ? 'aborted' : 'error',\n };\n // Usage belongs to the finished record only, not the failure marker.\n if (message.stopReason === 'aborted') await current.recordWarning('pi.turn.failed', undefined, failure);\n else await current.recordError('pi.turn.failed', undefined, failure);\n }\n if (Object.keys(usage).length > 0) {\n for (const result of results) {\n await current.recordDebug('pi.tool_token_sample', {\n ...attributes,\n ...usage,\n 'tool.name': result.toolName,\n token_attribution: 'toolCallTurn',\n });\n }\n }\n },\n },\n {\n event: 'tool_execution_start',\n async handle(event, execution) {\n if (!telemetry) return;\n if (typeof event.toolCallId === 'string') tools.set(event.toolCallId, Date.now());\n await telemetry.recordDebug('pi.tool_call', {\n ...contextAttributes(execution),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n });\n },\n },\n {\n event: 'tool_execution_end',\n async handle(event, execution) {\n const current = telemetry;\n if (!current) return;\n const startedAt = typeof event.toolCallId === 'string' ? tools.get(event.toolCallId) : undefined;\n if (typeof event.toolCallId === 'string') tools.delete(event.toolCallId);\n await current.recordEvent('pi.tool_result', {\n ...contextAttributes(execution),\n 'tool.name': event.toolName,\n 'tool.call.id': event.toolCallId,\n success: event.isError !== true,\n 'tool.result.error': event.isError === true,\n ...(startedAt === undefined ? {} : { 'tool.duration_ms': Date.now() - startedAt }),\n });\n // External MCP calls have no agent_settled event to flush their results.\n if (typeof event.toolCallId === 'string' && event.toolCallId.startsWith('external-')) await current.flush();\n },\n },\n {\n event: 'agent_start',\n async handle(_event, execution) {\n if (!telemetry) return;\n agentStartedAt = Date.now();\n await telemetry.recordEvent('pi.agent.started', contextAttributes(execution));\n },\n },\n ];\n // Telemetry sink status is operator information, not model-actionable.\n return { activities: [activity], commands: [command], hooks };\n}\n","import { createServerTelemetry } from '../../../../../services/serverTelemetry';\nexport const createLogServerRoot = () => {\n const runtime = createServerTelemetry();\n return { value: runtime, activities: runtime.activities };\n};\nexport type LogServerScope = Awaited<ReturnType<typeof createLogServerRoot>>['value'];\n","import { defineRoot } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { createLogServerRoot } from './_lib/serverRoot';\n\nexport default defineRoot(createLogServerRoot);\n","import { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nimport { resolveLogSinkInstance } from '@agimon-ai/log-sink-mcp';\n\nimport type { AgentIssueSample, IssuesQueryParams, IssuesReport, IssuesSource } from '../../types/issuesSource';\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 ...(params.startTime === undefined ? [] : ['--start-time', params.startTime]),\n ...(params.endTime === undefined ? [] : ['--end-time', params.endTime]),\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","/**\n * The metrics API, shared by this package's hub-scoped route and its cockpit\n * plugin. The two halves run in different processes, so the wire vocabulary is\n * declared here: the `(frontend)` half may reach `src/types` and nothing else\n * on the node side.\n *\n * The shapes are narrower than log-sink-mcp's own report, which carries filter\n * echoes, span counts and token distributions the page never draws.\n * Re-exporting the sink's types would also tie the browser bundle to a node\n * package. What crosses the wire is what a chart reads.\n *\n * Two things the sink does not have, so this contract does not pretend to:\n * money, and per-tool failures. This metrics report records tokens, and its tool rows\n * carry an invocation count with no error field.\n */\n\n/**\n * The segment this package's API is mounted under.\n *\n * Read by the hub's `DoomApi`, which still declares its own base path. The\n * browser does not read it: the generated client is handed the same segment by\n * the build, off the `api/log/` folder that creates the mount, so no URL is\n * spelled twice.\n */\nexport const LOG_API_BASE_PATH = 'log';\n\n/**\n * The dimensions the page offers.\n *\n * log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and\n * 'step'. Those are omitted on purpose: no DoomPi package emits the matching\n * attributes today, so offering them would draw an empty chart and read as a\n * bug rather than as a gap.\n */\nexport const METRICS_DIMENSIONS = ['session', 'agent', 'model', 'provider'] as const;\nexport type MetricsDimension = (typeof METRICS_DIMENSIONS)[number];\n\nexport const METRICS_PERIODS = ['day', 'week', 'month', 'all'] as const;\nexport type MetricsPeriod = (typeof METRICS_PERIODS)[number];\n\nexport const METRICS_QUERY_PARAMS = {\n dimension: 'dimension',\n period: 'period',\n /** Narrows the whole report to one value of the current dimension. */\n focus: 'focus',\n} as const;\n\n/** How many groups and tools one response carries; the page ranks, it does not page. */\nexport const METRICS_GROUP_LIMIT = 20;\n\n/** One point on the token timeline. */\nexport interface MetricsBucket {\n /**\n * The sink's own local-time label ('YYYY-MM-DD', or 'YYYY-MM-DD HH:00' for an\n * hour bucket). Carried rather than derived: bucket boundaries are aligned to\n * local time, so slicing the ISO instant renders the wrong day east of\n * Greenwich.\n */\n label: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n}\n\n/** One row of the selected dimension. */\nexport interface MetricsGroup {\n /**\n * The group's identity as the sink reports it. For the session dimension\n * this is an opaque hash, never a session the cockpit can open, because\n * doompi-telemetry hashes identifier-shaped attributes before export.\n */\n key: string;\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Problems the sink detected for this group; 0 when it saw none. */\n issueCount: number;\n failed: boolean;\n}\n\n/**\n * One tool's activity.\n *\n * `calls` counts token-attributed samples, not every execution. External MCP\n * calls without model usage remain queryable as logs but have no sample here.\n * `p90TotalTokens` attributes the whole turn's tokens to each tool in that turn,\n * so it ranks tools rather than measuring their own consumption.\n *\n * There is no failure count here because the sink's tool rows do not carry\n * one. Per-tool failures live in its separate agent-issues report, which the\n * running daemon does not expose over HTTP.\n */\nexport interface MetricsTool {\n name: string;\n calls: number;\n p90TotalTokens: number;\n}\n\n/** Which transport answered, so the page can say where its numbers came from. */\nexport type MetricsTransport = 'http' | 'worker';\n\nexport interface MetricsTotals {\n /** Optional so an older hub remains distinguishable from a measured zero. */\n usageEventCount?: number;\n totalRecords?: number;\n /**\n * The providers' own reported total. It is not inputTokens + outputTokens:\n * providers report these counters independently. Their relationship cannot\n * be inferred from a difference, so the page must not stack them as parts\n * of the total or label the difference as cache traffic.\n */\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n /** Independently reported cache reads, not an inferred remainder. */\n cachedTokens: number;\n reasoningTokens: number;\n groupCount: number;\n /** Groups the sink flagged as having failed, out of groupCount. */\n failedGroups: number;\n issueCount: number;\n}\n\nexport interface MetricsReport {\n /** Echoed query window, so issue details can use the same recorded period. */\n startTime?: string;\n endTime?: string;\n generatedAt: string;\n dimension: MetricsDimension;\n period: MetricsPeriod;\n /**\n * The width of one timeline bucket, chosen by the sink from the period. A\n * long period over a short history collapses to a single bucket, so the page\n * says which unit it is drawing rather than implying a series it does not\n * have.\n */\n bucketUnit: string;\n /**\n * The dimension value this report is narrowed to, echoed back from the sink\n * rather than from the request. A daemon older than the filter ignores it\n * and returns everything, so trusting the request would label unfiltered\n * numbers as filtered; absence here means the drill-down did not happen.\n */\n focus?: string;\n /** Undefined when the source could not say which transport answered. */\n transport?: MetricsTransport;\n totals: MetricsTotals;\n timeline: MetricsBucket[];\n groups: MetricsGroup[];\n tools: MetricsTool[];\n}\n\n/**\n * Why the page has nothing to draw.\n *\n * The sink is a separate daemon that may be missing or simply empty, and those\n * are different things to tell a reader. Neither is an error: the metrics\n * source already treats a rejecting daemon as unavailable rather than fatal.\n *\n * 'no-api' is the third case and belongs to the cockpit rather than the sink.\n * A hub serving a bundle without package APIs mounted answers this route with\n * the SPA shell, and a 200 of HTML must read as a feature that is not\n * installed, not as a corrupt response.\n */\nexport type MetricsUnavailableReason = 'no-sink' | 'no-data' | 'no-api' | 'query-error';\n\nexport interface MetricsUnavailable {\n unavailable: MetricsUnavailableReason;\n detail: string;\n}\n\nexport type MetricsResponse = MetricsReport | MetricsUnavailable;\n\nexport function isMetricsUnavailable(response: MetricsResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\nexport function isMetricsDimension(value: string): value is MetricsDimension {\n return (METRICS_DIMENSIONS as readonly string[]).includes(value);\n}\n\nexport function isMetricsPeriod(value: string): value is MetricsPeriod {\n return (METRICS_PERIODS as readonly string[]).includes(value);\n}\n\n/** One tool's failure count, paired with its call count from the metrics report. */\nexport interface IssueToolRow {\n name: string;\n failures: number;\n}\n\n/**\n * One recurring problem, as the sink fingerprints it.\n *\n * `occurrenceCount` is how many times it happened, which is the number a\n * reader acts on. `detail` is the actionable line; `message` is often only a\n * record name such as 'pi.tool_result'.\n */\nexport interface IssueSample {\n fingerprint: string;\n occurrenceCount: number;\n category: string;\n timestamp: string;\n level: string;\n message: string;\n detail: string;\n tool: string | null;\n errorType: string | null;\n agentName: string | null;\n model: string | null;\n statusCode: string | null;\n}\n\n/**\n * The detail behind the issue count.\n *\n * Fetched separately from the report because the running sink exposes no\n * issues route over HTTP, so this costs a subprocess. The page asks for it\n * when a reader opens the section, not with every refresh.\n */\nexport interface IssuesView {\n totalIssues: number;\n uniqueIncidents: number;\n byCategory: Record<string, number>;\n byTool: Record<string, number>;\n byErrorType: Record<string, number>;\n samples: IssueSample[];\n}\n\nexport type IssuesResponse = IssuesView | MetricsUnavailable;\n\nexport function isIssuesUnavailable(response: IssuesResponse): response is MetricsUnavailable {\n return 'unavailable' in response;\n}\n\n/** How many incidents the issues section ranks. */\nexport const ISSUE_SAMPLE_LIMIT = 50;\n\n/** How many tool rows the report ranks, so a failing tool has a denominator. */\nexport const METRICS_TOOL_LIMIT = 15;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-core/web';\n\nimport { METRICS_QUERY_PARAMS, type IssuesResponse, type MetricsResponse } from './webMetrics';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/(backend)/api/log/`, so the mount is stated once, by the\n * folder that creates it. They used to be written here, in the contract, and\n * again inside every URL the page built, and the copies were free to disagree.\n *\n * This is the one file the routes, the Hono app and the browser all read, so a\n * path cannot move on one side alone.\n */\nexport default defineApiRoutes({\n metrics: {\n method: 'GET',\n path: '/metrics',\n query: [METRICS_QUERY_PARAMS.dimension, METRICS_QUERY_PARAMS.period, METRICS_QUERY_PARAMS.focus],\n response: apiResponse<MetricsResponse>(),\n },\n /**\n * Separate from the report because the hub answers it from a subprocess: the\n * running daemon exposes no issues route, so folding it in would make every\n * refresh wait on the slowest transport.\n */\n issues: {\n method: 'GET',\n path: '/issues',\n query: [METRICS_QUERY_PARAMS.focus],\n response: apiResponse<IssuesResponse>(),\n },\n});\n","import type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-core/packageApi';\nimport type { LogMetricGroupRow, LogMetricsReport, ToolMetricRow } from '@agimon-ai/log-sink-mcp';\nimport { Hono } from 'hono';\n\nimport { PACKAGE_NAME, SERVICE_NAME } from '../../constants/telemetry';\nimport { createIssuesSource } from '../../services/issuesSource';\nimport { createMetricsSource } from '../../services/metricsSource';\nimport routes from '../../types/apiRoutes';\nimport type { IssuesSource } from '../../types/issuesSource';\nimport type { MetricsFilter, MetricsSource } from '../../types/metricsSource';\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';\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: Date | string): string {\n if (value instanceof Date) return value.toISOString();\n const parsed = new Date(value);\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 ...(report.filters?.startTime === undefined ? {} : { startTime: generatedAtIso(report.filters.startTime) }),\n endTime: generatedAtIso(report.filters?.endTime ?? 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 usageEventCount: report.totals.usageEventCount,\n totalRecords: report.totals.totalRecords,\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 with nothing in it reads as \"no data\", not as an empty chart. The\n * reader may have used the HTTP sink or a worker against an empty history, so\n * the empty-state detail must not claim the sink is running.\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 env?: NodeJS.ProcessEnv;\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 identity = {\n packageName: PACKAGE_NAME,\n serviceName: SERVICE_NAME,\n ...(options.env === undefined ? {} : { env: options.env }),\n };\n const source = options.source ?? createMetricsSource(identity);\n const issues = options.issues ?? createIssuesSource(identity);\n\n app.get(routes.metrics.path, 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 // A missing daemon can still be read through its database. Failure of both\n // transports is a query fault, including a broken worker dependency graph.\n const unavailable: MetricsUnavailable = {\n unavailable: 'query-error',\n detail: error instanceof Error ? error.message : 'The metrics query failed.',\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: 'No recorded usage was found 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(routes.issues.path, async (context) => {\n const focus = context.req.query(METRICS_QUERY_PARAMS.focus);\n const startTime = context.req.query('startTime');\n const endTime = context.req.query('endTime');\n // Only ISO instants are accepted at this boundary, not locale-dependent dates.\n const validTime = (value: string | undefined): boolean =>\n value === undefined ||\n (/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/.test(value) &&\n Number.isFinite(Date.parse(value)));\n if (\n !validTime(startTime) ||\n !validTime(endTime) ||\n (startTime !== undefined && endTime !== undefined && Date.parse(startTime) > Date.parse(endTime))\n ) {\n return context.json({ error: 'Issue time bounds must be valid ISO instants in chronological order.' }, 400);\n }\n try {\n const report = await issues.query({\n limit: ISSUE_SAMPLE_LIMIT,\n ...(startTime === undefined ? {} : { startTime }),\n ...(endTime === undefined ? {} : { endTime }),\n ...(focus === undefined || focus === '' ? {} : { sessionId: focus }),\n });\n return context.json(report satisfies IssuesView);\n } catch (error) {\n const unavailable: MetricsUnavailable = {\n unavailable: 'query-error',\n detail: error instanceof Error ? error.message : 'The issues query failed.',\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 env = { ...context.environment };\n const source = createMetricsSource({ env, packageName: PACKAGE_NAME, serviceName: SERVICE_NAME });\n const app = createLogHubApi({ source, env });\n return {\n fetch: (request) => app.fetch(request),\n close: () => source.close?.(),\n };\n },\n};\n","import { defineRoute } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { api } from '../../../../services/hubApi';\n\nexport default defineRoute(api);\n","import { defineServerCommand, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineServerCommand(\n (context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.commands[0]!,\n);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[1]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[7]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[2]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[0]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[6]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[5]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[4]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { LogServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, LogServerScope>) => context.root.hooks[3]!);\n","import { defineResource } from '@agimon-ai/doompi-core/extensionFile';\nimport { DOOM_HELP_WHEN as HELP_WHEN } from '@agimon-ai/doompi-core/help';\nimport { packageResourcePath, readPackageResource } from '@agimon-ai/doompi-core/serverFacet';\n\nimport { AGENT_DIAGNOSTIC_SKILL } from '../../../../../constants/diagnostics';\n\nexport default defineResource({\n ...AGENT_DIAGNOSTIC_SKILL,\n when: HELP_WHEN,\n kind: 'skill' as const,\n path: packageResourcePath(import.meta.url, 'src/prompts/doompi-debug-agent/SKILL.md'),\n read: () => readPackageResource(import.meta.url, 'src/prompts/doompi-debug-agent/SKILL.md'),\n});\n","import { defineTool } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport { PACKAGE_NAME } from '../../../../../constants/telemetry';\nimport { createAgentDiagnosticsTool } from '../../../../../services/agentDiagnostics';\n\nexport default defineTool(({ agent, signal: ownerSignal }: DoomServerPluginContext) => {\n if (!agent) throw new Error('Agent diagnostics require a session host.');\n return createAgentDiagnosticsTool(\n () => ({\n sessionId: agent.context.sessionId,\n options: {\n cwd: agent.context.cwd,\n env: { ...agent.context.environment, PI_SESSION_ID: agent.context.sessionId },\n },\n }),\n (signal) => {\n ownerSignal.throwIfAborted();\n signal?.throwIfAborted();\n agent.assertActive(PACKAGE_NAME);\n if (!(agent.context.selection.state?.['minor-mode'] ?? []).includes('help'))\n throw new Error('Help mode is not active.');\n return signal ? AbortSignal.any([signal, ownerSignal]) : ownerSignal;\n },\n );\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/doompi-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport apiRoute from '../src/extensions/(backend)/api/log/route.server';\nimport commandLogMetrics from '../src/extensions/workspaces/sessions/(backend)/command/log-metrics.server';\nimport hookAgentSettled from '../src/extensions/workspaces/sessions/(backend)/hook/agent-settled.server';\nimport hookAgentStart from '../src/extensions/workspaces/sessions/(backend)/hook/agent-start.server';\nimport hookSessionShutdown from '../src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server';\nimport hookSessionStart from '../src/extensions/workspaces/sessions/(backend)/hook/session-start.server';\nimport hookToolExecutionEnd from '../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server';\nimport hookToolExecutionStart from '../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server';\nimport hookTurnEnd from '../src/extensions/workspaces/sessions/(backend)/hook/turn-end.server';\nimport hookTurnStart from '../src/extensions/workspaces/sessions/(backend)/hook/turn-start.server';\nimport resourceDoompiDebugAgent from '../src/extensions/workspaces/sessions/(backend)/resource/doompi-debug-agent.server';\nimport toolDiagnoseAgent from '../src/extensions/workspaces/sessions/(backend)/tool/diagnose_agent.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n name: '@agimon-ai/doompi-log',\n global: (context) => ({\n get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, context)]; },\n }),\n workspace: (context) => ({\n get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, context)]; },\n }),\n session: async (context) => {\n const scopeSession = await rootSession(context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, context)]; },\n get commands(): DoomServerSessionPlugin['commands'] { return [via({ name: 'log-metrics' }, at(commandLogMetrics, contextSession))]; },\n get hooks(): DoomServerSessionPlugin['hooks'] { return [via({ event: 'agent_settled' }, at(hookAgentSettled, contextSession)), via({ event: 'agent_start' }, at(hookAgentStart, contextSession)), via({ event: 'session_shutdown' }, at(hookSessionShutdown, contextSession)), via({ event: 'session_start' }, at(hookSessionStart, contextSession)), via({ event: 'tool_execution_end' }, at(hookToolExecutionEnd, contextSession)), via({ event: 'tool_execution_start' }, at(hookToolExecutionStart, contextSession)), via({ event: 'turn_end' }, at(hookTurnEnd, contextSession)), via({ event: 'turn_start' }, at(hookTurnStart, contextSession))]; },\n get resources(): DoomServerSessionPlugin['resources'] { return [at(resourceDoompiDebugAgent, contextSession)]; },\n get tools(): DoomServerSessionPlugin['tools'] { return [via({ name: 'diagnose_agent' }, at(toolDiagnoseAgent, contextSession))]; },\n services: [...(scopeSession.services ?? [])],\n get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default facet;\n"],"mappings":"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"}
@@ -245,6 +245,8 @@ function runCli(params, dbPath) {
245
245
  "--limit",
246
246
  String(params.limit),
247
247
  ...params.sessionId === void 0 ? [] : ["--session-id", params.sessionId],
248
+ ...params.startTime === void 0 ? [] : ["--start-time", params.startTime],
249
+ ...params.endTime === void 0 ? [] : ["--end-time", params.endTime],
248
250
  ...dbPath === void 0 ? [] : ["--db-path", dbPath]
249
251
  ], {
250
252
  timeout: CLI_TIMEOUT_MS,
@@ -482,12 +484,16 @@ function toReport(report, source, dimension) {
482
484
  const focus = appliedFocus(report, dimension);
483
485
  return {
484
486
  generatedAt: generatedAtIso(report.generatedAt),
487
+ ...report.filters?.startTime === void 0 ? {} : { startTime: generatedAtIso(report.filters.startTime) },
488
+ endTime: generatedAtIso(report.filters?.endTime ?? report.generatedAt),
485
489
  dimension: report.groupBy,
486
490
  period: report.period,
487
491
  bucketUnit: String(report.bucket),
488
492
  transport: source.lastTransport(),
489
493
  ...focus === void 0 ? {} : { focus },
490
494
  totals: {
495
+ usageEventCount: report.totals.usageEventCount,
496
+ totalRecords: report.totals.totalRecords,
491
497
  totalTokens: report.totals.totalTokens,
492
498
  inputTokens: report.totals.inputTokens,
493
499
  outputTokens: report.totals.outputTokens,
@@ -557,9 +563,15 @@ function createLogHubApi(options = {}) {
557
563
  */
558
564
  app.get(apiRoutes_default.issues.path, async (context) => {
559
565
  const focus = context.req.query(METRICS_QUERY_PARAMS.focus);
566
+ const startTime = context.req.query("startTime");
567
+ const endTime = context.req.query("endTime");
568
+ const validTime = (value) => value === void 0 || /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && Number.isFinite(Date.parse(value));
569
+ if (!validTime(startTime) || !validTime(endTime) || startTime !== void 0 && endTime !== void 0 && Date.parse(startTime) > Date.parse(endTime)) return context.json({ error: "Issue time bounds must be valid ISO instants in chronological order." }, 400);
560
570
  try {
561
571
  const report = await issues.query({
562
572
  limit: 50,
573
+ ...startTime === void 0 ? {} : { startTime },
574
+ ...endTime === void 0 ? {} : { endTime },
563
575
  ...focus === void 0 || focus === "" ? {} : { sessionId: focus }
564
576
  });
565
577
  return context.json(report);