@agimon-ai/po-log 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/apiContracts.cjs +164 -0
  4. package/dist/apiContracts.cjs.map +1 -0
  5. package/dist/apiContracts.d.mts +53 -0
  6. package/dist/apiContracts.d.mts.map +1 -0
  7. package/dist/apiContracts.mjs +159 -0
  8. package/dist/apiContracts.mjs.map +1 -0
  9. package/dist/extensions/server.cjs +1034 -0
  10. package/dist/extensions/server.cjs.map +1 -0
  11. package/dist/extensions/server.d.mts +5 -0
  12. package/dist/extensions/server.d.mts.map +1 -0
  13. package/dist/extensions/server.mjs +1006 -0
  14. package/dist/extensions/server.mjs.map +1 -0
  15. package/dist/extensions/web.mjs +1542 -0
  16. package/dist/extensions/web.mjs.map +1 -0
  17. package/dist/index-CTZMUfTZ.d.mts +185 -0
  18. package/dist/index-CTZMUfTZ.d.mts.map +1 -0
  19. package/dist/index.cjs +13 -0
  20. package/dist/index.d.mts +36 -0
  21. package/dist/index.d.mts.map +1 -0
  22. package/dist/index.mjs +2 -0
  23. package/dist/metrics-DywCpDJ_.mjs +508 -0
  24. package/dist/metrics-DywCpDJ_.mjs.map +1 -0
  25. package/dist/metrics-jCZe13Xg.cjs +573 -0
  26. package/dist/metrics-jCZe13Xg.cjs.map +1 -0
  27. package/dist/metrics.cjs +13 -0
  28. package/dist/metrics.d.mts +2 -0
  29. package/dist/metrics.mjs +2 -0
  30. package/dist/metricsSource-DXZLawQu.mjs +130 -0
  31. package/dist/metricsSource-DXZLawQu.mjs.map +1 -0
  32. package/dist/metricsSource-DdwQNzoY.cjs +135 -0
  33. package/dist/metricsSource-DdwQNzoY.cjs.map +1 -0
  34. package/dist/metricsSource-cZnErdv4.d.mts +51 -0
  35. package/dist/metricsSource-cZnErdv4.d.mts.map +1 -0
  36. package/dist/metricsSource.cjs +3 -0
  37. package/dist/metricsSource.d.mts +18 -0
  38. package/dist/metricsSource.d.mts.map +1 -0
  39. package/dist/metricsSource.mjs +2 -0
  40. package/llms.txt +7 -0
  41. package/package.json +140 -0
  42. package/src/prompts/po-debug-agent/SKILL.md +28 -0
@@ -0,0 +1,2 @@
1
+ import { S as TURN_FINISHED_RECORD, _ as MAX_RETAINED_TOKEN_BUCKETS, a as LogMetricsAggregator, b as TOOL_TOKEN_SAMPLE_RECORD, c as LogMetricsEventCount, d as LogMetricsSnapshot, f as LogMetricsTokenTotals, g as MAX_RETAINED_STATE_PER_TOOL, h as MAX_RETAINED_METRIC_NAMES, i as API_ERROR_RECORD, l as LogMetricsOperationDuration, m as LogMetricsToolLatency, n as LogMetricsFindingSeverity, o as LogMetricsAggregatorOptions, p as LogMetricsToolCost, r as deriveFindings, s as LogMetricsError, t as LogMetricsFinding, u as LogMetricsRecorder, v as METRIC_OVERFLOW_NAME, x as TURN_FAILED_RECORD, y as TOOL_RESULT_RECORD } from "./index-CTZMUfTZ.mjs";
2
+ export { API_ERROR_RECORD, LogMetricsAggregator, type LogMetricsAggregatorOptions, type LogMetricsError, type LogMetricsEventCount, type LogMetricsFinding, type LogMetricsFindingSeverity, type LogMetricsOperationDuration, type LogMetricsRecorder, type LogMetricsSnapshot, type LogMetricsTokenTotals, type LogMetricsToolCost, type LogMetricsToolLatency, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings };
@@ -0,0 +1,2 @@
1
+ import { a as MAX_RETAINED_TOKEN_BUCKETS, c as TOOL_TOKEN_SAMPLE_RECORD, d as deriveFindings, i as MAX_RETAINED_STATE_PER_TOOL, l as TURN_FAILED_RECORD, n as LogMetricsAggregator, o as METRIC_OVERFLOW_NAME, r as MAX_RETAINED_METRIC_NAMES, s as TOOL_RESULT_RECORD, t as API_ERROR_RECORD, u as TURN_FINISHED_RECORD } from "./metrics-DywCpDJ_.mjs";
2
+ export { API_ERROR_RECORD, LogMetricsAggregator, MAX_RETAINED_METRIC_NAMES, MAX_RETAINED_STATE_PER_TOOL, MAX_RETAINED_TOKEN_BUCKETS, METRIC_OVERFLOW_NAME, TOOL_RESULT_RECORD, TOOL_TOKEN_SAMPLE_RECORD, TURN_FAILED_RECORD, TURN_FINISHED_RECORD, deriveFindings };
@@ -0,0 +1,130 @@
1
+ import { createAsyncLogMetricsReader, resolveLogSinkInstance, resolveLogSinkPort } from "@agimon-ai/log-sink-mcp";
2
+ //#region src/services/metricsSource/index.ts
3
+ /**
4
+ * Historical metrics access for the `SPC h l` overlay.
5
+ *
6
+ * Two transports, in order: the running sink's HTTP API, then a worker-backed
7
+ * database reader. The sink daemon is long-lived and often lags the installed
8
+ * package, so a daemon that rejects newer query parameters is treated as
9
+ * unavailable rather than fatal. SQLite queries run in the package worker so a
10
+ * one-day window cannot freeze the TUI.
11
+ *
12
+ * Both transports resolve the sink instance from the same identity the Pi
13
+ * telemetry extension writes under. Without it log-sink falls back to
14
+ * `npm_package_name`, which a running Pi process does not set, so the reader
15
+ * would land on the local instance while the writer exported to the global one
16
+ * and the panel would report an empty history that no session ever filled.
17
+ */
18
+ const HTTP_TIMEOUT_MS = 5e3;
19
+ /**
20
+ * The overlay ranks one tool, but a page pairing tool calls against failure
21
+ * counts needs a denominator for every tool that failed, so callers ask.
22
+ */
23
+ const DEFAULT_TOOL_LIMIT = 1;
24
+ /** The panel ranks consumers, so ask for the biggest rather than the most recent. */
25
+ const TOKEN_SORT = "total-tokens";
26
+ const FILTER_QUERY_KEYS = {
27
+ sessionId: "sessionId",
28
+ agentName: "agentName",
29
+ model: "model",
30
+ provider: "provider"
31
+ };
32
+ function filterEntries(params) {
33
+ const filter = params.filter;
34
+ if (filter === void 0) return [];
35
+ return Object.keys(FILTER_QUERY_KEYS).flatMap((key) => {
36
+ const value = filter[key];
37
+ return value === void 0 || value === "" ? [] : [[key, value]];
38
+ });
39
+ }
40
+ function searchParams(params) {
41
+ const search = new URLSearchParams({
42
+ groupBy: params.groupBy,
43
+ period: params.period,
44
+ sort: TOKEN_SORT,
45
+ limit: String(params.limit),
46
+ toolLimit: String(params.toolLimit ?? DEFAULT_TOOL_LIMIT)
47
+ });
48
+ for (const [key, value] of filterEntries(params)) search.set(FILTER_QUERY_KEYS[key], value);
49
+ return search.toString();
50
+ }
51
+ async function queryHttp(endpoint, params, fetchImpl) {
52
+ const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, { signal: AbortSignal.timeout(HTTP_TIMEOUT_MS) });
53
+ if (!response.ok) throw new Error(`sink responded ${response.status}`);
54
+ const body = await response.json();
55
+ if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? "sink is an older version");
56
+ return body;
57
+ }
58
+ /**
59
+ * A malformed `.logsink.yaml` must not take the overlay down with it. Both
60
+ * transports still run when resolution fails; they fall back to log-sink's own
61
+ * resolution and the panel reports an unknown instance.
62
+ */
63
+ function resolveInstance(identity) {
64
+ try {
65
+ const resolved = resolveLogSinkInstance(identity);
66
+ return {
67
+ scope: resolved.scope,
68
+ dbPath: resolved.dbPath,
69
+ ...resolved.registeredName === void 0 ? {} : { registeredName: resolved.registeredName }
70
+ };
71
+ } catch {
72
+ return;
73
+ }
74
+ }
75
+ function createMetricsSource(options = {}) {
76
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
77
+ const identity = {
78
+ env: options.env,
79
+ cwd: options.cwd,
80
+ packageName: options.packageName,
81
+ serviceName: options.serviceName
82
+ };
83
+ let transport;
84
+ let endpoint;
85
+ const instance = resolveInstance(identity);
86
+ let workerReader = options.workerReader;
87
+ const resolveEndpoint = async () => {
88
+ if (endpoint !== void 0) return endpoint ?? void 0;
89
+ try {
90
+ endpoint = (await resolveLogSinkPort({
91
+ ...identity,
92
+ healthCheck: true
93
+ }))?.endpoint ?? null;
94
+ } catch {
95
+ endpoint = null;
96
+ }
97
+ return endpoint ?? void 0;
98
+ };
99
+ return {
100
+ lastTransport: () => transport,
101
+ instance: () => instance,
102
+ async query(params) {
103
+ const httpEndpoint = await resolveEndpoint();
104
+ if (httpEndpoint && fetchImpl) try {
105
+ const report = await queryHttp(httpEndpoint, params, fetchImpl);
106
+ transport = "http";
107
+ return report;
108
+ } catch {
109
+ endpoint = null;
110
+ }
111
+ workerReader ??= createAsyncLogMetricsReader(instance === void 0 ? { cwd: options.cwd } : { dbPath: instance.dbPath });
112
+ const report = await workerReader.getMetrics(params.filter, {
113
+ groupBy: params.groupBy,
114
+ period: params.period,
115
+ sort: TOKEN_SORT,
116
+ limit: params.limit,
117
+ toolLimit: params.toolLimit ?? DEFAULT_TOOL_LIMIT
118
+ });
119
+ transport = "worker";
120
+ return report;
121
+ },
122
+ async close() {
123
+ await workerReader?.close();
124
+ }
125
+ };
126
+ }
127
+ //#endregion
128
+ export { createMetricsSource as t };
129
+
130
+ //# sourceMappingURL=metricsSource-DXZLawQu.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsSource-DXZLawQu.mjs","names":[],"sources":["../src/services/metricsSource/index.ts"],"sourcesContent":["/**\n * Historical metrics access for the `SPC h l` overlay.\n *\n * Two transports, in order: the running sink's HTTP API, then a worker-backed\n * database reader. The sink daemon is long-lived and often lags the installed\n * package, so a daemon that rejects newer query parameters is treated as\n * unavailable rather than fatal. SQLite queries run in the package worker so a\n * one-day window cannot freeze the TUI.\n *\n * Both transports resolve the sink instance from the same identity the Pi\n * telemetry extension writes under. Without it log-sink falls back to\n * `npm_package_name`, which a running Pi process does not set, so the reader\n * would land on the local instance while the writer exported to the global one\n * and the panel would report an empty history that no session ever filled.\n */\nimport {\n createAsyncLogMetricsReader,\n type AsyncLogMetricsReader,\n type LogMetricsReport,\n resolveLogSinkInstance,\n resolveLogSinkPort,\n} from '@agimon-ai/log-sink-mcp';\n\nimport type { MetricsInstance, MetricsQueryParams, MetricsSource, MetricsTransport } from '../../types/metricsSource';\n\nconst HTTP_TIMEOUT_MS = 5000;\n/**\n * The overlay ranks one tool, but a page pairing tool calls against failure\n * counts needs a denominator for every tool that failed, so callers ask.\n */\nconst DEFAULT_TOOL_LIMIT = 1;\n/** The panel ranks consumers, so ask for the biggest rather than the most recent. */\nconst TOKEN_SORT = 'total-tokens';\n\n/** The identity both transports resolve their sink instance from. */\ninterface ResolveIdentity {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n}\n\nexport interface MetricsSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Identity the sink is registered under; must match the telemetry writer's. */\n packageName?: string;\n serviceName?: string;\n /** Overrides HTTP transport discovery in tests. */\n fetchImpl?: typeof fetch;\n /** Overrides the worker-backed reader in tests. */\n workerReader?: Pick<AsyncLogMetricsReader, 'getMetrics' | 'close'>;\n}\n\nconst FILTER_QUERY_KEYS = {\n sessionId: 'sessionId',\n agentName: 'agentName',\n model: 'model',\n provider: 'provider',\n} as const;\n\nfunction filterEntries(params: MetricsQueryParams): [keyof typeof FILTER_QUERY_KEYS, string][] {\n const filter = params.filter;\n if (filter === undefined) return [];\n return (Object.keys(FILTER_QUERY_KEYS) as (keyof typeof FILTER_QUERY_KEYS)[]).flatMap((key) => {\n const value = filter[key];\n return value === undefined || value === '' ? [] : [[key, value] as [keyof typeof FILTER_QUERY_KEYS, string]];\n });\n}\n\nfunction searchParams(params: MetricsQueryParams): string {\n const search = new URLSearchParams({\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: String(params.limit),\n toolLimit: String(params.toolLimit ?? DEFAULT_TOOL_LIMIT),\n });\n for (const [key, value] of filterEntries(params)) search.set(FILTER_QUERY_KEYS[key], value);\n return search.toString();\n}\n\nasync function queryHttp(\n endpoint: string,\n params: MetricsQueryParams,\n fetchImpl: typeof fetch,\n): Promise<LogMetricsReport> {\n const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, {\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n });\n if (!response.ok) throw new Error(`sink responded ${response.status}`);\n\n const body = (await response.json()) as LogMetricsReport & { error?: string };\n // An older daemon rejects unknown group-by values and omits the timeline.\n if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? 'sink is an older version');\n return body;\n}\n\n/**\n * A malformed `.logsink.yaml` must not take the overlay down with it. Both\n * transports still run when resolution fails; they fall back to log-sink's own\n * resolution and the panel reports an unknown instance.\n */\nfunction resolveInstance(identity: ResolveIdentity): MetricsInstance | undefined {\n try {\n const resolved = resolveLogSinkInstance(identity);\n return {\n scope: resolved.scope,\n dbPath: resolved.dbPath,\n ...(resolved.registeredName === undefined ? {} : { registeredName: resolved.registeredName }),\n };\n } catch {\n return undefined;\n }\n}\n\nexport function createMetricsSource(options: MetricsSourceOptions = {}): MetricsSource {\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n const identity = {\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n };\n let transport: MetricsTransport | undefined;\n let endpoint: string | undefined | null;\n const instance = resolveInstance(identity);\n let workerReader = options.workerReader;\n\n const resolveEndpoint = async (): Promise<string | undefined> => {\n if (endpoint !== undefined) return endpoint ?? undefined;\n try {\n const resolved = await resolveLogSinkPort({ ...identity, healthCheck: true });\n endpoint = resolved?.endpoint ?? null;\n } catch {\n endpoint = null;\n }\n return endpoint ?? undefined;\n };\n\n return {\n lastTransport: () => transport,\n instance: () => instance,\n async query(params) {\n const httpEndpoint = await resolveEndpoint();\n if (httpEndpoint && fetchImpl) {\n try {\n const report = await queryHttp(httpEndpoint, params, fetchImpl);\n transport = 'http';\n return report;\n } catch {\n // A stale or unhealthy daemon must not hide data the worker can still read.\n endpoint = null;\n }\n }\n\n workerReader ??= createAsyncLogMetricsReader(\n instance === undefined ? { cwd: options.cwd } : { dbPath: instance.dbPath },\n );\n const report = await workerReader.getMetrics(params.filter, {\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: params.limit,\n toolLimit: params.toolLimit ?? DEFAULT_TOOL_LIMIT,\n });\n transport = 'worker';\n return report;\n },\n async close() {\n await workerReader?.close();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ let _agimon_ai_log_sink_mcp = require("@agimon-ai/log-sink-mcp");
2
+ //#region src/services/metricsSource/index.ts
3
+ /**
4
+ * Historical metrics access for the `SPC h l` overlay.
5
+ *
6
+ * Two transports, in order: the running sink's HTTP API, then a worker-backed
7
+ * database reader. The sink daemon is long-lived and often lags the installed
8
+ * package, so a daemon that rejects newer query parameters is treated as
9
+ * unavailable rather than fatal. SQLite queries run in the package worker so a
10
+ * one-day window cannot freeze the TUI.
11
+ *
12
+ * Both transports resolve the sink instance from the same identity the Pi
13
+ * telemetry extension writes under. Without it log-sink falls back to
14
+ * `npm_package_name`, which a running Pi process does not set, so the reader
15
+ * would land on the local instance while the writer exported to the global one
16
+ * and the panel would report an empty history that no session ever filled.
17
+ */
18
+ const HTTP_TIMEOUT_MS = 5e3;
19
+ /**
20
+ * The overlay ranks one tool, but a page pairing tool calls against failure
21
+ * counts needs a denominator for every tool that failed, so callers ask.
22
+ */
23
+ const DEFAULT_TOOL_LIMIT = 1;
24
+ /** The panel ranks consumers, so ask for the biggest rather than the most recent. */
25
+ const TOKEN_SORT = "total-tokens";
26
+ const FILTER_QUERY_KEYS = {
27
+ sessionId: "sessionId",
28
+ agentName: "agentName",
29
+ model: "model",
30
+ provider: "provider"
31
+ };
32
+ function filterEntries(params) {
33
+ const filter = params.filter;
34
+ if (filter === void 0) return [];
35
+ return Object.keys(FILTER_QUERY_KEYS).flatMap((key) => {
36
+ const value = filter[key];
37
+ return value === void 0 || value === "" ? [] : [[key, value]];
38
+ });
39
+ }
40
+ function searchParams(params) {
41
+ const search = new URLSearchParams({
42
+ groupBy: params.groupBy,
43
+ period: params.period,
44
+ sort: TOKEN_SORT,
45
+ limit: String(params.limit),
46
+ toolLimit: String(params.toolLimit ?? DEFAULT_TOOL_LIMIT)
47
+ });
48
+ for (const [key, value] of filterEntries(params)) search.set(FILTER_QUERY_KEYS[key], value);
49
+ return search.toString();
50
+ }
51
+ async function queryHttp(endpoint, params, fetchImpl) {
52
+ const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, { signal: AbortSignal.timeout(HTTP_TIMEOUT_MS) });
53
+ if (!response.ok) throw new Error(`sink responded ${response.status}`);
54
+ const body = await response.json();
55
+ if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? "sink is an older version");
56
+ return body;
57
+ }
58
+ /**
59
+ * A malformed `.logsink.yaml` must not take the overlay down with it. Both
60
+ * transports still run when resolution fails; they fall back to log-sink's own
61
+ * resolution and the panel reports an unknown instance.
62
+ */
63
+ function resolveInstance(identity) {
64
+ try {
65
+ const resolved = (0, _agimon_ai_log_sink_mcp.resolveLogSinkInstance)(identity);
66
+ return {
67
+ scope: resolved.scope,
68
+ dbPath: resolved.dbPath,
69
+ ...resolved.registeredName === void 0 ? {} : { registeredName: resolved.registeredName }
70
+ };
71
+ } catch {
72
+ return;
73
+ }
74
+ }
75
+ function createMetricsSource(options = {}) {
76
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
77
+ const identity = {
78
+ env: options.env,
79
+ cwd: options.cwd,
80
+ packageName: options.packageName,
81
+ serviceName: options.serviceName
82
+ };
83
+ let transport;
84
+ let endpoint;
85
+ const instance = resolveInstance(identity);
86
+ let workerReader = options.workerReader;
87
+ const resolveEndpoint = async () => {
88
+ if (endpoint !== void 0) return endpoint ?? void 0;
89
+ try {
90
+ endpoint = (await (0, _agimon_ai_log_sink_mcp.resolveLogSinkPort)({
91
+ ...identity,
92
+ healthCheck: true
93
+ }))?.endpoint ?? null;
94
+ } catch {
95
+ endpoint = null;
96
+ }
97
+ return endpoint ?? void 0;
98
+ };
99
+ return {
100
+ lastTransport: () => transport,
101
+ instance: () => instance,
102
+ async query(params) {
103
+ const httpEndpoint = await resolveEndpoint();
104
+ if (httpEndpoint && fetchImpl) try {
105
+ const report = await queryHttp(httpEndpoint, params, fetchImpl);
106
+ transport = "http";
107
+ return report;
108
+ } catch {
109
+ endpoint = null;
110
+ }
111
+ workerReader ??= (0, _agimon_ai_log_sink_mcp.createAsyncLogMetricsReader)(instance === void 0 ? { cwd: options.cwd } : { dbPath: instance.dbPath });
112
+ const report = await workerReader.getMetrics(params.filter, {
113
+ groupBy: params.groupBy,
114
+ period: params.period,
115
+ sort: TOKEN_SORT,
116
+ limit: params.limit,
117
+ toolLimit: params.toolLimit ?? DEFAULT_TOOL_LIMIT
118
+ });
119
+ transport = "worker";
120
+ return report;
121
+ },
122
+ async close() {
123
+ await workerReader?.close();
124
+ }
125
+ };
126
+ }
127
+ //#endregion
128
+ Object.defineProperty(exports, "createMetricsSource", {
129
+ enumerable: true,
130
+ get: function() {
131
+ return createMetricsSource;
132
+ }
133
+ });
134
+
135
+ //# sourceMappingURL=metricsSource-DdwQNzoY.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsSource-DdwQNzoY.cjs","names":["resolveLogSinkInstance","resolveLogSinkPort","createAsyncLogMetricsReader"],"sources":["../src/services/metricsSource/index.ts"],"sourcesContent":["/**\n * Historical metrics access for the `SPC h l` overlay.\n *\n * Two transports, in order: the running sink's HTTP API, then a worker-backed\n * database reader. The sink daemon is long-lived and often lags the installed\n * package, so a daemon that rejects newer query parameters is treated as\n * unavailable rather than fatal. SQLite queries run in the package worker so a\n * one-day window cannot freeze the TUI.\n *\n * Both transports resolve the sink instance from the same identity the Pi\n * telemetry extension writes under. Without it log-sink falls back to\n * `npm_package_name`, which a running Pi process does not set, so the reader\n * would land on the local instance while the writer exported to the global one\n * and the panel would report an empty history that no session ever filled.\n */\nimport {\n createAsyncLogMetricsReader,\n type AsyncLogMetricsReader,\n type LogMetricsReport,\n resolveLogSinkInstance,\n resolveLogSinkPort,\n} from '@agimon-ai/log-sink-mcp';\n\nimport type { MetricsInstance, MetricsQueryParams, MetricsSource, MetricsTransport } from '../../types/metricsSource';\n\nconst HTTP_TIMEOUT_MS = 5000;\n/**\n * The overlay ranks one tool, but a page pairing tool calls against failure\n * counts needs a denominator for every tool that failed, so callers ask.\n */\nconst DEFAULT_TOOL_LIMIT = 1;\n/** The panel ranks consumers, so ask for the biggest rather than the most recent. */\nconst TOKEN_SORT = 'total-tokens';\n\n/** The identity both transports resolve their sink instance from. */\ninterface ResolveIdentity {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n packageName?: string;\n serviceName?: string;\n}\n\nexport interface MetricsSourceOptions {\n env?: NodeJS.ProcessEnv;\n cwd?: string;\n /** Identity the sink is registered under; must match the telemetry writer's. */\n packageName?: string;\n serviceName?: string;\n /** Overrides HTTP transport discovery in tests. */\n fetchImpl?: typeof fetch;\n /** Overrides the worker-backed reader in tests. */\n workerReader?: Pick<AsyncLogMetricsReader, 'getMetrics' | 'close'>;\n}\n\nconst FILTER_QUERY_KEYS = {\n sessionId: 'sessionId',\n agentName: 'agentName',\n model: 'model',\n provider: 'provider',\n} as const;\n\nfunction filterEntries(params: MetricsQueryParams): [keyof typeof FILTER_QUERY_KEYS, string][] {\n const filter = params.filter;\n if (filter === undefined) return [];\n return (Object.keys(FILTER_QUERY_KEYS) as (keyof typeof FILTER_QUERY_KEYS)[]).flatMap((key) => {\n const value = filter[key];\n return value === undefined || value === '' ? [] : [[key, value] as [keyof typeof FILTER_QUERY_KEYS, string]];\n });\n}\n\nfunction searchParams(params: MetricsQueryParams): string {\n const search = new URLSearchParams({\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: String(params.limit),\n toolLimit: String(params.toolLimit ?? DEFAULT_TOOL_LIMIT),\n });\n for (const [key, value] of filterEntries(params)) search.set(FILTER_QUERY_KEYS[key], value);\n return search.toString();\n}\n\nasync function queryHttp(\n endpoint: string,\n params: MetricsQueryParams,\n fetchImpl: typeof fetch,\n): Promise<LogMetricsReport> {\n const response = await fetchImpl(`${endpoint}/api/metrics?${searchParams(params)}`, {\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),\n });\n if (!response.ok) throw new Error(`sink responded ${response.status}`);\n\n const body = (await response.json()) as LogMetricsReport & { error?: string };\n // An older daemon rejects unknown group-by values and omits the timeline.\n if (body.error || !Array.isArray(body.timeline)) throw new Error(body.error ?? 'sink is an older version');\n return body;\n}\n\n/**\n * A malformed `.logsink.yaml` must not take the overlay down with it. Both\n * transports still run when resolution fails; they fall back to log-sink's own\n * resolution and the panel reports an unknown instance.\n */\nfunction resolveInstance(identity: ResolveIdentity): MetricsInstance | undefined {\n try {\n const resolved = resolveLogSinkInstance(identity);\n return {\n scope: resolved.scope,\n dbPath: resolved.dbPath,\n ...(resolved.registeredName === undefined ? {} : { registeredName: resolved.registeredName }),\n };\n } catch {\n return undefined;\n }\n}\n\nexport function createMetricsSource(options: MetricsSourceOptions = {}): MetricsSource {\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n const identity = {\n env: options.env,\n cwd: options.cwd,\n packageName: options.packageName,\n serviceName: options.serviceName,\n };\n let transport: MetricsTransport | undefined;\n let endpoint: string | undefined | null;\n const instance = resolveInstance(identity);\n let workerReader = options.workerReader;\n\n const resolveEndpoint = async (): Promise<string | undefined> => {\n if (endpoint !== undefined) return endpoint ?? undefined;\n try {\n const resolved = await resolveLogSinkPort({ ...identity, healthCheck: true });\n endpoint = resolved?.endpoint ?? null;\n } catch {\n endpoint = null;\n }\n return endpoint ?? undefined;\n };\n\n return {\n lastTransport: () => transport,\n instance: () => instance,\n async query(params) {\n const httpEndpoint = await resolveEndpoint();\n if (httpEndpoint && fetchImpl) {\n try {\n const report = await queryHttp(httpEndpoint, params, fetchImpl);\n transport = 'http';\n return report;\n } catch {\n // A stale or unhealthy daemon must not hide data the worker can still read.\n endpoint = null;\n }\n }\n\n workerReader ??= createAsyncLogMetricsReader(\n instance === undefined ? { cwd: options.cwd } : { dbPath: instance.dbPath },\n );\n const report = await workerReader.getMetrics(params.filter, {\n groupBy: params.groupBy,\n period: params.period,\n sort: TOKEN_SORT,\n limit: params.limit,\n toolLimit: params.toolLimit ?? DEFAULT_TOOL_LIMIT,\n });\n transport = 'worker';\n return report;\n },\n async close() {\n await workerReader?.close();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { LogMetricsGroupBy, LogMetricsPeriod, LogMetricsReport } from "@agimon-ai/log-sink-mcp";
2
+ //#region src/types/metricsSource.d.ts
3
+ /**
4
+ * Narrowing one query to a single group, for a reader who clicked a row.
5
+ *
6
+ * Every field is optional and callers that pass none behave exactly as before,
7
+ * which is what keeps the TUI overlay unaffected. The sink echoes the filters
8
+ * it actually applied back on the report, so a caller can tell an honoured
9
+ * filter from one an older daemon ignored rather than presenting unfiltered
10
+ * numbers as filtered ones.
11
+ */
12
+ interface MetricsFilter {
13
+ sessionId?: string;
14
+ agentName?: string;
15
+ model?: string;
16
+ provider?: string;
17
+ }
18
+ interface MetricsQueryParams {
19
+ groupBy: LogMetricsGroupBy;
20
+ period: LogMetricsPeriod;
21
+ limit: number;
22
+ /** How many tool rows to rank; defaults to the overlay's one. */
23
+ toolLimit?: number;
24
+ filter?: MetricsFilter;
25
+ }
26
+ type MetricsQuery = (params: MetricsQueryParams) => Promise<LogMetricsReport>;
27
+ type MetricsTransport = 'http' | 'worker';
28
+ /**
29
+ * The log-sink instance the history transports read.
30
+ *
31
+ * A local instance is scoped to one repository and a global one is shared, so
32
+ * an empty history panel means something different in each case. The overlay
33
+ * reports this rather than leaving the reader to guess which database answered.
34
+ */
35
+ interface MetricsInstance {
36
+ scope: 'local' | 'global';
37
+ dbPath: string;
38
+ /** The name that selected the scope: the package the sink is registered under. */
39
+ registeredName?: string;
40
+ }
41
+ interface MetricsSource {
42
+ query: MetricsQuery;
43
+ lastTransport(): MetricsTransport | undefined;
44
+ /** Undefined when instance resolution failed; the transports still work. */
45
+ instance?(): MetricsInstance | undefined;
46
+ /** Releases a lazily created worker-backed reader. */
47
+ close?(): Promise<void>;
48
+ }
49
+ //#endregion
50
+ export { MetricsTransport as a, MetricsSource as i, MetricsQuery as n, MetricsQueryParams as r, MetricsInstance as t };
51
+ //# sourceMappingURL=metricsSource-cZnErdv4.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsSource-cZnErdv4.d.mts","names":[],"sources":["../src/types/metricsSource.ts"],"mappings":";;;;;;;;;;;UAWiB;EACf;EACA;EACA;EACA;;UAGe;EACf,SAAS;EACT,QAAQ;EACR;;EAEA;EACA,SAAS;;KAGC,gBAAgB,QAAQ,uBAAuB,QAAQ;KAEvD;;;;;;;;UASK;EACf;EACA;;EAEA;;UAGe;EACf,OAAO;EACP,iBAAiB;;EAEjB,aAAa;;EAEb,UAAU"}
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_metricsSource = require("./metricsSource-DdwQNzoY.cjs");
3
+ exports.createMetricsSource = require_metricsSource.createMetricsSource;
@@ -0,0 +1,18 @@
1
+ import { a as MetricsTransport, i as MetricsSource, n as MetricsQuery, r as MetricsQueryParams, t as MetricsInstance } from "./metricsSource-cZnErdv4.mjs";
2
+ import { AsyncLogMetricsReader } from "@agimon-ai/log-sink-mcp";
3
+ //#region src/services/metricsSource/index.d.ts
4
+ interface MetricsSourceOptions {
5
+ env?: NodeJS.ProcessEnv;
6
+ cwd?: string;
7
+ /** Identity the sink is registered under; must match the telemetry writer's. */
8
+ packageName?: string;
9
+ serviceName?: string;
10
+ /** Overrides HTTP transport discovery in tests. */
11
+ fetchImpl?: typeof fetch;
12
+ /** Overrides the worker-backed reader in tests. */
13
+ workerReader?: Pick<AsyncLogMetricsReader, 'getMetrics' | 'close'>;
14
+ }
15
+ export declare function createMetricsSource(options?: MetricsSourceOptions): MetricsSource;
16
+ //#endregion
17
+ export type { MetricsInstance, MetricsQuery, MetricsQueryParams, MetricsSource, MetricsSourceOptions, MetricsTransport };
18
+ //# sourceMappingURL=metricsSource.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsSource.d.mts","names":[],"sources":["../src/services/metricsSource/index.ts"],"mappings":";;;UA0CiB;EACf,MAAM,OAAO;EACb;;EAEA;EACA;;EAEA,mBAAmB;;EAEnB,eAAe,KAAK;;wBAiEN,oBAAoB,UAAS,uBAA4B"}
@@ -0,0 +1,2 @@
1
+ import { t as createMetricsSource } from "./metricsSource-DXZLawQu.mjs";
2
+ export { createMetricsSource };
package/llms.txt ADDED
@@ -0,0 +1,7 @@
1
+ # Po Log
2
+
3
+ Session telemetry and evidence-based agent diagnostics.
4
+
5
+ ## Help
6
+
7
+ - [po-debug-agent](./src/prompts/po-debug-agent/SKILL.md): Diagnose captured failures and token usage without changing the sink or exposing secrets.
package/package.json ADDED
@@ -0,0 +1,140 @@
1
+ {
2
+ "name": "@agimon-ai/po-log",
3
+ "version": "0.0.0-alpha.0",
4
+ "description": "Session metrics, findings, and sink status for headless agent observability.",
5
+ "keywords": [
6
+ "agent-observability",
7
+ "ai",
8
+ "coding-agent",
9
+ "developer-tools",
10
+ "log-sink",
11
+ "logging",
12
+ "metrics",
13
+ "pi-extension",
14
+ "pi-package",
15
+ "po",
16
+ "telemetry"
17
+ ],
18
+ "homepage": "https://agimon.ai",
19
+ "bugs": {
20
+ "url": "https://github.com/AgiFlow/po/issues"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Vuong Ngo",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/AgiFlow/po.git",
27
+ "directory": "packages/default/po-log"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "llms.txt",
32
+ "src/prompts"
33
+ ],
34
+ "type": "module",
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.mjs",
37
+ "types": "./dist/index.d.mts",
38
+ "jsnext:main": "./dist/index.mjs",
39
+ "exports": {
40
+ ".": {
41
+ "types": "./dist/index.d.mts",
42
+ "import": "./dist/index.mjs",
43
+ "require": "./dist/index.cjs"
44
+ },
45
+ "./apiContracts": {
46
+ "types": "./dist/apiContracts.d.mts",
47
+ "import": "./dist/apiContracts.mjs",
48
+ "require": "./dist/apiContracts.cjs"
49
+ },
50
+ "./extensions/server": {
51
+ "types": "./dist/extensions/server.d.mts",
52
+ "import": "./dist/extensions/server.mjs",
53
+ "require": "./dist/extensions/server.cjs"
54
+ },
55
+ "./extensions/web": {
56
+ "import": "./dist/extensions/web.mjs"
57
+ },
58
+ "./metrics": {
59
+ "types": "./dist/metrics.d.mts",
60
+ "import": "./dist/metrics.mjs",
61
+ "require": "./dist/metrics.cjs"
62
+ },
63
+ "./metricsSource": {
64
+ "types": "./dist/metricsSource.d.mts",
65
+ "import": "./dist/metricsSource.mjs",
66
+ "require": "./dist/metricsSource.cjs"
67
+ },
68
+ "./package.json": "./package.json"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public",
72
+ "registry": "https://registry.npmjs.org/"
73
+ },
74
+ "dependencies": {
75
+ "@agimon-ai/log-sink-mcp": "0.30.12",
76
+ "@agimon-ai/po-core": "0.0.0-alpha.0",
77
+ "@agimon-ai/po-telemetry": "0.0.0-alpha.0",
78
+ "@agimon-ai/po-web-components": "0.0.0-alpha.0",
79
+ "@agimon-ai/po-web-security": "0.0.0-alpha.0",
80
+ "@deepseek-ai/cordis": "4.0.4",
81
+ "hono": "4.13.12",
82
+ "typebox": "1.3.34"
83
+ },
84
+ "devDependencies": {
85
+ "@agimon-ai/po-build": "0.0.0-alpha.0",
86
+ "@agimon-ai/po-ui": "0.0.0-alpha.0",
87
+ "@earendil-works/pi-coding-agent": "1.0.2",
88
+ "@modelcontextprotocol/client": "2.2.0",
89
+ "@tanstack/react-store": "0.11.2",
90
+ "@tanstack/store": "0.11.2",
91
+ "@types/node": "26.6.4",
92
+ "@types/react": "19.3.0",
93
+ "@vitest/coverage-v8": "5.0.3",
94
+ "react": "19.3.0",
95
+ "react-dom": "19.3.0",
96
+ "tsdown": "0.23.0",
97
+ "typescript": "7.0.2",
98
+ "vitest": "5.0.3"
99
+ },
100
+ "peerDependencies": {
101
+ "@earendil-works/pi-coding-agent": "1.0.2"
102
+ },
103
+ "peerDependenciesMeta": {
104
+ "@earendil-works/pi-coding-agent": {
105
+ "optional": true
106
+ }
107
+ },
108
+ "engines": {
109
+ "node": ">=24"
110
+ },
111
+ "poServer": {
112
+ "entry": "./generated/server.ts",
113
+ "dist": "./dist/extensions/server.mjs",
114
+ "scopes": [
115
+ "global",
116
+ "workspace",
117
+ "session"
118
+ ],
119
+ "contracts": {
120
+ "entry": "./src/exports/apiContracts.ts",
121
+ "dist": "./dist/apiContracts.mjs"
122
+ }
123
+ },
124
+ "poWeb": {
125
+ "pluginId": "log",
126
+ "channels": [],
127
+ "client": "./dist/extensions/web.mjs",
128
+ "scopes": [
129
+ "global",
130
+ "session"
131
+ ]
132
+ },
133
+ "scripts": {
134
+ "build": "tsdown",
135
+ "test": "vitest --run",
136
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json",
137
+ "lint": "oxlint . && oxfmt . --check",
138
+ "fixcode": "oxlint . --fix && oxfmt ."
139
+ }
140
+ }
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: po-debug-agent
3
+ description: Debug a Po agent using session-scoped failure and usage evidence. Distinguish missing telemetry from a healthy run, diagnose expensive or failed tools, and investigate deeper traces without exposing secrets or changing the sink.
4
+ ---
5
+
6
+ # Debug a Po agent
7
+
8
+ Start with the user's symptom and an expected outcome. Identify the affected session and the shortest reproduction. Do not attribute a failure to the model before checking the tools and runtime it depends on.
9
+
10
+ ## Read captured evidence
11
+
12
+ While Help is active, call `help_status` to see which diagnostics are actually available. Use `diagnose_agent` for the current session, initially with the default one-day window. Use `period: week` only when the relevant run is older. The tool has no arbitrary session or filesystem selector.
13
+
14
+ The result distinguishes unavailable data, an empty window, and captured evidence. `capturedIssues` describes the records inspected, not every failure that ever happened. Zero captured records does not establish that telemetry was working. Token coverage describes usage records, not completeness of the event stream. Tool token samples belong to the containing turn and are not exclusive tool cost.
15
+
16
+ If setup or capabilities are suspect, use `diagnose_setup` and the configuration, domain, mode, or skill guidance identified by the failed check. Preserve the current selection and unrelated settings.
17
+
18
+ ## Investigate beyond the summary
19
+
20
+ The diagnostic deliberately omits prompts, raw error messages, tool inputs, and trace bodies. It does not open a sink or repair logging. Use existing authorized repository tools and the installed log-sink CLI only when deeper investigation is necessary. Resolve the same sink identity as the writer, keep queries restricted to the current session, and inspect the CLI's installed help before selecting unsupported query options. Verify trace ownership against that session before reading a trace; a guessed trace ID is not authorization.
21
+
22
+ When no shell or trace reader is available, report that capability limitation. Do not invent evidence or enable a server, domain, or permission merely to make a command available.
23
+
24
+ ## Propose and verify a repair
25
+
26
+ Separate observations from hypotheses. Present the smallest change and the expected verification. Configuration changes, model changes, package installation, authentication, restarts, and repairs use existing approval and permission boundaries. Help activation grants none of those actions by itself.
27
+
28
+ Never clear logs, restart or stop a sink as part of diagnosis, or paste credentials and raw private logs into a report. After an authorized change, reproduce the original symptom and report the observed result and remaining evidence gaps.